From 1f83300cec0a26bb05f887075f709d6be83bf4fa Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Sun, 3 Oct 2021 21:18:39 -0700 Subject: [PATCH] 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. --- pkg/editor_ui_menubar.go | 12 +++ pkg/windows/pip_canvas.go | 208 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 pkg/windows/pip_canvas.go diff --git a/pkg/editor_ui_menubar.go b/pkg/editor_ui_menubar.go index e84ddb2..4ad8a86 100644 --- a/pkg/editor_ui_menubar.go +++ b/pkg/editor_ui_menubar.go @@ -137,6 +137,18 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar { levelMenu.AddItem("Open screenshot folder", func() { 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() + }) } //////// diff --git a/pkg/windows/pip_canvas.go b/pkg/windows/pip_canvas.go new file mode 100644 index 0000000..ca9e917 --- /dev/null +++ b/pkg/windows/pip_canvas.go @@ -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 +}