Hook up keybinds like Ctrl-N, Ctrl-S

* Menu keybinds that weren't working before, like Ctrl-N, Ctrl-S, Ctrl-O
  to create and open levels are now working.
This commit is contained in:
Noah 2021-06-17 19:43:30 -07:00
parent dce32ea14b
commit d0cfa50625
5 changed files with 399 additions and 250 deletions

View File

@ -1,5 +1,48 @@
# Changes
## v0.7.0 (TBD)
This is the first release of the game where the "free version" drifts meaningfully
away from the "full version". Free versions of the game will show the label
"(shareware)" next to the game version numbers and will not support embedding
doodads inside of level files -- for creating them or playing them. Check the
website for how you can register the full version of the game.
This release brings several improvements to the game:
* **Brush Patterns** for your level palette. Instead of your colors drawing on as
plain, solid pixels, a color swatch can _sample_ with a Pattern to create a
textured appearance when plotted on your level. Several patterns are built in
including Noise, Marker, Ink, and others. The idea is that your brush strokes can
look as though they were drawn in pencil graphite or similar.
* **Title Screen:** the demo level shown on the title screen will leisurely scroll
around the page. The arrow keys may still manually scroll the level any direction.
* **Attach Doodads to Level Files:** this is the first release that supports _truly_
portable custom levels! By attaching your custom doodads _with_ your custom level
file, it will "just play" on someone else's computer, and they don't need to copy
all your custom doodads for it to work! But, free versions of the game will not
get to enjoy this feature.
Some small bits of polish in the game's user interface:
* Some buttons are more colorful! The "Ok" button in alert boxes is blue and pressing
Enter will select the "Ok" button.
* When opening a Level or Doodad to play or edit, a blue **Browse...** button is
added so you can more easily find downloaded custom levels and play them.
* In the Level Editor, the "Level -> **Attached Files**" menu will let you see
and manage files attached to your level, such as its custom wallpaper image or
any custom doodads that were published with the level.
* The keyboard shortcut to open the developer console is now the tilde/grave key
instead of Enter.
This release also makes the game a little bit more functional on smartphone-sized
devices like the Pine64 Pinephone:
* **Horizontal Toolbars** option for the Level Editor. If the game is started with
the `-w mobile` command line option, the game window takes on a mobile form factor
and the Horizontal Toolbars are enabled by default.
* Alternatively, press the tilde/grave key and type: `boolProp horizontalToolbars true`
## v0.6.0-alpha (June 6 2021)
This release brings less jank and some new features.

View File

