2018-08-17 03:37:19 +00:00
|
|
|
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
|
|
|
|
|
2018-09-23 22:20:45 +00:00
|
|
|
chunks *Chunker
|
2018-08-17 03:37:19 +00:00
|
|
|
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.
|
2018-09-23 22:20:45 +00:00
|
|
|
func NewCanvas(size int, editable bool) *Canvas {
|
2018-08-17 03:37:19 +00:00
|
|
|
w := &Canvas{
|
|
|
|
Editable: editable,
|
|
|
|
Palette: NewPalette(),
|
2018-09-23 22:20:45 +00:00
|
|
|
chunks: NewChunker(size),
|
2018-08-17 03:37:19 +00:00
|
|
|
}
|
|
|
|
w.setup()
|
|
|
|
return w
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load initializes the Canvas using an existing Palette and Grid.
|
2018-09-23 22:20:45 +00:00
|
|
|
func (w *Canvas) Load(p *Palette, g *Chunker) {
|
2018-08-17 03:37:19 +00:00
|
|
|
w.Palette = p
|
2018-09-23 22:20:45 +00:00
|
|
|
w.chunks = g
|
2018-08-17 03:37:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// LoadFilename initializes the Canvas using a file on disk.
|
|
|
|
func (w *Canvas) LoadFilename(filename string) error {
|
|
|
|
m, err := LoadJSON(filename)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
w.Palette = m.Palette
|
2018-09-23 22:20:45 +00:00
|
|
|
w.chunks = m.Chunker
|
2018-08-17 03:37:19 +00:00
|
|
|
|
|
|
|
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 {
|
|
|
|
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
|
2018-09-23 22:20:45 +00:00
|
|
|
cursor := render.Point{
|
|
|
|
X: ev.CursorX.Now - P.X + w.Scroll.X,
|
|
|
|
Y: ev.CursorY.Now - P.Y + w.Scroll.Y,
|
|
|
|
}
|
2018-08-17 03:37:19 +00:00
|
|
|
pixel := &Pixel{
|
2018-09-23 22:20:45 +00:00
|
|
|
X: cursor.X,
|
|
|
|
Y: cursor.Y,
|
2018-08-17 03:37:19 +00:00
|
|
|
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) {
|
2018-09-23 22:20:45 +00:00
|
|
|
w.chunks.Set(point, lastPixel.Swatch)
|
2018-08-17 03:37:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
w.lastPixel = pixel
|
|
|
|
w.pixelHistory = append(w.pixelHistory, pixel)
|
|
|
|
|
|
|
|
// Save in the pixel canvas map.
|
2018-09-23 22:20:45 +00:00
|
|
|
w.chunks.Set(cursor, pixel.Swatch)
|
2018-08-17 03:37:19 +00:00
|
|
|
}
|
|
|
|
} 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),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-23 22:20:45 +00:00
|
|
|
// Chunker returns the underlying Chunker object.
|
|
|
|
func (w *Canvas) Chunker() *Chunker {
|
|
|
|
return w.chunks
|
2018-08-17 03:37:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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),
|
|
|
|
})
|
|
|
|
|
2018-09-23 22:20:45 +00:00
|
|
|
for px := range w.chunks.IterViewport(Viewport) {
|
|
|
|
// This pixel is visible in the canvas, but offset it by the
|
|
|
|
// scroll height.
|
|
|
|
px.X -= Viewport.X
|
|
|
|
px.Y -= Viewport.Y
|
|
|
|
color := px.Swatch.Color
|
|
|
|
e.DrawPoint(color, render.Point{
|
|
|
|
X: p.X + w.BoxThickness(1) + px.X,
|
|
|
|
Y: p.Y + w.BoxThickness(1) + px.Y,
|
|
|
|
})
|
2018-08-17 03:37:19 +00:00
|
|
|
}
|
|
|
|
}
|