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
This commit is contained in:
Noah 2020-04-06 23:21:17 -07:00
parent 2bd420ff54
commit f0101ba048
13 changed files with 685 additions and 447 deletions

93
pkg/cheats.go Normal file
View File

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

View File

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

View File

@ -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
},
},
}

View File

@ -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)

View File

@ -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.

View File

@ -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{

View File

@ -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

View File

@ -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{

View File

@ -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()
}
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,
})
}},
{"Cancel", func(ed ui.EventData) {
},
OnCancel: func() {
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
}
// 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.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) {
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{})
}},
}
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
}

View File

@ -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,

View File

@ -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.

View File

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

View File

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