Picture-in-Picture Window (WIP)

In the Level Editor, the "Level->New viewport" menu opens a window with
its own view into your level. You can open as many viewports as you
want.

* Mouse over a viewport and the arrow keys scroll that canvas instead of
  the main editor canvas!
* You can draw inside the viewports! A selectbox to choose the tool to
  draw with. No palette or thickness support yet!
* The actors are installed as-is when the viewport is created and it
  doesn't show any changes to actors after. Make a new viewport for a
  refreshed view.
* Strokes committed inside the viewport show up in the main editor (and
  in other viewports), and vice versa. The viewports accurately track
  changes to the level's colors, just not the actors.
* Fun feature to load a DIFFERENT level inside of the viewport! Editing
  that level doesn't save changes or anything.
This commit is contained in:
Noah 2021-10-03 21:18:39 -07:00
parent 4469847c72
commit 1f83300cec
2 changed files with 220 additions and 0 deletions

View File

@ -137,6 +137,18 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar {
levelMenu.AddItem("Open screenshot folder", func() { levelMenu.AddItem("Open screenshot folder", func() {
native.OpenLocalURL(userdir.ScreenshotDirectory) native.OpenLocalURL(userdir.ScreenshotDirectory)
}) })
levelMenu.AddSeparator()
levelMenu.AddItem("New viewport", func() {
pip := windows.MakePiPWindow(340, 480, windows.PiP{
Supervisor: u.Supervisor,
Engine: u.d.Engine,
Level: u.Scene.Level,
Event: u.d.event,
})
pip.Show()
})
} }
//////// ////////

208
pkg/windows/pip_canvas.go Normal file
View File

@ -0,0 +1,208 @@
package windows
import (
"git.kirsle.net/apps/doodle/pkg/balance"
"git.kirsle.net/apps/doodle/pkg/drawtool"
"git.kirsle.net/apps/doodle/pkg/level"
"git.kirsle.net/apps/doodle/pkg/shmem"
"git.kirsle.net/apps/doodle/pkg/uix"
"git.kirsle.net/go/render"
"git.kirsle.net/go/render/event"
"git.kirsle.net/go/ui"
)
// PiP window.
type PiP struct {
// Settings passed in by doodle
Supervisor *ui.Supervisor
Engine render.Engine
Level *level.Level
Event *event.State
OnCancel func()
}
// MakePiPWindow initializes a license window for any scene.
// The window width/height are the actual SDL2 window dimensions.
func MakePiPWindow(windowWidth, windowHeight int, cfg PiP) *ui.Window {
win := NewPiPWindow(cfg)
win.Compute(cfg.Engine)
win.Supervise(cfg.Supervisor)
// Center the window.
size := win.Size()
win.MoveTo(render.Point{
X: (windowWidth / 2) - (size.W / 2),
Y: (windowHeight / 2) - (size.H / 2),
})
return win
}
// NewPiPWindow initializes the window.
func NewPiPWindow(cfg PiP) *ui.Window {
var (
windowWidth = 340
windowHeight = 320
)
window := ui.NewWindow("Viewport (WORK IN PROGRESS!)")
window.SetButtons(ui.CloseButton)
window.Configure(ui.Config{
Width: windowWidth,
Height: windowHeight,
Background: render.RGBA(255, 200, 255, 255),
})
canvas := uix.NewCanvas(128, true)
canvas.Name = "Viewport"
canvas.LoadLevel(cfg.Level)
canvas.InstallActors(cfg.Level.Actors)
canvas.Scrollable = true
canvas.Editable = true
canvas.Resize(render.NewRect(windowWidth, windowHeight))
// NOTE: my UI toolkit calls this every tick, if this is "fixed"
// in the future make one that does.
window.Handle(ui.MouseMove, func(ed ui.EventData) error {
canvas.Loop(cfg.Event)
return nil
})
window.Pack(canvas, ui.Pack{
Side: ui.N,
FillX: true,
})
/////////////
// Buttons at bottom of window
bottomFrame := ui.NewFrame("Button Frame")
window.Pack(bottomFrame, ui.Pack{
Side: ui.N,
FillX: true,
})
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)
}},
{"???", "Load a different drawing", nil, func() {
shmem.Prompt("Filename to open: ", func(answer string) {
if answer == "" {
return
}
if lvl, err := level.LoadFile(answer); err == nil {
canvas.ClearActors()
canvas.LoadLevel(lvl)
canvas.InstallActors(lvl.Actors)
} else {
shmem.Flash(err.Error())
}
})
}},
}
for _, button := range buttons {
button := button
btn := ui.NewButton(button.label, ui.NewLabel(ui.Label{
Text: button.label,
Font: balance.MenuFont,
}))
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.Compute(cfg.Engine)
cfg.Supervisor.Add(btn)
ui.NewTooltip(btn, ui.Tooltip{
Text: button.tooltip,
Edge: ui.Top,
})
frame.Pack(btn, ui.Pack{
Side: ui.W,
PadX: 4,
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,
})
return window
}