2018-06-21 01:43:14 +00:00
|
|
|
package doodle
|
|
|
|
|
|
|
|
import (
|
2018-09-26 17:04:46 +00:00
|
|
|
"errors"
|
2018-09-25 16:40:34 +00:00
|
|
|
"fmt"
|
2018-06-21 01:43:14 +00:00
|
|
|
"os"
|
2018-10-02 17:11:38 +00:00
|
|
|
"strings"
|
2022-01-03 06:36:32 +00:00
|
|
|
"time"
|
2018-06-21 01:43:14 +00:00
|
|
|
|
2022-01-03 06:36:32 +00:00
|
|
|
"git.kirsle.net/apps/doodle/pkg/balance"
|
2022-05-05 05:38:26 +00:00
|
|
|
"git.kirsle.net/apps/doodle/pkg/cursor"
|
2019-04-10 00:35:44 +00:00
|
|
|
"git.kirsle.net/apps/doodle/pkg/doodads"
|
2019-07-04 00:19:25 +00:00
|
|
|
"git.kirsle.net/apps/doodle/pkg/drawtool"
|
2019-04-10 00:35:44 +00:00
|
|
|
"git.kirsle.net/apps/doodle/pkg/enum"
|
2020-11-18 02:22:48 +00:00
|
|
|
"git.kirsle.net/apps/doodle/pkg/keybind"
|
2019-04-10 00:35:44 +00:00
|
|
|
"git.kirsle.net/apps/doodle/pkg/level"
|
2022-01-18 02:51:11 +00:00
|
|
|
"git.kirsle.net/apps/doodle/pkg/level/publishing"
|
2021-06-17 05:35:01 +00:00
|
|
|
"git.kirsle.net/apps/doodle/pkg/license"
|
2019-04-10 00:35:44 +00:00
|
|
|
"git.kirsle.net/apps/doodle/pkg/log"
|
2020-11-16 02:02:35 +00:00
|
|
|
"git.kirsle.net/apps/doodle/pkg/modal"
|
2021-07-19 03:04:24 +00:00
|
|
|
"git.kirsle.net/apps/doodle/pkg/modal/loadscreen"
|
2021-06-20 05:14:41 +00:00
|
|
|
"git.kirsle.net/apps/doodle/pkg/usercfg"
|
2018-10-19 20:31:58 +00:00
|
|
|
"git.kirsle.net/apps/doodle/pkg/userdir"
|
2021-10-07 05:22:34 +00:00
|
|
|
"git.kirsle.net/apps/doodle/pkg/windows"
|
2019-12-28 03:16:34 +00:00
|
|
|
"git.kirsle.net/go/render"
|
|
|
|
"git.kirsle.net/go/render/event"
|
2018-06-21 01:43:14 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// EditorScene manages the "Edit Level" game mode.
|
|
|
|
type EditorScene struct {
|
2018-07-24 03:10:53 +00:00
|
|
|
// Configuration for the scene initializer.
|
2021-10-05 03:49:11 +00:00
|
|
|
DrawingType enum.DrawingType
|
|
|
|
OpenFile bool
|
|
|
|
Filename string
|
|
|
|
DoodadSize int
|
|
|
|
RememberScrollPosition render.Point // Play mode remembers it for us
|
2018-07-24 03:10:53 +00:00
|
|
|
|
2018-08-05 19:54:57 +00:00
|
|
|
UI *EditorUI
|
2018-10-28 05:22:13 +00:00
|
|
|
d *Doodle
|
2018-08-05 19:54:57 +00:00
|
|
|
|
2018-09-26 17:04:46 +00:00
|
|
|
// The current level or doodad object being edited, based on the
|
|
|
|
// DrawingType.
|
2020-11-17 07:20:24 +00:00
|
|
|
Level *level.Level
|
|
|
|
Doodad *doodads.Doodad
|
|
|
|
ActiveLayer int // which layer (of a doodad) is being edited now?
|
2018-09-25 16:40:34 +00:00
|
|
|
|
2019-04-10 01:28:08 +00:00
|
|
|
// Custom debug overlay values.
|
2022-04-10 19:39:27 +00:00
|
|
|
debTool *string
|
|
|
|
debSwatch *string
|
|
|
|
debWorldIndex *string
|
|
|
|
debLoadingViewport *string
|
2019-04-10 01:28:08 +00:00
|
|
|
|
2018-09-25 16:40:34 +00:00
|
|
|
// Last saved filename by the user.
|
|
|
|
filename string
|
2022-01-03 06:36:32 +00:00
|
|
|
|
|
|
|
lastAutosaveAt time.Time
|
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.
|
2018-07-21 22:11:00 +00:00
|
|
|
func (s *EditorScene) Setup(d *Doodle) error {
|
2019-04-10 01:28:08 +00:00
|
|
|
// Debug overlay values.
|
|
|
|
s.debTool = new(string)
|
|
|
|
s.debSwatch = new(string)
|
|
|
|
s.debWorldIndex = new(string)
|
2022-04-10 19:39:27 +00:00
|
|
|
s.debLoadingViewport = new(string)
|
2019-04-10 01:28:08 +00:00
|
|
|
customDebugLabels = []debugLabel{
|
2019-04-19 22:08:00 +00:00
|
|
|
{"Pixel:", s.debWorldIndex},
|
|
|
|
{"Tool:", s.debTool},
|
|
|
|
{"Swatch:", s.debSwatch},
|
2022-04-10 19:39:27 +00:00
|
|
|
{"Chunks:", s.debLoadingViewport},
|
2019-04-10 01:28:08 +00:00
|
|
|
}
|
|
|
|
|
2022-01-03 06:36:32 +00:00
|
|
|
// Initialize autosave time.
|
|
|
|
s.lastAutosaveAt = time.Now()
|
|
|
|
|
2021-07-19 03:04:24 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2021-09-12 04:18:22 +00:00
|
|
|
// 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,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-07-19 03:04:24 +00:00
|
|
|
// setupAsync initializes trhe editor scene in the background,
|
|
|
|
// underneath a loading screen.
|
|
|
|
func (s *EditorScene) setupAsync(d *Doodle) error {
|
2018-10-08 17:38:49 +00:00
|
|
|
// Initialize the user interface. It references the palette and such so it
|
|
|
|
// must be initialized after those things.
|
2018-10-28 05:22:13 +00:00
|
|
|
s.d = d
|
2018-10-08 17:38:49 +00:00
|
|
|
s.UI = NewEditorUI(d, s)
|
2018-09-25 16:40:34 +00:00
|
|
|
|
2018-09-26 17:04:46 +00:00
|
|
|
// Were we given configuration data?
|
2018-07-24 03:10:53 +00:00
|
|
|
if s.Filename != "" {
|
2018-09-25 16:40:34 +00:00
|
|
|
log.Debug("EditorScene.Setup: Set filename to %s", s.Filename)
|
2018-07-24 03:10:53 +00:00
|
|
|
s.filename = s.Filename
|
|
|
|
s.Filename = ""
|
2018-09-25 16:40:34 +00:00
|
|
|
}
|
2018-09-26 17:04:46 +00:00
|
|
|
|
|
|
|
// 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")
|
2021-07-19 03:04:24 +00:00
|
|
|
loadscreen.SetSubtitle(
|
|
|
|
"Opening: "+s.Level.Title,
|
|
|
|
"by "+s.Level.Author,
|
|
|
|
)
|
2021-07-20 00:14:00 +00:00
|
|
|
s.UI.Canvas.LoadLevel(s.Level)
|
2019-04-14 22:25:03 +00:00
|
|
|
s.UI.Canvas.InstallActors(s.Level.Actors)
|
2018-09-26 17:04:46 +00:00
|
|
|
} else if s.filename != "" && s.OpenFile {
|
|
|
|
log.Debug("EditorScene.Setup: Loading map from filename at %s", s.filename)
|
2021-07-19 03:04:24 +00:00
|
|
|
loadscreen.SetSubtitle(
|
|
|
|
"Opening: " + s.filename,
|
|
|
|
)
|
2018-09-26 17:04:46 +00:00
|
|
|
if err := s.LoadLevel(s.filename); err != nil {
|
2021-10-08 01:24:18 +00:00
|
|
|
d.FlashError("LoadLevel error: %s", err)
|
2019-04-14 22:25:03 +00:00
|
|
|
} else {
|
|
|
|
s.UI.Canvas.InstallActors(s.Level.Actors)
|
2018-09-26 17:04:46 +00:00
|
|
|
}
|
2018-07-24 03:10:53 +00:00
|
|
|
}
|
2018-09-25 16:40:34 +00:00
|
|
|
|
2019-07-07 06:28:11 +00:00
|
|
|
// Write locked level?
|
|
|
|
if s.Level != nil && s.Level.Locked {
|
2021-06-20 05:14:41 +00:00
|
|
|
if usercfg.Current.WriteLockOverride {
|
2019-07-07 06:28:11 +00:00
|
|
|
d.Flash("Note: write lock has been overridden")
|
|
|
|
} else {
|
2021-10-08 01:24:18 +00:00
|
|
|
d.FlashError("That level is write-protected and cannot be viewed in the editor.")
|
2019-07-07 06:28:11 +00:00
|
|
|
s.Level = nil
|
|
|
|
s.UI.Canvas.ClearActors()
|
|
|
|
s.filename = ""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-26 17:04:46 +00:00
|
|
|
// No level?
|
|
|
|
if s.Level == nil {
|
|
|
|
log.Debug("EditorScene.Setup: initializing a new Level")
|
|
|
|
s.Level = level.New()
|
|
|
|
s.Level.Palette = level.DefaultPalette()
|
2021-07-20 00:14:00 +00:00
|
|
|
s.UI.Canvas.LoadLevel(s.Level)
|
2018-10-08 17:38:49 +00:00
|
|
|
s.UI.Canvas.ScrollTo(render.Origin)
|
|
|
|
s.UI.Canvas.Scrollable = true
|
2018-09-26 17:04:46 +00:00
|
|
|
}
|
2021-07-19 03:04:24 +00:00
|
|
|
|
|
|
|
// Update the loading screen with level info.
|
|
|
|
loadscreen.SetSubtitle(
|
|
|
|
"Opening: "+s.Level.Title,
|
|
|
|
"by "+s.Level.Author,
|
|
|
|
)
|
2018-09-26 17:04:46 +00:00
|
|
|
case enum.DoodadDrawing:
|
2019-07-07 06:28:11 +00:00
|
|
|
// Getting a doodad from file?
|
2018-09-26 17:04:46 +00:00
|
|
|
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 {
|
2021-10-08 01:24:18 +00:00
|
|
|
d.FlashError("LoadDoodad error: %s", err)
|
2018-09-26 17:04:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-07 06:28:11 +00:00
|
|
|
// Write locked doodad?
|
|
|
|
if s.Doodad != nil && s.Doodad.Locked {
|
2021-06-20 05:14:41 +00:00
|
|
|
if usercfg.Current.WriteLockOverride {
|
2019-07-07 06:28:11 +00:00
|
|
|
d.Flash("Note: write lock has been overridden")
|
|
|
|
} else {
|
2021-10-08 01:24:18 +00:00
|
|
|
d.FlashError("That doodad is write-protected and cannot be viewed in the editor.")
|
2019-07-07 06:28:11 +00:00
|
|
|
s.Doodad = nil
|
|
|
|
s.filename = ""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-26 17:04:46 +00:00
|
|
|
// No Doodad?
|
|
|
|
if s.Doodad == nil {
|
|
|
|
log.Debug("EditorScene.Setup: initializing a new Doodad")
|
|
|
|
s.Doodad = doodads.New(s.DoodadSize)
|
2018-10-08 17:38:49 +00:00
|
|
|
s.UI.Canvas.LoadDoodad(s.Doodad)
|
2018-09-26 17:04:46 +00:00
|
|
|
}
|
|
|
|
|
2021-07-19 03:04:24 +00:00
|
|
|
// Update the loading screen with level info.
|
|
|
|
loadscreen.SetSubtitle(
|
|
|
|
s.Doodad.Title,
|
|
|
|
"by "+s.Doodad.Author,
|
|
|
|
)
|
|
|
|
|
2018-09-26 17:04:46 +00:00
|
|
|
// TODO: move inside the UI. Just an approximate position for now.
|
2019-12-28 03:16:34 +00:00
|
|
|
s.UI.Canvas.Resize(render.NewRect(s.DoodadSize, s.DoodadSize))
|
2018-10-08 17:38:49 +00:00
|
|
|
s.UI.Canvas.ScrollTo(render.Origin)
|
|
|
|
s.UI.Canvas.Scrollable = false
|
|
|
|
s.UI.Workspace.Compute(d.Engine)
|
2018-07-24 03:10:53 +00:00
|
|
|
}
|
|
|
|
|
2021-07-19 03:04:24 +00:00
|
|
|
// 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())
|
|
|
|
|
2019-06-26 00:43:23 +00:00
|
|
|
// Recompute the UI Palette window for the level's palette.
|
|
|
|
s.UI.FinishSetup(d)
|
|
|
|
|
2021-10-05 03:49:11 +00:00
|
|
|
// Scroll the level to the remembered position from when we went
|
|
|
|
// to Play Mode and back. If no remembered position, this is zero
|
|
|
|
// anyway.
|
2022-05-06 04:35:32 +00:00
|
|
|
if s.RememberScrollPosition.IsZero() && s.Level != nil {
|
|
|
|
s.UI.Canvas.ScrollTo(s.Level.ScrollPosition)
|
|
|
|
} else {
|
|
|
|
s.UI.Canvas.ScrollTo(s.RememberScrollPosition)
|
|
|
|
}
|
2021-10-05 03:49:11 +00:00
|
|
|
|
2021-07-19 03:04:24 +00:00
|
|
|
d.Flash("Editor Mode.")
|
|
|
|
if s.DrawingType == enum.LevelDrawing {
|
|
|
|
d.Flash("Press 'P' to playtest this level.")
|
|
|
|
}
|
2018-07-24 03:10:53 +00:00
|
|
|
|
2018-06-21 01:43:14 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-06-26 01:36:53 +00:00
|
|
|
// Playtest switches the level into Play Mode.
|
|
|
|
func (s *EditorScene) Playtest() {
|
|
|
|
log.Info("Play Mode, Go!")
|
|
|
|
s.d.Goto(&PlayScene{
|
2021-10-05 03:49:11 +00:00
|
|
|
Filename: s.filename,
|
|
|
|
Level: s.Level,
|
|
|
|
CanEdit: true,
|
|
|
|
RememberScrollPosition: s.UI.Canvas.Scroll,
|
2019-06-26 01:36:53 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-10-05 05:02:00 +00:00
|
|
|
// 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,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-11-16 02:02:35 +00:00
|
|
|
// 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(
|
2020-11-17 07:20:24 +00:00
|
|
|
"This drawing has unsaved changes. Are you sure you\nwant to continue and lose your changes?",
|
|
|
|
).WithTitle("Confirm Closing Drawing").Then(fn)
|
2020-11-16 02:02:35 +00:00
|
|
|
}
|
|
|
|
|
2018-06-21 01:43:14 +00:00
|
|
|
// Loop the editor scene.
|
2019-12-22 22:11:01 +00:00
|
|
|
func (s *EditorScene) Loop(d *Doodle, ev *event.State) error {
|
2021-07-19 03:04:24 +00:00
|
|
|
// Skip if still loading.
|
|
|
|
if loadscreen.IsActive() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-04-10 01:28:08 +00:00
|
|
|
// Update debug overlay values.
|
|
|
|
*s.debTool = s.UI.Canvas.Tool.String()
|
2021-07-14 02:23:09 +00:00
|
|
|
*s.debSwatch = "???"
|
2019-04-10 01:28:08 +00:00
|
|
|
*s.debWorldIndex = s.UI.Canvas.WorldIndexAt(s.UI.cursor).String()
|
2022-04-10 19:39:27 +00:00
|
|
|
*s.debLoadingViewport = "???"
|
2019-04-10 01:28:08 +00:00
|
|
|
|
2021-07-14 02:23:09 +00:00
|
|
|
// Safely...
|
|
|
|
if s.UI.Canvas.Palette != nil && s.UI.Canvas.Palette.ActiveSwatch != nil {
|
|
|
|
*s.debSwatch = s.UI.Canvas.Palette.ActiveSwatch.Name
|
|
|
|
}
|
2022-04-10 19:39:27 +00:00
|
|
|
if s.UI.Canvas != nil {
|
|
|
|
inside, outside := s.UI.Canvas.LoadUnloadMetrics()
|
2022-05-08 00:16:03 +00:00
|
|
|
*s.debLoadingViewport = fmt.Sprintf("%d in %d out %d cached %d gc", inside, outside, s.UI.Canvas.Chunker().CacheSize(), s.UI.Canvas.Chunker().GCSize())
|
2022-04-10 19:39:27 +00:00
|
|
|
}
|
2021-07-14 02:23:09 +00:00
|
|
|
|
2018-10-19 20:31:58 +00:00
|
|
|
// Has the window been resized?
|
2019-12-22 22:11:01 +00:00
|
|
|
if ev.WindowResized {
|
2022-03-06 20:07:59 +00:00
|
|
|
s.UI.Resized(d)
|
|
|
|
return nil
|
2018-10-19 20:31:58 +00:00
|
|
|
}
|
|
|
|
|
2021-10-07 05:22:34 +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()
|
|
|
|
},
|
|
|
|
},
|
2021-06-18 02:43:30 +00:00
|
|
|
}
|
2021-10-07 05:22:34 +00:00
|
|
|
for _, bind := range binders {
|
|
|
|
if bind.v {
|
|
|
|
bind.f()
|
2020-11-20 04:08:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-18 02:43:30 +00:00
|
|
|
// s.UI.Loop(ev)
|
2018-06-21 01:43:14 +00:00
|
|
|
|
2018-07-24 03:10:53 +00:00
|
|
|
// Switching to Play Mode?
|
2021-07-19 03:04:24 +00:00
|
|
|
if s.DrawingType == enum.LevelDrawing && keybind.GotoPlay(ev) {
|
2019-06-26 01:36:53 +00:00
|
|
|
s.Playtest()
|
2020-11-18 02:22:48 +00:00
|
|
|
} else if keybind.LineTool(ev) {
|
2019-07-04 00:19:25 +00:00
|
|
|
d.Flash("Line Tool selected.")
|
|
|
|
s.UI.Canvas.Tool = drawtool.LineTool
|
2019-07-04 04:55:15 +00:00
|
|
|
s.UI.activeTool = s.UI.Canvas.Tool.String()
|
2020-11-18 02:22:48 +00:00
|
|
|
} else if keybind.PencilTool(ev) {
|
2019-07-04 00:19:25 +00:00
|
|
|
d.Flash("Pencil Tool selected.")
|
|
|
|
s.UI.Canvas.Tool = drawtool.PencilTool
|
2019-07-04 04:55:15 +00:00
|
|
|
s.UI.activeTool = s.UI.Canvas.Tool.String()
|
2020-11-18 02:22:48 +00:00
|
|
|
} else if keybind.RectTool(ev) {
|
2019-07-04 00:19:25 +00:00
|
|
|
d.Flash("Rectangle Tool selected.")
|
|
|
|
s.UI.Canvas.Tool = drawtool.RectTool
|
2019-07-04 04:55:15 +00:00
|
|
|
s.UI.activeTool = s.UI.Canvas.Tool.String()
|
2020-11-18 02:22:48 +00:00
|
|
|
} 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) {
|
2022-02-20 04:20:58 +00:00
|
|
|
s.UI.OpenDoodadDropper()
|
2018-07-24 03:10:53 +00:00
|
|
|
}
|
|
|
|
|
2021-06-18 02:43:30 +00:00
|
|
|
s.UI.Loop(ev)
|
|
|
|
|
2022-01-03 06:36:32 +00:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-22 03:43:01 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Draw the current frame.
|
|
|
|
func (s *EditorScene) Draw(d *Doodle) error {
|
2021-07-19 03:04:24 +00:00
|
|
|
// Skip if still loading.
|
|
|
|
if loadscreen.IsActive() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-08-17 03:37:19 +00:00
|
|
|
// Clear the canvas and fill it with magenta so it's clear if any spots are missed.
|
2019-07-02 22:24:46 +00:00
|
|
|
d.Engine.Clear(render.RGBA(160, 120, 160, 255))
|
Menu Toolbar for Editor + Shell Prompts + Theme
* Added a "menu toolbar" to the top of the Edit Mode with useful buttons
that work: New Level, New Doodad (same thing), Save, Save as, Open.
* Added ability for the dev console to prompt the user for a question,
which opens the console automatically. "Save", "Save as" and "Load"
ask for their filenames this way.
* Started groundwork for theming the app. The palette window is a light
brown with an orange title bar, the Menu Toolbar has a black
background, etc.
* Added support for multiple fonts instead of just monospace. DejaVu
Sans (normal and bold) are used now for most labels and window titles,
respectively. The dev console uses DejaVu Sans Mono as before.
* Update ui.Label to accept PadX and PadY separately instead of only
having the Padding option which did both.
* Improvements to Frame packing algorithm.
* Set the SDL draw mode to BLEND so we can use alpha colors properly,
so now the dev console is semi-translucent.
2018-08-12 00:30:00 +00:00
|
|
|
|
2018-08-05 19:54:57 +00:00
|
|
|
s.UI.Present(d.Engine)
|
2018-08-17 03:37:19 +00:00
|
|
|
|
2018-06-21 01:43:14 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// LoadLevel loads a level from disk.
|
2018-07-21 22:11:00 +00:00
|
|
|
func (s *EditorScene) LoadLevel(filename string) error {
|
2018-07-22 03:43:01 +00:00
|
|
|
s.filename = filename
|
2018-08-11 00:19:47 +00:00
|
|
|
|
2019-05-05 21:03:20 +00:00
|
|
|
level, err := level.LoadFile(filename)
|
2018-09-25 16:40:34 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("EditorScene.LoadLevel(%s): %s", filename, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
s.DrawingType = enum.LevelDrawing
|
|
|
|
s.Level = level
|
2021-07-20 00:14:00 +00:00
|
|
|
s.UI.Canvas.LoadLevel(s.Level)
|
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 {
|
2021-06-17 05:35:01 +00:00
|
|
|
summary := "This level references some doodads that were not found:"
|
|
|
|
if strings.Contains(err.Error(), license.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")
|
2018-10-19 20:31:58 +00:00
|
|
|
return fmt.Errorf("EditorScene.LoadLevel: InstallActors: %s", err)
|
|
|
|
}
|
|
|
|
|
2018-09-25 16:40:34 +00:00
|
|
|
return nil
|
2018-06-21 01:43:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// SaveLevel saves the level to disk.
|
2018-09-26 17:04:46 +00:00
|
|
|
func (s *EditorScene) SaveLevel(filename string) error {
|
|
|
|
if s.DrawingType != enum.LevelDrawing {
|
|
|
|
return errors.New("SaveLevel: current drawing is not a Level type")
|
|
|
|
}
|
|
|
|
|
2018-10-19 20:31:58 +00:00
|
|
|
if !strings.HasSuffix(filename, enum.LevelExt) {
|
|
|
|
filename += enum.LevelExt
|
2018-10-02 17:11:38 +00:00
|
|
|
}
|
|
|
|
|
2018-07-22 03:43:01 +00:00
|
|
|
s.filename = filename
|
2018-08-11 00:19:47 +00:00
|
|
|
|
2018-09-25 16:40:34 +00:00
|
|
|
m := s.Level
|
|
|
|
if m.Title == "" {
|
|
|
|
m.Title = "Alpha"
|
|
|
|
}
|
|
|
|
if m.Author == "" {
|
|
|
|
m.Author = os.Getenv("USER")
|
|
|
|
}
|
|
|
|
|
2018-10-08 17:38:49 +00:00
|
|
|
m.Palette = s.UI.Canvas.Palette
|
|
|
|
m.Chunker = s.UI.Canvas.Chunker()
|
2018-06-21 01:43:14 +00:00
|
|
|
|
2022-05-06 04:35:32 +00:00
|
|
|
// Store the scroll position.
|
|
|
|
m.ScrollPosition = s.UI.Canvas.Scroll
|
|
|
|
|
2020-11-16 02:02:35 +00:00
|
|
|
// Clear the modified flag on the level.
|
|
|
|
s.UI.Canvas.SetModified(false)
|
|
|
|
|
2022-01-18 02:51:11 +00:00
|
|
|
// Attach doodads to the level on save.
|
|
|
|
if err := publishing.Publish(m); err != nil {
|
|
|
|
log.Error("Error publishing level: %s", err.Error())
|
|
|
|
}
|
|
|
|
|
2022-05-03 03:35:53 +00:00
|
|
|
s.lastAutosaveAt = time.Now()
|
2019-05-05 21:03:20 +00:00
|
|
|
return m.WriteFile(filename)
|
2018-09-26 17:04:46 +00:00
|
|
|
}
|
|
|
|
|
2022-01-03 06:36:32 +00:00
|
|
|
// AutoSave takes an autosave snapshot of the level or drawing.
|
|
|
|
func (s *EditorScene) AutoSave() error {
|
2022-05-03 03:35:53 +00:00
|
|
|
var (
|
|
|
|
filename = "_autosave.level"
|
|
|
|
err error
|
|
|
|
)
|
2022-01-03 06:36:32 +00:00
|
|
|
|
2022-05-03 03:35:53 +00:00
|
|
|
s.d.FlashError("Beginning AutoSave() in a background thread")
|
2022-01-03 06:36:32 +00:00
|
|
|
|
2022-05-03 03:35:53 +00:00
|
|
|
// 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)
|
|
|
|
}
|
2022-01-03 06:36:32 +00:00
|
|
|
|
2022-05-03 03:35:53 +00:00
|
|
|
if err != nil {
|
|
|
|
s.d.FlashError("Error saving %s: %s", filename, err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
return err
|
2022-01-03 06:36:32 +00:00
|
|
|
}
|
|
|
|
|
2018-09-26 17:04:46 +00:00
|
|
|
// LoadDoodad loads a doodad from disk.
|
|
|
|
func (s *EditorScene) LoadDoodad(filename string) error {
|
|
|
|
s.filename = filename
|
|
|
|
|
2019-05-05 22:12:15 +00:00
|
|
|
doodad, err := doodads.LoadFile(filename)
|
2018-09-26 17:04:46 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("EditorScene.LoadDoodad(%s): %s", filename, err)
|
2018-06-21 01:43:14 +00:00
|
|
|
}
|
2018-09-26 17:04:46 +00:00
|
|
|
|
|
|
|
s.DrawingType = enum.DoodadDrawing
|
|
|
|
s.Doodad = doodad
|
|
|
|
s.DoodadSize = doodad.Layers[0].Chunker.Size
|
2018-10-08 17:38:49 +00:00
|
|
|
s.UI.Canvas.LoadDoodad(s.Doodad)
|
2018-09-26 17:04:46 +00:00
|
|
|
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")
|
|
|
|
}
|
|
|
|
|
2018-10-19 20:31:58 +00:00
|
|
|
if !strings.HasSuffix(filename, enum.DoodadExt) {
|
|
|
|
filename += enum.DoodadExt
|
2018-10-02 17:11:38 +00:00
|
|
|
}
|
|
|
|
|
2018-09-26 17:04:46 +00:00
|
|
|
s.filename = filename
|
|
|
|
d := s.Doodad
|
|
|
|
if d.Title == "" {
|
|
|
|
d.Title = "Untitled Doodad"
|
|
|
|
}
|
|
|
|
if d.Author == "" {
|
|
|
|
d.Author = os.Getenv("USER")
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: is this copying necessary?
|
2018-10-08 17:38:49 +00:00
|
|
|
d.Palette = s.UI.Canvas.Palette
|
2020-11-17 07:20:24 +00:00
|
|
|
d.Layers[s.ActiveLayer].Chunker = s.UI.Canvas.Chunker()
|
2018-09-26 17:04:46 +00:00
|
|
|
|
2020-11-16 02:02:35 +00:00
|
|
|
// Clear the modified flag on the level.
|
|
|
|
s.UI.Canvas.SetModified(false)
|
|
|
|
|
2018-10-02 17:11:38 +00:00
|
|
|
// Save it to their profile directory.
|
2018-10-19 20:31:58 +00:00
|
|
|
filename = userdir.DoodadPath(filename)
|
2018-10-02 17:11:38 +00:00
|
|
|
log.Info("Write Doodad: %s", filename)
|
2019-06-24 00:52:48 +00:00
|
|
|
return d.WriteFile(filename)
|
2018-06-21 01:43:14 +00:00
|
|
|
}
|
|
|
|
|
2018-07-24 03:10:53 +00:00
|
|
|
// Destroy the scene.
|
|
|
|
func (s *EditorScene) Destroy() error {
|
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()
|
|
|
|
|
2022-05-05 05:38:26 +00:00
|
|
|
// Reset the cursor to default.
|
|
|
|
cursor.Current = cursor.NewPointer(s.d.Engine)
|
|
|
|
|
2018-07-24 03:10:53 +00:00
|
|
|
return nil
|
|
|
|
}
|