2018-10-21 00:08:20 +00:00
|
|
|
package uix
|
|
|
|
|
|
|
|
import (
|
2022-09-24 22:17:25 +00:00
|
|
|
"git.kirsle.net/SketchyMaze/doodle/pkg/balance"
|
|
|
|
"git.kirsle.net/SketchyMaze/doodle/pkg/drawtool"
|
|
|
|
"git.kirsle.net/SketchyMaze/doodle/pkg/keybind"
|
|
|
|
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
|
|
|
|
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
|
|
|
|
"git.kirsle.net/SketchyMaze/doodle/pkg/shmem"
|
2019-12-23 02:21:58 +00:00
|
|
|
"git.kirsle.net/go/render"
|
|
|
|
"git.kirsle.net/go/render/event"
|
2019-12-28 00:31:58 +00:00
|
|
|
"git.kirsle.net/go/ui"
|
2018-10-21 00:08:20 +00:00
|
|
|
)
|
|
|
|
|
2020-11-16 02:02:35 +00:00
|
|
|
// Modified returns whether the canvas has been modified since it was last
|
|
|
|
// loaded. Methods like Load and LoadFile will set modified to false, and
|
|
|
|
// commitStroke sets it to true.
|
|
|
|
func (w *Canvas) Modified() bool {
|
|
|
|
return w.modified
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetModified sets the modified bit on the canvas.
|
|
|
|
func (w *Canvas) SetModified(v bool) {
|
|
|
|
w.modified = v
|
|
|
|
}
|
|
|
|
|
Eraser Tool, Brush Sizes
* Implement Brush Sizes for drawtool.Stroke and add a UI to the tools panel
to control the brush size.
* Brush sizes: 1, 2, 4, 8, 16, 24, 32, 48, 64
* Add the Eraser Tool to editor mode. It uses a default brush size of 16
and a max size of 32 due to some performance issues.
* The Undo/Redo system now remembers the original color of pixels when
you change them, so that Undo will set them back how they were instead
of deleting the pixel entirely. Due to performance issues, this only
happens when your Brush Size is 0 (drawing single-pixel shapes).
* UI: Add an IntVariable option to ui.Label to bind showing the value of
an int reference.
Aforementioned performance issues:
* When we try to remember whole rects of pixels for drawing thick
shapes, it requires a ton of scanning for each step of the shape. Even
de-duplicating pixel checks, tons of extra reads are constantly
checked.
* The Eraser is the only tool that absolutely needs to be able to
remember wiped pixels AND have large brush sizes. The performance
sucks and lags a bit if you erase a lot all at once, but it's a
trade-off for now.
* So pixels aren't remembered when drawing lines in your level with
thick brushes, so the Undo action will simply delete your pixels and not
reset them. Only the Eraser can bring back pixels.
2019-07-12 02:07:46 +00:00
|
|
|
// commitStroke is the common function that applies a stroke the user is
|
|
|
|
// actively drawing onto the canvas. This is for Edit Mode.
|
|
|
|
func (w *Canvas) commitStroke(tool drawtool.Tool, addHistory bool) {
|
|
|
|
if w.currentStroke == nil {
|
|
|
|
// nothing to commit
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-09-11 23:52:22 +00:00
|
|
|
// Zoom the stroke coordinates (this modifies the pointer).
|
|
|
|
// Note: all the points on the stroke were mouse cursor coordinates on the screen.
|
2021-07-12 04:54:28 +00:00
|
|
|
w.currentStroke = w.ZoomStroke(w.currentStroke)
|
2020-11-20 04:08:38 +00:00
|
|
|
|
2020-11-16 02:02:35 +00:00
|
|
|
// Mark the canvas as modified.
|
|
|
|
w.modified = true
|
|
|
|
|
Eraser Tool, Brush Sizes
* Implement Brush Sizes for drawtool.Stroke and add a UI to the tools panel
to control the brush size.
* Brush sizes: 1, 2, 4, 8, 16, 24, 32, 48, 64
* Add the Eraser Tool to editor mode. It uses a default brush size of 16
and a max size of 32 due to some performance issues.
* The Undo/Redo system now remembers the original color of pixels when
you change them, so that Undo will set them back how they were instead
of deleting the pixel entirely. Due to performance issues, this only
happens when your Brush Size is 0 (drawing single-pixel shapes).
* UI: Add an IntVariable option to ui.Label to bind showing the value of
an int reference.
Aforementioned performance issues:
* When we try to remember whole rects of pixels for drawing thick
shapes, it requires a ton of scanning for each step of the shape. Even
de-duplicating pixel checks, tons of extra reads are constantly
checked.
* The Eraser is the only tool that absolutely needs to be able to
remember wiped pixels AND have large brush sizes. The performance
sucks and lags a bit if you erase a lot all at once, but it's a
trade-off for now.
* So pixels aren't remembered when drawing lines in your level with
thick brushes, so the Undo action will simply delete your pixels and not
reset them. Only the Eraser can bring back pixels.
2019-07-12 02:07:46 +00:00
|
|
|
var (
|
|
|
|
deleting = w.currentStroke.Shape == drawtool.Eraser
|
|
|
|
dedupe = map[render.Point]interface{}{} // don't revisit the same point twice
|
|
|
|
|
|
|
|
// Helper functions to set pixels on the level while storing the original
|
|
|
|
// value of any pixel being replaced.
|
|
|
|
set = func(pt render.Point, sw *level.Swatch) {
|
|
|
|
// Take note of what pixel was originally here before we change it.
|
|
|
|
if swatch, err := w.chunks.Get(pt); err == nil {
|
|
|
|
if _, ok := dedupe[pt]; !ok {
|
|
|
|
w.currentStroke.OriginalPoints[pt] = swatch
|
|
|
|
dedupe[pt] = nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if deleting {
|
|
|
|
w.chunks.Delete(pt)
|
|
|
|
} else if sw != nil {
|
|
|
|
w.chunks.Set(pt, sw)
|
|
|
|
} else {
|
|
|
|
panic("Canvas.commitStroke.set: current stroke has no level.Swatch in ExtraData")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Rects: read existing pixels first, then write new pixels
|
|
|
|
readRect = func(rect render.Rect) {
|
|
|
|
for pt := range w.chunks.IterViewport(rect) {
|
|
|
|
point := pt.Point()
|
|
|
|
if _, ok := dedupe[point]; !ok {
|
|
|
|
w.currentStroke.OriginalPoints[pt.Point()] = pt.Swatch
|
|
|
|
dedupe[point] = nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
setRect = func(rect render.Rect, sw *level.Swatch) {
|
|
|
|
if deleting {
|
|
|
|
w.chunks.DeleteRect(rect)
|
|
|
|
} else if sw != nil {
|
|
|
|
w.chunks.SetRect(rect, sw)
|
|
|
|
} else {
|
|
|
|
panic("Canvas.commitStroke.setRect: current stroke has no level.Swatch in ExtraData")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
var swatch *level.Swatch
|
|
|
|
if v, ok := w.currentStroke.ExtraData.(*level.Swatch); ok {
|
|
|
|
swatch = v
|
|
|
|
}
|
|
|
|
|
|
|
|
if w.currentStroke.Thickness > 0 {
|
|
|
|
// Eraser Tool only: record which pixels will be blown away by this.
|
|
|
|
// This is SLOW for thick (rect-based) lines, but eraser tool must have it.
|
|
|
|
if deleting {
|
|
|
|
for rect := range w.currentStroke.IterThickPoints() {
|
|
|
|
readRect(rect)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for rect := range w.currentStroke.IterThickPoints() {
|
|
|
|
setRect(rect, swatch)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for pt := range w.currentStroke.IterPoints() {
|
|
|
|
// note: set already records the original pixel if changing it.
|
|
|
|
set(pt, swatch)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add the stroke to level history.
|
2022-03-26 20:55:06 +00:00
|
|
|
if addHistory {
|
|
|
|
w.strokeToHistory(w.currentStroke)
|
Eraser Tool, Brush Sizes
* Implement Brush Sizes for drawtool.Stroke and add a UI to the tools panel
to control the brush size.
* Brush sizes: 1, 2, 4, 8, 16, 24, 32, 48, 64
* Add the Eraser Tool to editor mode. It uses a default brush size of 16
and a max size of 32 due to some performance issues.
* The Undo/Redo system now remembers the original color of pixels when
you change them, so that Undo will set them back how they were instead
of deleting the pixel entirely. Due to performance issues, this only
happens when your Brush Size is 0 (drawing single-pixel shapes).
* UI: Add an IntVariable option to ui.Label to bind showing the value of
an int reference.
Aforementioned performance issues:
* When we try to remember whole rects of pixels for drawing thick
shapes, it requires a ton of scanning for each step of the shape. Even
de-duplicating pixel checks, tons of extra reads are constantly
checked.
* The Eraser is the only tool that absolutely needs to be able to
remember wiped pixels AND have large brush sizes. The performance
sucks and lags a bit if you erase a lot all at once, but it's a
trade-off for now.
* So pixels aren't remembered when drawing lines in your level with
thick brushes, so the Undo action will simply delete your pixels and not
reset them. Only the Eraser can bring back pixels.
2019-07-12 02:07:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
w.RemoveStroke(w.currentStroke)
|
|
|
|
w.currentStroke = nil
|
|
|
|
|
|
|
|
w.lastPixel = nil
|
|
|
|
}
|
|
|
|
|
2022-03-26 20:55:06 +00:00
|
|
|
// Add a recently drawn stroke to the UndoHistory.
|
|
|
|
func (w *Canvas) strokeToHistory(stroke *drawtool.Stroke) {
|
|
|
|
if w.level != nil {
|
|
|
|
w.level.UndoHistory.AddStroke(stroke)
|
|
|
|
} else if w.doodad != nil {
|
|
|
|
if w.doodad.UndoHistory == nil {
|
|
|
|
// HACK: if UndoHistory was not initialized properly.
|
|
|
|
w.doodad.UndoHistory = drawtool.NewHistory(balance.UndoHistory)
|
|
|
|
}
|
|
|
|
w.doodad.UndoHistory.AddStroke(stroke)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-21 00:08:20 +00:00
|
|
|
// loopEditable handles the Loop() part for editable canvases.
|
2019-12-22 22:11:01 +00:00
|
|
|
func (w *Canvas) loopEditable(ev *event.State) error {
|
2018-10-21 00:08:20 +00:00
|
|
|
// Get the absolute position of the canvas on screen to accurately match
|
|
|
|
// it up to mouse clicks.
|
|
|
|
var (
|
|
|
|
P = ui.AbsolutePosition(w)
|
|
|
|
cursor = render.Point{
|
2019-12-28 03:16:34 +00:00
|
|
|
X: ev.CursorX - P.X - w.Scroll.X,
|
|
|
|
Y: ev.CursorY - P.Y - w.Scroll.Y,
|
2018-10-21 00:08:20 +00:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
Eraser Tool, Brush Sizes
* Implement Brush Sizes for drawtool.Stroke and add a UI to the tools panel
to control the brush size.
* Brush sizes: 1, 2, 4, 8, 16, 24, 32, 48, 64
* Add the Eraser Tool to editor mode. It uses a default brush size of 16
and a max size of 32 due to some performance issues.
* The Undo/Redo system now remembers the original color of pixels when
you change them, so that Undo will set them back how they were instead
of deleting the pixel entirely. Due to performance issues, this only
happens when your Brush Size is 0 (drawing single-pixel shapes).
* UI: Add an IntVariable option to ui.Label to bind showing the value of
an int reference.
Aforementioned performance issues:
* When we try to remember whole rects of pixels for drawing thick
shapes, it requires a ton of scanning for each step of the shape. Even
de-duplicating pixel checks, tons of extra reads are constantly
checked.
* The Eraser is the only tool that absolutely needs to be able to
remember wiped pixels AND have large brush sizes. The performance
sucks and lags a bit if you erase a lot all at once, but it's a
trade-off for now.
* So pixels aren't remembered when drawing lines in your level with
thick brushes, so the Undo action will simply delete your pixels and not
reset them. Only the Eraser can bring back pixels.
2019-07-12 02:07:46 +00:00
|
|
|
// If the actual cursor is not over the actual Canvas UI element, don't
|
|
|
|
// pay any attention to clicks. I added this when I saw you were able to
|
|
|
|
// accidentally draw (with large brush size) when clicking on the Palette
|
|
|
|
// panel and not the drawing itself.
|
|
|
|
if !w.IsCursorOver() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-10-21 00:08:20 +00:00
|
|
|
switch w.Tool {
|
2022-03-05 23:31:09 +00:00
|
|
|
case drawtool.PanTool:
|
|
|
|
// Pan tool = click to pan the level.
|
2022-12-09 04:03:53 +00:00
|
|
|
var delta render.Point
|
2022-03-26 20:55:06 +00:00
|
|
|
if keybind.LeftClick(ev) || keybind.MiddleClick(ev) {
|
2022-03-05 23:31:09 +00:00
|
|
|
if !w.scrollDragging {
|
|
|
|
w.scrollDragging = true
|
|
|
|
w.scrollStartAt = shmem.Cursor
|
|
|
|
w.scrollWasAt = w.Scroll
|
|
|
|
} else {
|
2022-12-09 04:03:53 +00:00
|
|
|
delta = shmem.Cursor.Compare(w.scrollStartAt)
|
2022-03-05 23:31:09 +00:00
|
|
|
w.Scroll = w.scrollWasAt
|
|
|
|
w.Scroll.Subtract(delta)
|
|
|
|
|
|
|
|
// TODO: if I don't call this, the user is able to (temporarily!)
|
|
|
|
// pan outside the level boundaries before it snaps-back when they
|
|
|
|
// release. But the normal middle-click to pan code doesn't let
|
|
|
|
// them do this.. investigate why later.
|
|
|
|
w.loopConstrainScroll()
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if w.scrollDragging {
|
|
|
|
w.scrollDragging = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-09 04:03:53 +00:00
|
|
|
// All the Pan tool to still interact with the Settings button on mouse-over
|
|
|
|
// of an actor. On touch devices it's difficult to access an actor's settings
|
|
|
|
// without accidentally dragging the actor, so the Pan Tool allows safe access.
|
|
|
|
// NOTE: code copied from Actor Tool but with delete and drag/drop hooks removed.
|
|
|
|
var WP = w.WorldIndexAt(cursor)
|
|
|
|
for _, actor := range w.actors {
|
|
|
|
|
|
|
|
// Compute the bounding box on screen where this doodad
|
|
|
|
// visually appears.
|
|
|
|
var scrollBias = render.Point{
|
|
|
|
X: w.Scroll.X,
|
|
|
|
Y: w.Scroll.Y,
|
|
|
|
}
|
|
|
|
if w.Zoom != 0 {
|
|
|
|
scrollBias.X = w.ZoomDivide(scrollBias.X)
|
|
|
|
scrollBias.Y = w.ZoomDivide(scrollBias.Y)
|
|
|
|
}
|
|
|
|
box := render.Rect{
|
|
|
|
X: actor.Actor.Point.X - scrollBias.X - w.ZoomDivide(P.X),
|
|
|
|
Y: actor.Actor.Point.Y - scrollBias.Y - w.ZoomDivide(P.Y),
|
|
|
|
W: actor.Canvas.Size().W,
|
|
|
|
H: actor.Canvas.Size().H,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mouse hover?
|
|
|
|
if WP.Inside(box) {
|
|
|
|
actor.Canvas.Configure(ui.Config{
|
|
|
|
BorderSize: 1,
|
|
|
|
BorderColor: render.RGBA(153, 153, 153, 255),
|
|
|
|
BorderStyle: ui.BorderSolid,
|
|
|
|
Background: render.White, // TODO: cuz the border draws a bgcolor
|
|
|
|
})
|
|
|
|
|
|
|
|
// Show doodad buttons.
|
|
|
|
actor.Canvas.ShowDoodadButtons = true
|
|
|
|
|
|
|
|
// Check for a mouse down event to begin dragging this
|
|
|
|
// canvas around.
|
|
|
|
if keybind.LeftClick(ev) && delta == render.Origin {
|
|
|
|
// Did they click onto the doodad buttons?
|
|
|
|
if shmem.Cursor.Inside(actor.Canvas.doodadButtonRect()) {
|
|
|
|
keybind.ClearLeftClick(ev)
|
|
|
|
if w.OnDoodadConfig != nil {
|
|
|
|
w.OnDoodadConfig(actor)
|
|
|
|
} else {
|
|
|
|
log.Error("OnDoodadConfig: handler not defined for parent canvas")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
break
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
actor.Canvas.SetBorderSize(0)
|
|
|
|
actor.Canvas.SetBackground(render.RGBA(0, 0, 1, 0)) // TODO
|
|
|
|
actor.Canvas.ShowDoodadButtons = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-04 00:19:25 +00:00
|
|
|
case drawtool.PencilTool:
|
2018-10-21 00:08:20 +00:00
|
|
|
// 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.
|
2022-03-26 20:55:06 +00:00
|
|
|
if keybind.LeftClick(ev) {
|
2019-07-03 23:22:30 +00:00
|
|
|
// Initialize a new Stroke for this atomic drawing operation?
|
|
|
|
if w.currentStroke == nil {
|
|
|
|
w.currentStroke = drawtool.NewStroke(drawtool.Freehand, w.Palette.ActiveSwatch.Color)
|
2021-06-10 05:36:32 +00:00
|
|
|
w.currentStroke.Pattern = w.Palette.ActiveSwatch.Pattern
|
Eraser Tool, Brush Sizes
* Implement Brush Sizes for drawtool.Stroke and add a UI to the tools panel
to control the brush size.
* Brush sizes: 1, 2, 4, 8, 16, 24, 32, 48, 64
* Add the Eraser Tool to editor mode. It uses a default brush size of 16
and a max size of 32 due to some performance issues.
* The Undo/Redo system now remembers the original color of pixels when
you change them, so that Undo will set them back how they were instead
of deleting the pixel entirely. Due to performance issues, this only
happens when your Brush Size is 0 (drawing single-pixel shapes).
* UI: Add an IntVariable option to ui.Label to bind showing the value of
an int reference.
Aforementioned performance issues:
* When we try to remember whole rects of pixels for drawing thick
shapes, it requires a ton of scanning for each step of the shape. Even
de-duplicating pixel checks, tons of extra reads are constantly
checked.
* The Eraser is the only tool that absolutely needs to be able to
remember wiped pixels AND have large brush sizes. The performance
sucks and lags a bit if you erase a lot all at once, but it's a
trade-off for now.
* So pixels aren't remembered when drawing lines in your level with
thick brushes, so the Undo action will simply delete your pixels and not
reset them. Only the Eraser can bring back pixels.
2019-07-12 02:07:46 +00:00
|
|
|
w.currentStroke.Thickness = w.BrushSize
|
2019-07-03 23:22:30 +00:00
|
|
|
w.currentStroke.ExtraData = w.Palette.ActiveSwatch
|
|
|
|
w.AddStroke(w.currentStroke)
|
|
|
|
}
|
|
|
|
|
2018-10-21 00:08:20 +00:00
|
|
|
lastPixel := w.lastPixel
|
|
|
|
pixel := &level.Pixel{
|
|
|
|
X: cursor.X,
|
|
|
|
Y: cursor.Y,
|
|
|
|
Swatch: w.Palette.ActiveSwatch,
|
|
|
|
}
|
|
|
|
|
2019-06-27 05:44:08 +00:00
|
|
|
// If the user is holding the mouse down over one spot and not
|
|
|
|
// moving, don't do anything. The pixel has already been set and
|
|
|
|
// needless writes to the map cause needless cache rewrites etc.
|
|
|
|
if lastPixel != nil {
|
|
|
|
if pixel.X == lastPixel.X && pixel.Y == lastPixel.Y {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-21 00:08:20 +00:00
|
|
|
// Append unique new pixels.
|
Eraser Tool, Brush Sizes
* Implement Brush Sizes for drawtool.Stroke and add a UI to the tools panel
to control the brush size.
* Brush sizes: 1, 2, 4, 8, 16, 24, 32, 48, 64
* Add the Eraser Tool to editor mode. It uses a default brush size of 16
and a max size of 32 due to some performance issues.
* The Undo/Redo system now remembers the original color of pixels when
you change them, so that Undo will set them back how they were instead
of deleting the pixel entirely. Due to performance issues, this only
happens when your Brush Size is 0 (drawing single-pixel shapes).
* UI: Add an IntVariable option to ui.Label to bind showing the value of
an int reference.
Aforementioned performance issues:
* When we try to remember whole rects of pixels for drawing thick
shapes, it requires a ton of scanning for each step of the shape. Even
de-duplicating pixel checks, tons of extra reads are constantly
checked.
* The Eraser is the only tool that absolutely needs to be able to
remember wiped pixels AND have large brush sizes. The performance
sucks and lags a bit if you erase a lot all at once, but it's a
trade-off for now.
* So pixels aren't remembered when drawing lines in your level with
thick brushes, so the Undo action will simply delete your pixels and not
reset them. Only the Eraser can bring back pixels.
2019-07-12 02:07:46 +00:00
|
|
|
if lastPixel != nil || lastPixel != pixel {
|
|
|
|
// Draw the pixels in between.
|
|
|
|
if lastPixel != nil && lastPixel != pixel {
|
2019-07-14 21:18:44 +00:00
|
|
|
for point := range render.IterLine(lastPixel.Point(), pixel.Point()) {
|
Eraser Tool, Brush Sizes
* Implement Brush Sizes for drawtool.Stroke and add a UI to the tools panel
to control the brush size.
* Brush sizes: 1, 2, 4, 8, 16, 24, 32, 48, 64
* Add the Eraser Tool to editor mode. It uses a default brush size of 16
and a max size of 32 due to some performance issues.
* The Undo/Redo system now remembers the original color of pixels when
you change them, so that Undo will set them back how they were instead
of deleting the pixel entirely. Due to performance issues, this only
happens when your Brush Size is 0 (drawing single-pixel shapes).
* UI: Add an IntVariable option to ui.Label to bind showing the value of
an int reference.
Aforementioned performance issues:
* When we try to remember whole rects of pixels for drawing thick
shapes, it requires a ton of scanning for each step of the shape. Even
de-duplicating pixel checks, tons of extra reads are constantly
checked.
* The Eraser is the only tool that absolutely needs to be able to
remember wiped pixels AND have large brush sizes. The performance
sucks and lags a bit if you erase a lot all at once, but it's a
trade-off for now.
* So pixels aren't remembered when drawing lines in your level with
thick brushes, so the Undo action will simply delete your pixels and not
reset them. Only the Eraser can bring back pixels.
2019-07-12 02:07:46 +00:00
|
|
|
w.currentStroke.AddPoint(point)
|
2018-10-21 00:08:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
w.lastPixel = pixel
|
|
|
|
|
2019-07-03 23:22:30 +00:00
|
|
|
// Save the pixel in the current stroke.
|
|
|
|
w.currentStroke.AddPoint(render.Point{
|
|
|
|
X: cursor.X,
|
|
|
|
Y: cursor.Y,
|
|
|
|
})
|
2018-10-21 00:08:20 +00:00
|
|
|
}
|
|
|
|
} else {
|
Eraser Tool, Brush Sizes
* Implement Brush Sizes for drawtool.Stroke and add a UI to the tools panel
to control the brush size.
* Brush sizes: 1, 2, 4, 8, 16, 24, 32, 48, 64
* Add the Eraser Tool to editor mode. It uses a default brush size of 16
and a max size of 32 due to some performance issues.
* The Undo/Redo system now remembers the original color of pixels when
you change them, so that Undo will set them back how they were instead
of deleting the pixel entirely. Due to performance issues, this only
happens when your Brush Size is 0 (drawing single-pixel shapes).
* UI: Add an IntVariable option to ui.Label to bind showing the value of
an int reference.
Aforementioned performance issues:
* When we try to remember whole rects of pixels for drawing thick
shapes, it requires a ton of scanning for each step of the shape. Even
de-duplicating pixel checks, tons of extra reads are constantly
checked.
* The Eraser is the only tool that absolutely needs to be able to
remember wiped pixels AND have large brush sizes. The performance
sucks and lags a bit if you erase a lot all at once, but it's a
trade-off for now.
* So pixels aren't remembered when drawing lines in your level with
thick brushes, so the Undo action will simply delete your pixels and not
reset them. Only the Eraser can bring back pixels.
2019-07-12 02:07:46 +00:00
|
|
|
w.commitStroke(w.Tool, true)
|
2018-10-21 00:08:20 +00:00
|
|
|
}
|
2022-03-26 20:55:06 +00:00
|
|
|
|
2019-07-04 00:19:25 +00:00
|
|
|
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.
|
2022-03-26 20:55:06 +00:00
|
|
|
if keybind.LeftClick(ev) {
|
2019-07-04 00:19:25 +00:00
|
|
|
// Initialize a new Stroke for this atomic drawing operation?
|
|
|
|
if w.currentStroke == nil {
|
|
|
|
w.currentStroke = drawtool.NewStroke(drawtool.Line, w.Palette.ActiveSwatch.Color)
|
2021-06-10 05:36:32 +00:00
|
|
|
w.currentStroke.Pattern = w.Palette.ActiveSwatch.Pattern
|
Eraser Tool, Brush Sizes
* Implement Brush Sizes for drawtool.Stroke and add a UI to the tools panel
to control the brush size.
* Brush sizes: 1, 2, 4, 8, 16, 24, 32, 48, 64
* Add the Eraser Tool to editor mode. It uses a default brush size of 16
and a max size of 32 due to some performance issues.
* The Undo/Redo system now remembers the original color of pixels when
you change them, so that Undo will set them back how they were instead
of deleting the pixel entirely. Due to performance issues, this only
happens when your Brush Size is 0 (drawing single-pixel shapes).
* UI: Add an IntVariable option to ui.Label to bind showing the value of
an int reference.
Aforementioned performance issues:
* When we try to remember whole rects of pixels for drawing thick
shapes, it requires a ton of scanning for each step of the shape. Even
de-duplicating pixel checks, tons of extra reads are constantly
checked.
* The Eraser is the only tool that absolutely needs to be able to
remember wiped pixels AND have large brush sizes. The performance
sucks and lags a bit if you erase a lot all at once, but it's a
trade-off for now.
* So pixels aren't remembered when drawing lines in your level with
thick brushes, so the Undo action will simply delete your pixels and not
reset them. Only the Eraser can bring back pixels.
2019-07-12 02:07:46 +00:00
|
|
|
w.currentStroke.Thickness = w.BrushSize
|
2019-07-04 00:19:25 +00:00
|
|
|
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 {
|
Eraser Tool, Brush Sizes
* Implement Brush Sizes for drawtool.Stroke and add a UI to the tools panel
to control the brush size.
* Brush sizes: 1, 2, 4, 8, 16, 24, 32, 48, 64
* Add the Eraser Tool to editor mode. It uses a default brush size of 16
and a max size of 32 due to some performance issues.
* The Undo/Redo system now remembers the original color of pixels when
you change them, so that Undo will set them back how they were instead
of deleting the pixel entirely. Due to performance issues, this only
happens when your Brush Size is 0 (drawing single-pixel shapes).
* UI: Add an IntVariable option to ui.Label to bind showing the value of
an int reference.
Aforementioned performance issues:
* When we try to remember whole rects of pixels for drawing thick
shapes, it requires a ton of scanning for each step of the shape. Even
de-duplicating pixel checks, tons of extra reads are constantly
checked.
* The Eraser is the only tool that absolutely needs to be able to
remember wiped pixels AND have large brush sizes. The performance
sucks and lags a bit if you erase a lot all at once, but it's a
trade-off for now.
* So pixels aren't remembered when drawing lines in your level with
thick brushes, so the Undo action will simply delete your pixels and not
reset them. Only the Eraser can bring back pixels.
2019-07-12 02:07:46 +00:00
|
|
|
w.commitStroke(w.Tool, true)
|
2019-07-04 00:19:25 +00:00
|
|
|
}
|
2022-03-26 20:55:06 +00:00
|
|
|
|
2019-07-04 00:19:25 +00:00
|
|
|
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.
|
2022-03-26 20:55:06 +00:00
|
|
|
if keybind.LeftClick(ev) {
|
2019-07-04 00:19:25 +00:00
|
|
|
// Initialize a new Stroke for this atomic drawing operation?
|
|
|
|
if w.currentStroke == nil {
|
|
|
|
w.currentStroke = drawtool.NewStroke(drawtool.Rectangle, w.Palette.ActiveSwatch.Color)
|
2021-06-10 05:36:32 +00:00
|
|
|
w.currentStroke.Pattern = w.Palette.ActiveSwatch.Pattern
|
Eraser Tool, Brush Sizes
* Implement Brush Sizes for drawtool.Stroke and add a UI to the tools panel
to control the brush size.
* Brush sizes: 1, 2, 4, 8, 16, 24, 32, 48, 64
* Add the Eraser Tool to editor mode. It uses a default brush size of 16
and a max size of 32 due to some performance issues.
* The Undo/Redo system now remembers the original color of pixels when
you change them, so that Undo will set them back how they were instead
of deleting the pixel entirely. Due to performance issues, this only
happens when your Brush Size is 0 (drawing single-pixel shapes).
* UI: Add an IntVariable option to ui.Label to bind showing the value of
an int reference.
Aforementioned performance issues:
* When we try to remember whole rects of pixels for drawing thick
shapes, it requires a ton of scanning for each step of the shape. Even
de-duplicating pixel checks, tons of extra reads are constantly
checked.
* The Eraser is the only tool that absolutely needs to be able to
remember wiped pixels AND have large brush sizes. The performance
sucks and lags a bit if you erase a lot all at once, but it's a
trade-off for now.
* So pixels aren't remembered when drawing lines in your level with
thick brushes, so the Undo action will simply delete your pixels and not
reset them. Only the Eraser can bring back pixels.
2019-07-12 02:07:46 +00:00
|
|
|
w.currentStroke.Thickness = w.BrushSize
|
2019-07-04 00:19:25 +00:00
|
|
|
w.currentStroke.ExtraData = w.Palette.ActiveSwatch
|
|
|
|
w.currentStroke.PointA = render.NewPoint(cursor.X, cursor.Y)
|
|
|
|
w.AddStroke(w.currentStroke)
|
|
|
|
}
|
|
|
|
|
2019-07-14 21:18:44 +00:00
|
|
|
w.currentStroke.PointB = render.NewPoint(cursor.X, cursor.Y)
|
|
|
|
} else {
|
|
|
|
w.commitStroke(w.Tool, true)
|
|
|
|
}
|
2022-03-26 20:55:06 +00:00
|
|
|
|
2019-07-14 21:18:44 +00:00
|
|
|
case drawtool.EllipseTool:
|
|
|
|
if w.Palette.ActiveSwatch == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-03-26 20:55:06 +00:00
|
|
|
if keybind.LeftClick(ev) {
|
2019-07-14 21:18:44 +00:00
|
|
|
if w.currentStroke == nil {
|
|
|
|
w.currentStroke = drawtool.NewStroke(drawtool.Ellipse, w.Palette.ActiveSwatch.Color)
|
2021-06-10 05:36:32 +00:00
|
|
|
w.currentStroke.Pattern = w.Palette.ActiveSwatch.Pattern
|
2019-07-14 21:18:44 +00:00
|
|
|
w.currentStroke.Thickness = w.BrushSize
|
|
|
|
w.currentStroke.ExtraData = w.Palette.ActiveSwatch
|
|
|
|
w.currentStroke.PointA = render.NewPoint(cursor.X, cursor.Y)
|
|
|
|
w.AddStroke(w.currentStroke)
|
|
|
|
}
|
|
|
|
|
2019-07-04 00:19:25 +00:00
|
|
|
w.currentStroke.PointB = render.NewPoint(cursor.X, cursor.Y)
|
|
|
|
} else {
|
Eraser Tool, Brush Sizes
* Implement Brush Sizes for drawtool.Stroke and add a UI to the tools panel
to control the brush size.
* Brush sizes: 1, 2, 4, 8, 16, 24, 32, 48, 64
* Add the Eraser Tool to editor mode. It uses a default brush size of 16
and a max size of 32 due to some performance issues.
* The Undo/Redo system now remembers the original color of pixels when
you change them, so that Undo will set them back how they were instead
of deleting the pixel entirely. Due to performance issues, this only
happens when your Brush Size is 0 (drawing single-pixel shapes).
* UI: Add an IntVariable option to ui.Label to bind showing the value of
an int reference.
Aforementioned performance issues:
* When we try to remember whole rects of pixels for drawing thick
shapes, it requires a ton of scanning for each step of the shape. Even
de-duplicating pixel checks, tons of extra reads are constantly
checked.
* The Eraser is the only tool that absolutely needs to be able to
remember wiped pixels AND have large brush sizes. The performance
sucks and lags a bit if you erase a lot all at once, but it's a
trade-off for now.
* So pixels aren't remembered when drawing lines in your level with
thick brushes, so the Undo action will simply delete your pixels and not
reset them. Only the Eraser can bring back pixels.
2019-07-12 02:07:46 +00:00
|
|
|
w.commitStroke(w.Tool, true)
|
|
|
|
}
|
2022-03-26 20:55:06 +00:00
|
|
|
|
2022-03-05 23:31:09 +00:00
|
|
|
case drawtool.TextTool:
|
|
|
|
// The Text Tool popup should initialize this for us, if somehow not
|
|
|
|
// initialized skip this tool processing.
|
|
|
|
if w.Palette.ActiveSwatch == nil || drawtool.TT.IsZero() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Do we need to create the Label?
|
|
|
|
if drawtool.TT.Label == nil {
|
|
|
|
drawtool.TT.Label = ui.NewLabel(ui.Label{
|
|
|
|
Text: drawtool.TT.Message,
|
|
|
|
Font: render.Text{
|
|
|
|
FontFilename: drawtool.TT.Font,
|
|
|
|
Size: drawtool.TT.Size,
|
|
|
|
Color: w.Palette.ActiveSwatch.Color,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Do we need to update the color of the label?
|
|
|
|
if drawtool.TT.Label.Font.Color != w.Palette.ActiveSwatch.Color {
|
|
|
|
drawtool.TT.Label.Font.Color = w.Palette.ActiveSwatch.Color
|
|
|
|
}
|
|
|
|
|
|
|
|
// NOTE: Canvas.presentStrokes() will handle drawing the font preview
|
|
|
|
// at the cursor location while the TextTool is active.
|
|
|
|
|
|
|
|
// On mouse click, commit the text to the drawing.
|
2022-03-26 20:55:06 +00:00
|
|
|
if keybind.LeftClick(ev) {
|
2022-03-05 23:31:09 +00:00
|
|
|
if stroke, err := drawtool.TT.ToStroke(shmem.CurrentRenderEngine, w.Palette.ActiveSwatch.Color, cursor); err != nil {
|
|
|
|
shmem.FlashError("Text Tool error: %s", err)
|
|
|
|
return nil
|
|
|
|
} else {
|
|
|
|
w.currentStroke = stroke
|
|
|
|
w.currentStroke.ExtraData = w.Palette.ActiveSwatch
|
|
|
|
w.commitStroke(drawtool.PencilTool, true)
|
|
|
|
}
|
|
|
|
|
2022-03-26 20:55:06 +00:00
|
|
|
keybind.ClearLeftClick(ev)
|
|
|
|
}
|
|
|
|
|
|
|
|
case drawtool.FloodTool:
|
|
|
|
if w.Palette.ActiveSwatch == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Click to activate.
|
|
|
|
if keybind.LeftClick(ev) {
|
|
|
|
var (
|
|
|
|
chunker = w.Chunker()
|
|
|
|
stroke = drawtool.NewStroke(drawtool.Freehand, w.Palette.ActiveSwatch.Color)
|
|
|
|
)
|
|
|
|
|
|
|
|
// Set some max boundaries to prevent runaway infinite loops, e.g. if user
|
|
|
|
// clicked the wide open void the flood fill would never finish!
|
|
|
|
limit := balance.FloodToolLimit
|
|
|
|
|
|
|
|
// Get the original color at this location.
|
|
|
|
// Error cases can include: no chunk at this spot, or no pixel at this spot.
|
|
|
|
// Treat these as just a null color and proceed anyway, user should be able
|
|
|
|
// to flood fill blank areas of their level.
|
|
|
|
baseColor, err := chunker.Get(cursor)
|
|
|
|
if err != nil {
|
|
|
|
limit = balance.FloodToolVoidLimit
|
|
|
|
log.Warn("FloodTool: couldn't get base color at %s: %s (got %s)", cursor, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// If no change, do nothing.
|
|
|
|
if baseColor == w.Palette.ActiveSwatch {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// The flood fill algorithm.
|
|
|
|
queue := []render.Point{cursor}
|
|
|
|
for len(queue) > 0 {
|
|
|
|
node := queue[0]
|
|
|
|
queue = queue[1:]
|
|
|
|
|
|
|
|
colorAt, _ := chunker.Get(node)
|
|
|
|
if colorAt != baseColor {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// For Undo history, store the original color at this point.
|
|
|
|
if colorAt != nil {
|
|
|
|
stroke.OriginalPoints[node] = colorAt
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add the neighboring pixels.
|
|
|
|
for _, neighbor := range []render.Point{
|
|
|
|
{X: node.X - 1, Y: node.Y},
|
|
|
|
{X: node.X + 1, Y: node.Y},
|
|
|
|
{X: node.X, Y: node.Y - 1},
|
|
|
|
{X: node.X, Y: node.Y + 1},
|
|
|
|
} {
|
|
|
|
// Only if not too far from the origin!
|
|
|
|
if render.AbsInt(neighbor.X-cursor.X) <= limit && render.AbsInt(neighbor.Y-cursor.Y) <= limit {
|
|
|
|
queue = append(queue, neighbor)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
stroke.AddPoint(node)
|
|
|
|
err = chunker.Set(node, w.Palette.ActiveSwatch)
|
|
|
|
if err != nil {
|
|
|
|
log.Error("FloodTool: error setting %s to %s: %s", node, w.Palette.ActiveSwatch, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
w.strokeToHistory(stroke)
|
|
|
|
keybind.ClearLeftClick(ev)
|
2022-03-05 23:31:09 +00:00
|
|
|
}
|
|
|
|
|
Eraser Tool, Brush Sizes
* Implement Brush Sizes for drawtool.Stroke and add a UI to the tools panel
to control the brush size.
* Brush sizes: 1, 2, 4, 8, 16, 24, 32, 48, 64
* Add the Eraser Tool to editor mode. It uses a default brush size of 16
and a max size of 32 due to some performance issues.
* The Undo/Redo system now remembers the original color of pixels when
you change them, so that Undo will set them back how they were instead
of deleting the pixel entirely. Due to performance issues, this only
happens when your Brush Size is 0 (drawing single-pixel shapes).
* UI: Add an IntVariable option to ui.Label to bind showing the value of
an int reference.
Aforementioned performance issues:
* When we try to remember whole rects of pixels for drawing thick
shapes, it requires a ton of scanning for each step of the shape. Even
de-duplicating pixel checks, tons of extra reads are constantly
checked.
* The Eraser is the only tool that absolutely needs to be able to
remember wiped pixels AND have large brush sizes. The performance
sucks and lags a bit if you erase a lot all at once, but it's a
trade-off for now.
* So pixels aren't remembered when drawing lines in your level with
thick brushes, so the Undo action will simply delete your pixels and not
reset them. Only the Eraser can bring back pixels.
2019-07-12 02:07:46 +00:00
|
|
|
case drawtool.EraserTool:
|
|
|
|
// Clicking? Log all the pixels while doing so.
|
2022-03-26 20:55:06 +00:00
|
|
|
if keybind.LeftClick(ev) {
|
Eraser Tool, Brush Sizes
* Implement Brush Sizes for drawtool.Stroke and add a UI to the tools panel
to control the brush size.
* Brush sizes: 1, 2, 4, 8, 16, 24, 32, 48, 64
* Add the Eraser Tool to editor mode. It uses a default brush size of 16
and a max size of 32 due to some performance issues.
* The Undo/Redo system now remembers the original color of pixels when
you change them, so that Undo will set them back how they were instead
of deleting the pixel entirely. Due to performance issues, this only
happens when your Brush Size is 0 (drawing single-pixel shapes).
* UI: Add an IntVariable option to ui.Label to bind showing the value of
an int reference.
Aforementioned performance issues:
* When we try to remember whole rects of pixels for drawing thick
shapes, it requires a ton of scanning for each step of the shape. Even
de-duplicating pixel checks, tons of extra reads are constantly
checked.
* The Eraser is the only tool that absolutely needs to be able to
remember wiped pixels AND have large brush sizes. The performance
sucks and lags a bit if you erase a lot all at once, but it's a
trade-off for now.
* So pixels aren't remembered when drawing lines in your level with
thick brushes, so the Undo action will simply delete your pixels and not
reset them. Only the Eraser can bring back pixels.
2019-07-12 02:07:46 +00:00
|
|
|
// Initialize a new Stroke for this atomic drawing operation?
|
|
|
|
if w.currentStroke == nil {
|
|
|
|
// The color is white, will look like white-out that covers the
|
|
|
|
// wallpaper during the stroke.
|
|
|
|
w.currentStroke = drawtool.NewStroke(drawtool.Eraser, render.White)
|
|
|
|
w.currentStroke.Thickness = w.BrushSize
|
|
|
|
w.AddStroke(w.currentStroke)
|
|
|
|
}
|
|
|
|
|
|
|
|
lastPixel := w.lastPixel
|
|
|
|
pixel := &level.Pixel{
|
|
|
|
X: cursor.X,
|
|
|
|
Y: cursor.Y,
|
|
|
|
Swatch: w.Palette.ActiveSwatch,
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the user is holding the mouse down over one spot and not
|
|
|
|
// moving, don't do anything. The pixel has already been set and
|
|
|
|
// needless writes to the map cause needless cache rewrites etc.
|
|
|
|
if lastPixel != nil {
|
|
|
|
if pixel.X == lastPixel.X && pixel.Y == lastPixel.Y {
|
|
|
|
break
|
2019-07-04 00:19:25 +00:00
|
|
|
}
|
Eraser Tool, Brush Sizes
* Implement Brush Sizes for drawtool.Stroke and add a UI to the tools panel
to control the brush size.
* Brush sizes: 1, 2, 4, 8, 16, 24, 32, 48, 64
* Add the Eraser Tool to editor mode. It uses a default brush size of 16
and a max size of 32 due to some performance issues.
* The Undo/Redo system now remembers the original color of pixels when
you change them, so that Undo will set them back how they were instead
of deleting the pixel entirely. Due to performance issues, this only
happens when your Brush Size is 0 (drawing single-pixel shapes).
* UI: Add an IntVariable option to ui.Label to bind showing the value of
an int reference.
Aforementioned performance issues:
* When we try to remember whole rects of pixels for drawing thick
shapes, it requires a ton of scanning for each step of the shape. Even
de-duplicating pixel checks, tons of extra reads are constantly
checked.
* The Eraser is the only tool that absolutely needs to be able to
remember wiped pixels AND have large brush sizes. The performance
sucks and lags a bit if you erase a lot all at once, but it's a
trade-off for now.
* So pixels aren't remembered when drawing lines in your level with
thick brushes, so the Undo action will simply delete your pixels and not
reset them. Only the Eraser can bring back pixels.
2019-07-12 02:07:46 +00:00
|
|
|
}
|
2019-07-04 00:19:25 +00:00
|
|
|
|
Eraser Tool, Brush Sizes
* Implement Brush Sizes for drawtool.Stroke and add a UI to the tools panel
to control the brush size.
* Brush sizes: 1, 2, 4, 8, 16, 24, 32, 48, 64
* Add the Eraser Tool to editor mode. It uses a default brush size of 16
and a max size of 32 due to some performance issues.
* The Undo/Redo system now remembers the original color of pixels when
you change them, so that Undo will set them back how they were instead
of deleting the pixel entirely. Due to performance issues, this only
happens when your Brush Size is 0 (drawing single-pixel shapes).
* UI: Add an IntVariable option to ui.Label to bind showing the value of
an int reference.
Aforementioned performance issues:
* When we try to remember whole rects of pixels for drawing thick
shapes, it requires a ton of scanning for each step of the shape. Even
de-duplicating pixel checks, tons of extra reads are constantly
checked.
* The Eraser is the only tool that absolutely needs to be able to
remember wiped pixels AND have large brush sizes. The performance
sucks and lags a bit if you erase a lot all at once, but it's a
trade-off for now.
* So pixels aren't remembered when drawing lines in your level with
thick brushes, so the Undo action will simply delete your pixels and not
reset them. Only the Eraser can bring back pixels.
2019-07-12 02:07:46 +00:00
|
|
|
// Append unique new pixels.
|
|
|
|
if lastPixel == nil || lastPixel != pixel {
|
|
|
|
if lastPixel != nil && lastPixel != pixel {
|
2019-07-14 21:18:44 +00:00
|
|
|
for point := range render.IterLine(lastPixel.Point(), pixel.Point()) {
|
Eraser Tool, Brush Sizes
* Implement Brush Sizes for drawtool.Stroke and add a UI to the tools panel
to control the brush size.
* Brush sizes: 1, 2, 4, 8, 16, 24, 32, 48, 64
* Add the Eraser Tool to editor mode. It uses a default brush size of 16
and a max size of 32 due to some performance issues.
* The Undo/Redo system now remembers the original color of pixels when
you change them, so that Undo will set them back how they were instead
of deleting the pixel entirely. Due to performance issues, this only
happens when your Brush Size is 0 (drawing single-pixel shapes).
* UI: Add an IntVariable option to ui.Label to bind showing the value of
an int reference.
Aforementioned performance issues:
* When we try to remember whole rects of pixels for drawing thick
shapes, it requires a ton of scanning for each step of the shape. Even
de-duplicating pixel checks, tons of extra reads are constantly
checked.
* The Eraser is the only tool that absolutely needs to be able to
remember wiped pixels AND have large brush sizes. The performance
sucks and lags a bit if you erase a lot all at once, but it's a
trade-off for now.
* So pixels aren't remembered when drawing lines in your level with
thick brushes, so the Undo action will simply delete your pixels and not
reset them. Only the Eraser can bring back pixels.
2019-07-12 02:07:46 +00:00
|
|
|
w.currentStroke.AddPoint(point)
|
|
|
|
}
|
2019-07-04 00:19:25 +00:00
|
|
|
}
|
|
|
|
|
Eraser Tool, Brush Sizes
* Implement Brush Sizes for drawtool.Stroke and add a UI to the tools panel
to control the brush size.
* Brush sizes: 1, 2, 4, 8, 16, 24, 32, 48, 64
* Add the Eraser Tool to editor mode. It uses a default brush size of 16
and a max size of 32 due to some performance issues.
* The Undo/Redo system now remembers the original color of pixels when
you change them, so that Undo will set them back how they were instead
of deleting the pixel entirely. Due to performance issues, this only
happens when your Brush Size is 0 (drawing single-pixel shapes).
* UI: Add an IntVariable option to ui.Label to bind showing the value of
an int reference.
Aforementioned performance issues:
* When we try to remember whole rects of pixels for drawing thick
shapes, it requires a ton of scanning for each step of the shape. Even
de-duplicating pixel checks, tons of extra reads are constantly
checked.
* The Eraser is the only tool that absolutely needs to be able to
remember wiped pixels AND have large brush sizes. The performance
sucks and lags a bit if you erase a lot all at once, but it's a
trade-off for now.
* So pixels aren't remembered when drawing lines in your level with
thick brushes, so the Undo action will simply delete your pixels and not
reset them. Only the Eraser can bring back pixels.
2019-07-12 02:07:46 +00:00
|
|
|
w.lastPixel = pixel
|
|
|
|
w.currentStroke.AddPoint(render.Point{
|
|
|
|
X: cursor.X,
|
|
|
|
Y: cursor.Y,
|
|
|
|
})
|
2019-07-04 00:19:25 +00:00
|
|
|
}
|
Eraser Tool, Brush Sizes
* Implement Brush Sizes for drawtool.Stroke and add a UI to the tools panel
to control the brush size.
* Brush sizes: 1, 2, 4, 8, 16, 24, 32, 48, 64
* Add the Eraser Tool to editor mode. It uses a default brush size of 16
and a max size of 32 due to some performance issues.
* The Undo/Redo system now remembers the original color of pixels when
you change them, so that Undo will set them back how they were instead
of deleting the pixel entirely. Due to performance issues, this only
happens when your Brush Size is 0 (drawing single-pixel shapes).
* UI: Add an IntVariable option to ui.Label to bind showing the value of
an int reference.
Aforementioned performance issues:
* When we try to remember whole rects of pixels for drawing thick
shapes, it requires a ton of scanning for each step of the shape. Even
de-duplicating pixel checks, tons of extra reads are constantly
checked.
* The Eraser is the only tool that absolutely needs to be able to
remember wiped pixels AND have large brush sizes. The performance
sucks and lags a bit if you erase a lot all at once, but it's a
trade-off for now.
* So pixels aren't remembered when drawing lines in your level with
thick brushes, so the Undo action will simply delete your pixels and not
reset them. Only the Eraser can bring back pixels.
2019-07-12 02:07:46 +00:00
|
|
|
} else {
|
|
|
|
w.commitStroke(w.Tool, true)
|
2019-07-04 00:19:25 +00:00
|
|
|
}
|
2022-03-26 20:55:06 +00:00
|
|
|
|
2019-07-04 00:19:25 +00:00
|
|
|
case drawtool.ActorTool:
|
2018-10-21 00:08:20 +00:00
|
|
|
// See if any of the actors are below the mouse cursor.
|
|
|
|
var WP = w.WorldIndexAt(cursor)
|
|
|
|
|
2022-04-09 21:41:24 +00:00
|
|
|
var deleteActors = []*Actor{}
|
2018-10-21 00:08:20 +00:00
|
|
|
for _, actor := range w.actors {
|
2021-09-12 21:42:39 +00:00
|
|
|
|
|
|
|
// Compute the bounding box on screen where this doodad
|
|
|
|
// visually appears.
|
|
|
|
var scrollBias = render.Point{
|
|
|
|
X: w.Scroll.X,
|
|
|
|
Y: w.Scroll.Y,
|
|
|
|
}
|
|
|
|
if w.Zoom != 0 {
|
|
|
|
scrollBias.X = w.ZoomDivide(scrollBias.X)
|
|
|
|
scrollBias.Y = w.ZoomDivide(scrollBias.Y)
|
|
|
|
}
|
2018-10-21 00:08:20 +00:00
|
|
|
box := render.Rect{
|
2021-09-12 22:26:51 +00:00
|
|
|
X: actor.Actor.Point.X - scrollBias.X - w.ZoomDivide(P.X),
|
|
|
|
Y: actor.Actor.Point.Y - scrollBias.Y - w.ZoomDivide(P.Y),
|
2018-10-21 00:08:20 +00:00
|
|
|
W: actor.Canvas.Size().W,
|
|
|
|
H: actor.Canvas.Size().H,
|
|
|
|
}
|
|
|
|
|
Doodad/Actor Runtime Options
* Add "Options" support for Doodads: these allow for individual Actor instances
on your level to customize properties about the doodad. They're like "Tags"
except the player can customize them on a per-actor basis.
* Doodad Editor: you can specify the Options in the Doodad Properties window.
* Level Editor: when the Actor Tool is selected, on mouse-over of an actor,
clicking on the gear icon will open a new "Actor Properties" window which
shows metadata (title, author, ID, position) and an Options tab to configure
the actor's options.
Updates to the scripting API:
* Self.Options() returns a list of option names defined on the Doodad.
* Self.GetOption(name) returns the value for the named option, or nil if
neither the actor nor its doodad have the option defined. The return type
will be correctly a string, boolean or integer type.
Updates to the doodad command-line tool:
* `doodad show` will print the Options on a .doodad file and, when showing a
.level file with --actors, prints any customized Options with the actors.
* `doodad edit-doodad` adds a --option parameter to define options.
Options added to the game's built-in doodads:
* Warp Doors: "locked (exit only)" will make it so the door can not be opened
by the player, giving the "locked" message (as if it had no linked door),
but the player may still exit from the door if sent by another warp door.
* Electric Door & Electric Trapdoor: "opened" can make the door be opened by
default when the level begins instead of closed. A switch or a button that
removes power will close the door as normal.
* Colored Doors & Small Key Door: "unlocked" will make the door unlocked at
level start, not requiring a key to open it.
* Colored Keys & Small Key: "has gravity" will make the key subject to gravity
and set its Mobile flag so that if it falls onto a button, it will activate.
* Gemstones: they had gravity by default; you can now uncheck "has gravity" to
remove their Gravity and IsMobile status.
* Gemstone Totems: "has gemstone" will set the totem to its unlocked status by
default with the gemstone inserted. No power signal will be emitted; it is
cosmetic only.
* Fire Region: "name" can let you set a name for the fire region similarly to
names for fire pixels: "Watch out for ${name}!"
* Invisible Warp Door: "locked (exit only)" added as well.
2022-10-10 00:41:24 +00:00
|
|
|
// Mouse hover?
|
2018-10-21 00:08:20 +00:00
|
|
|
if WP.Inside(box) {
|
|
|
|
actor.Canvas.Configure(ui.Config{
|
|
|
|
BorderSize: 1,
|
|
|
|
BorderColor: render.RGBA(255, 153, 0, 255),
|
|
|
|
BorderStyle: ui.BorderSolid,
|
|
|
|
Background: render.White, // TODO: cuz the border draws a bgcolor
|
|
|
|
})
|
|
|
|
|
Doodad/Actor Runtime Options
* Add "Options" support for Doodads: these allow for individual Actor instances
on your level to customize properties about the doodad. They're like "Tags"
except the player can customize them on a per-actor basis.
* Doodad Editor: you can specify the Options in the Doodad Properties window.
* Level Editor: when the Actor Tool is selected, on mouse-over of an actor,
clicking on the gear icon will open a new "Actor Properties" window which
shows metadata (title, author, ID, position) and an Options tab to configure
the actor's options.
Updates to the scripting API:
* Self.Options() returns a list of option names defined on the Doodad.
* Self.GetOption(name) returns the value for the named option, or nil if
neither the actor nor its doodad have the option defined. The return type
will be correctly a string, boolean or integer type.
Updates to the doodad command-line tool:
* `doodad show` will print the Options on a .doodad file and, when showing a
.level file with --actors, prints any customized Options with the actors.
* `doodad edit-doodad` adds a --option parameter to define options.
Options added to the game's built-in doodads:
* Warp Doors: "locked (exit only)" will make it so the door can not be opened
by the player, giving the "locked" message (as if it had no linked door),
but the player may still exit from the door if sent by another warp door.
* Electric Door & Electric Trapdoor: "opened" can make the door be opened by
default when the level begins instead of closed. A switch or a button that
removes power will close the door as normal.
* Colored Doors & Small Key Door: "unlocked" will make the door unlocked at
level start, not requiring a key to open it.
* Colored Keys & Small Key: "has gravity" will make the key subject to gravity
and set its Mobile flag so that if it falls onto a button, it will activate.
* Gemstones: they had gravity by default; you can now uncheck "has gravity" to
remove their Gravity and IsMobile status.
* Gemstone Totems: "has gemstone" will set the totem to its unlocked status by
default with the gemstone inserted. No power signal will be emitted; it is
cosmetic only.
* Fire Region: "name" can let you set a name for the fire region similarly to
names for fire pixels: "Watch out for ${name}!"
* Invisible Warp Door: "locked (exit only)" added as well.
2022-10-10 00:41:24 +00:00
|
|
|
// Show doodad buttons.
|
|
|
|
actor.Canvas.ShowDoodadButtons = true
|
|
|
|
|
2018-10-21 00:08:20 +00:00
|
|
|
// Check for a mouse down event to begin dragging this
|
|
|
|
// canvas around.
|
2022-03-26 20:55:06 +00:00
|
|
|
if keybind.LeftClick(ev) {
|
Doodad/Actor Runtime Options
* Add "Options" support for Doodads: these allow for individual Actor instances
on your level to customize properties about the doodad. They're like "Tags"
except the player can customize them on a per-actor basis.
* Doodad Editor: you can specify the Options in the Doodad Properties window.
* Level Editor: when the Actor Tool is selected, on mouse-over of an actor,
clicking on the gear icon will open a new "Actor Properties" window which
shows metadata (title, author, ID, position) and an Options tab to configure
the actor's options.
Updates to the scripting API:
* Self.Options() returns a list of option names defined on the Doodad.
* Self.GetOption(name) returns the value for the named option, or nil if
neither the actor nor its doodad have the option defined. The return type
will be correctly a string, boolean or integer type.
Updates to the doodad command-line tool:
* `doodad show` will print the Options on a .doodad file and, when showing a
.level file with --actors, prints any customized Options with the actors.
* `doodad edit-doodad` adds a --option parameter to define options.
Options added to the game's built-in doodads:
* Warp Doors: "locked (exit only)" will make it so the door can not be opened
by the player, giving the "locked" message (as if it had no linked door),
but the player may still exit from the door if sent by another warp door.
* Electric Door & Electric Trapdoor: "opened" can make the door be opened by
default when the level begins instead of closed. A switch or a button that
removes power will close the door as normal.
* Colored Doors & Small Key Door: "unlocked" will make the door unlocked at
level start, not requiring a key to open it.
* Colored Keys & Small Key: "has gravity" will make the key subject to gravity
and set its Mobile flag so that if it falls onto a button, it will activate.
* Gemstones: they had gravity by default; you can now uncheck "has gravity" to
remove their Gravity and IsMobile status.
* Gemstone Totems: "has gemstone" will set the totem to its unlocked status by
default with the gemstone inserted. No power signal will be emitted; it is
cosmetic only.
* Fire Region: "name" can let you set a name for the fire region similarly to
names for fire pixels: "Watch out for ${name}!"
* Invisible Warp Door: "locked (exit only)" added as well.
2022-10-10 00:41:24 +00:00
|
|
|
// Did they click onto the doodad buttons?
|
|
|
|
if shmem.Cursor.Inside(actor.Canvas.doodadButtonRect()) {
|
|
|
|
keybind.ClearLeftClick(ev)
|
|
|
|
if w.OnDoodadConfig != nil {
|
|
|
|
w.OnDoodadConfig(actor)
|
|
|
|
} else {
|
|
|
|
log.Error("OnDoodadConfig: handler not defined for parent canvas")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-10-21 00:08:20 +00:00
|
|
|
// Pop this canvas out for the drag/drop.
|
|
|
|
if w.OnDragStart != nil {
|
2022-04-09 21:41:24 +00:00
|
|
|
deleteActors = append(deleteActors, actor)
|
2019-07-05 23:04:36 +00:00
|
|
|
w.OnDragStart(actor.Actor)
|
2018-10-21 00:08:20 +00:00
|
|
|
}
|
|
|
|
break
|
2019-12-31 02:13:28 +00:00
|
|
|
} else if ev.Button3 {
|
2018-10-21 00:08:20 +00:00
|
|
|
// Right click to delete an actor.
|
2022-04-09 21:41:24 +00:00
|
|
|
deleteActors = append(deleteActors, actor)
|
2018-10-21 00:08:20 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
actor.Canvas.SetBorderSize(0)
|
|
|
|
actor.Canvas.SetBackground(render.RGBA(0, 0, 1, 0)) // TODO
|
Doodad/Actor Runtime Options
* Add "Options" support for Doodads: these allow for individual Actor instances
on your level to customize properties about the doodad. They're like "Tags"
except the player can customize them on a per-actor basis.
* Doodad Editor: you can specify the Options in the Doodad Properties window.
* Level Editor: when the Actor Tool is selected, on mouse-over of an actor,
clicking on the gear icon will open a new "Actor Properties" window which
shows metadata (title, author, ID, position) and an Options tab to configure
the actor's options.
Updates to the scripting API:
* Self.Options() returns a list of option names defined on the Doodad.
* Self.GetOption(name) returns the value for the named option, or nil if
neither the actor nor its doodad have the option defined. The return type
will be correctly a string, boolean or integer type.
Updates to the doodad command-line tool:
* `doodad show` will print the Options on a .doodad file and, when showing a
.level file with --actors, prints any customized Options with the actors.
* `doodad edit-doodad` adds a --option parameter to define options.
Options added to the game's built-in doodads:
* Warp Doors: "locked (exit only)" will make it so the door can not be opened
by the player, giving the "locked" message (as if it had no linked door),
but the player may still exit from the door if sent by another warp door.
* Electric Door & Electric Trapdoor: "opened" can make the door be opened by
default when the level begins instead of closed. A switch or a button that
removes power will close the door as normal.
* Colored Doors & Small Key Door: "unlocked" will make the door unlocked at
level start, not requiring a key to open it.
* Colored Keys & Small Key: "has gravity" will make the key subject to gravity
and set its Mobile flag so that if it falls onto a button, it will activate.
* Gemstones: they had gravity by default; you can now uncheck "has gravity" to
remove their Gravity and IsMobile status.
* Gemstone Totems: "has gemstone" will set the totem to its unlocked status by
default with the gemstone inserted. No power signal will be emitted; it is
cosmetic only.
* Fire Region: "name" can let you set a name for the fire region similarly to
names for fire pixels: "Watch out for ${name}!"
* Invisible Warp Door: "locked (exit only)" added as well.
2022-10-10 00:41:24 +00:00
|
|
|
actor.Canvas.ShowDoodadButtons = false
|
2018-10-21 00:08:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Change in actor count?
|
|
|
|
if len(deleteActors) > 0 && w.OnDeleteActors != nil {
|
|
|
|
w.OnDeleteActors(deleteActors)
|
|
|
|
}
|
2022-03-26 20:55:06 +00:00
|
|
|
|
2019-07-04 00:19:25 +00:00
|
|
|
case drawtool.LinkTool:
|
2019-06-23 23:15:09 +00:00
|
|
|
// See if any of the actors are below the mouse cursor.
|
|
|
|
var WP = w.WorldIndexAt(cursor)
|
|
|
|
|
|
|
|
for _, actor := range w.actors {
|
2021-09-12 21:42:39 +00:00
|
|
|
// Compute the bounding box on screen where this doodad
|
|
|
|
// visually appears.
|
|
|
|
var scrollBias = render.Point{
|
|
|
|
X: w.Scroll.X,
|
|
|
|
Y: w.Scroll.Y,
|
|
|
|
}
|
|
|
|
if w.Zoom != 0 {
|
|
|
|
scrollBias.X = w.ZoomDivide(scrollBias.X)
|
|
|
|
scrollBias.Y = w.ZoomDivide(scrollBias.Y)
|
|
|
|
}
|
2019-06-23 23:15:09 +00:00
|
|
|
box := render.Rect{
|
2021-09-12 22:26:51 +00:00
|
|
|
X: actor.Actor.Point.X - scrollBias.X - w.ZoomDivide(P.X),
|
|
|
|
Y: actor.Actor.Point.Y - scrollBias.Y - w.ZoomDivide(P.Y),
|
2019-06-23 23:15:09 +00:00
|
|
|
W: actor.Canvas.Size().W,
|
|
|
|
H: actor.Canvas.Size().H,
|
|
|
|
}
|
|
|
|
|
|
|
|
if WP.Inside(box) {
|
|
|
|
actor.Canvas.Configure(ui.Config{
|
|
|
|
BorderSize: 1,
|
|
|
|
BorderColor: render.RGBA(255, 153, 255, 255),
|
|
|
|
BorderStyle: ui.BorderSolid,
|
|
|
|
Background: render.White, // TODO: cuz the border draws a bgcolor
|
|
|
|
})
|
|
|
|
|
|
|
|
// Click handler to start linking this actor.
|
2022-03-26 20:55:06 +00:00
|
|
|
if keybind.LeftClick(ev) {
|
2019-06-23 23:15:09 +00:00
|
|
|
if err := w.LinkAdd(actor); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-07-10 02:38:37 +00:00
|
|
|
|
|
|
|
// TODO: reset the Button1 state so we don't finish a
|
|
|
|
// link and then LinkAdd the clicked doodad immediately
|
|
|
|
// (causing link chaining)
|
2022-03-26 20:55:06 +00:00
|
|
|
keybind.ClearLeftClick(ev)
|
2019-06-23 23:15:09 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
actor.Canvas.SetBorderSize(0)
|
|
|
|
actor.Canvas.SetBackground(render.RGBA(0, 0, 1, 0)) // TODO
|
|
|
|
}
|
2022-05-03 03:35:53 +00:00
|
|
|
|
|
|
|
// Permanently color the actor if it's the current subject of the
|
|
|
|
// Link Tool (after 1st click, until 2nd click of other actor)
|
|
|
|
if w.linkFirst == actor {
|
|
|
|
actor.Canvas.Configure(ui.Config{
|
|
|
|
Background: render.RGBA(255, 153, 255, 153),
|
|
|
|
})
|
|
|
|
}
|
2019-06-23 23:15:09 +00:00
|
|
|
}
|
2018-10-21 00:08:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
|
|
}
|