doodle/pkg/uix/canvas.go

429 lines
13 KiB
Go
Raw Normal View History

package uix
import (
"fmt"
"runtime"
"strings"
"git.kirsle.net/apps/doodle/pkg/balance"
"git.kirsle.net/apps/doodle/pkg/collision"
"git.kirsle.net/apps/doodle/pkg/doodads"
"git.kirsle.net/apps/doodle/pkg/drawtool"
"git.kirsle.net/apps/doodle/pkg/filesystem"
"git.kirsle.net/apps/doodle/pkg/level"
"git.kirsle.net/apps/doodle/pkg/log"
"git.kirsle.net/apps/doodle/pkg/scripting"
Wallpapers and Bounded Levels Implement the Wallpaper system into the levels and the concept of Bounded and Unbounded levels. The first wallpaper image is notepad.png which looks like standard ruled notebook paper. On bounded levels, the top/left edges of the page look as you would expect and the blue lines tile indefinitely in the positive directions. On unbounded levels, you only get the repeating blue lines but not the edge pieces. A wallpaper is just a rectangular image file. The image is divided into four equal quadrants to be the Corner, Top, Left and Repeat textures for the wallpaper. The Repeat texture is ALWAYS used and fills all the empty space behind the drawing. (Doodads draw with blank canvases as before because only levels have wallpapers!) Levels have four options of a "Page Type": - Unbounded (default, infinite space) - NoNegativeSpace (has a top left edge but can grow infinitely) - Bounded (has a top left edge and bounded size) - Bordered (bounded with bordered texture; NOT IMPLEMENTED!) The scrollable viewport of a Canvas will respect the wallpaper and page type settings of a Level loaded into it. That is, if the level has a top left edge (not Unbounded) you can NOT scroll to see negative coordinates below (0,0) -- and if the level has a max dimension set, you can't scroll to see pixels outside those dimensions. The Canvas property NoLimitScroll=true will override the scroll locking and let you see outside the bounds, for debugging. - Default map settings for New Level are now: - Page Type: NoNegativeSpace - Wallpaper: notepad.png (default) - MaxWidth: 2550 (8.5" * 300 ppi) - MaxHeight: 3300 ( 11" * 300 ppi)
2018-10-28 05:22:13 +00:00
"git.kirsle.net/apps/doodle/pkg/wallpaper"
"git.kirsle.net/go/render"
"git.kirsle.net/go/render/event"
"git.kirsle.net/go/ui"
)
// Canvas is a custom ui.Widget that manages a single drawing.
type Canvas struct {
ui.Frame
Palette *level.Palette
// Parent Canvas widget, e.g. for Actors inside of a Level so they can
// find the parent canvas and see where they are drawing in relation to
// it (to handle top/left edge cropping on scroll)
parent *Canvas
// Editable and Scrollable go hand in hand and, if you initialize a
// NewCanvas() with editable=true, they are both enabled.
Editable bool // Clicking will edit pixels of this canvas.
Scrollable bool // Cursor keys will scroll the viewport of this canvas.
Zoom int // Zoom level on the canvas.
// Custom label to place in the lower-right corner of the canvas.
// Used for e.g. the quantity badge on Inventory items.
CornerLabel string
// Selected draw tool/mode, default Pencil, for editable canvases.
Tool drawtool.Tool
BrushSize int // thickness of selected brush
// MaskColor will force every pixel to render as this color regardless of
// the palette index of that pixel. Otherwise pixels behave the same and
// the palette does work as normal. Set to render.Invisible (zero value)
// to remove the mask.
MaskColor render.Color
// Actor ID to follow the camera on automatically, i.e. the main player.
FollowActor string
Wallpapers and Bounded Levels Implement the Wallpaper system into the levels and the concept of Bounded and Unbounded levels. The first wallpaper image is notepad.png which looks like standard ruled notebook paper. On bounded levels, the top/left edges of the page look as you would expect and the blue lines tile indefinitely in the positive directions. On unbounded levels, you only get the repeating blue lines but not the edge pieces. A wallpaper is just a rectangular image file. The image is divided into four equal quadrants to be the Corner, Top, Left and Repeat textures for the wallpaper. The Repeat texture is ALWAYS used and fills all the empty space behind the drawing. (Doodads draw with blank canvases as before because only levels have wallpapers!) Levels have four options of a "Page Type": - Unbounded (default, infinite space) - NoNegativeSpace (has a top left edge but can grow infinitely) - Bounded (has a top left edge and bounded size) - Bordered (bounded with bordered texture; NOT IMPLEMENTED!) The scrollable viewport of a Canvas will respect the wallpaper and page type settings of a Level loaded into it. That is, if the level has a top left edge (not Unbounded) you can NOT scroll to see negative coordinates below (0,0) -- and if the level has a max dimension set, you can't scroll to see pixels outside those dimensions. The Canvas property NoLimitScroll=true will override the scroll locking and let you see outside the bounds, for debugging. - Default map settings for New Level are now: - Page Type: NoNegativeSpace - Wallpaper: notepad.png (default) - MaxWidth: 2550 (8.5" * 300 ppi) - MaxHeight: 3300 ( 11" * 300 ppi)
2018-10-28 05:22:13 +00:00
// Debug tools
// NoLimitScroll suppresses the scroll limit for bounded levels.
NoLimitScroll bool
Draw Actors Embedded in Levels in Edit Mode Add the JSON format for embedding Actors (Doodad instances) inside of a Level. I made a test map that manually inserted a couple of actors. Actors are given to the Canvas responsible for the Level via the function `InstallActors()`. So it means you'll call LoadLevel and then InstallActors to hook everything up. The Canvas creates sub-Canvas widgets from each Actor. After drawing the main level geometry from the Canvas.Chunker, it calls the drawActors() function which does the same but for Actors. Levels keep a global map of all Actors that exist. For any Actors that are visible within the Viewport, their sub-Canvas widgets are presented appropriately on top of the parent Canvas. In case their sub-Canvas overlaps the parent's boundaries, their sub-Canvas is resized and moved appropriately. - Allow the MainWindow to be resized at run time, and the UI recalculates its sizing and position. - Made the in-game Shell properties editable via environment variables. The kirsle.env file sets a blue and pink color scheme. - Begin the ground work for Levels and Doodads to embed files inside their data via the level.FileSystem type. - UI: Labels can now contain line break characters. It will appropriately render multiple lines of render.Text and take into account the proper BoxSize to contain them all. - Add environment variable DOODLE_DEBUG_ALL=true that will turn on ALL debug overlay and visualization options. - Add debug overlay to "tag" each Canvas widget with some of its details, like its Name and World Position. Can be enabled with the environment variable DEBUG_CANVAS_LABEL=true - Improved the FPS debug overlay to show in labeled columns and multiple colors, with easy ability to add new data points to it.
2018-10-19 20:31:58 +00:00
// Underlying chunk data for the drawing.
level *level.Level
chunks *level.Chunker
doodad *doodads.Doodad
modified bool // set to True when the drawing has been modified, like in Editor Mode.
Draw Actors Embedded in Levels in Edit Mode Add the JSON format for embedding Actors (Doodad instances) inside of a Level. I made a test map that manually inserted a couple of actors. Actors are given to the Canvas responsible for the Level via the function `InstallActors()`. So it means you'll call LoadLevel and then InstallActors to hook everything up. The Canvas creates sub-Canvas widgets from each Actor. After drawing the main level geometry from the Canvas.Chunker, it calls the drawActors() function which does the same but for Actors. Levels keep a global map of all Actors that exist. For any Actors that are visible within the Viewport, their sub-Canvas widgets are presented appropriately on top of the parent Canvas. In case their sub-Canvas overlaps the parent's boundaries, their sub-Canvas is resized and moved appropriately. - Allow the MainWindow to be resized at run time, and the UI recalculates its sizing and position. - Made the in-game Shell properties editable via environment variables. The kirsle.env file sets a blue and pink color scheme. - Begin the ground work for Levels and Doodads to embed files inside their data via the level.FileSystem type. - UI: Labels can now contain line break characters. It will appropriately render multiple lines of render.Text and take into account the proper BoxSize to contain them all. - Add environment variable DOODLE_DEBUG_ALL=true that will turn on ALL debug overlay and visualization options. - Add debug overlay to "tag" each Canvas widget with some of its details, like its Name and World Position. Can be enabled with the environment variable DEBUG_CANVAS_LABEL=true - Improved the FPS debug overlay to show in labeled columns and multiple colors, with easy ability to add new data points to it.
2018-10-19 20:31:58 +00:00
// Actors to superimpose on top of the drawing.
actor *Actor // if this canvas IS an actor
actors []*Actor // if this canvas CONTAINS actors (i.e., is a level)
Draw Actors Embedded in Levels in Edit Mode Add the JSON format for embedding Actors (Doodad instances) inside of a Level. I made a test map that manually inserted a couple of actors. Actors are given to the Canvas responsible for the Level via the function `InstallActors()`. So it means you'll call LoadLevel and then InstallActors to hook everything up. The Canvas creates sub-Canvas widgets from each Actor. After drawing the main level geometry from the Canvas.Chunker, it calls the drawActors() function which does the same but for Actors. Levels keep a global map of all Actors that exist. For any Actors that are visible within the Viewport, their sub-Canvas widgets are presented appropriately on top of the parent Canvas. In case their sub-Canvas overlaps the parent's boundaries, their sub-Canvas is resized and moved appropriately. - Allow the MainWindow to be resized at run time, and the UI recalculates its sizing and position. - Made the in-game Shell properties editable via environment variables. The kirsle.env file sets a blue and pink color scheme. - Begin the ground work for Levels and Doodads to embed files inside their data via the level.FileSystem type. - UI: Labels can now contain line break characters. It will appropriately render multiple lines of render.Text and take into account the proper BoxSize to contain them all. - Add environment variable DOODLE_DEBUG_ALL=true that will turn on ALL debug overlay and visualization options. - Add debug overlay to "tag" each Canvas widget with some of its details, like its Name and World Position. Can be enabled with the environment variable DEBUG_CANVAS_LABEL=true - Improved the FPS debug overlay to show in labeled columns and multiple colors, with easy ability to add new data points to it.
2018-10-19 20:31:58 +00:00
// Collision memory for the actors.
Thief and Inventory APIs This commit adds the Thief character with starter graphics (no animations). The Thief walks back and forth and will steal items from other doodads, including the player. For singleton items that have no quantity, like the Colored Keys, the Thief will only steal one if he does not already have it. Quantitied items like the Small Key are always stolen. Flexibility in the playable character is introduced: Boy, Azulian, Bird, and Thief all respond to playable controls. There is not currently a method to enable these apart from modifying balance.PlayerCharacterDoodad at compile time. New and Changed Doodads * Thief: new doodad that walks back and forth and will steal items from other characters inventory. * Bird: has no inventory and cannot pick up items, unless player controlled. Its hitbox has also been fixed so it collides with floors correctly - not something normally seen in the Bird. * Boy: opts in to have inventory. * Keys (all): only gives themselves to actors having inventories. JavaScript API - New functions available * Self.IsPlayer() - returns if the current actor IS the player. * Self.SetInventory(bool) - doodads must opt-in to having an inventory. Keys should only give themselves to doodads having an inventory. * Self.HasInventory() bool * Self.AddItem(filename, qty) * Self.RemoveItem(filename, qty) * Self.HasItem(filename) * Self.Inventory() - returns map[string]int * Self.ClearInventory() * Self.OnLeave(func(e)) now receives a CollideEvent as parameter instead of the useless actor ID. Notably, e.Actor is the leaving actor and e.Settled is always true. Other Changes * Play Mode: if playing as a character which doesn't obey gravity, such as the bird, antigravity controls are enabled by default. If you `import antigravity` you can turn gravity back on. * Doodad collision scripts are no longer run in parallel goroutines. It made the Thief's job difficult trying to steal items in many threads simultaneously!
2021-08-10 05:42:22 +00:00
collidingActors map[*Actor]*Actor // mapping their IDs to each other
// Doodad scripting engine supervisor.
// NOTE: initialized and managed by the play_scene.
scripting *scripting.Supervisor
Wallpapers and Bounded Levels Implement the Wallpaper system into the levels and the concept of Bounded and Unbounded levels. The first wallpaper image is notepad.png which looks like standard ruled notebook paper. On bounded levels, the top/left edges of the page look as you would expect and the blue lines tile indefinitely in the positive directions. On unbounded levels, you only get the repeating blue lines but not the edge pieces. A wallpaper is just a rectangular image file. The image is divided into four equal quadrants to be the Corner, Top, Left and Repeat textures for the wallpaper. The Repeat texture is ALWAYS used and fills all the empty space behind the drawing. (Doodads draw with blank canvases as before because only levels have wallpapers!) Levels have four options of a "Page Type": - Unbounded (default, infinite space) - NoNegativeSpace (has a top left edge but can grow infinitely) - Bounded (has a top left edge and bounded size) - Bordered (bounded with bordered texture; NOT IMPLEMENTED!) The scrollable viewport of a Canvas will respect the wallpaper and page type settings of a Level loaded into it. That is, if the level has a top left edge (not Unbounded) you can NOT scroll to see negative coordinates below (0,0) -- and if the level has a max dimension set, you can't scroll to see pixels outside those dimensions. The Canvas property NoLimitScroll=true will override the scroll locking and let you see outside the bounds, for debugging. - Default map settings for New Level are now: - Page Type: NoNegativeSpace - Wallpaper: notepad.png (default) - MaxWidth: 2550 (8.5" * 300 ppi) - MaxHeight: 3300 ( 11" * 300 ppi)
2018-10-28 05:22:13 +00:00
// Wallpaper settings.
wallpaper *Wallpaper
// When the Canvas wants to delete Actors, but ultimately it is upstream
// that controls the actors. Upstream should delete them and then reinstall
// the actor list from scratch.
Optimize memory by freeing up SDL2 textures * Added to the F3 Debug Overlay is a "Texture:" label that counts the number of textures currently loaded by the (SDL2) render engine. * Added Teardown() functions to Level, Doodad and the Chunker they both use to free up SDL2 textures for all their cached graphics. * The Canvas.Destroy() function now cleans up all textures that the Canvas is responsible for: calling the Teardown() of the Level or Doodad, calling Destroy() on all level actors, and cleaning up Wallpaper textures. * The Destroy() method of the game's various Scenes will properly Destroy() their canvases to clean up when transitioning to another scene. The MainScene, MenuScene, EditorScene and PlayScene. * Fix the sprites package to actually cache the ui.Image widgets. The game has very few sprites so no need to free them just yet. Some tricky places that were leaking textures have been cleaned up: * Canvas.InstallActors() destroys the canvases of existing actors before it reinitializes the list and installs the replacements. * The DraggableActor when the user is dragging an actor around their level cleans up the blueprint masked drag/drop actor before nulling it out. Misc changes: * The player character cheats during Play Mode will immediately swap out the player character on the current level. * Properly call the Close() function instead of Hide() to dismiss popup windows. The Close() function itself calls Hide() but also triggers WindowClose event handlers. The Doodad Dropper subscribes to its close event to free textures for all its doodad canvases.
2022-04-09 21:41:24 +00:00
OnDeleteActors func([]*Actor)
OnDragStart func(*level.Actor)
// -- WHEN Canvas.Tool is "Link" --
2019-06-09 00:02:28 +00:00
// When the Canvas wants to link two actors together. Arguments are the IDs
// of the two actors.
OnLinkActors func(a, b *Actor)
linkFirst *Actor
2019-06-09 00:02:28 +00:00
// Collision handlers for level geometry.
OnLevelCollision func(*Actor, *collision.Collide)
// Handler when a doodad script called Actors.SetPlayerCharacter.
// The filename.doodad is given.
OnSetPlayerCharacter func(filename string)
// Handler for when a doodad script calls Level.ResetTimer().
OnResetTimer func()
/********
* Editable canvas private variables.
********/
// The current stroke actively being drawn by the user, during a
// mousedown-and-dragging event.
currentStroke *drawtool.Stroke
strokes map[int]*drawtool.Stroke // active stroke mapped by ID
lastPixel *level.Pixel
// We inherit the ui.Widget which manages the width and height.
Scroll render.Point // Scroll offset for which parts of canvas are visible.
scrollDragging bool // Middle-click to pan scroll
scrollStartAt render.Point // Cursor point at beginning of pan
scrollWasAt render.Point // copy of Scroll at beginning of pan
scrollLastDelta render.Point // multitouch spam
// LoadUnloadChunks metrics for the debug overlay.
loadUnloadInside int
loadUnloadOutside int
}
// NewCanvas initializes a Canvas widget.
//
// If editable is true, Scrollable is also set to true, which means the arrow
// keys will scroll the canvas viewport which is desirable in Edit Mode.
Implement Chunk System for Pixel Data Starts the implementation of the chunk-based pixel storage system for levels and drawings. Previously the levels had a Pixels structure which was just an array of X,Y and palette index triplets. The new chunk system divides the map up into square chunks, and lets each chunk manage its own memory layout. The "MapAccessor" layout is implemented first which is a map of X,Y coordinates to their Swatches (pointer to an index of the palette). When serialized the MapAccessor maps the "X,Y": "index" similarly to the old Pixels array. The object hierarchy for the chunk system is like: * Chunker: the manager of the chunks who keeps track of the ChunkSize and a map of "chunk coordinates" to the chunk in charge of it. * Chunk: a part of the drawing ChunkSize length square. A chunk has a Type (of how it stores its data, 0 being a map[Point]Swatch and 1 being a [][]Swatch 2D array), and the chunk has an Accessor which implements the underlying type. * Accessor: an interface for a Chunk to provide access to its pixels. * MapAccessor: a "sparse map" of coordinates to their Swatches. * GridAccessor: TBD, will be a "dense" 2D grid of Swatches. The JSON files are loaded in two passes: 1. The chunks only load their swatch indexes from disk. 2. With the palette also loaded, the chunks are "inflated" and linked to their swatch pointers. Misc changes: * The `level.Canvas` UI widget switches from the old Grid data type to being able to directly use a `level.Chunker` * The Chunker is a shared data type between the on-disk level format and the actual renderer (level.Canvas), so saving the level is easy because you can just pull the Chunker out from the canvas. * ChunkSize is stored inside the level file and the default value is at balance/numbers.go: 1000
2018-09-23 22:20:45 +00:00
func NewCanvas(size int, editable bool) *Canvas {
w := &Canvas{
Editable: editable,
Scrollable: editable,
Palette: level.NewPalette(),
Various updates New doodad interactions: * Sticky Buttons will emit a "sticky:down" event to linked doodads, with a boolean value showing the Sticky Button's state. * Normal Buttons will listen for "sticky:down" -- when a linked Sticky Button is pressed, the normal Button presses in as well, and stays pressed while the sticky:down signal is true. * When the Sticky Button is released (e.g. because it received power from another doodad), any linked buttons which were sticky:down release as well. * Switch doodads emit a new "switch:toggle" event JUST BEFORE sending the "power" event. Sensitive Doodads can listen for switches in particular this way. * The Electric Door listens for switch:toggle; if a Switch is activated, the Electric Door always flips its current state (open to close, or vice versa) and ignores the immediately following power event. This allows doors to toggle on/off regardless of sync with a Switch. Other changes: * When the player character dies by fire, instead of the message saying "Watch out for fire!" it will use the name of the fire swatch that hurt the player. This way levels could make it say "Watch out for spikes!" or "lava" or whatever they want. The "Fire" attribute now just means "instantly kills the player." * Level Editor: You can now edit the Title and Author name of your level in the Page Settings window. * Bugfix: only the player character ends the game by dying in fire. Other mobile doodads just turn dark but don't end the game. * Increase the size of Trapdoor doodad sprites by 150% as they were a bit small for the player character. * Rename the game from "Project: Doodle" to "Sketchy Maze"
2021-03-31 06:33:25 +00:00
BrushSize: 1,
chunks: level.NewChunker(size),
Draw Actors Embedded in Levels in Edit Mode Add the JSON format for embedding Actors (Doodad instances) inside of a Level. I made a test map that manually inserted a couple of actors. Actors are given to the Canvas responsible for the Level via the function `InstallActors()`. So it means you'll call LoadLevel and then InstallActors to hook everything up. The Canvas creates sub-Canvas widgets from each Actor. After drawing the main level geometry from the Canvas.Chunker, it calls the drawActors() function which does the same but for Actors. Levels keep a global map of all Actors that exist. For any Actors that are visible within the Viewport, their sub-Canvas widgets are presented appropriately on top of the parent Canvas. In case their sub-Canvas overlaps the parent's boundaries, their sub-Canvas is resized and moved appropriately. - Allow the MainWindow to be resized at run time, and the UI recalculates its sizing and position. - Made the in-game Shell properties editable via environment variables. The kirsle.env file sets a blue and pink color scheme. - Begin the ground work for Levels and Doodads to embed files inside their data via the level.FileSystem type. - UI: Labels can now contain line break characters. It will appropriately render multiple lines of render.Text and take into account the proper BoxSize to contain them all. - Add environment variable DOODLE_DEBUG_ALL=true that will turn on ALL debug overlay and visualization options. - Add debug overlay to "tag" each Canvas widget with some of its details, like its Name and World Position. Can be enabled with the environment variable DEBUG_CANVAS_LABEL=true - Improved the FPS debug overlay to show in labeled columns and multiple colors, with easy ability to add new data points to it.
2018-10-19 20:31:58 +00:00
actors: make([]*Actor, 0),
Wallpapers and Bounded Levels Implement the Wallpaper system into the levels and the concept of Bounded and Unbounded levels. The first wallpaper image is notepad.png which looks like standard ruled notebook paper. On bounded levels, the top/left edges of the page look as you would expect and the blue lines tile indefinitely in the positive directions. On unbounded levels, you only get the repeating blue lines but not the edge pieces. A wallpaper is just a rectangular image file. The image is divided into four equal quadrants to be the Corner, Top, Left and Repeat textures for the wallpaper. The Repeat texture is ALWAYS used and fills all the empty space behind the drawing. (Doodads draw with blank canvases as before because only levels have wallpapers!) Levels have four options of a "Page Type": - Unbounded (default, infinite space) - NoNegativeSpace (has a top left edge but can grow infinitely) - Bounded (has a top left edge and bounded size) - Bordered (bounded with bordered texture; NOT IMPLEMENTED!) The scrollable viewport of a Canvas will respect the wallpaper and page type settings of a Level loaded into it. That is, if the level has a top left edge (not Unbounded) you can NOT scroll to see negative coordinates below (0,0) -- and if the level has a max dimension set, you can't scroll to see pixels outside those dimensions. The Canvas property NoLimitScroll=true will override the scroll locking and let you see outside the bounds, for debugging. - Default map settings for New Level are now: - Page Type: NoNegativeSpace - Wallpaper: notepad.png (default) - MaxWidth: 2550 (8.5" * 300 ppi) - MaxHeight: 3300 ( 11" * 300 ppi)
2018-10-28 05:22:13 +00:00
wallpaper: &Wallpaper{},
strokes: map[int]*drawtool.Stroke{},
}
w.setup()
w.IDFunc(func() string {
var attrs []string
if w.Editable {
attrs = append(attrs, "editable")
} else {
attrs = append(attrs, "read-only")
}
if w.Scrollable {
attrs = append(attrs, "scrollable")
}
return fmt.Sprintf("Canvas<%d; %s>", size, strings.Join(attrs, "; "))
})
return w
}
Optimize memory by freeing up SDL2 textures * Added to the F3 Debug Overlay is a "Texture:" label that counts the number of textures currently loaded by the (SDL2) render engine. * Added Teardown() functions to Level, Doodad and the Chunker they both use to free up SDL2 textures for all their cached graphics. * The Canvas.Destroy() function now cleans up all textures that the Canvas is responsible for: calling the Teardown() of the Level or Doodad, calling Destroy() on all level actors, and cleaning up Wallpaper textures. * The Destroy() method of the game's various Scenes will properly Destroy() their canvases to clean up when transitioning to another scene. The MainScene, MenuScene, EditorScene and PlayScene. * Fix the sprites package to actually cache the ui.Image widgets. The game has very few sprites so no need to free them just yet. Some tricky places that were leaking textures have been cleaned up: * Canvas.InstallActors() destroys the canvases of existing actors before it reinitializes the list and installs the replacements. * The DraggableActor when the user is dragging an actor around their level cleans up the blueprint masked drag/drop actor before nulling it out. Misc changes: * The player character cheats during Play Mode will immediately swap out the player character on the current level. * Properly call the Close() function instead of Hide() to dismiss popup windows. The Close() function itself calls Hide() but also triggers WindowClose event handlers. The Doodad Dropper subscribes to its close event to free textures for all its doodad canvases.
2022-04-09 21:41:24 +00:00
/*
Destroy the canvas.
This function satisfies the ui.Widget interface but it also calls Teardown() methods
on the level or doodad as well as any level actors, which frees up SDL2 texture memory.
Note: the rest of the data can be garbage collected by Go normally, the textures are
able to regenerate themselves again if needed.
*/
func (w *Canvas) Destroy() {
if w.level != nil {
w.level.Teardown()
}
if w.doodad != nil {
w.doodad.Teardown()
}
for _, actor := range w.actors {
actor.Canvas.Destroy()
}
if w.wallpaper.WP != nil {
if freed := w.wallpaper.WP.Free(); freed > 0 {
log.Debug("%s.Destroy(): freed %d wallpaper textures", w, freed)
}
}
if w.scripting != nil {
w.scripting.Teardown()
}
Optimize memory by freeing up SDL2 textures * Added to the F3 Debug Overlay is a "Texture:" label that counts the number of textures currently loaded by the (SDL2) render engine. * Added Teardown() functions to Level, Doodad and the Chunker they both use to free up SDL2 textures for all their cached graphics. * The Canvas.Destroy() function now cleans up all textures that the Canvas is responsible for: calling the Teardown() of the Level or Doodad, calling Destroy() on all level actors, and cleaning up Wallpaper textures. * The Destroy() method of the game's various Scenes will properly Destroy() their canvases to clean up when transitioning to another scene. The MainScene, MenuScene, EditorScene and PlayScene. * Fix the sprites package to actually cache the ui.Image widgets. The game has very few sprites so no need to free them just yet. Some tricky places that were leaking textures have been cleaned up: * Canvas.InstallActors() destroys the canvases of existing actors before it reinitializes the list and installs the replacements. * The DraggableActor when the user is dragging an actor around their level cleans up the blueprint masked drag/drop actor before nulling it out. Misc changes: * The player character cheats during Play Mode will immediately swap out the player character on the current level. * Properly call the Close() function instead of Hide() to dismiss popup windows. The Close() function itself calls Hide() but also triggers WindowClose event handlers. The Doodad Dropper subscribes to its close event to free textures for all its doodad canvases.
2022-04-09 21:41:24 +00:00
}
2022-01-02 02:48:34 +00:00
// Load initializes the Canvas using an existing Palette and Grid.
func (w *Canvas) Load(p *level.Palette, g *level.Chunker) {
w.Palette = p
Implement Chunk System for Pixel Data Starts the implementation of the chunk-based pixel storage system for levels and drawings. Previously the levels had a Pixels structure which was just an array of X,Y and palette index triplets. The new chunk system divides the map up into square chunks, and lets each chunk manage its own memory layout. The "MapAccessor" layout is implemented first which is a map of X,Y coordinates to their Swatches (pointer to an index of the palette). When serialized the MapAccessor maps the "X,Y": "index" similarly to the old Pixels array. The object hierarchy for the chunk system is like: * Chunker: the manager of the chunks who keeps track of the ChunkSize and a map of "chunk coordinates" to the chunk in charge of it. * Chunk: a part of the drawing ChunkSize length square. A chunk has a Type (of how it stores its data, 0 being a map[Point]Swatch and 1 being a [][]Swatch 2D array), and the chunk has an Accessor which implements the underlying type. * Accessor: an interface for a Chunk to provide access to its pixels. * MapAccessor: a "sparse map" of coordinates to their Swatches. * GridAccessor: TBD, will be a "dense" 2D grid of Swatches. The JSON files are loaded in two passes: 1. The chunks only load their swatch indexes from disk. 2. With the palette also loaded, the chunks are "inflated" and linked to their swatch pointers. Misc changes: * The `level.Canvas` UI widget switches from the old Grid data type to being able to directly use a `level.Chunker` * The Chunker is a shared data type between the on-disk level format and the actual renderer (level.Canvas), so saving the level is easy because you can just pull the Chunker out from the canvas. * ChunkSize is stored inside the level file and the default value is at balance/numbers.go: 1000
2018-09-23 22:20:45 +00:00
w.chunks = g
w.modified = false
if len(w.Palette.Swatches) > 0 {
w.SetSwatch(w.Palette.Swatches[0])
}
}
// LoadLevel initializes a Canvas from a Level object.
func (w *Canvas) LoadLevel(level *level.Level) {
w.level = level
w.Load(level.Palette, level.Chunker)
Wallpapers and Bounded Levels Implement the Wallpaper system into the levels and the concept of Bounded and Unbounded levels. The first wallpaper image is notepad.png which looks like standard ruled notebook paper. On bounded levels, the top/left edges of the page look as you would expect and the blue lines tile indefinitely in the positive directions. On unbounded levels, you only get the repeating blue lines but not the edge pieces. A wallpaper is just a rectangular image file. The image is divided into four equal quadrants to be the Corner, Top, Left and Repeat textures for the wallpaper. The Repeat texture is ALWAYS used and fills all the empty space behind the drawing. (Doodads draw with blank canvases as before because only levels have wallpapers!) Levels have four options of a "Page Type": - Unbounded (default, infinite space) - NoNegativeSpace (has a top left edge but can grow infinitely) - Bounded (has a top left edge and bounded size) - Bordered (bounded with bordered texture; NOT IMPLEMENTED!) The scrollable viewport of a Canvas will respect the wallpaper and page type settings of a Level loaded into it. That is, if the level has a top left edge (not Unbounded) you can NOT scroll to see negative coordinates below (0,0) -- and if the level has a max dimension set, you can't scroll to see pixels outside those dimensions. The Canvas property NoLimitScroll=true will override the scroll locking and let you see outside the bounds, for debugging. - Default map settings for New Level are now: - Page Type: NoNegativeSpace - Wallpaper: notepad.png (default) - MaxWidth: 2550 (8.5" * 300 ppi) - MaxHeight: 3300 ( 11" * 300 ppi)
2018-10-28 05:22:13 +00:00
// TODO: wallpaper paths
filename := balance.EmbeddedWallpaperBasePath + level.Wallpaper
if runtime.GOOS != "js" {
// Check if the wallpaper wasn't found. Check bindata and file system.
if _, err := filesystem.FindFileEmbedded(filename, level); err != nil {
log.Error("LoadLevel: wallpaper %s did not appear to exist, default to notebook.png", filename)
filename = balance.EmbeddedWallpaperBasePath + "notebook.png"
}
Wallpapers and Bounded Levels Implement the Wallpaper system into the levels and the concept of Bounded and Unbounded levels. The first wallpaper image is notepad.png which looks like standard ruled notebook paper. On bounded levels, the top/left edges of the page look as you would expect and the blue lines tile indefinitely in the positive directions. On unbounded levels, you only get the repeating blue lines but not the edge pieces. A wallpaper is just a rectangular image file. The image is divided into four equal quadrants to be the Corner, Top, Left and Repeat textures for the wallpaper. The Repeat texture is ALWAYS used and fills all the empty space behind the drawing. (Doodads draw with blank canvases as before because only levels have wallpapers!) Levels have four options of a "Page Type": - Unbounded (default, infinite space) - NoNegativeSpace (has a top left edge but can grow infinitely) - Bounded (has a top left edge and bounded size) - Bordered (bounded with bordered texture; NOT IMPLEMENTED!) The scrollable viewport of a Canvas will respect the wallpaper and page type settings of a Level loaded into it. That is, if the level has a top left edge (not Unbounded) you can NOT scroll to see negative coordinates below (0,0) -- and if the level has a max dimension set, you can't scroll to see pixels outside those dimensions. The Canvas property NoLimitScroll=true will override the scroll locking and let you see outside the bounds, for debugging. - Default map settings for New Level are now: - Page Type: NoNegativeSpace - Wallpaper: notepad.png (default) - MaxWidth: 2550 (8.5" * 300 ppi) - MaxHeight: 3300 ( 11" * 300 ppi)
2018-10-28 05:22:13 +00:00
}
wp, err := wallpaper.FromFile(filename, level)
Wallpapers and Bounded Levels Implement the Wallpaper system into the levels and the concept of Bounded and Unbounded levels. The first wallpaper image is notepad.png which looks like standard ruled notebook paper. On bounded levels, the top/left edges of the page look as you would expect and the blue lines tile indefinitely in the positive directions. On unbounded levels, you only get the repeating blue lines but not the edge pieces. A wallpaper is just a rectangular image file. The image is divided into four equal quadrants to be the Corner, Top, Left and Repeat textures for the wallpaper. The Repeat texture is ALWAYS used and fills all the empty space behind the drawing. (Doodads draw with blank canvases as before because only levels have wallpapers!) Levels have four options of a "Page Type": - Unbounded (default, infinite space) - NoNegativeSpace (has a top left edge but can grow infinitely) - Bounded (has a top left edge and bounded size) - Bordered (bounded with bordered texture; NOT IMPLEMENTED!) The scrollable viewport of a Canvas will respect the wallpaper and page type settings of a Level loaded into it. That is, if the level has a top left edge (not Unbounded) you can NOT scroll to see negative coordinates below (0,0) -- and if the level has a max dimension set, you can't scroll to see pixels outside those dimensions. The Canvas property NoLimitScroll=true will override the scroll locking and let you see outside the bounds, for debugging. - Default map settings for New Level are now: - Page Type: NoNegativeSpace - Wallpaper: notepad.png (default) - MaxWidth: 2550 (8.5" * 300 ppi) - MaxHeight: 3300 ( 11" * 300 ppi)
2018-10-28 05:22:13 +00:00
if err != nil {
log.Error("wallpaper FromFile(%s): %s", filename, err)
}
w.wallpaper.maxWidth = level.MaxWidth
w.wallpaper.maxHeight = level.MaxHeight
err = w.wallpaper.Load(level.PageType, wp)
Wallpapers and Bounded Levels Implement the Wallpaper system into the levels and the concept of Bounded and Unbounded levels. The first wallpaper image is notepad.png which looks like standard ruled notebook paper. On bounded levels, the top/left edges of the page look as you would expect and the blue lines tile indefinitely in the positive directions. On unbounded levels, you only get the repeating blue lines but not the edge pieces. A wallpaper is just a rectangular image file. The image is divided into four equal quadrants to be the Corner, Top, Left and Repeat textures for the wallpaper. The Repeat texture is ALWAYS used and fills all the empty space behind the drawing. (Doodads draw with blank canvases as before because only levels have wallpapers!) Levels have four options of a "Page Type": - Unbounded (default, infinite space) - NoNegativeSpace (has a top left edge but can grow infinitely) - Bounded (has a top left edge and bounded size) - Bordered (bounded with bordered texture; NOT IMPLEMENTED!) The scrollable viewport of a Canvas will respect the wallpaper and page type settings of a Level loaded into it. That is, if the level has a top left edge (not Unbounded) you can NOT scroll to see negative coordinates below (0,0) -- and if the level has a max dimension set, you can't scroll to see pixels outside those dimensions. The Canvas property NoLimitScroll=true will override the scroll locking and let you see outside the bounds, for debugging. - Default map settings for New Level are now: - Page Type: NoNegativeSpace - Wallpaper: notepad.png (default) - MaxWidth: 2550 (8.5" * 300 ppi) - MaxHeight: 3300 ( 11" * 300 ppi)
2018-10-28 05:22:13 +00:00
if err != nil {
log.Error("wallpaper Load: %s", err)
}
}
// LoadDoodad initializes a Canvas from a Doodad object.
func (w *Canvas) LoadDoodad(d *doodads.Doodad) {
// TODO more safe
w.doodad = d
w.Load(d.Palette, d.Layers[0].Chunker)
}
// LoadDoodadToLayer initializes a Canvas from a Doodad object and picks
// a layer to load.
func (w *Canvas) LoadDoodadToLayer(d *doodads.Doodad, index int) {
if index < 0 || index > len(d.Layers) {
log.Error("LoadDoodadToLayer: index %d out of range", index)
return
}
w.Load(d.Palette, d.Layers[index].Chunker)
}
// SetSwatch changes the currently selected swatch for editing.
func (w *Canvas) SetSwatch(s *level.Swatch) {
w.Palette.ActiveSwatch = s
}
// setup common configs between both initializers of the canvas.
func (w *Canvas) setup() {
Draw Actors Embedded in Levels in Edit Mode Add the JSON format for embedding Actors (Doodad instances) inside of a Level. I made a test map that manually inserted a couple of actors. Actors are given to the Canvas responsible for the Level via the function `InstallActors()`. So it means you'll call LoadLevel and then InstallActors to hook everything up. The Canvas creates sub-Canvas widgets from each Actor. After drawing the main level geometry from the Canvas.Chunker, it calls the drawActors() function which does the same but for Actors. Levels keep a global map of all Actors that exist. For any Actors that are visible within the Viewport, their sub-Canvas widgets are presented appropriately on top of the parent Canvas. In case their sub-Canvas overlaps the parent's boundaries, their sub-Canvas is resized and moved appropriately. - Allow the MainWindow to be resized at run time, and the UI recalculates its sizing and position. - Made the in-game Shell properties editable via environment variables. The kirsle.env file sets a blue and pink color scheme. - Begin the ground work for Levels and Doodads to embed files inside their data via the level.FileSystem type. - UI: Labels can now contain line break characters. It will appropriately render multiple lines of render.Text and take into account the proper BoxSize to contain them all. - Add environment variable DOODLE_DEBUG_ALL=true that will turn on ALL debug overlay and visualization options. - Add debug overlay to "tag" each Canvas widget with some of its details, like its Name and World Position. Can be enabled with the environment variable DEBUG_CANVAS_LABEL=true - Improved the FPS debug overlay to show in labeled columns and multiple colors, with easy ability to add new data points to it.
2018-10-19 20:31:58 +00:00
// XXX: Debug code.
if balance.DebugCanvasBorder != render.Invisible {
w.Configure(ui.Config{
BorderColor: balance.DebugCanvasBorder,
BorderSize: 2,
BorderStyle: ui.BorderSolid,
})
}
}
// Loop is called on the scene's event loop to handle mouse interaction with
// the canvas, i.e. to edit it.
func (w *Canvas) Loop(ev *event.State) error {
// Process the arrow keys scrolling the level in Edit Mode.
// canvas_scrolling.go
w.loopEditorScroll(ev)
if err := w.loopFollowActor(ev); err != nil {
log.Error("Follow actor: %s", err) // not fatal but nice to know
}
_ = w.loopConstrainScroll()
// Every so often, eager-load/unload chunk bitmaps to save on memory.
w.LoadUnloadChunks()
// Remove any actors that were destroyed the previous tick.
var newActors []*Actor
for _, a := range w.actors {
if a.flagDestroy {
a.Canvas.Destroy()
continue
}
newActors = append(newActors, a)
}
if len(newActors) < len(w.actors) {
w.actors = newActors
}
// Check collisions between actors.
if w.scripting != nil {
if err := w.loopActorCollision(); err != nil {
log.Error("loopActorCollision: %s", err)
}
}
// If the canvas is editable, only care if it's over our space.
if w.Editable {
cursor := render.NewPoint(ev.CursorX, ev.CursorY)
if cursor.Inside(ui.AbsoluteRect(w)) {
return w.loopEditable(ev)
}
}
return nil
}
// Viewport returns a rect containing the viewable drawing coordinates in this
// canvas. The X,Y values are the scroll offset (top left) and the W,H values
// are the scroll offset plus the width/height of the Canvas widget.
Draw Actors Embedded in Levels in Edit Mode Add the JSON format for embedding Actors (Doodad instances) inside of a Level. I made a test map that manually inserted a couple of actors. Actors are given to the Canvas responsible for the Level via the function `InstallActors()`. So it means you'll call LoadLevel and then InstallActors to hook everything up. The Canvas creates sub-Canvas widgets from each Actor. After drawing the main level geometry from the Canvas.Chunker, it calls the drawActors() function which does the same but for Actors. Levels keep a global map of all Actors that exist. For any Actors that are visible within the Viewport, their sub-Canvas widgets are presented appropriately on top of the parent Canvas. In case their sub-Canvas overlaps the parent's boundaries, their sub-Canvas is resized and moved appropriately. - Allow the MainWindow to be resized at run time, and the UI recalculates its sizing and position. - Made the in-game Shell properties editable via environment variables. The kirsle.env file sets a blue and pink color scheme. - Begin the ground work for Levels and Doodads to embed files inside their data via the level.FileSystem type. - UI: Labels can now contain line break characters. It will appropriately render multiple lines of render.Text and take into account the proper BoxSize to contain them all. - Add environment variable DOODLE_DEBUG_ALL=true that will turn on ALL debug overlay and visualization options. - Add debug overlay to "tag" each Canvas widget with some of its details, like its Name and World Position. Can be enabled with the environment variable DEBUG_CANVAS_LABEL=true - Improved the FPS debug overlay to show in labeled columns and multiple colors, with easy ability to add new data points to it.
2018-10-19 20:31:58 +00:00
//
// The Viewport rect are the Absolute World Coordinates of the drawing that are
// visible inside the Canvas. The X,Y is the top left World Coordinate and the
// W,H are the bottom right World Coordinate, making this rect an absolute
// slice of the world. For a normal rect with a relative width and height,
// use ViewportRelative().
//
// The rect X,Y are the negative Scroll Value.
// The rect W,H are the Canvas widget size minus the Scroll Value.
func (w *Canvas) Viewport() render.Rect {
var S = w.Size()
return render.Rect{
X: -w.Scroll.X,
Y: -w.Scroll.Y,
W: S.W - w.Scroll.X,
H: S.H - w.Scroll.Y,
}
}
Draw Actors Embedded in Levels in Edit Mode Add the JSON format for embedding Actors (Doodad instances) inside of a Level. I made a test map that manually inserted a couple of actors. Actors are given to the Canvas responsible for the Level via the function `InstallActors()`. So it means you'll call LoadLevel and then InstallActors to hook everything up. The Canvas creates sub-Canvas widgets from each Actor. After drawing the main level geometry from the Canvas.Chunker, it calls the drawActors() function which does the same but for Actors. Levels keep a global map of all Actors that exist. For any Actors that are visible within the Viewport, their sub-Canvas widgets are presented appropriately on top of the parent Canvas. In case their sub-Canvas overlaps the parent's boundaries, their sub-Canvas is resized and moved appropriately. - Allow the MainWindow to be resized at run time, and the UI recalculates its sizing and position. - Made the in-game Shell properties editable via environment variables. The kirsle.env file sets a blue and pink color scheme. - Begin the ground work for Levels and Doodads to embed files inside their data via the level.FileSystem type. - UI: Labels can now contain line break characters. It will appropriately render multiple lines of render.Text and take into account the proper BoxSize to contain them all. - Add environment variable DOODLE_DEBUG_ALL=true that will turn on ALL debug overlay and visualization options. - Add debug overlay to "tag" each Canvas widget with some of its details, like its Name and World Position. Can be enabled with the environment variable DEBUG_CANVAS_LABEL=true - Improved the FPS debug overlay to show in labeled columns and multiple colors, with easy ability to add new data points to it.
2018-10-19 20:31:58 +00:00
// ViewportRelative returns a relative viewport where the Width and Height
// values are zero-relative: so you can use it with point.Inside(viewport)
// to see if a World Index point should be visible on screen.
//
// The rect X,Y are the negative Scroll Value
// The rect W,H are the Canvas widget size.
func (w *Canvas) ViewportRelative() render.Rect {
var S = w.Size()
return render.Rect{
X: -w.Scroll.X,
Y: -w.Scroll.Y,
W: S.W,
H: S.H,
}
}
// LoadingViewport is the viewport of chunks that ought to be preloaded and
// ready to display soon. It is the Viewport of chunks on screen + a margin
// of neighboring chunks outside the screen.
//
// For memory optimization, chunks falling inside this viewport have their
// Go image.Image rendered and cached ready to convert to an SDL2 Texture
// when they come on screen. Chunks outside of the LoadingViewport can be
// unloaded (textures and images freed) to keep memory consumption on large
// levels under control.
func (w *Canvas) LoadingViewport() render.Rect {
var (
chunkSize int
vp = w.Viewport()
margin = balance.LoadingViewportMarginChunks
)
// This function is meant for levels only, but..
if w.level != nil {
chunkSize = w.level.Chunker.Size
} else if w.doodad != nil {
chunkSize = w.doodad.ChunkSize()
} else {
chunkSize = balance.ChunkSize
log.Error("Canvas.LoadingViewport: no drawing to get chunk size from, default to %d", chunkSize)
}
return render.Rect{
X: vp.X - chunkSize*margin,
Y: vp.Y - chunkSize*margin,
W: vp.W + chunkSize*margin,
H: vp.H + chunkSize*margin,
}
}
// WorldIndexAt returns the World Index that corresponds to a Screen Pixel
// on the screen. If the screen pixel is the mouse coordinate (relative to
// the application window) this will return the World Index of the pixel below
// the mouse cursor.
func (w *Canvas) WorldIndexAt(screenPixel render.Point) render.Point {
var P = ui.AbsolutePosition(w)
world := render.Point{
X: screenPixel.X - P.X - w.Scroll.X,
Y: screenPixel.Y - P.Y - w.Scroll.Y,
}
// Handle Zoomies
if w.Zoom != 0 {
// Zoom Out - logic is 100% correct, do not touch.
// ZoomDivide's logic at time of writing is to:
// return int(float64(v) * divider)
// Where divider is a map of w.Zoom to:
// -2=4 -1=2 0=1 1=0.5 2=0.25 3=0.125
// The -2 and -1 do the right things (zoom out), zoom
// in was jank. NOW FIXED with the following maps:
// -2=4 -1=2 0=1 1=0.675 2=0.5 3=0.404
// Values for zoom levels 1 and 3 are jank but works?
world.X = w.ZoomDivide(world.X)
world.Y = w.ZoomDivide(world.Y)
}
return world
}
Implement Chunk System for Pixel Data Starts the implementation of the chunk-based pixel storage system for levels and drawings. Previously the levels had a Pixels structure which was just an array of X,Y and palette index triplets. The new chunk system divides the map up into square chunks, and lets each chunk manage its own memory layout. The "MapAccessor" layout is implemented first which is a map of X,Y coordinates to their Swatches (pointer to an index of the palette). When serialized the MapAccessor maps the "X,Y": "index" similarly to the old Pixels array. The object hierarchy for the chunk system is like: * Chunker: the manager of the chunks who keeps track of the ChunkSize and a map of "chunk coordinates" to the chunk in charge of it. * Chunk: a part of the drawing ChunkSize length square. A chunk has a Type (of how it stores its data, 0 being a map[Point]Swatch and 1 being a [][]Swatch 2D array), and the chunk has an Accessor which implements the underlying type. * Accessor: an interface for a Chunk to provide access to its pixels. * MapAccessor: a "sparse map" of coordinates to their Swatches. * GridAccessor: TBD, will be a "dense" 2D grid of Swatches. The JSON files are loaded in two passes: 1. The chunks only load their swatch indexes from disk. 2. With the palette also loaded, the chunks are "inflated" and linked to their swatch pointers. Misc changes: * The `level.Canvas` UI widget switches from the old Grid data type to being able to directly use a `level.Chunker` * The Chunker is a shared data type between the on-disk level format and the actual renderer (level.Canvas), so saving the level is easy because you can just pull the Chunker out from the canvas. * ChunkSize is stored inside the level file and the default value is at balance/numbers.go: 1000
2018-09-23 22:20:45 +00:00
// Chunker returns the underlying Chunker object.
func (w *Canvas) Chunker() *level.Chunker {
Implement Chunk System for Pixel Data Starts the implementation of the chunk-based pixel storage system for levels and drawings. Previously the levels had a Pixels structure which was just an array of X,Y and palette index triplets. The new chunk system divides the map up into square chunks, and lets each chunk manage its own memory layout. The "MapAccessor" layout is implemented first which is a map of X,Y coordinates to their Swatches (pointer to an index of the palette). When serialized the MapAccessor maps the "X,Y": "index" similarly to the old Pixels array. The object hierarchy for the chunk system is like: * Chunker: the manager of the chunks who keeps track of the ChunkSize and a map of "chunk coordinates" to the chunk in charge of it. * Chunk: a part of the drawing ChunkSize length square. A chunk has a Type (of how it stores its data, 0 being a map[Point]Swatch and 1 being a [][]Swatch 2D array), and the chunk has an Accessor which implements the underlying type. * Accessor: an interface for a Chunk to provide access to its pixels. * MapAccessor: a "sparse map" of coordinates to their Swatches. * GridAccessor: TBD, will be a "dense" 2D grid of Swatches. The JSON files are loaded in two passes: 1. The chunks only load their swatch indexes from disk. 2. With the palette also loaded, the chunks are "inflated" and linked to their swatch pointers. Misc changes: * The `level.Canvas` UI widget switches from the old Grid data type to being able to directly use a `level.Chunker` * The Chunker is a shared data type between the on-disk level format and the actual renderer (level.Canvas), so saving the level is easy because you can just pull the Chunker out from the canvas. * ChunkSize is stored inside the level file and the default value is at balance/numbers.go: 1000
2018-09-23 22:20:45 +00:00
return w.chunks
}
// ScrollTo sets the viewport scroll position.
func (w *Canvas) ScrollTo(to render.Point) {
w.Scroll.X = to.X
w.Scroll.Y = to.Y
}
// ScrollBy adjusts the viewport scroll position.
func (w *Canvas) ScrollBy(by render.Point) {
w.Scroll.Add(by)
}
// Compute the canvas.
func (w *Canvas) Compute(e render.Engine) {
}