From 5a1ec156cad93f120bef4875ca42af558d338da7 Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Wed, 3 Jul 2019 17:19:25 -0700 Subject: [PATCH] Editor Mode: Line Tool and Rectangle Tool * Add support for the LineTool and RectTool while in the EditorMode to easily draw straight lines and rectangle outlines. * Key bindings were added to toggle tools in lieu of a proper UI to select the tool from a toolbar. * "F" for Pencil (Freehand) Tool (since "P" is for "Playtest") * "L" for Line Tool * "R" for Rectangle Tool --- README.md | 10 +++ lib/render/interface.go | 41 +++++++++++ pkg/drawtool/stroke.go | 4 ++ pkg/{uix/draw_modes.go => drawtool/tools.go} | 8 ++- pkg/editor_scene.go | 14 +++- pkg/editor_ui.go | 3 +- pkg/editor_ui_palette.go | 6 +- pkg/uix/canvas.go | 2 +- pkg/uix/canvas_editable.go | 72 +++++++++++++++++++- pkg/uix/canvas_link_tool.go | 3 +- pkg/uix/canvas_strokes.go | 2 +- 11 files changed, 151 insertions(+), 14 deletions(-) rename pkg/{uix/draw_modes.go => drawtool/tools.go} (77%) diff --git a/README.md b/README.md index cb2903c..40d0bfb 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,16 @@ Cursor Keys Scroll the view of the map around. "P" Key Playtest the current map you're working on. +"F" Key + Switch to the Pencil (Freehand) Tool +"L" Key + Switch to the Line Tool +"R" Key + Switch to the Rectangle Tool +Ctrl-Z + Undo +Ctrl-Y + Redo ``` # Built-In Doodads diff --git a/lib/render/interface.go b/lib/render/interface.go index 82d3966..3654a38 100644 --- a/lib/render/interface.go +++ b/lib/render/interface.go @@ -235,3 +235,44 @@ func IterLine(x1, y1, x2, y2 int32) chan Point { func IterLine2(p1 Point, p2 Point) chan Point { return IterLine(p1.X, p1.Y, p2.X, p2.Y) } + +// IterRect loops through all the points forming a rectangle between the +// top-left point and the bottom-right point. +func IterRect(p1, p2 Point) chan Point { + generator := make(chan Point) + + go func() { + var ( + TopLeft = p1 + BottomRight = p2 + TopRight = Point{ + X: BottomRight.X, + Y: TopLeft.Y, + } + BottomLeft = Point{ + X: TopLeft.X, + Y: BottomRight.Y, + } + ) + + // Trace all four edges and yield it. + var edges = []struct { + A Point + B Point + }{ + {TopLeft, TopRight}, + {TopLeft, BottomLeft}, + {BottomLeft, BottomRight}, + {TopRight, BottomRight}, + } + for _, edge := range edges { + for pt := range IterLine2(edge.A, edge.B) { + generator <- pt + } + } + + close(generator) + }() + + return generator +} diff --git a/pkg/drawtool/stroke.go b/pkg/drawtool/stroke.go index 15a3960..bf7ec3e 100644 --- a/pkg/drawtool/stroke.go +++ b/pkg/drawtool/stroke.go @@ -61,6 +61,10 @@ func (s *Stroke) IterPoints() chan render.Point { for point := range render.IterLine2(s.PointA, s.PointB) { ch <- point } + case Rectangle: + for point := range render.IterRect(s.PointA, s.PointB) { + ch <- point + } } close(ch) }() diff --git a/pkg/uix/draw_modes.go b/pkg/drawtool/tools.go similarity index 77% rename from pkg/uix/draw_modes.go rename to pkg/drawtool/tools.go index ebf631f..a60d154 100644 --- a/pkg/uix/draw_modes.go +++ b/pkg/drawtool/tools.go @@ -1,4 +1,4 @@ -package uix +package drawtool // Tool is a draw mode for an editable Canvas. type Tool int @@ -6,12 +6,16 @@ type Tool int // Draw modes for editable Canvas. const ( PencilTool Tool = iota // draw pixels where the mouse clicks - ActorTool // drag and move actors + LineTool + RectTool + ActorTool // drag and move actors LinkTool ) var toolNames = []string{ "Pencil", + "Line", + "Rectangle", "Doodad", // readable name for ActorTool "Link", } diff --git a/pkg/editor_scene.go b/pkg/editor_scene.go index 33b1a56..3daab4b 100644 --- a/pkg/editor_scene.go +++ b/pkg/editor_scene.go @@ -9,6 +9,7 @@ import ( "git.kirsle.net/apps/doodle/lib/events" "git.kirsle.net/apps/doodle/lib/render" "git.kirsle.net/apps/doodle/pkg/doodads" + "git.kirsle.net/apps/doodle/pkg/drawtool" "git.kirsle.net/apps/doodle/pkg/enum" "git.kirsle.net/apps/doodle/pkg/level" "git.kirsle.net/apps/doodle/pkg/log" @@ -167,9 +168,18 @@ func (s *EditorScene) Loop(d *Doodle, ev *events.State) error { s.UI.Loop(ev) // Switching to Play Mode? - if ev.KeyName.Read() == "p" { + switch ev.KeyName.Read() { + case "p": s.Playtest() - return nil + case "l": + d.Flash("Line Tool selected.") + s.UI.Canvas.Tool = drawtool.LineTool + case "f": + d.Flash("Pencil Tool selected.") + s.UI.Canvas.Tool = drawtool.PencilTool + case "r": + d.Flash("Rectangle Tool selected.") + s.UI.Canvas.Tool = drawtool.RectTool } return nil diff --git a/pkg/editor_ui.go b/pkg/editor_ui.go index 67a05cc..64f6c43 100644 --- a/pkg/editor_ui.go +++ b/pkg/editor_ui.go @@ -11,6 +11,7 @@ import ( "git.kirsle.net/apps/doodle/pkg/balance" "git.kirsle.net/apps/doodle/pkg/branding" "git.kirsle.net/apps/doodle/pkg/doodads" + "git.kirsle.net/apps/doodle/pkg/drawtool" "git.kirsle.net/apps/doodle/pkg/enum" "git.kirsle.net/apps/doodle/pkg/level" "git.kirsle.net/apps/doodle/pkg/log" @@ -311,7 +312,7 @@ func (u *EditorUI) SetupCanvas(d *Doodle) *uix.Canvas { b.Actor.AddLink(idA) // Reset the Link tool. - drawing.Tool = uix.ActorTool + drawing.Tool = drawtool.ActorTool d.Flash("Linked '%s' and '%s' together", a.Doodad.Title, b.Doodad.Title) } diff --git a/pkg/editor_ui_palette.go b/pkg/editor_ui_palette.go index 9fd7544..fd5321d 100644 --- a/pkg/editor_ui_palette.go +++ b/pkg/editor_ui_palette.go @@ -4,9 +4,9 @@ import ( "git.kirsle.net/apps/doodle/lib/render" "git.kirsle.net/apps/doodle/lib/ui" "git.kirsle.net/apps/doodle/pkg/balance" + "git.kirsle.net/apps/doodle/pkg/drawtool" "git.kirsle.net/apps/doodle/pkg/enum" "git.kirsle.net/apps/doodle/pkg/log" - "git.kirsle.net/apps/doodle/pkg/uix" ) // SetupPalette sets up the palette panel. @@ -31,11 +31,11 @@ func (u *EditorUI) SetupPalette(d *Doodle) *ui.Window { })) tab.Handle(ui.Click, func(p render.Point) { if u.paletteTab == "Palette" { - u.Canvas.Tool = uix.PencilTool + u.Canvas.Tool = drawtool.PencilTool u.PaletteTab.Show() u.DoodadTab.Hide() } else { - u.Canvas.Tool = uix.ActorTool + u.Canvas.Tool = drawtool.ActorTool u.PaletteTab.Hide() u.DoodadTab.Show() } diff --git a/pkg/uix/canvas.go b/pkg/uix/canvas.go index 111eaa9..8d4e119 100644 --- a/pkg/uix/canvas.go +++ b/pkg/uix/canvas.go @@ -30,7 +30,7 @@ type Canvas struct { Scrollable bool // Cursor keys will scroll the viewport of this canvas. // Selected draw tool/mode, default Pencil, for editable canvases. - Tool Tool + Tool drawtool.Tool // MaskColor will force every pixel to render as this color regardless of // the palette index of that pixel. Otherwise pixels behave the same and diff --git a/pkg/uix/canvas_editable.go b/pkg/uix/canvas_editable.go index 1741110..65e49b6 100644 --- a/pkg/uix/canvas_editable.go +++ b/pkg/uix/canvas_editable.go @@ -21,7 +21,7 @@ func (w *Canvas) loopEditable(ev *events.State) error { ) switch w.Tool { - case PencilTool: + case drawtool.PencilTool: // If no swatch is active, do nothing with mouse clicks. if w.Palette.ActiveSwatch == nil { return nil @@ -90,7 +90,73 @@ func (w *Canvas) loopEditable(ev *events.State) error { w.lastPixel = nil } - case ActorTool: + case drawtool.LineTool: + // If no swatch is active, do nothing with mouse clicks. + if w.Palette.ActiveSwatch == nil { + return nil + } + + // Clicking? Log all the pixels while doing so. + if ev.Button1.Now { + // Initialize a new Stroke for this atomic drawing operation? + if w.currentStroke == nil { + w.currentStroke = drawtool.NewStroke(drawtool.Line, w.Palette.ActiveSwatch.Color) + w.currentStroke.ExtraData = w.Palette.ActiveSwatch + w.currentStroke.PointA = render.NewPoint(cursor.X, cursor.Y) + w.AddStroke(w.currentStroke) + } + + w.currentStroke.PointB = render.NewPoint(cursor.X, cursor.Y) + } else { + // Mouse released, commit the points to the drawing. + if w.currentStroke != nil { + for pt := range render.IterLine2(w.currentStroke.PointA, w.currentStroke.PointB) { + w.chunks.Set(pt, w.Palette.ActiveSwatch) + } + + // Add the stroke to level history. + if w.level != nil { + w.level.UndoHistory.AddStroke(w.currentStroke) + } + + w.RemoveStroke(w.currentStroke) + w.currentStroke = nil + } + } + case drawtool.RectTool: + // If no swatch is active, do nothing with mouse clicks. + if w.Palette.ActiveSwatch == nil { + return nil + } + + // Clicking? Log all the pixels while doing so. + if ev.Button1.Now { + // Initialize a new Stroke for this atomic drawing operation? + if w.currentStroke == nil { + w.currentStroke = drawtool.NewStroke(drawtool.Rectangle, w.Palette.ActiveSwatch.Color) + w.currentStroke.ExtraData = w.Palette.ActiveSwatch + w.currentStroke.PointA = render.NewPoint(cursor.X, cursor.Y) + w.AddStroke(w.currentStroke) + } + + w.currentStroke.PointB = render.NewPoint(cursor.X, cursor.Y) + } else { + // Mouse released, commit the points to the drawing. + if w.currentStroke != nil { + for pt := range render.IterRect(w.currentStroke.PointA, w.currentStroke.PointB) { + w.chunks.Set(pt, w.Palette.ActiveSwatch) + } + + // Add the stroke to level history. + if w.level != nil { + w.level.UndoHistory.AddStroke(w.currentStroke) + } + + w.RemoveStroke(w.currentStroke) + w.currentStroke = nil + } + } + case drawtool.ActorTool: // See if any of the actors are below the mouse cursor. var WP = w.WorldIndexAt(cursor) @@ -134,7 +200,7 @@ func (w *Canvas) loopEditable(ev *events.State) error { if len(deleteActors) > 0 && w.OnDeleteActors != nil { w.OnDeleteActors(deleteActors) } - case LinkTool: + case drawtool.LinkTool: // See if any of the actors are below the mouse cursor. var WP = w.WorldIndexAt(cursor) diff --git a/pkg/uix/canvas_link_tool.go b/pkg/uix/canvas_link_tool.go index 27e51b7..b8995f4 100644 --- a/pkg/uix/canvas_link_tool.go +++ b/pkg/uix/canvas_link_tool.go @@ -3,12 +3,13 @@ package uix import ( "errors" + "git.kirsle.net/apps/doodle/pkg/drawtool" "git.kirsle.net/apps/doodle/pkg/shmem" ) // LinkStart initializes the Link tool. func (w *Canvas) LinkStart() { - w.Tool = LinkTool + w.Tool = drawtool.LinkTool w.linkFirst = nil } diff --git a/pkg/uix/canvas_strokes.go b/pkg/uix/canvas_strokes.go index 4872eca..9fd647d 100644 --- a/pkg/uix/canvas_strokes.go +++ b/pkg/uix/canvas_strokes.go @@ -94,7 +94,7 @@ func (w *Canvas) presentStrokes(e render.Engine) { w.drawStrokes(e, strokes) // Dynamic actor links visible in the ActorTool and LinkTool. - if w.Tool == ActorTool || w.Tool == LinkTool { + if w.Tool == drawtool.ActorTool || w.Tool == drawtool.LinkTool { w.presentActorLinks(e) } }