From f0101ba0489891befc52de450bb9bbe044218e36 Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Mon, 6 Apr 2020 23:21:17 -0700 Subject: [PATCH] The Window Manager Update * Take advantage of the new Window Manager feature of the UI toolkit. * Move the MenuScene's "New Level" and "Play/Edit Level" windows into stand-alone functions in new pkg/windows/ package. The 'windows' package is isolated from the rest of Doodle and communicates using config variables and callback functions to avoid circular dependency. * MenuScene calls the window constructors from the new package. * Add an "Options" button to the Menu Bar in the Editor Scene, which opens the "New Level" window to allow changing the wallpaper or bounding type of the level currently being edited. * Move the cheat codes into their own file, cheats.go --- pkg/cheats.go | 93 +++++++ pkg/commands.go | 55 +--- pkg/editor_ui.go | 73 +++++- pkg/editor_ui_doodad.go | 9 +- pkg/editor_ui_palette.go | 7 +- pkg/editor_ui_toolbar.go | 6 +- pkg/guitest_scene.go | 12 +- pkg/main_scene.go | 6 +- pkg/menu_scene.go | 413 ++++--------------------------- pkg/play_scene.go | 6 +- pkg/uix/actor.go | 7 + pkg/windows/add_edit_level.go | 242 ++++++++++++++++++ pkg/windows/open_level_editor.go | 203 +++++++++++++++ 13 files changed, 685 insertions(+), 447 deletions(-) create mode 100644 pkg/cheats.go create mode 100644 pkg/windows/add_edit_level.go create mode 100644 pkg/windows/open_level_editor.go diff --git a/pkg/cheats.go b/pkg/cheats.go new file mode 100644 index 0000000..07c7cf2 --- /dev/null +++ b/pkg/cheats.go @@ -0,0 +1,93 @@ +package doodle + +// cheatCommand is a subroutine of the Command.Run() method of the Doodle +// developer shell (commands.go). It looks for special cheat codes entered +// into the command shell and executes them. +// +// Returns true if a cheat was intercepted, false if the command is not a cheat. +func (c Command) cheatCommand(d *Doodle) bool { + // Some cheats only work in Play Mode. + playScene, isPlay := d.Scene.(*PlayScene) + + // Cheat codes + switch c.Raw { + case "unleash the beast": + if fpsDoNotCap { + d.Flash("Reset frame rate throttle to factory default FPS") + } else { + d.Flash("Unleashing as many frames as we can render!") + } + fpsDoNotCap = !fpsDoNotCap + + case "don't edit and drive": + if isPlay { + playScene.drawing.Editable = true + d.Flash("Level canvas is now editable. Don't edit and drive!") + } else { + d.Flash("Use this cheat in Play Mode to make the level canvas editable.") + } + + case "scroll scroll scroll your boat": + if isPlay { + playScene.drawing.Scrollable = true + d.Flash("Level canvas is now scrollable with the arrow keys.") + } else { + d.Flash("Use this cheat in Play Mode to make the level scrollable.") + } + + case "import antigravity": + if isPlay { + playScene.antigravity = !playScene.antigravity + playScene.Player.SetGravity(!playScene.antigravity) + + if playScene.antigravity { + d.Flash("Gravity disabled for player character.") + } else { + d.Flash("Gravity restored for player character.") + } + } else { + d.Flash("Use this cheat in Play Mode to disable gravity for the player character.") + } + + case "ghost mode": + if isPlay { + playScene.noclip = !playScene.noclip + playScene.Player.SetNoclip(playScene.noclip) + + playScene.antigravity = playScene.noclip + playScene.Player.SetGravity(!playScene.antigravity) + + if playScene.noclip { + d.Flash("Clipping disabled for player character.") + } else { + d.Flash("Clipping and gravity restored for player character.") + } + } else { + d.Flash("Use this cheat in Play Mode to disable clipping for the player character.") + } + + case "give all keys": + if isPlay { + playScene.Player.AddItem("key-red.doodad", 0) + playScene.Player.AddItem("key-blue.doodad", 0) + playScene.Player.AddItem("key-green.doodad", 0) + playScene.Player.AddItem("key-yellow.doodad", 0) + d.Flash("Given all keys to the player character.") + } else { + d.Flash("Use this cheat in Play Mode to get all colored keys.") + } + + case "drop all items": + if isPlay { + playScene.Player.ClearInventory() + d.Flash("Cleared inventory of player character.") + } else { + d.Flash("Use this cheat in Play Mode to clear your inventory.") + } + + default: + return false + } + + return true +} diff --git a/pkg/commands.go b/pkg/commands.go index 7c61058..c2841ff 100644 --- a/pkg/commands.go +++ b/pkg/commands.go @@ -25,60 +25,7 @@ func (c Command) Run(d *Doodle) error { } // Cheat codes - if c.Raw == "unleash the beast" { - if fpsDoNotCap { - d.Flash("Reset frame rate throttle to factory default FPS") - } else { - d.Flash("Unleashing as many frames as we can render!") - } - fpsDoNotCap = !fpsDoNotCap - return nil - } else if c.Raw == "don't edit and drive" { - if playScene, ok := d.Scene.(*PlayScene); ok { - playScene.drawing.Editable = true - d.Flash("Level canvas is now editable. Don't edit and drive!") - } else { - d.Flash("Use this cheat in Play Mode to make the level canvas editable.") - } - return nil - } else if c.Raw == "scroll scroll scroll your boat" { - if playScene, ok := d.Scene.(*PlayScene); ok { - playScene.drawing.Scrollable = true - d.Flash("Level canvas is now scrollable with the arrow keys.") - } else { - d.Flash("Use this cheat in Play Mode to make the level scrollable.") - } - return nil - } else if c.Raw == "import antigravity" { - if playScene, ok := d.Scene.(*PlayScene); ok { - playScene.antigravity = !playScene.antigravity - playScene.Player.SetGravity(!playScene.antigravity) - - if playScene.antigravity { - d.Flash("Gravity disabled for player character.") - } else { - d.Flash("Gravity restored for player character.") - } - } else { - d.Flash("Use this cheat in Play Mode to disable gravity for the player character.") - } - return nil - } else if c.Raw == "ghost mode" { - if playScene, ok := d.Scene.(*PlayScene); ok { - playScene.noclip = !playScene.noclip - playScene.Player.SetNoclip(playScene.noclip) - - playScene.antigravity = playScene.noclip - playScene.Player.SetGravity(!playScene.antigravity) - - if playScene.noclip { - d.Flash("Clipping disabled for player character.") - } else { - d.Flash("Clipping and gravity restored for player character.") - } - } else { - d.Flash("Use this cheat in Play Mode to disable clipping for the player character.") - } + if cheat := c.cheatCommand(d); cheat { return nil } diff --git a/pkg/editor_ui.go b/pkg/editor_ui.go index 0542b1c..f194e03 100644 --- a/pkg/editor_ui.go +++ b/pkg/editor_ui.go @@ -12,6 +12,7 @@ import ( "git.kirsle.net/apps/doodle/pkg/level" "git.kirsle.net/apps/doodle/pkg/log" "git.kirsle.net/apps/doodle/pkg/uix" + "git.kirsle.net/apps/doodle/pkg/windows" "git.kirsle.net/go/render" "git.kirsle.net/go/render/event" "git.kirsle.net/go/ui" @@ -43,6 +44,9 @@ type EditorUI struct { ToolBar *ui.Frame PlayButton *ui.Button + // Popup windows. + levelSettingsWindow *ui.Window + // Palette window. Palette *ui.Window PaletteTab *ui.Frame @@ -98,8 +102,9 @@ func NewEditorUI(d *Doodle, s *EditorScene) *EditorUI { Text: "Play (P)", Font: balance.PlayButtonFont, })) - u.PlayButton.Handle(ui.Click, func(ed ui.EventData) { + u.PlayButton.Handle(ui.Click, func(ed ui.EventData) error { u.Scene.Playtest() + return nil }) u.Supervisor.Add(u.PlayButton) @@ -302,6 +307,9 @@ func (u *EditorUI) Present(e render.Engine) { )) } } + + // Draw any windows being managed by Supervisor. + u.Supervisor.Present(e) } // SetupWorkspace configures the main Workspace frame that takes up the full @@ -354,7 +362,7 @@ func (u *EditorUI) SetupCanvas(d *Doodle) *uix.Canvas { // Set up the drop handler for draggable doodads. // NOTE: The drag event begins at editor_ui_doodad.go when configuring the // Doodad Palette buttons. - drawing.Handle(ui.Drop, func(ed ui.EventData) { + drawing.Handle(ui.Drop, func(ed ui.EventData) error { log.Info("Drawing canvas has received a drop!") var P = ui.AbsolutePosition(drawing) @@ -363,7 +371,7 @@ func (u *EditorUI) SetupCanvas(d *Doodle) *uix.Canvas { log.Info("Actor is a %s", actor.doodad.Filename) if u.Scene.Level == nil { u.d.Flash("Can't drop doodads onto doodad drawings!") - return + return nil } var ( @@ -391,6 +399,8 @@ func (u *EditorUI) SetupCanvas(d *Doodle) *uix.Canvas { log.Error("Error installing actor onDrop to canvas: %s", err) } } + + return nil }) u.Supervisor.Add(drawing) return drawing @@ -439,18 +449,19 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.Frame { type menuButton struct { Text string - Click func(ui.EventData) + Click func(ui.EventData) error } buttons := []menuButton{ menuButton{ Text: "New Level", - Click: func(ed ui.EventData) { + Click: func(ed ui.EventData) error { d.GotoNewMenu() + return nil }, }, menuButton{ Text: "New Doodad", - Click: func(ed ui.EventData) { + Click: func(ed ui.EventData) error { d.Prompt("Doodad size [100]>", func(answer string) { size := balance.DoodadSize if answer != "" { @@ -463,11 +474,13 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.Frame { } d.NewDoodad(size) }) + + return nil }, }, menuButton{ Text: "Save", - Click: func(ed ui.EventData) { + Click: func(ed ui.EventData) error { if u.Scene.filename != "" { saveFunc(u.Scene.filename) } else { @@ -477,22 +490,64 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.Frame { } }) } + return nil }, }, menuButton{ Text: "Save as...", - Click: func(ed ui.EventData) { + Click: func(ed ui.EventData) error { d.Prompt("Save as filename>", func(answer string) { if answer != "" { saveFunc(answer) } }) + return nil }, }, menuButton{ Text: "Load", - Click: func(ed ui.EventData) { + Click: func(ed ui.EventData) error { d.GotoLoadMenu() + return nil + }, + }, + menuButton{ + Text: "Options", + Click: func(ed ui.EventData) error { + scene, _ := d.Scene.(*EditorScene) + log.Info("Opening the window") + + // Open the New Level window in edit-settings mode. + if u.levelSettingsWindow == nil { + u.levelSettingsWindow = windows.NewAddEditLevel(windows.AddEditLevel{ + Supervisor: u.Supervisor, + Engine: d.Engine, + EditLevel: scene.Level, + + OnChangePageTypeAndWallpaper: func(pageType level.PageType, wallpaper string) { + log.Info("OnChangePageTypeAndWallpaper called: %+v, %+v", pageType, wallpaper) + scene.Level.PageType = pageType + scene.Level.Wallpaper = wallpaper + u.Canvas.LoadLevel(d.Engine, scene.Level) + }, + OnCancel: func() { + u.levelSettingsWindow.Hide() + }, + }) + + u.levelSettingsWindow.Compute(d.Engine) + u.levelSettingsWindow.Supervise(u.Supervisor) + + // Center the window. + u.levelSettingsWindow.MoveTo(render.Point{ + X: (d.width / 2) - (u.levelSettingsWindow.Size().W / 2), + Y: 60, + }) + } else { + u.levelSettingsWindow.Show() + } + + return nil }, }, } diff --git a/pkg/editor_ui_doodad.go b/pkg/editor_ui_doodad.go index b8cbfcc..83aa751 100644 --- a/pkg/editor_ui_doodad.go +++ b/pkg/editor_ui_doodad.go @@ -77,8 +77,9 @@ func (u *EditorUI) setupDoodadFrame(e render.Engine, window *ui.Window) (*ui.Fra Text: "<", Font: balance.MenuFont, })) - leftBtn.Handle(ui.Click, func(ed ui.EventData) { + leftBtn.Handle(ui.Click, func(ed ui.EventData) error { u.scrollDoodadFrame(-1) + return nil }) u.Supervisor.Add(leftBtn) pager.Pack(leftBtn, ui.Pack{ @@ -100,8 +101,9 @@ func (u *EditorUI) setupDoodadFrame(e render.Engine, window *ui.Window) (*ui.Fra Text: ">", Font: balance.MenuFont, })) - rightBtn.Handle(ui.Click, func(ed ui.EventData) { + rightBtn.Handle(ui.Click, func(ed ui.EventData) error { u.scrollDoodadFrame(1) + return nil }) u.Supervisor.Add(rightBtn) pager.Pack(rightBtn, ui.Pack{ @@ -188,9 +190,10 @@ func (u *EditorUI) setupDoodadFrame(e render.Engine, window *ui.Window) (*ui.Fra // Begin the drag event to grab this Doodad. // NOTE: The drag target is the EditorUI.Canvas in // editor_ui.go#SetupCanvas() - btn.Handle(ui.MouseDown, func(ed ui.EventData) { + btn.Handle(ui.MouseDown, func(ed ui.EventData) error { log.Warn("MouseDown on doodad %s (%s)", doodad.Filename, doodad.Title) u.startDragActor(doodad, nil) + return nil }) u.Supervisor.Add(btn) diff --git a/pkg/editor_ui_palette.go b/pkg/editor_ui_palette.go index 61b2464..024fac8 100644 --- a/pkg/editor_ui_palette.go +++ b/pkg/editor_ui_palette.go @@ -12,7 +12,7 @@ import ( func (u *EditorUI) SetupPalette(d *Doodle) *ui.Window { window := ui.NewWindow("Palette") window.ConfigureTitle(balance.TitleConfig) - window.TitleBar().Font = balance.TitleFont + // window.TitleBar().Font = balance.TitleFont window.Configure(ui.Config{ Background: balance.WindowBackground, BorderColor: balance.WindowBorder, @@ -53,15 +53,16 @@ func (u *EditorUI) setupPaletteFrame(window *ui.Window) *ui.Frame { frame.SetBackground(balance.WindowBackground) // Handler function for the radio buttons being clicked. - onClick := func(ed ui.EventData) { + onClick := func(ed ui.EventData) error { name := u.selectedSwatch swatch, ok := u.Canvas.Palette.Get(name) if !ok { log.Error("Palette onClick: couldn't get swatch named '%s' from palette", name) - return + return nil } log.Info("Set swatch: %s", swatch) u.Canvas.SetSwatch(swatch) + return nil } // Draw the radio buttons for the palette. diff --git a/pkg/editor_ui_toolbar.go b/pkg/editor_ui_toolbar.go index fe7ae8c..35cedef 100644 --- a/pkg/editor_ui_toolbar.go +++ b/pkg/editor_ui_toolbar.go @@ -148,8 +148,9 @@ func (u *EditorUI) SetupToolbar(d *Doodle) *ui.Frame { var btnSize = btn.BoxThickness(2) + toolbarSpriteSize btn.Resize(render.NewRect(btnSize, btnSize)) - btn.Handle(ui.Click, func(ed ui.EventData) { + btn.Handle(ui.Click, func(ed ui.EventData) error { button.Click() + return nil }) u.Supervisor.Add(btn) @@ -256,8 +257,9 @@ func (u *EditorUI) SetupToolbar(d *Doodle) *ui.Frame { Text: button.Label, Font: balance.SmallMonoFont, })) - btn.Handle(ui.Click, func(ed ui.EventData) { + btn.Handle(ui.Click, func(ed ui.EventData) error { button.F() + return nil }) u.Supervisor.Add(btn) sizeBtnFrame.Pack(btn, ui.Pack{ diff --git a/pkg/guitest_scene.go b/pkg/guitest_scene.go index c450062..5bc1fe3 100644 --- a/pkg/guitest_scene.go +++ b/pkg/guitest_scene.go @@ -87,8 +87,9 @@ func (s *GUITestScene) Setup(d *Doodle) error { Text: label, Font: balance.StatusFont, })) - btn.Handle(ui.Click, func(ed ui.EventData) { + btn.Handle(ui.Click, func(ed ui.EventData) error { d.Flash("%s clicked", btn) + return nil }) s.Supervisor.Add(btn) leftFrame.Pack(btn, ui.Pack{ @@ -138,8 +139,9 @@ func (s *GUITestScene) Setup(d *Doodle) error { Height: 20, BorderStyle: ui.BorderRaised, }) - btn.Handle(ui.Click, func(ed ui.EventData) { + btn.Handle(ui.Click, func(ed ui.EventData) error { d.Flash("%s clicked", btn) + return nil }) rowFrame.Pack(btn, ui.Pack{ Side: ui.W, @@ -215,8 +217,9 @@ func (s *GUITestScene) Setup(d *Doodle) error { Font: balance.StatusFont, })) button1.SetBackground(render.Blue) - button1.Handle(ui.Click, func(ed ui.EventData) { + button1.Handle(ui.Click, func(ed ui.EventData) error { d.NewMap() + return nil }) log.Info("Button1 bg: %s", button1.Background()) @@ -225,10 +228,11 @@ func (s *GUITestScene) Setup(d *Doodle) error { Text: "Load Map", Font: balance.StatusFont, })) - button2.Handle(ui.Click, func(ed ui.EventData) { + button2.Handle(ui.Click, func(ed ui.EventData) error { d.Prompt("Map name>", func(name string) { d.EditDrawing(name) }) + return nil }) var align = ui.W diff --git a/pkg/main_scene.go b/pkg/main_scene.go index 596cb61..e65309b 100644 --- a/pkg/main_scene.go +++ b/pkg/main_scene.go @@ -85,8 +85,9 @@ func (s *MainScene) Setup(d *Doodle) error { Padding: 4, }, })) - s.updateButton.Handle(ui.Click, func(ed ui.EventData) { + s.updateButton.Handle(ui.Click, func(ed ui.EventData) error { native.OpenURL(s.updateInfo.DownloadURL) + return nil }) s.updateButton.Compute(d.Engine) s.updateButton.Hide() @@ -119,8 +120,9 @@ func (s *MainScene) Setup(d *Doodle) error { Text: button.Name, Font: balance.StatusFont, })) - btn.Handle(ui.Click, func(ed ui.EventData) { + btn.Handle(ui.Click, func(ed ui.EventData) error { button.Func() + return nil }) s.Supervisor.Add(btn) frame.Pack(btn, ui.Pack{ diff --git a/pkg/menu_scene.go b/pkg/menu_scene.go index 08ebc83..421c473 100644 --- a/pkg/menu_scene.go +++ b/pkg/menu_scene.go @@ -1,14 +1,11 @@ package doodle import ( - "fmt" - - "git.kirsle.net/apps/doodle/pkg/balance" "git.kirsle.net/apps/doodle/pkg/enum" "git.kirsle.net/apps/doodle/pkg/level" "git.kirsle.net/apps/doodle/pkg/log" "git.kirsle.net/apps/doodle/pkg/uix" - "git.kirsle.net/apps/doodle/pkg/userdir" + "git.kirsle.net/apps/doodle/pkg/windows" "git.kirsle.net/go/render" "git.kirsle.net/go/render/event" "git.kirsle.net/go/ui" @@ -105,6 +102,16 @@ func (s *MenuScene) Setup(d *Doodle) error { d.Flash("No Valid StartupMenu Given to MenuScene") } + // Whatever window we got, give it window manager controls under Supervisor. + s.window.Supervise(s.Supervisor) + s.window.Compute(d.Engine) + + // Center the window. + s.window.MoveTo(render.Point{ + X: (d.width / 2) - (s.window.Size().W / 2), + Y: 60, + }) + return nil } @@ -121,373 +128,43 @@ func (s *MenuScene) configureCanvas(e render.Engine, pageType level.PageType, wa // setupNewWindow sets up the UI for the "New" window. func (s *MenuScene) setupNewWindow(d *Doodle) error { - // Default scene options. - s.newPageType = level.Bounded.String() - s.newWallpaper = "notebook.png" - - window := ui.NewWindow("New Drawing") - window.Configure(ui.Config{ - Width: int(float64(d.width) * 0.75), - Height: int(float64(d.height) * 0.75), - Background: render.Grey, - }) - window.Compute(d.Engine) - - { - frame := ui.NewFrame("New Level Frame") - window.Pack(frame, ui.Pack{ - Side: ui.N, - Fill: true, - Expand: true, - }) - - /****************** - * Frame for selecting Page Type - ******************/ - - label1 := ui.NewLabel(ui.Label{ - Text: "Page Type", - Font: balance.LabelFont, - }) - frame.Pack(label1, ui.Pack{ - Side: ui.N, - FillX: true, - }) - - typeFrame := ui.NewFrame("Page Type Options Frame") - frame.Pack(typeFrame, ui.Pack{ - Side: ui.N, - FillX: true, - }) - - type typeObj struct { - Name string - Value level.PageType - } - var types = []typeObj{ - {"Unbounded", level.Unbounded}, - {"Bounded", level.Bounded}, - {"No Negative Space", level.NoNegativeSpace}, - // {"Bordered (TODO)", level.Bordered}, - } - for _, t := range types { - // TODO: Hide some options for the free version of the game. - // - At launch only Bounded and Bordered will be available - // in the shareware version. - // - For now, only hide Bordered as it's not yet implemented. - // -------- - // if balance.FreeVersion { - // if t.Value == level.Bordered { - // continue - // } - // } - - func(t typeObj) { - radio := ui.NewRadioButton(t.Name, - &s.newPageType, - t.Value.String(), - ui.NewLabel(ui.Label{ - Text: t.Name, - Font: balance.MenuFont, - }), - ) - radio.Handle(ui.Click, func(ed ui.EventData) { - s.configureCanvas(d.Engine, t.Value, s.newWallpaper) - }) - s.Supervisor.Add(radio) - typeFrame.Pack(radio, ui.Pack{ - Side: ui.W, - PadX: 4, - }) - }(t) - } - - /****************** - * Frame for selecting Level Wallpaper - ******************/ - - label2 := ui.NewLabel(ui.Label{ - Text: "Wallpaper", - Font: balance.LabelFont, - }) - frame.Pack(label2, ui.Pack{ - Side: ui.N, - FillX: true, - }) - - wpFrame := ui.NewFrame("Wallpaper Frame") - frame.Pack(wpFrame, ui.Pack{ - Side: ui.N, - FillX: true, - }) - - type wallpaperObj struct { - Name string - Value string - } - var wallpapers = []wallpaperObj{ - {"Notebook", "notebook.png"}, - {"Blueprint", "blueprint.png"}, - {"Legal Pad", "legal.png"}, - {"Pure White", "white.png"}, - // {"Placemat", "placemat.png"}, - } - for _, t := range wallpapers { - func(t wallpaperObj) { - radio := ui.NewRadioButton(t.Name, &s.newWallpaper, t.Value, ui.NewLabel(ui.Label{ - Text: t.Name, - Font: balance.MenuFont, - })) - radio.Handle(ui.Click, func(ed ui.EventData) { - log.Info("Set wallpaper to %s", t.Value) - if pageType, ok := level.PageTypeFromString(s.newPageType); ok { - s.configureCanvas(d.Engine, pageType, t.Value) - } - }) - s.Supervisor.Add(radio) - wpFrame.Pack(radio, ui.Pack{ - Side: ui.W, - PadX: 4, - }) - }(t) - } - - /****************** - * Confirm/cancel buttons. - ******************/ - - bottomFrame := ui.NewFrame("Button Frame") - // bottomFrame.Configure(ui.Config{ - // BorderSize: 1, - // BorderStyle: ui.BorderSunken, - // BorderColor: render.Black, - // }) - // bottomFrame.SetBackground(render.Grey) - frame.Pack(bottomFrame, ui.Pack{ - Side: ui.N, - FillX: true, - PadY: 8, - }) - - var buttons = []struct { - Label string - F func(ui.EventData) - }{ - {"Continue", func(ed ui.EventData) { - d.Flash("Create new map with %s page type and %s wallpaper", s.newPageType, s.newWallpaper) - pageType, ok := level.PageTypeFromString(s.newPageType) - if !ok { - d.Flash("Invalid Page Type '%s'", s.newPageType) - return - } - - lvl := level.New() - lvl.Palette = level.DefaultPalette() - lvl.Wallpaper = s.newWallpaper - lvl.PageType = pageType - - // Blueprint theme palette for the dark wallpaper color. - if lvl.Wallpaper == "blueprint.png" { - lvl.Palette = level.NewBlueprintPalette() - } - - d.Goto(&EditorScene{ - DrawingType: enum.LevelDrawing, - Level: lvl, - }) - }}, - - {"Cancel", func(ed ui.EventData) { - d.Goto(&MainScene{}) - }}, - } - for _, t := range buttons { - btn := ui.NewButton(t.Label, ui.NewLabel(ui.Label{ - Text: t.Label, - Font: balance.MenuFont, - })) - btn.Handle(ui.Click, t.F) - s.Supervisor.Add(btn) - bottomFrame.Pack(btn, ui.Pack{ - Side: ui.W, - PadX: 4, - PadY: 8, + window := windows.NewAddEditLevel(windows.AddEditLevel{ + Supervisor: s.Supervisor, + Engine: d.Engine, + OnChangePageTypeAndWallpaper: func(pageType level.PageType, wallpaper string) { + log.Info("OnChangePageTypeAndWallpaper called: %+v, %+v", pageType, wallpaper) + s.configureCanvas(d.Engine, pageType, wallpaper) + }, + OnCreateNewLevel: func(lvl *level.Level) { + d.Goto(&EditorScene{ + DrawingType: enum.LevelDrawing, + Level: lvl, }) - } - } - + }, + OnCancel: func() { + d.Goto(&MainScene{}) + }, + }) s.window = window return nil } // setupLoadWindow sets up the UI for the "New" window. func (s *MenuScene) setupLoadWindow(d *Doodle) error { - window := ui.NewWindow("Open Drawing") - window.Configure(ui.Config{ - Width: int(float64(d.width) * 0.75), - Height: int(float64(d.height) * 0.75), - Background: render.Grey, + window := windows.NewOpenLevelEditor(windows.OpenLevelEditor{ + Supervisor: s.Supervisor, + Engine: d.Engine, + LoadForPlay: s.loadForPlay, + OnPlayLevel: func(filename string) { + d.PlayLevel(filename) + }, + OnEditLevel: func(filename string) { + d.EditFile(filename) + }, + OnCancel: func() { + d.Goto(&MainScene{}) + }, }) - window.Compute(d.Engine) - - { - frame := ui.NewFrame("Open Drawing Frame") - window.Pack(frame, ui.Pack{ - Side: ui.N, - Fill: true, - Expand: true, - }) - - /****************** - * Frame for selecting User Levels - ******************/ - - label1 := ui.NewLabel(ui.Label{ - Text: "Levels", - Font: balance.LabelFont, - }) - frame.Pack(label1, ui.Pack{ - Side: ui.N, - FillX: true, - }) - - // Get the user's levels. - levels, _ := userdir.ListLevels() - - // Embedded levels, TODO - sysLevels, _ := level.ListSystemLevels() - levels = append(levels, sysLevels...) - - lvlRow := ui.NewFrame("Level Row 0") - frame.Pack(lvlRow, ui.Pack{ - Side: ui.N, - FillX: true, - PadY: 1, - }) - for i, lvl := range levels { - func(i int, lvl string) { - log.Info("Add file %s to row %s", lvl, lvlRow.Name) - btn := ui.NewButton("Level Btn", ui.NewLabel(ui.Label{ - Text: lvl, - Font: balance.MenuFont, - })) - btn.Handle(ui.Click, func(ed ui.EventData) { - if s.loadForPlay { - d.PlayLevel(lvl) - } else { - d.EditFile(lvl) - } - }) - s.Supervisor.Add(btn) - lvlRow.Pack(btn, ui.Pack{ - Side: ui.W, - Expand: true, - Fill: true, - }) - - if i > 0 && (i+1)%4 == 0 { - log.Warn("i=%d wrapped at mod 4", i) - lvlRow = ui.NewFrame(fmt.Sprintf("Level Row %d", i)) - frame.Pack(lvlRow, ui.Pack{ - Side: ui.N, - FillX: true, - PadY: 1, - }) - } - }(i, lvl) - } - - /****************** - * Frame for selecting User Doodads - ******************/ - - // Doodads not shown if we're loading a map to play, nor are they - // available to the free version. - if !s.loadForPlay && !balance.FreeVersion { - label2 := ui.NewLabel(ui.Label{ - Text: "Doodads", - Font: balance.LabelFont, - }) - frame.Pack(label2, ui.Pack{ - Side: ui.N, - FillX: true, - }) - - files, _ := userdir.ListDoodads() - ddRow := ui.NewFrame("Doodad Row 0") - frame.Pack(ddRow, ui.Pack{ - Side: ui.N, - FillX: true, - PadY: 1, - }) - for i, dd := range files { - func(i int, dd string) { - btn := ui.NewButton("Doodad Btn", ui.NewLabel(ui.Label{ - Text: dd, - Font: balance.MenuFont, - })) - btn.Handle(ui.Click, func(ed ui.EventData) { - d.EditFile(dd) - }) - s.Supervisor.Add(btn) - ddRow.Pack(btn, ui.Pack{ - Side: ui.W, - Expand: true, - Fill: true, - }) - - if i > 0 && (i+1)%4 == 0 { - ddRow = ui.NewFrame(fmt.Sprintf("Doodad Row %d", i)) - frame.Pack(ddRow, ui.Pack{ - Side: ui.N, - FillX: true, - PadY: 1, - }) - } - }(i, dd) - } - } - - /****************** - * Confirm/cancel buttons. - ******************/ - - bottomFrame := ui.NewFrame("Button Frame") - // bottomFrame.Configure(ui.Config{ - // BorderSize: 1, - // BorderStyle: ui.BorderSunken, - // BorderColor: render.Black, - // }) - // bottomFrame.SetBackground(render.Grey) - frame.Pack(bottomFrame, ui.Pack{ - Side: ui.N, - FillX: true, - PadY: 8, - }) - - var buttons = []struct { - Label string - F func(ui.EventData) - }{ - {"Cancel", func(ed ui.EventData) { - d.Goto(&MainScene{}) - }}, - } - for _, t := range buttons { - btn := ui.NewButton(t.Label, ui.NewLabel(ui.Label{ - Text: t.Label, - Font: balance.MenuFont, - })) - btn.Handle(ui.Click, t.F) - s.Supervisor.Add(btn) - bottomFrame.Pack(btn, ui.Pack{ - Side: ui.W, - PadX: 4, - PadY: 8, - }) - } - } - s.window = window return nil } @@ -518,12 +195,12 @@ func (s *MenuScene) Draw(d *Doodle) error { // Draw the background canvas. s.canvas.Present(d.Engine, render.Origin) + // TODO: if I don't call Compute here, buttons in the Edit Window get all + // bunched up. Investigate why later. s.window.Compute(d.Engine) - s.window.MoveTo(render.Point{ - X: (d.width / 2) - (s.window.Size().W / 2), - Y: 60, - }) - s.window.Present(d.Engine, s.window.Point()) + + // Draw the window managed by Supervisor. + s.Supervisor.Present(d.Engine) return nil } diff --git a/pkg/play_scene.go b/pkg/play_scene.go index f6da15d..d3928e1 100644 --- a/pkg/play_scene.go +++ b/pkg/play_scene.go @@ -119,8 +119,9 @@ func (s *PlayScene) Setup(d *Doodle) error { Text: "Edit (E)", Font: balance.PlayButtonFont, })) - s.editButton.Handle(ui.Click, func(ed ui.EventData) { + s.editButton.Handle(ui.Click, func(ed ui.EventData) error { s.EditLevel() + return nil }) s.supervisor.Add(s.editButton) @@ -300,8 +301,9 @@ func (s *PlayScene) SetupAlertbox() { Font: balance.LabelFont, Text: text, })) - btn.Handle(ui.Click, func(ed ui.EventData) { + btn.Handle(ui.Click, func(ed ui.EventData) error { handler() + return nil }) bottomFrame.Pack(btn, ui.Pack{ Side: ui.W, diff --git a/pkg/uix/actor.go b/pkg/uix/actor.go index 1c8c8db..cc16527 100644 --- a/pkg/uix/actor.go +++ b/pkg/uix/actor.go @@ -198,6 +198,13 @@ func (a *Actor) RemoveItem(itemName string, quantity int) bool { return false } +// ClearInventory removes all items from the actor's inventory. +func (a *Actor) ClearInventory() { + a.muInventory.Lock() + a.inventory = map[string]int{} + a.muInventory.Unlock() +} + // HasItem checks the actor's inventory for the item and returns the quantity. // // A return value of -1 means the item was not found. diff --git a/pkg/windows/add_edit_level.go b/pkg/windows/add_edit_level.go new file mode 100644 index 0000000..9f296a3 --- /dev/null +++ b/pkg/windows/add_edit_level.go @@ -0,0 +1,242 @@ +package windows + +import ( + "git.kirsle.net/apps/doodle/pkg/balance" + "git.kirsle.net/apps/doodle/pkg/level" + "git.kirsle.net/apps/doodle/pkg/shmem" + "git.kirsle.net/go/render" + "git.kirsle.net/go/ui" +) + +// AddEditLevel is the "Create New Level & Edit Level Properties" window +type AddEditLevel struct { + Supervisor *ui.Supervisor + Engine render.Engine + + // Editing settings for an existing level? + EditLevel *level.Level + + // Callback functions. + OnChangePageTypeAndWallpaper func(pageType level.PageType, wallpaper string) + OnCreateNewLevel func(*level.Level) + OnCancel func() +} + +// NewAddEditLevel initializes the window. +func NewAddEditLevel(config AddEditLevel) *ui.Window { + // Default options. + var ( + newPageType = level.Bounded.String() + newWallpaper = "notebook.png" + isNewLevel = config.EditLevel == nil + ) + + // Given a level to edit? + if config.EditLevel != nil { + newPageType = config.EditLevel.PageType.String() + newWallpaper = config.EditLevel.Wallpaper + } + + window := ui.NewWindow("New Drawing") + window.Configure(ui.Config{ + Width: 540, + Height: 350, + Background: render.Grey, + }) + + { + frame := ui.NewFrame("New Level Frame") + window.Pack(frame, ui.Pack{ + Side: ui.N, + Fill: true, + Expand: true, + }) + + /****************** + * Frame for selecting Page Type + ******************/ + + label1 := ui.NewLabel(ui.Label{ + Text: "Page Type", + Font: balance.LabelFont, + }) + frame.Pack(label1, ui.Pack{ + Side: ui.N, + FillX: true, + }) + + typeFrame := ui.NewFrame("Page Type Options Frame") + frame.Pack(typeFrame, ui.Pack{ + Side: ui.N, + FillX: true, + }) + + type typeObj struct { + Name string + Value level.PageType + } + var types = []typeObj{ + {"Unbounded", level.Unbounded}, + {"Bounded", level.Bounded}, + {"No Negative Space", level.NoNegativeSpace}, + // {"Bordered (TODO)", level.Bordered}, + } + for _, t := range types { + // TODO: Hide some options for the free version of the game. + // - At launch only Bounded and Bordered will be available + // in the shareware version. + // - For now, only hide Bordered as it's not yet implemented. + // -------- + // if balance.FreeVersion { + // if t.Value == level.Bordered { + // continue + // } + // } + + func(t typeObj) { + radio := ui.NewRadioButton(t.Name, + &newPageType, + t.Value.String(), + ui.NewLabel(ui.Label{ + Text: t.Name, + Font: balance.MenuFont, + }), + ) + radio.Handle(ui.Click, func(ed ui.EventData) error { + config.OnChangePageTypeAndWallpaper(t.Value, newWallpaper) + return nil + }) + config.Supervisor.Add(radio) + typeFrame.Pack(radio, ui.Pack{ + Side: ui.W, + PadX: 4, + }) + }(t) + } + + /****************** + * Frame for selecting Level Wallpaper + ******************/ + + label2 := ui.NewLabel(ui.Label{ + // Text: "Wallpaper", + TextVariable: &newWallpaper, + Font: balance.LabelFont, + }) + frame.Pack(label2, ui.Pack{ + Side: ui.N, + FillX: true, + }) + + wpFrame := ui.NewFrame("Wallpaper Frame") + frame.Pack(wpFrame, ui.Pack{ + Side: ui.N, + FillX: true, + }) + + type wallpaperObj struct { + Name string + Value string + } + var wallpapers = []wallpaperObj{ + {"Notebook", "notebook.png"}, + {"Blueprint", "blueprint.png"}, + {"Legal Pad", "legal.png"}, + {"Pure White", "white.png"}, + // {"Placemat", "placemat.png"}, + } + for _, t := range wallpapers { + func(t wallpaperObj) { + radio := ui.NewRadioButton(t.Name, &newWallpaper, t.Value, ui.NewLabel(ui.Label{ + Text: t.Name, + Font: balance.MenuFont, + })) + radio.Handle(ui.Click, func(ed ui.EventData) error { + if pageType, ok := level.PageTypeFromString(newPageType); ok { + config.OnChangePageTypeAndWallpaper(pageType, t.Value) + } + return nil + }) + config.Supervisor.Add(radio) + wpFrame.Pack(radio, ui.Pack{ + Side: ui.W, + PadX: 4, + }) + }(t) + } + + /****************** + * Confirm/cancel buttons. + ******************/ + + bottomFrame := ui.NewFrame("Button Frame") + // bottomFrame.Configure(ui.Config{ + // BorderSize: 1, + // BorderStyle: ui.BorderSunken, + // BorderColor: render.Black, + // }) + // bottomFrame.SetBackground(render.Grey) + frame.Pack(bottomFrame, ui.Pack{ + Side: ui.N, + FillX: true, + PadY: 8, + }) + + var buttons = []struct { + Label string + F func(ui.EventData) error + }{ + {"Continue", func(ed ui.EventData) error { + shmem.Flash("Create new map with %s page type and %s wallpaper", newPageType, newWallpaper) + pageType, ok := level.PageTypeFromString(newPageType) + if !ok { + shmem.Flash("Invalid Page Type '%s'", newPageType) + return nil + } + + lvl := level.New() + lvl.Palette = level.DefaultPalette() + lvl.Wallpaper = newWallpaper + lvl.PageType = pageType + + // Blueprint theme palette for the dark wallpaper color. + if lvl.Wallpaper == "blueprint.png" { + lvl.Palette = level.NewBlueprintPalette() + } + + config.OnCreateNewLevel(lvl) + return nil + }}, + + {"Cancel", func(ed ui.EventData) error { + config.OnCancel() + return nil + }}, + + // OK button is for editing an existing level. + {"OK", func(ed ui.EventData) error { + config.OnCancel() + return nil + }}, + } + for _, t := range buttons { + // If we're editing settings on an existing level, skip the Continue. + if isNewLevel && t.Label == "OK" { + continue + } + btn := ui.NewButton(t.Label, ui.NewLabel(ui.Label{ + Text: t.Label, + Font: balance.MenuFont, + })) + btn.Handle(ui.Click, t.F) + config.Supervisor.Add(btn) + bottomFrame.Pack(btn, ui.Pack{ + Side: ui.W, + PadX: 4, + PadY: 8, + }) + } + } + + return window +} diff --git a/pkg/windows/open_level_editor.go b/pkg/windows/open_level_editor.go new file mode 100644 index 0000000..c0d8e40 --- /dev/null +++ b/pkg/windows/open_level_editor.go @@ -0,0 +1,203 @@ +package windows + +import ( + "fmt" + + "git.kirsle.net/apps/doodle/pkg/balance" + "git.kirsle.net/apps/doodle/pkg/level" + "git.kirsle.net/apps/doodle/pkg/log" + "git.kirsle.net/apps/doodle/pkg/userdir" + "git.kirsle.net/go/render" + "git.kirsle.net/go/ui" +) + +// OpenLevelEditor is the "Open a Level to Edit It" window +type OpenLevelEditor struct { + Supervisor *ui.Supervisor + Engine render.Engine + + // Load it for playing instead of editing? + LoadForPlay bool + + // Callback functions. + OnPlayLevel func(filename string) + OnEditLevel func(filename string) + OnCancel func() +} + +// NewOpenLevelEditor initializes the window. +func NewOpenLevelEditor(config OpenLevelEditor) *ui.Window { + var ( + width, height = config.Engine.WindowSize() + ) + + window := ui.NewWindow("Open Drawing") + window.Configure(ui.Config{ + Width: int(float64(width) * 0.75), + Height: int(float64(height) * 0.75), + Background: render.Grey, + }) + + { + frame := ui.NewFrame("Open Drawing Frame") + window.Pack(frame, ui.Pack{ + Side: ui.N, + Fill: true, + Expand: true, + }) + + /****************** + * Frame for selecting User Levels + ******************/ + + label1 := ui.NewLabel(ui.Label{ + Text: "Levels", + Font: balance.LabelFont, + }) + frame.Pack(label1, ui.Pack{ + Side: ui.N, + FillX: true, + }) + + // Get the user's levels. + levels, _ := userdir.ListLevels() + + // Embedded levels, TODO + sysLevels, _ := level.ListSystemLevels() + levels = append(levels, sysLevels...) + + lvlRow := ui.NewFrame("Level Row 0") + frame.Pack(lvlRow, ui.Pack{ + Side: ui.N, + FillX: true, + PadY: 1, + }) + for i, lvl := range levels { + func(i int, lvl string) { + log.Info("Add file %s to row %s", lvl, lvlRow.Name) + btn := ui.NewButton("Level Btn", ui.NewLabel(ui.Label{ + Text: lvl, + Font: balance.MenuFont, + })) + btn.Handle(ui.Click, func(ed ui.EventData) error { + if config.LoadForPlay { + config.OnPlayLevel(lvl) + } else { + config.OnEditLevel(lvl) + } + return nil + }) + config.Supervisor.Add(btn) + lvlRow.Pack(btn, ui.Pack{ + Side: ui.W, + Expand: true, + Fill: true, + }) + + if i > 0 && (i+1)%4 == 0 { + log.Warn("i=%d wrapped at mod 4", i) + lvlRow = ui.NewFrame(fmt.Sprintf("Level Row %d", i)) + frame.Pack(lvlRow, ui.Pack{ + Side: ui.N, + FillX: true, + PadY: 1, + }) + } + }(i, lvl) + } + + /****************** + * Frame for selecting User Doodads + ******************/ + + // Doodads not shown if we're loading a map to play, nor are they + // available to the free version. + if !config.LoadForPlay && !balance.FreeVersion { + label2 := ui.NewLabel(ui.Label{ + Text: "Doodads", + Font: balance.LabelFont, + }) + frame.Pack(label2, ui.Pack{ + Side: ui.N, + FillX: true, + }) + + files, _ := userdir.ListDoodads() + ddRow := ui.NewFrame("Doodad Row 0") + frame.Pack(ddRow, ui.Pack{ + Side: ui.N, + FillX: true, + PadY: 1, + }) + for i, dd := range files { + func(i int, dd string) { + btn := ui.NewButton("Doodad Btn", ui.NewLabel(ui.Label{ + Text: dd, + Font: balance.MenuFont, + })) + btn.Handle(ui.Click, func(ed ui.EventData) error { + config.OnEditLevel(dd) + return nil + }) + config.Supervisor.Add(btn) + ddRow.Pack(btn, ui.Pack{ + Side: ui.W, + Expand: true, + Fill: true, + }) + + if i > 0 && (i+1)%4 == 0 { + ddRow = ui.NewFrame(fmt.Sprintf("Doodad Row %d", i)) + frame.Pack(ddRow, ui.Pack{ + Side: ui.N, + FillX: true, + PadY: 1, + }) + } + }(i, dd) + } + } + + /****************** + * Confirm/cancel buttons. + ******************/ + + bottomFrame := ui.NewFrame("Button Frame") + // bottomFrame.Configure(ui.Config{ + // BorderSize: 1, + // BorderStyle: ui.BorderSunken, + // BorderColor: render.Black, + // }) + // bottomFrame.SetBackground(render.Grey) + frame.Pack(bottomFrame, ui.Pack{ + Side: ui.N, + FillX: true, + PadY: 8, + }) + + var buttons = []struct { + Label string + F func(ui.EventData) error + }{ + {"Cancel", func(ed ui.EventData) error { + config.OnCancel() + return nil + }}, + } + for _, t := range buttons { + btn := ui.NewButton(t.Label, ui.NewLabel(ui.Label{ + Text: t.Label, + Font: balance.MenuFont, + })) + btn.Handle(ui.Click, t.F) + config.Supervisor.Add(btn) + bottomFrame.Pack(btn, ui.Pack{ + Side: ui.W, + PadX: 4, + PadY: 8, + }) + } + } + + return window +}