Noah Petherbridge
5434484b6e
The `level.Canvas` is a widget that holds onto its Palette and Grid and has interactions to allow scrolling and editing the grid using the swatches available on the palette. Thus all of the logic in the Editor Mode for drawing directly onto the root SDL surface are now handled inside a level.Canvas instance. The `level.Canvas` widget has the following properties: * Like any widget it has an X,Y position and a width/height. * It has a Scroll position to control which slice of its drawing will be visible inside its bounding box. * It supports levels having negative coordinates for their pixels. It doesn't care. The default Scroll position is (0,0) at the top left corner of the widget but you can scroll into the negatives and see the negative pixels. * Keyboard keys will scroll the viewport inside the canvas. * The canvas draws only the pixels that are visible inside its bounding box. This feature will eventually pave the way toward: * Doodads being dropped on top of your map, each Doodad being its own Canvas widget. * Using drawings as button icons for the user interface, as the Canvas is a normal widget.
217 lines
4.9 KiB
Go
217 lines
4.9 KiB
Go
package level
|
|
|
|
import (
|
|
"git.kirsle.net/apps/doodle/balance"
|
|
"git.kirsle.net/apps/doodle/events"
|
|
"git.kirsle.net/apps/doodle/render"
|
|
"git.kirsle.net/apps/doodle/ui"
|
|
)
|
|
|
|
// Canvas is a custom ui.Widget that manages a single drawing.
|
|
type Canvas struct {
|
|
ui.Frame
|
|
Palette *Palette
|
|
|
|
// Set to true to allow clicking to edit this canvas.
|
|
Editable bool
|
|
|
|
grid Grid
|
|
pixelHistory []*Pixel
|
|
lastPixel *Pixel
|
|
|
|
// We inherit the ui.Widget which manages the width and height.
|
|
Scroll render.Point // Scroll offset for which parts of canvas are visible.
|
|
}
|
|
|
|
// NewCanvas initializes a Canvas widget.
|
|
func NewCanvas(editable bool) *Canvas {
|
|
w := &Canvas{
|
|
Editable: editable,
|
|
Palette: NewPalette(),
|
|
grid: Grid{},
|
|
}
|
|
w.setup()
|
|
return w
|
|
}
|
|
|
|
// Load initializes the Canvas using an existing Palette and Grid.
|
|
func (w *Canvas) Load(p *Palette, g *Grid) {
|
|
w.Palette = p
|
|
w.grid = *g
|
|
}
|
|
|
|
// LoadFilename initializes the Canvas using a file on disk.
|
|
func (w *Canvas) LoadFilename(filename string) error {
|
|
w.grid = Grid{}
|
|
|
|
m, err := LoadJSON(filename)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, pixel := range m.Pixels {
|
|
w.grid[pixel] = nil
|
|
}
|
|
w.Palette = m.Palette
|
|
|
|
if len(w.Palette.Swatches) > 0 {
|
|
w.SetSwatch(w.Palette.Swatches[0])
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetSwatch changes the currently selected swatch for editing.
|
|
func (w *Canvas) SetSwatch(s *Swatch) {
|
|
w.Palette.ActiveSwatch = s
|
|
}
|
|
|
|
// setup common configs between both initializers of the canvas.
|
|
func (w *Canvas) setup() {
|
|
w.SetBackground(render.White)
|
|
w.Handle(ui.MouseOver, func(p render.Point) {
|
|
w.SetBackground(render.Yellow)
|
|
})
|
|
w.Handle(ui.MouseOut, func(p render.Point) {
|
|
w.SetBackground(render.SkyBlue)
|
|
})
|
|
}
|
|
|
|
// Loop is called on the scene's event loop to handle mouse interaction with
|
|
// the canvas, i.e. to edit it.
|
|
func (w *Canvas) Loop(ev *events.State) error {
|
|
log.Info("my territory")
|
|
var (
|
|
P = w.Point()
|
|
_ = P
|
|
)
|
|
|
|
// Arrow keys to scroll the view.
|
|
scrollBy := render.Point{}
|
|
if ev.Right.Now {
|
|
scrollBy.X += balance.CanvasScrollSpeed
|
|
} else if ev.Left.Now {
|
|
scrollBy.X -= balance.CanvasScrollSpeed
|
|
}
|
|
if ev.Down.Now {
|
|
scrollBy.Y += balance.CanvasScrollSpeed
|
|
} else if ev.Up.Now {
|
|
scrollBy.Y -= balance.CanvasScrollSpeed
|
|
}
|
|
if !scrollBy.IsZero() {
|
|
w.ScrollBy(scrollBy)
|
|
}
|
|
|
|
// Only care if the cursor is over our space.
|
|
cursor := render.NewPoint(ev.CursorX.Now, ev.CursorY.Now)
|
|
if !cursor.Inside(w.Rect()) {
|
|
return nil
|
|
}
|
|
|
|
// 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 {
|
|
// log.Warn("Button1: %+v", ev.Button1)
|
|
lastPixel := w.lastPixel
|
|
pixel := &Pixel{
|
|
X: ev.CursorX.Now - P.X + w.Scroll.X,
|
|
Y: ev.CursorY.Now - P.Y + w.Scroll.Y,
|
|
Palette: w.Palette,
|
|
Swatch: w.Palette.ActiveSwatch,
|
|
}
|
|
|
|
// Append unique new pixels.
|
|
if len(w.pixelHistory) == 0 || w.pixelHistory[len(w.pixelHistory)-1] != pixel {
|
|
if lastPixel != nil {
|
|
// Draw the pixels in between.
|
|
if lastPixel != pixel {
|
|
for point := range render.IterLine(lastPixel.X, lastPixel.Y, pixel.X, pixel.Y) {
|
|
dot := &Pixel{
|
|
X: point.X,
|
|
Y: point.Y,
|
|
Palette: lastPixel.Palette,
|
|
Swatch: lastPixel.Swatch,
|
|
}
|
|
w.grid[dot] = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
w.lastPixel = pixel
|
|
w.pixelHistory = append(w.pixelHistory, pixel)
|
|
|
|
// Save in the pixel canvas map.
|
|
w.grid[pixel] = nil
|
|
}
|
|
} else {
|
|
w.lastPixel = nil
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Viewport returns a rect containing the viewable drawing coordinates in this
|
|
// canvas. The X,Y values are the scroll offset (top left) and the W,H values
|
|
// are the scroll offset plus the width/height of the Canvas widget.
|
|
func (w *Canvas) Viewport() render.Rect {
|
|
var S = w.Size()
|
|
return render.Rect{
|
|
X: w.Scroll.X,
|
|
Y: w.Scroll.Y,
|
|
W: S.W - w.BoxThickness(2),
|
|
H: S.H - w.BoxThickness(2),
|
|
}
|
|
}
|
|
|
|
// Grid returns the underlying grid object.
|
|
func (w *Canvas) Grid() *Grid {
|
|
return &w.grid
|
|
}
|
|
|
|
// ScrollBy adjusts the viewport scroll position.
|
|
func (w *Canvas) ScrollBy(by render.Point) {
|
|
w.Scroll.Add(by)
|
|
}
|
|
|
|
// Compute the canvas.
|
|
func (w *Canvas) Compute(e render.Engine) {
|
|
|
|
}
|
|
|
|
// Present the canvas.
|
|
func (w *Canvas) Present(e render.Engine, p render.Point) {
|
|
var (
|
|
S = w.Size()
|
|
Viewport = w.Viewport()
|
|
)
|
|
w.MoveTo(p)
|
|
w.DrawBox(e, p)
|
|
e.DrawBox(w.Background(), render.Rect{
|
|
X: p.X + w.BoxThickness(1),
|
|
Y: p.Y + w.BoxThickness(1),
|
|
W: S.W - w.BoxThickness(2),
|
|
H: S.H - w.BoxThickness(2),
|
|
})
|
|
|
|
for pixel := range w.grid {
|
|
point := render.NewPoint(pixel.X, pixel.Y)
|
|
if point.Inside(Viewport) {
|
|
// This pixel is visible in the canvas, but offset it by the
|
|
// scroll height.
|
|
point.Add(render.Point{
|
|
X: -Viewport.X,
|
|
Y: -Viewport.Y,
|
|
})
|
|
color := pixel.Swatch.Color
|
|
e.DrawPoint(color, render.Point{
|
|
X: p.X + w.BoxThickness(1) + point.X,
|
|
Y: p.Y + w.BoxThickness(1) + point.Y,
|
|
})
|
|
}
|
|
}
|
|
}
|