doodle/pkg/editor_scene.go

671 lines
17 KiB
Go
Raw Permalink Normal View History

2018-06-21 01:43:14 +00:00
package doodle
import (
"errors"
"fmt"
"strings"
"time"
2018-06-21 01:43:14 +00:00
2022-09-24 22:17:25 +00:00
"git.kirsle.net/SketchyMaze/doodle/pkg/balance"
"git.kirsle.net/SketchyMaze/doodle/pkg/cursor"
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
"git.kirsle.net/SketchyMaze/doodle/pkg/drawtool"
"git.kirsle.net/SketchyMaze/doodle/pkg/enum"
"git.kirsle.net/SketchyMaze/doodle/pkg/keybind"
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
"git.kirsle.net/SketchyMaze/doodle/pkg/level/giant_screenshot"
2022-09-24 22:17:25 +00:00
"git.kirsle.net/SketchyMaze/doodle/pkg/level/publishing"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/modal"
"git.kirsle.net/SketchyMaze/doodle/pkg/modal/loadscreen"
"git.kirsle.net/SketchyMaze/doodle/pkg/native"
"git.kirsle.net/SketchyMaze/doodle/pkg/plus"
2022-09-24 22:17:25 +00:00
"git.kirsle.net/SketchyMaze/doodle/pkg/usercfg"
"git.kirsle.net/SketchyMaze/doodle/pkg/userdir"
"git.kirsle.net/SketchyMaze/doodle/pkg/windows"
"git.kirsle.net/go/render"
"git.kirsle.net/go/render/event"
"git.kirsle.net/go/ui"
2018-06-21 01:43:14 +00:00
)
// EditorScene manages the "Edit Level" game mode.
type EditorScene struct {
// Configuration for the scene initializer.
DrawingType enum.DrawingType
OpenFile bool
Filename string
DoodadSize render.Rect
RememberScrollPosition render.Point // Play mode remembers it for us
UI *EditorUI
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
d *Doodle
// The current level or doodad object being edited, based on the
// DrawingType.
Level *level.Level
Doodad *doodads.Doodad
ActiveLayer int // which layer (of a doodad) is being edited now?
// Custom debug overlay values.
debTool *string
debSwatch *string
debWorldIndex *string
debLoadingViewport *string
// Last saved filename by the user.
filename string
lastAutosaveAt time.Time
winOpenLevel *ui.Window
2018-06-21 01:43:14 +00:00
}
2018-06-21 02:00:46 +00:00
// Name of the scene.
func (s *EditorScene) Name() string {
return "Edit"
}
2018-06-21 01:43:14 +00:00
// Setup the editor scene.
func (s *EditorScene) Setup(d *Doodle) error {
// Debug overlay values.
s.debTool = new(string)
s.debSwatch = new(string)
s.debWorldIndex = new(string)
s.debLoadingViewport = new(string)
customDebugLabels = []debugLabel{
2019-04-19 22:08:00 +00:00
{"Pixel:", s.debWorldIndex},
{"Tool:", s.debTool},
{"Swatch:", s.debSwatch},
{"Chunks:", s.debLoadingViewport},
}
// Initialize autosave time.
s.lastAutosaveAt = time.Now()
// Show the loading screen.
loadscreen.ShowWithProgress()
go func() {
if err := s.setupAsync(d); err != nil {
log.Error("EditorScene.setupAsync: %s", err)
}
loadscreen.Hide()
}()
return nil
}
// Reset the editor scene from scratch. Good nuclear option when you change the level's
// palette on-the-fly or some other sticky situation and want to reload the editor.
func (s *EditorScene) Reset() {
if s.Level != nil {
s.Level.Chunker.Redraw()
}
if s.Doodad != nil {
s.Doodad.Layers[s.ActiveLayer].Chunker.Redraw()
}
s.d.Goto(&EditorScene{
Filename: s.Filename,
Level: s.Level,
Doodad: s.Doodad,
})
}
// setupAsync initializes trhe editor scene in the background,
// underneath a loading screen.
func (s *EditorScene) setupAsync(d *Doodle) error {
// Initialize the user interface. It references the palette and such so it
// must be initialized after those things.
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
s.d = d
s.UI = NewEditorUI(d, s)
// Were we given configuration data?
if s.Filename != "" {
log.Debug("EditorScene.Setup: Set filename to %s", s.Filename)
s.filename = s.Filename
s.Filename = ""
}
// Loading a Level or a Doodad?
switch s.DrawingType {
case enum.LevelDrawing:
if s.Level != nil {
log.Debug("EditorScene.Setup: received level from scene caller")
loadscreen.SetSubtitle(
"Opening: "+s.Level.Title,
"by "+s.Level.Author,
)
s.UI.Canvas.LoadLevel(s.Level)
if err := s.installActors(); err != nil {
log.Error("InstallActors: %s", err)
}
} else if s.filename != "" && s.OpenFile {
log.Debug("EditorScene.Setup: Loading map from filename at %s", s.filename)
loadscreen.SetSubtitle(
"Opening: " + s.filename,
)
if err := s.LoadLevel(s.filename); err != nil {
d.FlashError("LoadLevel error: %s", err)
} else {
if err := s.installActors(); err != nil {
log.Error("InstallActors: %s", err)
}
}
}
// Write locked level?
if s.Level != nil && s.Level.Locked {
if usercfg.Current.WriteLockOverride {
d.Flash("Note: write lock has been overridden")
} else {
d.FlashError("That level is write-protected and cannot be viewed in the editor.")
s.Level = nil
s.UI.Canvas.ClearActors()
s.filename = ""
}
}
// No level?
if s.Level == nil {
log.Debug("EditorScene.Setup: initializing a new Level")
s.Level = level.New()
s.Level.Palette = level.DefaultPalette()
s.UI.Canvas.LoadLevel(s.Level)
s.UI.Canvas.ScrollTo(render.Origin)
s.UI.Canvas.Scrollable = true
}
// Update the loading screen with level info.
loadscreen.SetSubtitle(
"Opening: "+s.Level.Title,
"by "+s.Level.Author,
)
case enum.DoodadDrawing:
// Getting a doodad from file?
if s.filename != "" && s.OpenFile {
log.Debug("EditorScene.Setup: Loading doodad from filename at %s", s.filename)
if err := s.LoadDoodad(s.filename); err != nil {
d.FlashError("LoadDoodad error: %s", err)
}
}
// Write locked doodad?
if s.Doodad != nil && s.Doodad.Locked {
if usercfg.Current.WriteLockOverride {
d.Flash("Note: write lock has been overridden")
} else {
d.FlashError("That doodad is write-protected and cannot be viewed in the editor.")
s.Doodad = nil
s.filename = ""
}
}
// No Doodad?
if s.Doodad == nil {
log.Debug("EditorScene.Setup: initializing a new Doodad")
s.Doodad = doodads.New(s.DoodadSize.W, s.DoodadSize.H)
s.UI.Canvas.LoadDoodad(s.Doodad)
}
// Update the loading screen with level info.
loadscreen.SetSubtitle(
s.Doodad.Title,
"by "+s.Doodad.Author,
)
// TODO: move inside the UI. Just an approximate position for now.
s.UI.Canvas.Resize(s.DoodadSize)
s.UI.Canvas.ScrollTo(render.Origin)
s.UI.Canvas.Scrollable = false
s.UI.Workspace.Compute(d.Engine)
}
// Pre-cache all bitmap images from the level chunks.
// Note: we are not running on the main thread, so SDL2 Textures
// don't get created yet, but we do the full work of caching bitmap
// images which later get fed directly into SDL2 saving speed at
// runtime, + the bitmap generation is pretty wicked fast anyway.
loadscreen.PreloadAllChunkBitmaps(s.UI.Canvas.Chunker())
// Recompute the UI Palette window for the level's palette.
s.UI.FinishSetup(d)
// Scroll the level to the remembered position from when we went
// to Play Mode and back. If no remembered position, this is zero
// anyway.
if s.RememberScrollPosition.IsZero() && s.Level != nil {
s.UI.Canvas.ScrollTo(s.Level.ScrollPosition)
} else {
s.UI.Canvas.ScrollTo(s.RememberScrollPosition)
}
d.Flash("Editor Mode.")
if s.DrawingType == enum.LevelDrawing {
d.Flash("Press 'P' to playtest this level.")
}
2018-06-21 01:43:14 +00:00
return nil
}
// Common function to install the actors into the level.
//
// InstallActors may return an error if doodads were not found - because the
// player is on the free version and can't load attached doodads from nonsigned
// files.
func (s *EditorScene) installActors() error {
if err := s.UI.Canvas.InstallActors(s.Level.Actors); err != nil {
summary := "This level references some doodads that were not found:"
if strings.Contains(err.Error(), plus.ErrRegisteredFeature.Error()) {
summary = "This level contains embedded doodads, but this is not\n" +
"available in the free version of the game. The following\n" +
"doodads could not be loaded:"
}
modal.Alert("%s\n\n%s", summary, err).WithTitle("Level Errors")
return fmt.Errorf("EditorScene.LoadLevel: InstallActors: %s", err)
}
return nil
}
// Playtest switches the level into Play Mode.
func (s *EditorScene) Playtest() {
log.Info("Play Mode, Go!")
s.d.Goto(&PlayScene{
Filename: s.filename,
Level: s.Level,
CanEdit: true,
RememberScrollPosition: s.UI.Canvas.Scroll,
})
}
// PlaytestFrom enters play mode starting at a custom spawn point.
func (s *EditorScene) PlaytestFrom(p render.Point) {
log.Info("Play Mode, Go!")
s.d.Goto(&PlayScene{
Filename: s.filename,
Level: s.Level,
CanEdit: true,
RememberScrollPosition: s.UI.Canvas.Scroll,
SpawnPoint: p,
})
}
// ConfirmUnload may pop up a confirmation modal to save the level before the
// user performs an action that may close the level, such as click File->New.
func (s *EditorScene) ConfirmUnload(fn func()) {
if !s.UI.Canvas.Modified() {
fn()
return
}
modal.Confirm(
"This drawing has unsaved changes. Are you sure you\nwant to continue and lose your changes?",
).WithTitle("Confirm Closing Drawing").Then(fn)
}
2018-06-21 01:43:14 +00:00
// Loop the editor scene.
func (s *EditorScene) Loop(d *Doodle, ev *event.State) error {
// Skip if still loading.
if loadscreen.IsActive() {
return nil
}
// Update debug overlay values.
*s.debTool = s.UI.Canvas.Tool.String()
*s.debSwatch = "???"
*s.debWorldIndex = s.UI.Canvas.WorldIndexAt(s.UI.cursor).String()
*s.debLoadingViewport = "???"
// Safely...
if s.UI.Canvas.Palette != nil && s.UI.Canvas.Palette.ActiveSwatch != nil {
*s.debSwatch = s.UI.Canvas.Palette.ActiveSwatch.Name
}
if s.UI.Canvas != nil {
inside, outside := s.UI.Canvas.LoadUnloadMetrics()
*s.debLoadingViewport = fmt.Sprintf("%d in %d out %d cached %d gc", inside, outside, s.UI.Canvas.Chunker().CacheSize(), s.UI.Canvas.Chunker().GCSize())
}
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
// Has the window been resized?
if ev.WindowResized {
s.UI.Resized(d)
return nil
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
}
// Run all of the keybinds.
binders := []struct {
v bool
f func()
}{
{
keybind.NewLevel(ev), func() {
// Ctrl-N, New Level
s.MenuNewLevel()
},
},
{
keybind.SaveAs(ev), func() {
// Shift-Ctrl-S, Save As
s.MenuSave(true)()
},
},
{
keybind.Save(ev), func() {
// Ctrl-S, Save
s.MenuSave(false)()
},
},
{
keybind.Open(ev), func() {
// Ctrl-O, Open
s.MenuOpen()
},
},
{
keybind.Undo(ev), func() {
// Ctrl-Z, Undo
s.UI.Canvas.UndoStroke()
ev.ResetKeyDown()
},
},
{
keybind.Redo(ev), func() {
// Ctrl-Y, Undo
s.UI.Canvas.RedoStroke()
ev.ResetKeyDown()
},
},
{
keybind.ZoomIn(ev), func() {
s.UI.Canvas.Zoom++
ev.ResetKeyDown()
},
},
{
keybind.ZoomOut(ev), func() {
s.UI.Canvas.Zoom--
ev.ResetKeyDown()
},
},
{
keybind.ZoomReset(ev), func() {
s.UI.Canvas.Zoom = 0
ev.ResetKeyDown()
},
},
{
keybind.Origin(ev), func() {
d.Flash("Scrolled back to level origin (0,0)")
s.UI.Canvas.ScrollTo(render.Origin)
ev.ResetKeyDown()
},
},
{
keybind.CloseAllWindows(ev), func() {
s.UI.Supervisor.CloseAllWindows()
},
},
{
keybind.CloseTopmostWindow(ev), func() {
s.UI.Supervisor.CloseActiveWindow()
},
},
{
keybind.NewViewport(ev), func() {
if s.DrawingType != enum.LevelDrawing {
return
}
pip := windows.MakePiPWindow(d.width, d.height, windows.PiP{
Supervisor: s.UI.Supervisor,
Engine: s.d.Engine,
Level: s.Level,
Event: s.d.event,
Tool: &s.UI.Canvas.Tool,
BrushSize: &s.UI.Canvas.BrushSize,
})
pip.Show()
},
},
}
for _, bind := range binders {
if bind.v {
bind.f()
}
}
// s.UI.Loop(ev)
2018-06-21 01:43:14 +00:00
// Switching to Play Mode?
if s.DrawingType == enum.LevelDrawing && keybind.GotoPlay(ev) {
s.Playtest()
} else if keybind.LineTool(ev) {
d.Flash("Line Tool selected.")
s.UI.Canvas.Tool = drawtool.LineTool
s.UI.activeTool = s.UI.Canvas.Tool.String()
} else if keybind.PencilTool(ev) {
d.Flash("Pencil Tool selected.")
s.UI.Canvas.Tool = drawtool.PencilTool
s.UI.activeTool = s.UI.Canvas.Tool.String()
} else if keybind.RectTool(ev) {
d.Flash("Rectangle Tool selected.")
s.UI.Canvas.Tool = drawtool.RectTool
s.UI.activeTool = s.UI.Canvas.Tool.String()
} else if keybind.EllipseTool(ev) {
d.Flash("Ellipse Tool selected.")
s.UI.Canvas.Tool = drawtool.EllipseTool
s.UI.activeTool = s.UI.Canvas.Tool.String()
} else if keybind.EraserTool(ev) {
d.Flash("Eraser Tool selected.")
s.UI.Canvas.Tool = drawtool.EraserTool
s.UI.activeTool = s.UI.Canvas.Tool.String()
} else if keybind.DoodadDropper(ev) {
s.UI.OpenDoodadDropper()
}
s.UI.Loop(ev)
// Trigger auto-save of the level in case of crash or accidental closure.
if time.Since(s.lastAutosaveAt) > balance.AutoSaveInterval {
s.lastAutosaveAt = time.Now()
if !usercfg.Current.DisableAutosave {
if err := s.AutoSave(); err != nil {
d.FlashError("Autosave error: %s", err)
}
}
}
return nil
}
// Draw the current frame.
func (s *EditorScene) Draw(d *Doodle) error {
// Skip if still loading.
if loadscreen.IsActive() {
return nil
}
// Clear the canvas and fill it with magenta so it's clear if any spots are missed.
d.Engine.Clear(render.RGBA(160, 120, 160, 255))
s.UI.Present(d.Engine)
2018-06-21 01:43:14 +00:00
return nil
}
// LoadLevel loads a level from disk.
func (s *EditorScene) LoadLevel(filename string) error {
s.filename = filename
level, err := level.LoadFile(filename)
if err != nil {
return fmt.Errorf("EditorScene.LoadLevel(%s): %s", filename, err)
}
s.DrawingType = enum.LevelDrawing
s.Level = level
s.UI.Canvas.LoadLevel(s.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
log.Info("Installing %d actors into the drawing", len(level.Actors))
if err := s.UI.Canvas.InstallActors(level.Actors); err != nil {
summary := "This level references some doodads that were not found:"
if strings.Contains(err.Error(), plus.ErrRegisteredFeature.Error()) {
summary = "This level contains embedded doodads, but this is not\n" +
"available in the free version of the game. The following\n" +
"doodads could not be loaded:"
}
modal.Alert("%s\n\n%s", summary, err).WithTitle("Level Errors")
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
return fmt.Errorf("EditorScene.LoadLevel: InstallActors: %s", err)
}
return nil
2018-06-21 01:43:14 +00:00
}
// SaveLevel saves the level to disk.
func (s *EditorScene) SaveLevel(filename string) error {
if s.DrawingType != enum.LevelDrawing {
return errors.New("SaveLevel: current drawing is not a Level type")
}
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
if !strings.HasSuffix(filename, enum.LevelExt) {
filename += enum.LevelExt
}
s.filename = filename
m := s.Level
if m.Title == "" {
m.Title = "Alpha"
}
if m.Author == "" {
m.Author = native.DefaultAuthor
}
m.Palette = s.UI.Canvas.Palette
m.Chunker = s.UI.Canvas.Chunker()
2018-06-21 01:43:14 +00:00
// Store the scroll position.
m.ScrollPosition = s.UI.Canvas.Scroll
// Clear the modified flag on the level.
s.UI.Canvas.SetModified(false)
// Attach doodads to the level on save.
if err := publishing.Publish(m); err != nil {
log.Error("Error publishing level: %s", err.Error())
}
s.lastAutosaveAt = time.Now()
// Save screenshots into the level file.
if !m.HasScreenshot() {
s.UpdateLevelScreenshot(m)
}
return m.WriteFile(filename)
}
// UpdateLevelScreenshot updates a level screenshot in its zipfile.
func (s *EditorScene) UpdateLevelScreenshot(lvl *level.Level) error {
// The level must have been saved and have a filename to update in.
if s.filename == "" {
return errors.New("Save your level to disk before updating its screenshot.")
}
if err := giant_screenshot.UpdateLevelScreenshots(lvl, s.UI.Canvas.Viewport().Point()); err != nil {
return fmt.Errorf("Error saving level screenshots: %s", err)
}
return nil
}
// AutoSave takes an autosave snapshot of the level or drawing.
func (s *EditorScene) AutoSave() error {
var (
filename = "_autosave.level"
err error
)
s.d.FlashError("Beginning AutoSave() in a background thread")
// Trigger the auto-save in the background to not block the main thread.
go func() {
var err error
switch s.DrawingType {
case enum.LevelDrawing:
err = s.Level.WriteFile(filename)
s.d.Flash("Automatically saved level to %s", filename)
case enum.DoodadDrawing:
filename = "_autosave.doodad"
err = s.Doodad.WriteFile(filename)
s.d.Flash("Automatically saved doodad to %s", filename)
}
if err != nil {
s.d.FlashError("Error saving %s: %s", filename, err)
}
}()
return err
}
// LoadDoodad loads a doodad from disk.
func (s *EditorScene) LoadDoodad(filename string) error {
s.filename = filename
doodad, err := doodads.LoadFile(filename)
if err != nil {
return fmt.Errorf("EditorScene.LoadDoodad(%s): %s", filename, err)
2018-06-21 01:43:14 +00:00
}
s.DrawingType = enum.DoodadDrawing
s.Doodad = doodad
s.DoodadSize = doodad.Size
s.UI.Canvas.LoadDoodad(s.Doodad)
return nil
}
// SaveDoodad saves the doodad to disk.
func (s *EditorScene) SaveDoodad(filename string) error {
if s.DrawingType != enum.DoodadDrawing {
return errors.New("SaveDoodad: current drawing is not a Doodad type")
}
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
if !strings.HasSuffix(filename, enum.DoodadExt) {
filename += enum.DoodadExt
}
s.filename = filename
d := s.Doodad
if d.Title == "" {
d.Title = "Untitled Doodad"
}
if d.Author == "" {
d.Author = native.DefaultAuthor
}
// TODO: is this copying necessary?
d.Palette = s.UI.Canvas.Palette
d.Layers[s.ActiveLayer].Chunker = s.UI.Canvas.Chunker()
// Clear the modified flag on the level.
s.UI.Canvas.SetModified(false)
// Save it to their profile directory.
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
filename = userdir.DoodadPath(filename)
log.Info("Write Doodad: %s", filename)
return d.WriteFile(filename)
2018-06-21 01:43:14 +00:00
}
// Destroy the scene.
func (s *EditorScene) Destroy() error {
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
// Free SDL2 textures. Note: if they are switching to the Editor, the chunks still have
// their bitmaps cached and will regen the textures as needed.
s.UI.Teardown()
// Reset the cursor to default.
cursor.Current = cursor.NewPointer(s.d.Engine)
return nil
}