From 0b0af70a62a4e18f5932e2656cf3e2c74ebcce62 Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Wed, 6 Oct 2021 22:22:34 -0700 Subject: [PATCH] Viewport Windows, Quality of Life, Spit and Polish * New keybind: 'v' to open a new Viewport in the Level Editor. * New keybind: Backspace to close the topmost UI window, and Shift+Backspace to close them all. * Zoom has graduated out of experimental feature status. Still a bit buggy but workable. * Viewport windows now copy the Tool and BrushSize of the toplevel editor, so drawing in and out of viewports works well. * Viewport window UI improved: buttons to grow or shrink the window size, refresh the actors, etc. --- pkg/balance/theme.go | 2 +- pkg/branding/branding.go | 1 + pkg/editor_scene.go | 145 +++++++++++++++++++++++----------- pkg/editor_ui_menubar.go | 52 ++++++++---- pkg/keybind/keybind.go | 25 ++++++ pkg/windows/pip_canvas.go | 162 +++++++++++++++++++------------------- 6 files changed, 246 insertions(+), 141 deletions(-) diff --git a/pkg/balance/theme.go b/pkg/balance/theme.go index 6d8fd54..3e3cb85 100644 --- a/pkg/balance/theme.go +++ b/pkg/balance/theme.go @@ -141,7 +141,7 @@ var ( // Small font SmallFont = render.Text{ Size: 10, - Padding: 4, + Padding: 2, Color: render.Black, } diff --git a/pkg/branding/branding.go b/pkg/branding/branding.go index d03cde8..d99b96b 100644 --- a/pkg/branding/branding.go +++ b/pkg/branding/branding.go @@ -11,4 +11,5 @@ const ( // Update check URL UpdateCheckJSON = "https://download.sketchymaze.com/version.json" + GuidebookURL = "https://www.sketchymaze.com/guidebook/" ) diff --git a/pkg/editor_scene.go b/pkg/editor_scene.go index f877180..88a7e50 100644 --- a/pkg/editor_scene.go +++ b/pkg/editor_scene.go @@ -6,7 +6,6 @@ import ( "os" "strings" - "git.kirsle.net/apps/doodle/pkg/balance" "git.kirsle.net/apps/doodle/pkg/doodads" "git.kirsle.net/apps/doodle/pkg/drawtool" "git.kirsle.net/apps/doodle/pkg/enum" @@ -18,6 +17,7 @@ import ( "git.kirsle.net/apps/doodle/pkg/modal/loadscreen" "git.kirsle.net/apps/doodle/pkg/usercfg" "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" ) @@ -288,54 +288,111 @@ 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() - } + // Run all of the keybinds. + binders := []struct { + v bool + f func() + }{ + { + keybind.NewLevel(ev), func() { + // Ctrl-N, New Level + s.MenuNewLevel() + }, + }, + { + keybind.SaveAs(ev), func() { + // Shift-Ctrl-S, Save As + s.MenuSave(true)() + }, + }, + { + keybind.Save(ev), func() { + // Ctrl-S, Save + s.MenuSave(false)() + }, + }, + { + keybind.Open(ev), func() { + // Ctrl-O, Open + s.MenuOpen() + }, + }, + { + keybind.Undo(ev), func() { + // Ctrl-Z, Undo + s.UI.Canvas.UndoStroke() + ev.ResetKeyDown() + }, + }, + { + keybind.Redo(ev), func() { + // Ctrl-Y, Undo + s.UI.Canvas.RedoStroke() + ev.ResetKeyDown() + }, + }, + { - // Undo/Redo key bindings. - if keybind.Undo(ev) { - s.UI.Canvas.UndoStroke() - ev.ResetKeyDown() - } else if keybind.Redo(ev) { - s.UI.Canvas.RedoStroke() - ev.ResetKeyDown() - } + keybind.ZoomIn(ev), func() { + s.UI.Canvas.Zoom++ + ev.ResetKeyDown() + }, + }, + { + keybind.ZoomOut(ev), func() { + s.UI.Canvas.Zoom-- + ev.ResetKeyDown() + }, + }, + { + keybind.ZoomReset(ev), func() { + s.UI.Canvas.Zoom = 0 + ev.ResetKeyDown() + }, + }, + { + keybind.Origin(ev), func() { + d.Flash("Scrolled back to level origin (0,0)") + s.UI.Canvas.ScrollTo(render.Origin) + ev.ResetKeyDown() + }, + }, + { + keybind.CloseAllWindows(ev), func() { + s.UI.Supervisor.CloseAllWindows() + }, + }, + { + keybind.CloseTopmostWindow(ev), func() { + s.UI.Supervisor.CloseActiveWindow() + }, + }, + { + keybind.NewViewport(ev), func() { + if s.DrawingType != enum.LevelDrawing { + return + } - // Zoom in/out. - if balance.Feature.Zoom { - if keybind.ZoomIn(ev) { - d.Flash("Zoom in") - s.UI.Canvas.Zoom++ - ev.ResetKeyDown() - } else if keybind.ZoomOut(ev) { - d.Flash("Zoom out") - s.UI.Canvas.Zoom-- - ev.ResetKeyDown() - } else if keybind.ZoomReset(ev) { - d.Flash("Reset zoom") - s.UI.Canvas.Zoom = 0 - ev.ResetKeyDown() + pip := windows.MakePiPWindow(d.width, d.height, windows.PiP{ + Supervisor: s.UI.Supervisor, + Engine: s.d.Engine, + Level: s.Level, + Event: s.d.event, + + Tool: &s.UI.Canvas.Tool, + BrushSize: &s.UI.Canvas.BrushSize, + }) + + pip.Show() + }, + }, + } + for _, bind := range binders { + if bind.v { + bind.f() } } - // More keybinds - if keybind.Origin(ev) { - d.Flash("Scrolled back to level origin (0,0)") - s.UI.Canvas.ScrollTo(render.Origin) - ev.ResetKeyDown() - } - // s.UI.Loop(ev) // Switching to Play Mode? diff --git a/pkg/editor_ui_menubar.go b/pkg/editor_ui_menubar.go index 4ad8a86..20a7cab 100644 --- a/pkg/editor_ui_menubar.go +++ b/pkg/editor_ui_menubar.go @@ -6,6 +6,7 @@ package doodle import ( "git.kirsle.net/apps/doodle/pkg/balance" + "git.kirsle.net/apps/doodle/pkg/branding" "git.kirsle.net/apps/doodle/pkg/drawtool" "git.kirsle.net/apps/doodle/pkg/enum" "git.kirsle.net/apps/doodle/pkg/level/giant_screenshot" @@ -139,12 +140,15 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar { }) levelMenu.AddSeparator() - levelMenu.AddItem("New viewport", func() { - pip := windows.MakePiPWindow(340, 480, windows.PiP{ + levelMenu.AddItemAccel("New viewport", "v", func() { + pip := windows.MakePiPWindow(d.width, d.height, windows.PiP{ Supervisor: u.Supervisor, Engine: u.d.Engine, Level: u.Scene.Level, Event: u.d.event, + + Tool: &u.Scene.UI.Canvas.Tool, + BrushSize: &u.Scene.UI.Canvas.BrushSize, }) pip.Show() @@ -172,21 +176,28 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar { //////// // 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) - }) - } + 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) + }) + + viewMenu.AddSeparator() + + viewMenu.AddItemAccel("Close window", "←", func() { + u.Supervisor.CloseActiveWindow() + }) + viewMenu.AddItemAccel("Close all windows", "Shift-←", func() { + u.Supervisor.CloseAllWindows() + }) //////// // Tools menu @@ -270,6 +281,13 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar { } u.aboutWindow.Show() }) + helpMenu.AddSeparator() + helpMenu.AddItem("Go to Website", func() { + native.OpenURL(branding.Website) + }) + helpMenu.AddItem("Guidebook Online", func() { + native.OpenURL(branding.GuidebookURL) + }) menu.Supervise(u.Supervisor) menu.Compute(d.Engine) diff --git a/pkg/keybind/keybind.go b/pkg/keybind/keybind.go index 3a9a612..907b99c 100644 --- a/pkg/keybind/keybind.go +++ b/pkg/keybind/keybind.go @@ -109,6 +109,31 @@ func DebugCollision(ev *event.State) bool { return result } +// CloseTopmostWindow (Backspace) +func CloseTopmostWindow(ev *event.State) bool { + result := ev.KeyDown(`\b`) + ev.SetKeyDown(`\b`, false) + return result +} + +// CloseAllWindows (Shift+Backspace) +func CloseAllWindows(ev *event.State) bool { + result := ev.KeyDown(`\b`) && ev.Shift + if result { + ev.SetKeyDown(`\b`, false) + } + return result +} + +// NewViewport (V) +func NewViewport(ev *event.State) bool { + result := ev.KeyDown("v") + if result { + ev.SetKeyDown("v", false) + } + return result +} + // Undo (Ctrl-Z) func Undo(ev *event.State) bool { return ev.Ctrl && ev.KeyDown("z") diff --git a/pkg/windows/pip_canvas.go b/pkg/windows/pip_canvas.go index ca9e917..3f7f11f 100644 --- a/pkg/windows/pip_canvas.go +++ b/pkg/windows/pip_canvas.go @@ -18,6 +18,12 @@ type PiP struct { Engine render.Engine Level *level.Level Event *event.State + Tool *drawtool.Tool + BrushSize *int + + // Or sensible defaults: + Width int + Height int OnCancel func() } @@ -43,29 +49,53 @@ func MakePiPWindow(windowWidth, windowHeight int, cfg PiP) *ui.Window { func NewPiPWindow(cfg PiP) *ui.Window { var ( windowWidth = 340 - windowHeight = 320 + windowHeight = 300 ) - window := ui.NewWindow("Viewport (WORK IN PROGRESS!)") + if cfg.Width+cfg.Height > 0 { + windowWidth = cfg.Width + windowHeight = cfg.Height + } + + var ( + canvasWidth = windowWidth - 8 + canvasHeight = windowHeight - 4 - 48 // for the titlebar? + ) + + window := ui.NewWindow("Viewport") window.SetButtons(ui.CloseButton) window.Configure(ui.Config{ - Width: windowWidth, - Height: windowHeight, - Background: render.RGBA(255, 200, 255, 255), + Width: windowWidth, + Height: windowHeight, }) canvas := uix.NewCanvas(128, true) - canvas.Name = "Viewport" + canvas.Name = "Viewport (WIP)" canvas.LoadLevel(cfg.Level) canvas.InstallActors(cfg.Level.Actors) canvas.Scrollable = true canvas.Editable = true - canvas.Resize(render.NewRect(windowWidth, windowHeight)) + canvas.Resize(render.NewRect(canvasWidth, canvasHeight)) // NOTE: my UI toolkit calls this every tick, if this is "fixed" // in the future make one that does. + var ( + curTool = *cfg.Tool + curThicc = *cfg.BrushSize + ) + canvas.Tool = curTool window.Handle(ui.MouseMove, func(ed ui.EventData) error { canvas.Loop(cfg.Event) + + // Check if bound values have modified. + if *cfg.Tool != curTool { + curTool = *cfg.Tool + canvas.Tool = curTool + } + if *cfg.BrushSize != curThicc { + curThicc = *cfg.BrushSize + canvas.BrushSize = curThicc + } return nil }) @@ -81,31 +111,46 @@ func NewPiPWindow(cfg PiP) *ui.Window { window.Pack(bottomFrame, ui.Pack{ Side: ui.N, FillX: true, + PadY: 4, }) frame := ui.NewFrame("Button frame") buttons := []struct { label string tooltip string - down func() f func() }{ - {"^", "Scroll up", func() { - canvas.ScrollBy(render.NewPoint(0, 64)) - }, nil}, - {"v", "Scroll down", func() { - canvas.ScrollBy(render.NewPoint(0, -64)) - }, nil}, - {"<", "Scroll left", func() { - canvas.ScrollBy(render.NewPoint(64, 0)) - }, nil}, - {">", "Scroll right", func() { - canvas.ScrollBy(render.NewPoint(-64, 0)) - }, nil}, - {"0", "Reset to origin", nil, func() { - canvas.ScrollTo(render.Origin) + {"Smaller", "Shrink this viewport window by 20%", func() { + // Make a smaller version of the same window, and close. + cfg.Width = int(float64(windowWidth) * 0.8) + cfg.Height = int(float64(windowHeight) * 0.8) + pip := MakePiPWindow(cfg.Width, cfg.Height, cfg) + pip.MoveTo(window.Point()) + window.Close() + pip.Show() }}, - {"???", "Load a different drawing", nil, func() { + {"Larger", "Grow this viewport window by 20%", func() { + // Make a smaller version of the same window, and close. + cfg.Width = int(float64(windowWidth) * 1.2) + cfg.Height = int(float64(windowHeight) * 1.2) + pip := MakePiPWindow(cfg.Width, cfg.Height, cfg) + pip.MoveTo(window.Point()) + window.Close() + pip.Show() + }}, + {"Refresh", "Update the state of doodads placed in this level", func() { + canvas.ClearActors() + canvas.InstallActors(cfg.Level.Actors) + }}, + {"Rename", "Give this viewport window a custom name", func() { + shmem.Prompt("Give this viewport a name: ", func(answer string) { + if answer == "" { + return + } + window.Title = answer + }) + }}, + {"???", "Load a different drawing (experimental!)", func() { shmem.Prompt("Filename to open: ", func(answer string) { if answer == "" { return @@ -121,27 +166,25 @@ func NewPiPWindow(cfg PiP) *ui.Window { }) }}, } - for _, button := range buttons { + for i, button := range buttons { + // Start axing buttons if window size is too small. + if windowWidth < 150 && i > 1 { + break + } else if windowWidth < 250 && i > 2 { + break + } + button := button btn := ui.NewButton(button.label, ui.NewLabel(ui.Label{ Text: button.label, - Font: balance.MenuFont, + Font: balance.SmallFont, })) - if button.down != nil { - btn.Handle(ui.MouseDown, func(ed ui.EventData) error { - button.down() - return nil - }) - } - - if button.f != nil { - btn.Handle(ui.Click, func(ed ui.EventData) error { - button.f() - return nil - }) - } + btn.Handle(ui.Click, func(ed ui.EventData) error { + button.f() + return nil + }) btn.Compute(cfg.Engine) cfg.Supervisor.Add(btn) @@ -153,55 +196,16 @@ func NewPiPWindow(cfg PiP) *ui.Window { frame.Pack(btn, ui.Pack{ Side: ui.W, - PadX: 4, + PadX: 1, Expand: true, Fill: true, }) } - // Tool selector. - toolBtn := ui.NewSelectBox("Tool Select", ui.Label{ - Font: ui.MenuFont, - }) - toolBtn.AlwaysChange = true - frame.Pack(toolBtn, ui.Pack{ - Side: ui.W, - Expand: true, - }) - - toolBtn.AddItem("Pencil", drawtool.PencilTool, func() {}) - toolBtn.AddItem("Line", drawtool.LineTool, func() {}) - toolBtn.AddItem("Rectangle", drawtool.RectTool, func() {}) - toolBtn.AddItem("Ellipse", drawtool.EllipseTool, func() {}) - - // TODO: Actor and Link Tools don't work as the canvas needs - // hooks for their events. The code in EditorUI#SetupCanvas should - // be made reusable here. - // toolBtn.AddItem("Link", drawtool.LinkTool, func() {}) - // toolBtn.AddItem("Actor", drawtool.ActorTool, func() {}) - - toolBtn.Handle(ui.Change, func(ed ui.EventData) error { - selection, _ := toolBtn.GetValue() - tool, _ := selection.Value.(drawtool.Tool) - - // log.Error("Change: %d, b4: %s", value, canvas.Tool) - canvas.Tool = tool - - return nil - }) - - ui.NewTooltip(toolBtn, ui.Tooltip{ - Text: "Draw tool (viewport only)", - Edge: ui.Top, - }) - - toolBtn.Supervise(cfg.Supervisor) - cfg.Supervisor.Add(toolBtn) - bottomFrame.Pack(frame, ui.Pack{ Side: ui.N, PadX: 8, - PadY: 12, + PadY: 0, }) return window