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.
pull/84/head
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
SmallFont = render.Text{
Size: 10,
Padding: 4,
Padding: 2,
Color: render.Black,
}

View File

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

View File

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

View File

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

View File

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

View File

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