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.
This commit is contained in:
Noah 2021-10-06 22:22:34 -07:00
parent a24c94a161
commit 0b0af70a62
6 changed files with 246 additions and 141 deletions

View File

@ -141,7 +141,7 @@ var (
// Small font // Small font
SmallFont = render.Text{ SmallFont = render.Text{
Size: 10, Size: 10,
Padding: 4, Padding: 2,
Color: render.Black, Color: render.Black,
} }

View File

@ -11,4 +11,5 @@ const (
// Update check URL // Update check URL
UpdateCheckJSON = "https://download.sketchymaze.com/version.json" UpdateCheckJSON = "https://download.sketchymaze.com/version.json"
GuidebookURL = "https://www.sketchymaze.com/guidebook/"
) )

View File

@ -6,7 +6,6 @@ import (
"os" "os"
"strings" "strings"
"git.kirsle.net/apps/doodle/pkg/balance"
"git.kirsle.net/apps/doodle/pkg/doodads" "git.kirsle.net/apps/doodle/pkg/doodads"
"git.kirsle.net/apps/doodle/pkg/drawtool" "git.kirsle.net/apps/doodle/pkg/drawtool"
"git.kirsle.net/apps/doodle/pkg/enum" "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/modal/loadscreen"
"git.kirsle.net/apps/doodle/pkg/usercfg" "git.kirsle.net/apps/doodle/pkg/usercfg"
"git.kirsle.net/apps/doodle/pkg/userdir" "git.kirsle.net/apps/doodle/pkg/userdir"
"git.kirsle.net/apps/doodle/pkg/windows"
"git.kirsle.net/go/render" "git.kirsle.net/go/render"
"git.kirsle.net/go/render/event" "git.kirsle.net/go/render/event"
) )
@ -288,54 +288,111 @@ func (s *EditorScene) Loop(d *Doodle, ev *event.State) error {
} }
} }
// Menu key bindings. // Run all of the keybinds.
if keybind.NewLevel(ev) { binders := []struct {
// Ctrl-N, New Level v bool
s.MenuNewLevel() f func()
} else if keybind.SaveAs(ev) { }{
// Shift-Ctrl-S, Save As {
s.MenuSave(true)() keybind.NewLevel(ev), func() {
} else if keybind.Save(ev) { // Ctrl-N, New Level
// Ctrl-S, Save s.MenuNewLevel()
s.MenuSave(false)() },
} else if keybind.Open(ev) { },
// Ctrl-O, Open {
s.MenuOpen() 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. keybind.ZoomIn(ev), func() {
if keybind.Undo(ev) { s.UI.Canvas.Zoom++
s.UI.Canvas.UndoStroke() ev.ResetKeyDown()
ev.ResetKeyDown() },
} else if keybind.Redo(ev) { },
s.UI.Canvas.RedoStroke() {
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. pip := windows.MakePiPWindow(d.width, d.height, windows.PiP{
if balance.Feature.Zoom { Supervisor: s.UI.Supervisor,
if keybind.ZoomIn(ev) { Engine: s.d.Engine,
d.Flash("Zoom in") Level: s.Level,
s.UI.Canvas.Zoom++ Event: s.d.event,
ev.ResetKeyDown()
} else if keybind.ZoomOut(ev) { Tool: &s.UI.Canvas.Tool,
d.Flash("Zoom out") BrushSize: &s.UI.Canvas.BrushSize,
s.UI.Canvas.Zoom-- })
ev.ResetKeyDown()
} else if keybind.ZoomReset(ev) { pip.Show()
d.Flash("Reset zoom") },
s.UI.Canvas.Zoom = 0 },
ev.ResetKeyDown() }
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) // s.UI.Loop(ev)
// Switching to Play Mode? // Switching to Play Mode?

View File

