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 +}