@ -196,6 +196,21 @@ func (s *EditorScene) Loop(d *Doodle, ev *event.State) error {
}
}
// Menu key bindings.
if keybind.NewLevel(ev) {
// Ctrl-N, New Level
s.MenuNewLevel()
} else if keybind.SaveAs(ev) {
// Shift-Ctrl-S, Save As
s.MenuSave(true)()
} else if keybind.Save(ev) {
// Ctrl-S, Save
s.MenuSave(false)()
} else if keybind.Open(ev) {
// Ctrl-O, Open
s.MenuOpen()
}
// Undo/Redo key bindings.
if keybind.Undo(ev) {
s.UI.Canvas.UndoStroke()
@ -223,7 +238,7 @@ func (s *EditorScene) Loop(d *Doodle, ev *event.State) error {
s.UI.Canvas.ScrollTo(render.Origin)
}
s.UI.Loop(ev)
// s.UI.Loop(ev)
// Switching to Play Mode?
if keybind.GotoPlay(ev) {
@ -252,6 +267,8 @@ func (s *EditorScene) Loop(d *Doodle, ev *event.State) error {
s.UI.doodadWindow.Show()
}
s.UI.Loop(ev)
return nil
}

View File

@ -3,7 +3,6 @@ package doodle
import (
"fmt"
"path/filepath"
"strconv"
"git.kirsle.net/apps/doodle/pkg/balance"
"git.kirsle.net/apps/doodle/pkg/branding"
@ -12,9 +11,7 @@ import (
"git.kirsle.net/apps/doodle/pkg/level"
"git.kirsle.net/apps/doodle/pkg/license"
"git.kirsle.net/apps/doodle/pkg/log"
"git.kirsle.net/apps/doodle/pkg/native"
"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"
@ -469,252 +466,6 @@ func (u *EditorUI) ExpandCanvas(e render.Engine) {
u.Workspace.Compute(e)
}
// SetupMenuBar sets up the menu bar.
func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar {
menu := ui.NewMenuBar("Main Menu")
// Save and Save As common menu handler
var (
drawingType string
saveFunc func(filename string)
)
switch u.Scene.DrawingType {
case enum.LevelDrawing:
drawingType = "level"
saveFunc = func(filename string) {
if err := u.Scene.SaveLevel(filename); err != nil {
d.Flash("Error: %s", err)
} else {
d.Flash("Saved level: %s", filename)
}
}
case enum.DoodadDrawing:
drawingType = "doodad"
saveFunc = func(filename string) {
if err := u.Scene.SaveDoodad(filename); err != nil {
d.Flash("Error: %s", err)
} else {
d.Flash("Saved doodad: %s", filename)
}
}
default:
d.Flash("Error: Scene.DrawingType is not a valid type")
}
////////
// File menu
fileMenu := menu.AddMenu("File")
fileMenu.AddItemAccel("New level", "Ctrl-N*", func() {
u.Scene.ConfirmUnload(func() {
d.GotoNewMenu()
})
})
fileMenu.AddItem("New doodad", func() {
u.Scene.ConfirmUnload(func() {
d.Prompt("Doodad size [100]>", func(answer string) {
size := balance.DoodadSize
if answer != "" {
i, err := strconv.Atoi(answer)
if err != nil {
d.Flash("Error: Doodad size must be a number.")
return
}
size = i
}
d.NewDoodad(size)
})
})
})
fileMenu.AddItemAccel("Save", "Ctrl-S*", func() {
if u.Scene.filename != "" {
saveFunc(u.Scene.filename)
} else {
d.Prompt("Save filename>", func(answer string) {
if answer != "" {
saveFunc(answer)
}
})
}
})
fileMenu.AddItem("Save as...", func() {
d.Prompt("Save as filename>", func(answer string) {
if answer != "" {
saveFunc(answer)
}
})
})
if balance.Feature.EmbeddableDoodads && drawingType == "level" {
fileMenu.AddItem("Publish level", func() {
u.OpenPublishWindow()
})
}
fileMenu.AddItemAccel("Open...", "Ctrl-O*", func() {
u.Scene.ConfirmUnload(func() {
d.GotoLoadMenu()
})
})
fileMenu.AddSeparator()
fileMenu.AddItem("Close "+drawingType, func() {
u.Scene.ConfirmUnload(func() {
d.Goto(&MainScene{})
})
})
fileMenu.AddItemAccel("Quit", "Escape", func() {
d.ConfirmExit()
})
////////
// Edit menu
editMenu := menu.AddMenu("Edit")
editMenu.AddItemAccel("Undo", "Ctrl-Z", func() {
u.Canvas.UndoStroke()
})
editMenu.AddItemAccel("Redo", "Ctrl-Y", func() {
u.Canvas.RedoStroke()
})
////////
// Level menu
if u.Scene.DrawingType == enum.LevelDrawing {
levelMenu := menu.AddMenu("Level")
levelMenu.AddItem("Page settings", func() {
log.Info("Opening the window")
// Open the New Level window in edit-settings mode.
u.levelSettingsWindow.Hide()
u.levelSettingsWindow = nil
u.SetupPopups(u.d)
u.levelSettingsWindow.Show()
})
levelMenu.AddItem("Attached files", func() {
log.Info("Opening the FileSystem window")
u.OpenFileSystemWindow()
})
levelMenu.AddItemAccel("Playtest", "P", func() {
u.Scene.Playtest()
})
}
////////
// View menu
if balance.Feature.Zoom {
viewMenu := menu.AddMenu("View")
viewMenu.AddItemAccel("Zoom in", "+", func() {
u.Canvas.Zoom++
})
viewMenu.AddItemAccel("Zoom out", "-", func() {
u.Canvas.Zoom--
})
viewMenu.AddItemAccel("Reset zoom", "1", func() {
u.Canvas.Zoom = 0
})
viewMenu.AddItemAccel("Scroll drawing to origin", "0", func() {
u.Canvas.ScrollTo(render.Origin)
})
}
////////
// Tools menu
toolMenu := menu.AddMenu("Tools")
toolMenu.AddItemAccel("Debug overlay", "F3", func() {
DebugOverlay = !DebugOverlay
if DebugOverlay {
d.Flash("Debug overlay enabled. Press F3 to turn it off.")
}
})
toolMenu.AddItemAccel("Command shell", "Enter", func() {
d.shell.Open = true
})
toolMenu.AddSeparator()
toolMenu.AddItem("Edit Palette", func() {
u.OpenPaletteWindow()
})
if u.Scene.DrawingType == enum.LevelDrawing {
toolMenu.AddItemAccel("Doodads", "d", func() {
log.Info("Open the DoodadDropper")
u.doodadWindow.Show()
})
} else if u.Scene.DrawingType == enum.DoodadDrawing {
toolMenu.AddItem("Layers", func() {
u.OpenLayersWindow()
})
}
// Draw Tools
toolMenu.AddItemAccel("Pencil Tool", "F", func() {
u.Canvas.Tool = drawtool.PencilTool
u.activeTool = u.Canvas.Tool.String()
d.Flash("Pencil Tool selected.")
})
toolMenu.AddItemAccel("Line Tool", "L", func() {
u.Canvas.Tool = drawtool.LineTool
u.activeTool = u.Canvas.Tool.String()
d.Flash("Line Tool selected.")
})
toolMenu.AddItemAccel("Rectangle Tool", "R", func() {
u.Canvas.Tool = drawtool.RectTool
u.activeTool = u.Canvas.Tool.String()
d.Flash("Rectangle Tool selected.")
})
toolMenu.AddItemAccel("Ellipse Tool", "C", func() {
u.Canvas.Tool = drawtool.EllipseTool
u.activeTool = u.Canvas.Tool.String()
d.Flash("Ellipse Tool selected.")
})
toolMenu.AddItemAccel("Eraser Tool", "x", func() {
u.Canvas.Tool = drawtool.EraserTool
u.activeTool = u.Canvas.Tool.String()
d.Flash("Eraser Tool selected.")
})
if u.Scene.DrawingType == enum.LevelDrawing {
toolMenu.AddItemAccel("Doodads", "d", func() {
log.Info("Open the DoodadDropper")
u.doodadWindow.Show()
})
toolMenu.AddItem("Link Tool", func() {
u.Canvas.Tool = drawtool.LinkTool
u.activeTool = u.Canvas.Tool.String()
d.Flash("Link Tool selected. Click a doodad in your level to link it to another.")
})
}
////////
// Help menu
helpMenu := menu.AddMenu("Help")
helpMenu.AddItemAccel("User Manual", "F1", func() {
native.OpenLocalURL(balance.GuidebookPath)
})
helpMenu.AddItem("Register", func() {
u.licenseWindow.Show()
})
helpMenu.AddItem("About", func() {
if u.aboutWindow == nil {
u.aboutWindow = windows.NewAboutWindow(windows.About{
Supervisor: u.Supervisor,
Engine: d.Engine,
})
u.aboutWindow.Compute(d.Engine)
u.aboutWindow.Supervise(u.Supervisor)
// Center the window.
u.aboutWindow.MoveTo(render.Point{
X: (d.width / 2) - (u.aboutWindow.Size().W / 2),
Y: 60,
})
}
u.aboutWindow.Show()
})
menu.Supervise(u.Supervisor)
menu.Compute(d.Engine)
return menu
}
// SetupStatusBar sets up the status bar widget along the bottom of the window.
func (u *EditorUI) SetupStatusBar(d *Doodle) *ui.Frame {
frame := ui.NewFrame("Status Bar")

318
pkg/editor_ui_menubar.go Normal file
View File

@ -0,0 +1,318 @@
package doodle
// Menu Bar features for Edit Mode.
// In here is the SetupMenuBar() and menu item functions.
// The rest of it is controlled in editor_ui.go
import (
"strconv"
"git.kirsle.net/apps/doodle/pkg/balance"
"git.kirsle.net/apps/doodle/pkg/drawtool"
"git.kirsle.net/apps/doodle/pkg/enum"
"git.kirsle.net/apps/doodle/pkg/log"
"git.kirsle.net/apps/doodle/pkg/native"
"git.kirsle.net/apps/doodle/pkg/windows"
"git.kirsle.net/go/render"
"git.kirsle.net/go/ui"
)
// SetupMenuBar sets up the menu bar.
func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar {
menu := ui.NewMenuBar("Main Menu")
// Save and Save As common menu handler
var (
drawingType string
saveFunc func(filename string)
)
switch u.Scene.DrawingType {
case enum.LevelDrawing:
drawingType = "level"
saveFunc = func(filename string) {
if err := u.Scene.SaveLevel(filename); err != nil {
d.Flash("Error: %s", err)
} else {
d.Flash("Saved level: %s", filename)
}
}
case enum.DoodadDrawing:
drawingType = "doodad"
saveFunc = func(filename string) {
if err := u.Scene.SaveDoodad(filename); err != nil {
d.Flash("Error: %s", err)
} else {
d.Flash("Saved doodad: %s", filename)
}
}
default:
d.Flash("Error: Scene.DrawingType is not a valid type")
}
////////
// File menu
fileMenu := menu.AddMenu("File")
fileMenu.AddItemAccel("New level", "Ctrl-N", u.Scene.MenuNewLevel)
fileMenu.AddItem("New doodad", func() {
u.Scene.ConfirmUnload(func() {
d.Prompt("Doodad size [100]>", func(answer string) {
size := balance.DoodadSize
if answer != "" {
i, err := strconv.Atoi(answer)
if err != nil {
d.Flash("Error: Doodad size must be a number.")
return
}
size = i
}
d.NewDoodad(size)
})
})
})
fileMenu.AddItemAccel("Save", "Ctrl-S", u.Scene.MenuSave(false))
fileMenu.AddItemAccel("Save as...", "Shift-Ctrl-S", func() {
d.Prompt("Save as filename>", func(answer string) {
if answer != "" {
saveFunc(answer)
}
})
})
if balance.Feature.EmbeddableDoodads && drawingType == "level" {
fileMenu.AddItem("Publish level", func() {
u.OpenPublishWindow()
})
}
fileMenu.AddItemAccel("Open...", "Ctrl-O", u.Scene.MenuOpen)
fileMenu.AddSeparator()
fileMenu.AddItem("Close "+drawingType, func() {
u.Scene.ConfirmUnload(func() {
d.Goto(&MainScene{})
})
})
fileMenu.AddItemAccel("Quit", "Escape", func() {
d.ConfirmExit()
})
////////
// Edit menu
editMenu := menu.AddMenu("Edit")
editMenu.AddItemAccel("Undo", "Ctrl-Z", func() {
u.Canvas.UndoStroke()
})
editMenu.AddItemAccel("Redo", "Ctrl-Y", func() {
u.Canvas.RedoStroke()
})
////////
// Level menu
if u.Scene.DrawingType == enum.LevelDrawing {
levelMenu := menu.AddMenu("Level")
levelMenu.AddItem("Page settings", func() {
log.Info("Opening the window")
// Open the New Level window in edit-settings mode.
u.levelSettingsWindow.Hide()
u.levelSettingsWindow = nil
u.SetupPopups(u.d)
u.levelSettingsWindow.Show()
})
levelMenu.AddItem("Attached files", func() {
log.Info("Opening the FileSystem window")
u.OpenFileSystemWindow()
})
levelMenu.AddItemAccel("Playtest", "P", func() {
u.Scene.Playtest()
})
}
////////
// View menu
if balance.Feature.Zoom {
viewMenu := menu.AddMenu("View")
viewMenu.AddItemAccel("Zoom in", "+", func() {
u.Canvas.Zoom++
})
viewMenu.AddItemAccel("Zoom out", "-", func() {
u.Canvas.Zoom--
})
viewMenu.AddItemAccel("Reset zoom", "1", func() {
u.Canvas.Zoom = 0
})
viewMenu.AddItemAccel("Scroll drawing to origin", "0", func() {
u.Canvas.ScrollTo(render.Origin)
})
}
////////
// Tools menu
toolMenu := menu.AddMenu("Tools")
toolMenu.AddItemAccel("Debug overlay", "F3", func() {
DebugOverlay = !DebugOverlay
if DebugOverlay {
d.Flash("Debug overlay enabled. Press F3 to turn it off.")
}
})
toolMenu.AddItemAccel("Command shell", "`", func() {
d.shell.Open = true
})
toolMenu.AddSeparator()
toolMenu.AddItem("Edit Palette", func() {
u.OpenPaletteWindow()
})
if u.Scene.DrawingType == enum.LevelDrawing {
toolMenu.AddItemAccel("Doodads", "d", func() {
log.Info("Open the DoodadDropper")
u.doodadWindow.Show()
})
} else if u.Scene.DrawingType == enum.DoodadDrawing {
toolMenu.AddItem("Layers", func() {
u.OpenLayersWindow()
})
}
// Draw Tools
toolMenu.AddItemAccel("Pencil Tool", "F", func() {
u.Canvas.Tool = drawtool.PencilTool
u.activeTool = u.Canvas.Tool.String()
d.Flash("Pencil Tool selected.")
})
toolMenu.AddItemAccel("Line Tool", "L", func() {
u.Canvas.Tool = drawtool.LineTool
u.activeTool = u.Canvas.Tool.String()
d.Flash("Line Tool selected.")
})
toolMenu.AddItemAccel("Rectangle Tool", "R", func() {
u.Canvas.Tool = drawtool.RectTool
u.activeTool = u.Canvas.Tool.String()
d.Flash("Rectangle Tool selected.")
})
toolMenu.AddItemAccel("Ellipse Tool", "C", func() {
u.Canvas.Tool = drawtool.EllipseTool
u.activeTool = u.Canvas.Tool.String()
d.Flash("Ellipse Tool selected.")
})
toolMenu.AddItemAccel("Eraser Tool", "x", func() {
u.Canvas.Tool = drawtool.EraserTool
u.activeTool = u.Canvas.Tool.String()
d.Flash("Eraser Tool selected.")
})
if u.Scene.DrawingType == enum.LevelDrawing {
toolMenu.AddItemAccel("Doodads", "d", func() {
log.Info("Open the DoodadDropper")
u.doodadWindow.Show()
})
toolMenu.AddItem("Link Tool", func() {
u.Canvas.Tool = drawtool.LinkTool
u.activeTool = u.Canvas.Tool.String()
d.Flash("Link Tool selected. Click a doodad in your level to link it to another.")
})
}
////////
// Help menu
helpMenu := menu.AddMenu("Help")
helpMenu.AddItemAccel("User Manual", "F1", func() {
native.OpenLocalURL(balance.GuidebookPath)
})
helpMenu.AddItem("Register", func() {
u.licenseWindow.Show()
})
helpMenu.AddItem("About", func() {
if u.aboutWindow == nil {
u.aboutWindow = windows.NewAboutWindow(windows.About{
Supervisor: u.Supervisor,
Engine: d.Engine,
})
u.aboutWindow.Compute(d.Engine)
u.aboutWindow.Supervise(u.Supervisor)
// Center the window.
u.aboutWindow.MoveTo(render.Point{
X: (d.width / 2) - (u.aboutWindow.Size().W / 2),
Y: 60,
})
}
u.aboutWindow.Show()
})
menu.Supervise(u.Supervisor)
menu.Compute(d.Engine)
return menu
}
// Menu functions that have keybind callbacks below.
// File->New level, or Ctrl-N
func (s *EditorScene) MenuNewLevel() {
s.ConfirmUnload(func() {
s.d.GotoNewMenu()
})
}
// File->Open, or Ctrl-O
func (s *EditorScene) MenuOpen() {
s.ConfirmUnload(func() {
s.d.GotoLoadMenu()
})
}
// File->Save, or Ctrl-S
// File->Save As, or Shift-Ctrl-S
// NOTICE: this one returns a func() so you need to call that one!
func (s *EditorScene) MenuSave(as bool) func() {
return func() {
var (
// drawingType string
saveFunc func(filename string)
)
switch s.DrawingType {
case enum.LevelDrawing:
// drawingType = "level"
saveFunc = func(filename string) {
if err := s.SaveLevel(filename); err != nil {
s.d.Flash("Error: %s", err)
} else {
s.d.Flash("Saved level: %s", filename)
}
}
case enum.DoodadDrawing:
// drawingType = "doodad"
saveFunc = func(filename string) {
if err := s.SaveDoodad(filename); err != nil {
s.d.Flash("Error: %s", err)
} else {
s.d.Flash("Saved doodad: %s", filename)
}
}
default:
s.d.Flash("Error: Scene.DrawingType is not a valid type")
}
// "Save As"?
if as {
s.d.Prompt("Save as filename>", func(answer string) {
if answer != "" {
saveFunc(answer)
}
})
return
}
// "Save", write to existing filename or prompt for it.
if s.filename != "" {
saveFunc(s.filename)
} else {
s.d.Prompt("Save filename>", func(answer string) {
if answer != "" {
saveFunc(answer)
}
})
}
}
}

View File

@ -45,6 +45,26 @@ func Redo(ev *event.State) bool {
return ev.Ctrl && ev.KeyDown("y")
}
// New Level (Ctrl-N)
func NewLevel(ev *event.State) bool {
return ev.Ctrl && ev.KeyDown("n")
}
// Save (Ctrl-S)
func Save(ev *event.State) bool {
return ev.Ctrl && ev.KeyDown("s")
}
// SaveAs (Shift-Ctrl-S)
func SaveAs(ev *event.State) bool {
return ev.Ctrl && ev.Shift && ev.KeyDown("s")
}
// Open (Ctrl-O)
func Open(ev *event.State) bool {
return ev.Ctrl && ev.KeyDown("o")
}
// ZoomIn (+)
func ZoomIn(ev *event.State) bool {
return ev.KeyDown("=") || ev.KeyDown("+")