@ -6,6 +6,7 @@ package doodle
import ( import (
"git.kirsle.net/apps/doodle/pkg/balance" "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/drawtool"
"git.kirsle.net/apps/doodle/pkg/enum" "git.kirsle.net/apps/doodle/pkg/enum"
"git.kirsle.net/apps/doodle/pkg/level/giant_screenshot" "git.kirsle.net/apps/doodle/pkg/level/giant_screenshot"
@ -139,12 +140,15 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar {
}) })
levelMenu.AddSeparator() levelMenu.AddSeparator()
levelMenu.AddItem("New viewport", func() { levelMenu.AddItemAccel("New viewport", "v", func() {
pip := windows.MakePiPWindow(340, 480, windows.PiP{ pip := windows.MakePiPWindow(d.width, d.height, windows.PiP{
Supervisor: u.Supervisor, Supervisor: u.Supervisor,
Engine: u.d.Engine, Engine: u.d.Engine,
Level: u.Scene.Level, Level: u.Scene.Level,
Event: u.d.event, Event: u.d.event,
Tool: &u.Scene.UI.Canvas.Tool,
BrushSize: &u.Scene.UI.Canvas.BrushSize,
}) })
pip.Show() pip.Show()
@ -172,21 +176,28 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar {
//////// ////////
// View menu // View menu
if balance.Feature.Zoom { viewMenu := menu.AddMenu("View")
viewMenu := menu.AddMenu("View") viewMenu.AddItemAccel("Zoom in", "+", func() {
viewMenu.AddItemAccel("Zoom in", "+", func() { u.Canvas.Zoom++
u.Canvas.Zoom++ })
}) viewMenu.AddItemAccel("Zoom out", "-", func() {
viewMenu.AddItemAccel("Zoom out", "-", func() { u.Canvas.Zoom--
u.Canvas.Zoom-- })
}) viewMenu.AddItemAccel("Reset zoom", "1", func() {
viewMenu.AddItemAccel("Reset zoom", "1", func() { u.Canvas.Zoom = 0
u.Canvas.Zoom = 0 })
}) viewMenu.AddItemAccel("Scroll drawing to origin", "0", func() {
viewMenu.AddItemAccel("Scroll drawing to origin", "0", func() { u.Canvas.ScrollTo(render.Origin)
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 // Tools menu
@ -270,6 +281,13 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar {
} }
u.aboutWindow.Show() 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.Supervise(u.Supervisor)
menu.Compute(d.Engine) menu.Compute(d.Engine)

View File

@ -109,6 +109,31 @@ func DebugCollision(ev *event.State) bool {
return result 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) // Undo (Ctrl-Z)
func Undo(ev *event.State) bool { func Undo(ev *event.State) bool {
return ev.Ctrl && ev.KeyDown("z") return ev.Ctrl && ev.KeyDown("z")

View File

@ -18,6 +18,12 @@ type PiP struct {
Engine render.Engine Engine render.Engine
Level *level.Level Level *level.Level
Event *event.State Event *event.State
Tool *drawtool.Tool
BrushSize *int
// Or sensible defaults:
Width int
Height int
OnCancel func() OnCancel func()
} }
@ -43,29 +49,53 @@ func MakePiPWindow(windowWidth, windowHeight int, cfg PiP) *ui.Window {
func NewPiPWindow(cfg PiP) *ui.Window { func NewPiPWindow(cfg PiP) *ui.Window {
var ( var (
windowWidth = 340 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.SetButtons(ui.CloseButton)
window.Configure(ui.Config{ window.Configure(ui.Config{
Width: windowWidth, Width: windowWidth,
Height: windowHeight, Height: windowHeight,
Background: render.RGBA(255, 200, 255, 255),
}) })
canvas := uix.NewCanvas(128, true) canvas := uix.NewCanvas(128, true)
canvas.Name = "Viewport" canvas.Name = "Viewport (WIP)"
canvas.LoadLevel(cfg.Level) canvas.LoadLevel(cfg.Level)
canvas.InstallActors(cfg.Level.Actors) canvas.InstallActors(cfg.Level.Actors)
canvas.Scrollable = true canvas.Scrollable = true
canvas.Editable = 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" // NOTE: my UI toolkit calls this every tick, if this is "fixed"
// in the future make one that does. // 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 { window.Handle(ui.MouseMove, func(ed ui.EventData) error {
canvas.Loop(cfg.Event) 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 return nil
}) })
@ -81,31 +111,46 @@ func NewPiPWindow(cfg PiP) *ui.Window {
window.Pack(bottomFrame, ui.Pack{ window.Pack(bottomFrame, ui.Pack{
Side: ui.N, Side: ui.N,
FillX: true, FillX: true,
PadY: 4,
}) })
frame := ui.NewFrame("Button frame") frame := ui.NewFrame("Button frame")
buttons := []struct { buttons := []struct {
label string label string
tooltip string tooltip string
down func()
f func() f func()
}{ }{
{"^", "Scroll up", func() { {"Smaller", "Shrink this viewport window by 20%", func() {
canvas.ScrollBy(render.NewPoint(0, 64)) // Make a smaller version of the same window, and close.
}, nil}, cfg.Width = int(float64(windowWidth) * 0.8)
{"v", "Scroll down", func() { cfg.Height = int(float64(windowHeight) * 0.8)
canvas.ScrollBy(render.NewPoint(0, -64)) pip := MakePiPWindow(cfg.Width, cfg.Height, cfg)
}, nil}, pip.MoveTo(window.Point())
{"<", "Scroll left", func() { window.Close()
canvas.ScrollBy(render.NewPoint(64, 0)) pip.Show()
}, nil},
{">", "Scroll right", func() {
canvas.ScrollBy(render.NewPoint(-64, 0))
}, nil},
{"0", "Reset to origin", nil, func() {
canvas.ScrollTo(render.Origin)
}}, }},
{"???", "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) { shmem.Prompt("Filename to open: ", func(answer string) {
if answer == "" { if answer == "" {
return 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 button := button
btn := ui.NewButton(button.label, ui.NewLabel(ui.Label{ btn := ui.NewButton(button.label, ui.NewLabel(ui.Label{
Text: button.label, Text: button.label,
Font: balance.MenuFont, Font: balance.SmallFont,
})) }))
if button.down != nil { btn.Handle(ui.Click, func(ed ui.EventData) error {
btn.Handle(ui.MouseDown, func(ed ui.EventData) error { button.f()
button.down() return nil
return nil })
})
}
if button.f != nil {
btn.Handle(ui.Click, func(ed ui.EventData) error {
button.f()
return nil
})
}
btn.Compute(cfg.Engine) btn.Compute(cfg.Engine)
cfg.Supervisor.Add(btn) cfg.Supervisor.Add(btn)
@ -153,55 +196,16 @@ func NewPiPWindow(cfg PiP) *ui.Window {
frame.Pack(btn, ui.Pack{ frame.Pack(btn, ui.Pack{
Side: ui.W, Side: ui.W,
PadX: 4, PadX: 1,
Expand: true, Expand: true,
Fill: 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{ bottomFrame.Pack(frame, ui.Pack{
Side: ui.N, Side: ui.N,
PadX: 8, PadX: 8,
PadY: 12, PadY: 0,
}) })
return window return window