Doodad Edit Mode: Saving and Loading From Disk
Adds the first features to Edit Mode to support creation of Doodad files! The "New Doodad" button pops up a prompt for a Doodad size (default 100px) and configures the Canvas widget and makes a Doodad struct instead of a Level to manage. * Move the custom Canvas widget from `level.Canvas` to `uix.Canvas` (the uix package is for our custom UI widgets now) * Rename the `doodads.Doodad` interface (for runtime instances of Doodads) to `doodads.Actor` and make `doodads.Doodad` describe the file format and JSON schema instead. * Rename the `EditLevel()` method to `EditDrawing()` and it inspects the file extension to know whether to launch the Edit Mode for a Level or for a Doodad drawing. * Doodads can be edited by using the `-edit` CLI flag or using the in-game file open features (including `edit` command of dev console). * Add a `Scrollable` boolean to uix.Canvas to restrict the keyboard being able to scroll the level, for editing Doodads which have a fixed size.
This commit is contained in:
parent
e25869644c
commit
a7fd3aa1ca
|
@ -7,4 +7,7 @@ var (
|
||||||
|
|
||||||
// Default chunk size for canvases.
|
// Default chunk size for canvases.
|
||||||
ChunkSize = 1000
|
ChunkSize = 1000
|
||||||
|
|
||||||
|
// Default size for a new Doodad.
|
||||||
|
DoodadSize = 100
|
||||||
)
|
)
|
||||||
|
|
|
@ -43,7 +43,7 @@ func main() {
|
||||||
app.SetupEngine()
|
app.SetupEngine()
|
||||||
if filename != "" {
|
if filename != "" {
|
||||||
if edit {
|
if edit {
|
||||||
app.EditLevel(filename)
|
app.EditDrawing(filename)
|
||||||
} else {
|
} else {
|
||||||
app.PlayLevel(filename)
|
app.PlayLevel(filename)
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,7 +132,7 @@ func (c Command) Edit(d *Doodle) error {
|
||||||
|
|
||||||
filename := c.Args[0]
|
filename := c.Args[0]
|
||||||
d.shell.Write("Editing level: " + filename)
|
d.shell.Write("Editing level: " + filename)
|
||||||
d.EditLevel(filename)
|
d.EditDrawing(filename)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
78
doodads/actor.go
Normal file
78
doodads/actor.go
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
package doodads
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.kirsle.net/apps/doodle/level"
|
||||||
|
"git.kirsle.net/apps/doodle/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Actor is a reusable run-time drawing component used in Doodle. Actors are an
|
||||||
|
// active instance of a Doodad which have a position, velocity, etc.
|
||||||
|
type Actor interface {
|
||||||
|
ID() string
|
||||||
|
|
||||||
|
// Position and velocity, not saved to disk.
|
||||||
|
Position() render.Point
|
||||||
|
Velocity() render.Point
|
||||||
|
Size() render.Rect
|
||||||
|
Grounded() bool
|
||||||
|
SetGrounded(bool)
|
||||||
|
|
||||||
|
// Movement commands.
|
||||||
|
MoveBy(render.Point) // Add {X,Y} to current Position.
|
||||||
|
MoveTo(render.Point) // Set current Position to {X,Y}.
|
||||||
|
|
||||||
|
// Implement the Draw function.
|
||||||
|
Draw(render.Engine)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBoundingRect computes the full pairs of points for the collision box
|
||||||
|
// around a doodad actor.
|
||||||
|
func GetBoundingRect(d Actor) render.Rect {
|
||||||
|
var (
|
||||||
|
P = d.Position()
|
||||||
|
S = d.Size()
|
||||||
|
)
|
||||||
|
return render.Rect{
|
||||||
|
X: P.X,
|
||||||
|
Y: P.Y,
|
||||||
|
W: S.W,
|
||||||
|
H: S.H,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScanBoundingBox scans all of the pixels in a bounding box on the grid and
|
||||||
|
// returns if any of them intersect with level geometry.
|
||||||
|
func (c *Collide) ScanBoundingBox(box render.Rect, grid *level.Chunker) bool {
|
||||||
|
col := GetCollisionBox(box)
|
||||||
|
|
||||||
|
c.ScanGridLine(col.Top[0], col.Top[1], grid, Top)
|
||||||
|
c.ScanGridLine(col.Bottom[0], col.Bottom[1], grid, Bottom)
|
||||||
|
c.ScanGridLine(col.Left[0], col.Left[1], grid, Left)
|
||||||
|
c.ScanGridLine(col.Right[0], col.Right[1], grid, Right)
|
||||||
|
return c.IsColliding()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScanGridLine scans all of the pixels between p1 and p2 on the grid and tests
|
||||||
|
// for any pixels to be set, implying a collision between level geometry and the
|
||||||
|
// bounding boxes of the doodad.
|
||||||
|
func (c *Collide) ScanGridLine(p1, p2 render.Point, grid *level.Chunker, side Side) {
|
||||||
|
for point := range render.IterLine2(p1, p2) {
|
||||||
|
if _, err := grid.Get(point); err == nil {
|
||||||
|
// A hit!
|
||||||
|
switch side {
|
||||||
|
case Top:
|
||||||
|
c.Top = true
|
||||||
|
c.TopPoint = point
|
||||||
|
case Bottom:
|
||||||
|
c.Bottom = true
|
||||||
|
c.BottomPoint = point
|
||||||
|
case Left:
|
||||||
|
c.Left = true
|
||||||
|
c.LeftPoint = point
|
||||||
|
case Right:
|
||||||
|
c.Right = true
|
||||||
|
c.RightPoint = point
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,27 +5,6 @@ import (
|
||||||
"git.kirsle.net/apps/doodle/render"
|
"git.kirsle.net/apps/doodle/render"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Doodad is a reusable drawing component used in Doodle. Doodads are buttons,
|
|
||||||
// doors, switches, the player characters themselves, anything that isn't a part
|
|
||||||
// of the level geometry.
|
|
||||||
type Doodad interface {
|
|
||||||
ID() string
|
|
||||||
|
|
||||||
// Position and velocity, not saved to disk.
|
|
||||||
Position() render.Point
|
|
||||||
Velocity() render.Point
|
|
||||||
Size() render.Rect
|
|
||||||
Grounded() bool
|
|
||||||
SetGrounded(bool)
|
|
||||||
|
|
||||||
// Movement commands.
|
|
||||||
MoveBy(render.Point) // Add {X,Y} to current Position.
|
|
||||||
MoveTo(render.Point) // Set current Position to {X,Y}.
|
|
||||||
|
|
||||||
// Implement the Draw function.
|
|
||||||
Draw(render.Engine)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collide describes how a collision occurred.
|
// Collide describes how a collision occurred.
|
||||||
type Collide struct {
|
type Collide struct {
|
||||||
Top bool
|
Top bool
|
||||||
|
@ -60,6 +39,52 @@ type CollisionBox struct {
|
||||||
Right []render.Point
|
Right []render.Point
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCollisionBox returns a CollisionBox with the four coordinates.
|
||||||
|
func GetCollisionBox(box render.Rect) CollisionBox {
|
||||||
|
return CollisionBox{
|
||||||
|
Top: []render.Point{
|
||||||
|
{
|
||||||
|
X: box.X,
|
||||||
|
Y: box.Y,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
X: box.X + box.W,
|
||||||
|
Y: box.Y,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Bottom: []render.Point{
|
||||||
|
{
|
||||||
|
X: box.X,
|
||||||
|
Y: box.Y + box.H,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
X: box.X + box.W,
|
||||||
|
Y: box.Y + box.H,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Left: []render.Point{
|
||||||
|
{
|
||||||
|
X: box.X,
|
||||||
|
Y: box.Y + box.H - 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
X: box.X,
|
||||||
|
Y: box.Y + 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Right: []render.Point{
|
||||||
|
{
|
||||||
|
X: box.X + box.W,
|
||||||
|
Y: box.Y + box.H - 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
X: box.X + box.W,
|
||||||
|
Y: box.Y + 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Side of the collision box (top, bottom, left, right)
|
// Side of the collision box (top, bottom, left, right)
|
||||||
type Side uint8
|
type Side uint8
|
||||||
|
|
||||||
|
@ -72,7 +97,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// CollidesWithGrid checks if a Doodad collides with level geometry.
|
// CollidesWithGrid checks if a Doodad collides with level geometry.
|
||||||
func CollidesWithGrid(d Doodad, grid *level.Chunker, target render.Point) (*Collide, bool) {
|
func CollidesWithGrid(d Actor, grid *level.Chunker, target render.Point) (*Collide, bool) {
|
||||||
var (
|
var (
|
||||||
P = d.Position()
|
P = d.Position()
|
||||||
S = d.Size()
|
S = d.Size()
|
||||||
|
@ -221,100 +246,3 @@ func CollidesWithGrid(d Doodad, grid *level.Chunker, target render.Point) (*Coll
|
||||||
func (c *Collide) IsColliding() bool {
|
func (c *Collide) IsColliding() bool {
|
||||||
return c.Top || c.Bottom || c.Left || c.Right
|
return c.Top || c.Bottom || c.Left || c.Right
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBoundingRect computes the full pairs of points for the collision box
|
|
||||||
// around a doodad.
|
|
||||||
func GetBoundingRect(d Doodad) render.Rect {
|
|
||||||
var (
|
|
||||||
P = d.Position()
|
|
||||||
S = d.Size()
|
|
||||||
)
|
|
||||||
return render.Rect{
|
|
||||||
X: P.X,
|
|
||||||
Y: P.Y,
|
|
||||||
W: S.W,
|
|
||||||
H: S.H,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetCollisionBox(box render.Rect) CollisionBox {
|
|
||||||
return CollisionBox{
|
|
||||||
Top: []render.Point{
|
|
||||||
{
|
|
||||||
X: box.X,
|
|
||||||
Y: box.Y,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
X: box.X + box.W,
|
|
||||||
Y: box.Y,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Bottom: []render.Point{
|
|
||||||
{
|
|
||||||
X: box.X,
|
|
||||||
Y: box.Y + box.H,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
X: box.X + box.W,
|
|
||||||
Y: box.Y + box.H,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Left: []render.Point{
|
|
||||||
{
|
|
||||||
X: box.X,
|
|
||||||
Y: box.Y + box.H - 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
X: box.X,
|
|
||||||
Y: box.Y + 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Right: []render.Point{
|
|
||||||
{
|
|
||||||
X: box.X + box.W,
|
|
||||||
Y: box.Y + box.H - 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
X: box.X + box.W,
|
|
||||||
Y: box.Y + 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ScanBoundingBox scans all of the pixels in a bounding box on the grid and
|
|
||||||
// returns if any of them intersect with level geometry.
|
|
||||||
func (c *Collide) ScanBoundingBox(box render.Rect, grid *level.Chunker) bool {
|
|
||||||
col := GetCollisionBox(box)
|
|
||||||
|
|
||||||
c.ScanGridLine(col.Top[0], col.Top[1], grid, Top)
|
|
||||||
c.ScanGridLine(col.Bottom[0], col.Bottom[1], grid, Bottom)
|
|
||||||
c.ScanGridLine(col.Left[0], col.Left[1], grid, Left)
|
|
||||||
c.ScanGridLine(col.Right[0], col.Right[1], grid, Right)
|
|
||||||
return c.IsColliding()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ScanGridLine scans all of the pixels between p1 and p2 on the grid and tests
|
|
||||||
// for any pixels to be set, implying a collision between level geometry and the
|
|
||||||
// bounding boxes of the doodad.
|
|
||||||
func (c *Collide) ScanGridLine(p1, p2 render.Point, grid *level.Chunker, side Side) {
|
|
||||||
for point := range render.IterLine2(p1, p2) {
|
|
||||||
if _, err := grid.Get(point); err == nil {
|
|
||||||
// A hit!
|
|
||||||
switch side {
|
|
||||||
case Top:
|
|
||||||
c.Top = true
|
|
||||||
c.TopPoint = point
|
|
||||||
case Bottom:
|
|
||||||
c.Bottom = true
|
|
||||||
c.BottomPoint = point
|
|
||||||
case Left:
|
|
||||||
c.Left = true
|
|
||||||
c.LeftPoint = point
|
|
||||||
case Right:
|
|
||||||
c.Right = true
|
|
||||||
c.RightPoint = point
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
48
doodads/doodad.go
Normal file
48
doodads/doodad.go
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
package doodads
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.kirsle.net/apps/doodle/balance"
|
||||||
|
"git.kirsle.net/apps/doodle/level"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Doodad is a reusable component for Levels that have scripts and graphics.
|
||||||
|
type Doodad struct {
|
||||||
|
level.Base
|
||||||
|
Palette *level.Palette `json:"palette"`
|
||||||
|
Script string `json:"script"`
|
||||||
|
Layers []Layer `json:"layers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Layer holds a layer of drawing data for a Doodad.
|
||||||
|
type Layer struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Chunker *level.Chunker `json:"chunks"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new Doodad.
|
||||||
|
func New(size int) *Doodad {
|
||||||
|
if size == 0 {
|
||||||
|
size = balance.DoodadSize
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Doodad{
|
||||||
|
Base: level.Base{
|
||||||
|
Version: 1,
|
||||||
|
},
|
||||||
|
Palette: level.DefaultPalette(),
|
||||||
|
Layers: []Layer{
|
||||||
|
{
|
||||||
|
Name: "main",
|
||||||
|
Chunker: level.NewChunker(size),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inflate attaches the pixels to their swatches after loading from disk.
|
||||||
|
func (d *Doodad) Inflate() {
|
||||||
|
d.Palette.Inflate()
|
||||||
|
for _, layer := range d.Layers {
|
||||||
|
layer.Chunker.Inflate(d.Palette)
|
||||||
|
}
|
||||||
|
}
|
54
doodads/json.go
Normal file
54
doodads/json.go
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package doodads
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ToJSON serializes the doodad as JSON.
|
||||||
|
func (d *Doodad) ToJSON() ([]byte, error) {
|
||||||
|
out := bytes.NewBuffer([]byte{})
|
||||||
|
encoder := json.NewEncoder(out)
|
||||||
|
encoder.SetIndent("", "\t")
|
||||||
|
err := encoder.Encode(d)
|
||||||
|
return out.Bytes(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteJSON writes a Doodad to JSON on disk.
|
||||||
|
func (d *Doodad) WriteJSON(filename string) error {
|
||||||
|
json, err := d.ToJSON()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Doodad.WriteJSON: JSON encode error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(filename, json, 0755)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Doodad.WriteJSON: WriteFile error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadJSON loads a map from JSON file.
|
||||||
|
func LoadJSON(filename string) (*Doodad, error) {
|
||||||
|
fh, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer fh.Close()
|
||||||
|
|
||||||
|
// Decode the JSON file from disk.
|
||||||
|
d := New(0)
|
||||||
|
decoder := json.NewDecoder(fh)
|
||||||
|
err = decoder.Decode(&d)
|
||||||
|
if err != nil {
|
||||||
|
return d, fmt.Errorf("doodad.LoadJSON: JSON decode error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inflate the chunk metadata to map the pixels to their palette indexes.
|
||||||
|
d.Inflate()
|
||||||
|
return d, err
|
||||||
|
}
|
37
doodle.go
37
doodle.go
|
@ -1,8 +1,11 @@
|
||||||
package doodle
|
package doodle
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.kirsle.net/apps/doodle/enum"
|
||||||
"git.kirsle.net/apps/doodle/render"
|
"git.kirsle.net/apps/doodle/render"
|
||||||
"github.com/kirsle/golog"
|
"github.com/kirsle/golog"
|
||||||
)
|
)
|
||||||
|
@ -163,13 +166,41 @@ func (d *Doodle) NewMap() {
|
||||||
d.Goto(scene)
|
d.Goto(scene)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditLevel loads a map from JSON into the EditorScene.
|
// NewDoodad loads a new Doodad in Edit Mode.
|
||||||
func (d *Doodle) EditLevel(filename string) error {
|
func (d *Doodle) NewDoodad(size int) {
|
||||||
log.Info("Loading level from file: %s", filename)
|
log.Info("Starting a new doodad")
|
||||||
|
scene := &EditorScene{
|
||||||
|
DrawingType: enum.DoodadDrawing,
|
||||||
|
DoodadSize: size,
|
||||||
|
}
|
||||||
|
d.Goto(scene)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EditDrawing loads a drawing (Level or Doodad) in Edit Mode.
|
||||||
|
func (d *Doodle) EditDrawing(filename string) error {
|
||||||
|
log.Info("Loading drawing from file: %s", filename)
|
||||||
|
parts := strings.Split(filename, ".")
|
||||||
|
if len(parts) < 2 {
|
||||||
|
return fmt.Errorf("filename `%s` has no file extension", filename)
|
||||||
|
}
|
||||||
|
ext := strings.ToLower(parts[len(parts)-1])
|
||||||
|
|
||||||
scene := &EditorScene{
|
scene := &EditorScene{
|
||||||
Filename: filename,
|
Filename: filename,
|
||||||
OpenFile: true,
|
OpenFile: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch ext {
|
||||||
|
case "level":
|
||||||
|
case "map":
|
||||||
|
log.Info("is a LEvel type")
|
||||||
|
scene.DrawingType = enum.LevelDrawing
|
||||||
|
case "doodad":
|
||||||
|
scene.DrawingType = enum.DoodadDrawing
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("file extension '%s' doesn't indicate its drawing type", ext)
|
||||||
|
}
|
||||||
|
|
||||||
d.Goto(scene)
|
d.Goto(scene)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,38 @@
|
||||||
package doodle
|
package doodle
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/balance"
|
"git.kirsle.net/apps/doodle/balance"
|
||||||
|
"git.kirsle.net/apps/doodle/doodads"
|
||||||
"git.kirsle.net/apps/doodle/enum"
|
"git.kirsle.net/apps/doodle/enum"
|
||||||
"git.kirsle.net/apps/doodle/events"
|
"git.kirsle.net/apps/doodle/events"
|
||||||
"git.kirsle.net/apps/doodle/level"
|
"git.kirsle.net/apps/doodle/level"
|
||||||
"git.kirsle.net/apps/doodle/render"
|
"git.kirsle.net/apps/doodle/render"
|
||||||
|
"git.kirsle.net/apps/doodle/uix"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EditorScene manages the "Edit Level" game mode.
|
// EditorScene manages the "Edit Level" game mode.
|
||||||
type EditorScene struct {
|
type EditorScene struct {
|
||||||
// Configuration for the scene initializer.
|
// Configuration for the scene initializer.
|
||||||
|
DrawingType enum.DrawingType
|
||||||
OpenFile bool
|
OpenFile bool
|
||||||
Filename string
|
Filename string
|
||||||
|
DoodadSize int
|
||||||
|
|
||||||
UI *EditorUI
|
UI *EditorUI
|
||||||
|
|
||||||
// The current level being edited.
|
// The current level or doodad object being edited, based on the
|
||||||
DrawingType enum.DrawingType
|
// DrawingType.
|
||||||
Level *level.Level
|
Level *level.Level
|
||||||
|
Doodad *doodads.Doodad
|
||||||
|
|
||||||
// The canvas widget that contains the map we're working on.
|
// The canvas widget that contains the map we're working on.
|
||||||
// XXX: in dev builds this is available at $ d.Scene.GetDrawing()
|
// XXX: in dev builds this is available at $ d.Scene.GetDrawing()
|
||||||
drawing *level.Canvas
|
drawing *uix.Canvas
|
||||||
|
|
||||||
// Last saved filename by the user.
|
// Last saved filename by the user.
|
||||||
filename string
|
filename string
|
||||||
|
@ -39,7 +45,7 @@ func (s *EditorScene) Name() string {
|
||||||
|
|
||||||
// Setup the editor scene.
|
// Setup the editor scene.
|
||||||
func (s *EditorScene) Setup(d *Doodle) error {
|
func (s *EditorScene) Setup(d *Doodle) error {
|
||||||
s.drawing = level.NewCanvas(balance.ChunkSize, true)
|
s.drawing = uix.NewCanvas(balance.ChunkSize, true)
|
||||||
if len(s.drawing.Palette.Swatches) > 0 {
|
if len(s.drawing.Palette.Swatches) > 0 {
|
||||||
s.drawing.SetSwatch(s.drawing.Palette.Swatches[0])
|
s.drawing.SetSwatch(s.drawing.Palette.Swatches[0])
|
||||||
}
|
}
|
||||||
|
@ -49,12 +55,16 @@ func (s *EditorScene) Setup(d *Doodle) error {
|
||||||
s.drawing.Resize(render.NewRect(d.width-150, d.height-44))
|
s.drawing.Resize(render.NewRect(d.width-150, d.height-44))
|
||||||
s.drawing.Compute(d.Engine)
|
s.drawing.Compute(d.Engine)
|
||||||
|
|
||||||
// // Were we given configuration data?
|
// Were we given configuration data?
|
||||||
if s.Filename != "" {
|
if s.Filename != "" {
|
||||||
log.Debug("EditorScene.Setup: Set filename to %s", s.Filename)
|
log.Debug("EditorScene.Setup: Set filename to %s", s.Filename)
|
||||||
s.filename = s.Filename
|
s.filename = s.Filename
|
||||||
s.Filename = ""
|
s.Filename = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Loading a Level or a Doodad?
|
||||||
|
switch s.DrawingType {
|
||||||
|
case enum.LevelDrawing:
|
||||||
if s.Level != nil {
|
if s.Level != nil {
|
||||||
log.Debug("EditorScene.Setup: received level from scene caller")
|
log.Debug("EditorScene.Setup: received level from scene caller")
|
||||||
s.drawing.LoadLevel(s.Level)
|
s.drawing.LoadLevel(s.Level)
|
||||||
|
@ -71,6 +81,31 @@ func (s *EditorScene) Setup(d *Doodle) error {
|
||||||
s.Level = level.New()
|
s.Level = level.New()
|
||||||
s.Level.Palette = level.DefaultPalette()
|
s.Level.Palette = level.DefaultPalette()
|
||||||
s.drawing.LoadLevel(s.Level)
|
s.drawing.LoadLevel(s.Level)
|
||||||
|
s.drawing.ScrollTo(render.Origin)
|
||||||
|
s.drawing.Scrollable = true
|
||||||
|
}
|
||||||
|
case enum.DoodadDrawing:
|
||||||
|
// No Doodad?
|
||||||
|
if s.filename != "" && s.OpenFile {
|
||||||
|
log.Debug("EditorScene.Setup: Loading doodad from filename at %s", s.filename)
|
||||||
|
if err := s.LoadDoodad(s.filename); err != nil {
|
||||||
|
d.Flash("LoadDoodad error: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No Doodad?
|
||||||
|
if s.Doodad == nil {
|
||||||
|
log.Debug("EditorScene.Setup: initializing a new Doodad")
|
||||||
|
s.Doodad = doodads.New(s.DoodadSize)
|
||||||
|
s.drawing.LoadDoodad(s.Doodad)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: move inside the UI. Just an approximate position for now.
|
||||||
|
s.drawing.MoveTo(render.NewPoint(200, 200))
|
||||||
|
s.drawing.Resize(render.NewRect(int32(s.DoodadSize), int32(s.DoodadSize)))
|
||||||
|
s.drawing.ScrollTo(render.Origin)
|
||||||
|
s.drawing.Scrollable = false
|
||||||
|
s.drawing.Compute(d.Engine)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the user interface. It references the palette and such so it
|
// Initialize the user interface. It references the palette and such so it
|
||||||
|
@ -127,7 +162,11 @@ func (s *EditorScene) LoadLevel(filename string) error {
|
||||||
|
|
||||||
// SaveLevel saves the level to disk.
|
// SaveLevel saves the level to disk.
|
||||||
// TODO: move this into the Canvas?
|
// TODO: move this into the Canvas?
|
||||||
func (s *EditorScene) SaveLevel(filename string) {
|
func (s *EditorScene) SaveLevel(filename string) error {
|
||||||
|
if s.DrawingType != enum.LevelDrawing {
|
||||||
|
return errors.New("SaveLevel: current drawing is not a Level type")
|
||||||
|
}
|
||||||
|
|
||||||
s.filename = filename
|
s.filename = filename
|
||||||
|
|
||||||
m := s.Level
|
m := s.Level
|
||||||
|
@ -143,15 +182,54 @@ func (s *EditorScene) SaveLevel(filename string) {
|
||||||
|
|
||||||
json, err := m.ToJSON()
|
json, err := m.ToJSON()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("SaveLevel error: %s", err)
|
return fmt.Errorf("SaveLevel error: %s", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ioutil.WriteFile(filename, json, 0644)
|
err = ioutil.WriteFile(filename, json, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Create map file error: %s", err)
|
return fmt.Errorf("Create map file error: %s", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadDoodad loads a doodad from disk.
|
||||||
|
func (s *EditorScene) LoadDoodad(filename string) error {
|
||||||
|
s.filename = filename
|
||||||
|
|
||||||
|
doodad, err := doodads.LoadJSON(filename)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("EditorScene.LoadDoodad(%s): %s", filename, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.DrawingType = enum.DoodadDrawing
|
||||||
|
s.Doodad = doodad
|
||||||
|
s.DoodadSize = doodad.Layers[0].Chunker.Size
|
||||||
|
s.drawing.LoadDoodad(s.Doodad)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveDoodad saves the doodad to disk.
|
||||||
|
func (s *EditorScene) SaveDoodad(filename string) error {
|
||||||
|
if s.DrawingType != enum.DoodadDrawing {
|
||||||
|
return errors.New("SaveDoodad: current drawing is not a Doodad type")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.filename = filename
|
||||||
|
d := s.Doodad
|
||||||
|
if d.Title == "" {
|
||||||
|
d.Title = "Untitled Doodad"
|
||||||
|
}
|
||||||
|
if d.Author == "" {
|
||||||
|
d.Author = os.Getenv("USER")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: is this copying necessary?
|
||||||
|
d.Palette = s.drawing.Palette
|
||||||
|
d.Layers[0].Chunker = s.drawing.Chunker()
|
||||||
|
|
||||||
|
err := d.WriteJSON(s.filename)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destroy the scene.
|
// Destroy the scene.
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
package doodle
|
package doodle
|
||||||
|
|
||||||
import "git.kirsle.net/apps/doodle/level"
|
import "git.kirsle.net/apps/doodle/uix"
|
||||||
|
|
||||||
// TODO: build flags to not include this in production builds.
|
// TODO: build flags to not include this in production builds.
|
||||||
// This adds accessors for private variables from the dev console.
|
// This adds accessors for private variables from the dev console.
|
||||||
|
|
||||||
// GetDrawing returns the level.Canvas
|
// GetDrawing returns the uix.Canvas
|
||||||
func (w *EditorScene) GetDrawing() *level.Canvas {
|
func (w *EditorScene) GetDrawing() *uix.Canvas {
|
||||||
return w.drawing
|
return w.drawing
|
||||||
}
|
}
|
||||||
|
|
53
editor_ui.go
53
editor_ui.go
|
@ -2,8 +2,10 @@ package doodle
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/balance"
|
"git.kirsle.net/apps/doodle/balance"
|
||||||
|
"git.kirsle.net/apps/doodle/enum"
|
||||||
"git.kirsle.net/apps/doodle/events"
|
"git.kirsle.net/apps/doodle/events"
|
||||||
"git.kirsle.net/apps/doodle/render"
|
"git.kirsle.net/apps/doodle/render"
|
||||||
"git.kirsle.net/apps/doodle/ui"
|
"git.kirsle.net/apps/doodle/ui"
|
||||||
|
@ -63,11 +65,16 @@ func (u *EditorUI) Loop(ev *events.State) {
|
||||||
|
|
||||||
// Statusbar filename label.
|
// Statusbar filename label.
|
||||||
filename := "untitled.map"
|
filename := "untitled.map"
|
||||||
|
fileType := "Level"
|
||||||
if u.Scene.filename != "" {
|
if u.Scene.filename != "" {
|
||||||
filename = u.Scene.filename
|
filename = u.Scene.filename
|
||||||
}
|
}
|
||||||
u.StatusFilenameText = fmt.Sprintf("Filename: %s",
|
if u.Scene.DrawingType == enum.DoodadDrawing {
|
||||||
|
fileType = "Doodad"
|
||||||
|
}
|
||||||
|
u.StatusFilenameText = fmt.Sprintf("Filename: %s (%s)",
|
||||||
filename,
|
filename,
|
||||||
|
fileType,
|
||||||
)
|
)
|
||||||
|
|
||||||
u.MenuBar.Compute(u.d.Engine)
|
u.MenuBar.Compute(u.d.Engine)
|
||||||
|
@ -110,20 +117,52 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.Frame {
|
||||||
menuButton{
|
menuButton{
|
||||||
Text: "New Doodad",
|
Text: "New Doodad",
|
||||||
Click: func(render.Point) {
|
Click: func(render.Point) {
|
||||||
d.NewMap()
|
d.Prompt("Doodad size [100]>", func(answer string) {
|
||||||
|
size := balance.DoodadSize
|
||||||
|
if answer != "" {
|
||||||
|
i, err := strconv.Atoi(answer)
|
||||||
|
if err != nil {
|
||||||
|
d.Flash("Error: Doodad size must be a number.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
size = i
|
||||||
|
}
|
||||||
|
d.NewDoodad(size)
|
||||||
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
menuButton{
|
menuButton{
|
||||||
Text: "Save",
|
Text: "Save",
|
||||||
Click: func(render.Point) {
|
Click: func(render.Point) {
|
||||||
|
var saveFunc func(filename string)
|
||||||
|
|
||||||
|
switch u.Scene.DrawingType {
|
||||||
|
case enum.LevelDrawing:
|
||||||
|
saveFunc = func(filename string) {
|
||||||
|
if err := u.Scene.SaveLevel(filename); err != nil {
|
||||||
|
d.Flash("Error: %s", err)
|
||||||
|
} else {
|
||||||
|
d.Flash("Saved level: %s", filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case enum.DoodadDrawing:
|
||||||
|
saveFunc = func(filename string) {
|
||||||
|
if err := u.Scene.SaveDoodad(filename); err != nil {
|
||||||
|
d.Flash("Error: %s", err)
|
||||||
|
} else {
|
||||||
|
d.Flash("Saved doodad: %s", filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
d.Flash("Error: Scene.DrawingType is not a valid type")
|
||||||
|
}
|
||||||
|
|
||||||
if u.Scene.filename != "" {
|
if u.Scene.filename != "" {
|
||||||
u.Scene.SaveLevel(u.Scene.filename)
|
saveFunc(u.Scene.filename)
|
||||||
d.Flash("Saved: %s", u.Scene.filename)
|
|
||||||
} else {
|
} else {
|
||||||
d.Prompt("Save filename>", func(answer string) {
|
d.Prompt("Save filename>", func(answer string) {
|
||||||
if answer != "" {
|
if answer != "" {
|
||||||
u.Scene.SaveLevel("./maps/" + answer) // TODO: maps path
|
saveFunc(answer)
|
||||||
d.Flash("Saved: %s", answer)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -145,7 +184,7 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.Frame {
|
||||||
Click: func(render.Point) {
|
Click: func(render.Point) {
|
||||||
d.Prompt("Open filename>", func(answer string) {
|
d.Prompt("Open filename>", func(answer string) {
|
||||||
if answer != "" {
|
if answer != "" {
|
||||||
u.d.EditLevel("./maps/" + answer) // TODO: maps path
|
u.d.EditDrawing("./maps/" + answer) // TODO: maps path
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
2
fps.go
2
fps.go
|
@ -58,7 +58,7 @@ func (d *Doodle) DrawDebugOverlay() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DrawCollisionBox draws the collision box around a Doodad.
|
// DrawCollisionBox draws the collision box around a Doodad.
|
||||||
func (d *Doodle) DrawCollisionBox(actor doodads.Doodad) {
|
func (d *Doodle) DrawCollisionBox(actor doodads.Actor) {
|
||||||
if !d.Debug || !DebugCollision {
|
if !d.Debug || !DebugCollision {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -221,7 +221,7 @@ func (s *GUITestScene) Setup(d *Doodle) error {
|
||||||
}))
|
}))
|
||||||
button2.Handle(ui.Click, func(p render.Point) {
|
button2.Handle(ui.Click, func(p render.Point) {
|
||||||
d.Prompt("Map name>", func(name string) {
|
d.Prompt("Map name>", func(name string) {
|
||||||
d.EditLevel(name)
|
d.EditDrawing(name)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -50,14 +50,5 @@ func LoadJSON(filename string) (*Level, error) {
|
||||||
|
|
||||||
// Inflate the private instance values.
|
// Inflate the private instance values.
|
||||||
m.Palette.Inflate()
|
m.Palette.Inflate()
|
||||||
for _, px := range m.Pixels {
|
|
||||||
if int(px.PaletteIndex) > len(m.Palette.Swatches) {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"pixel %s references palette index %d but there are only %d swatches in the palette",
|
|
||||||
px, px.PaletteIndex, len(m.Palette.Swatches),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
px.Swatch = m.Palette.Swatches[px.PaletteIndex]
|
|
||||||
}
|
|
||||||
return m, err
|
return m, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,37 +8,36 @@ import (
|
||||||
"git.kirsle.net/apps/doodle/render"
|
"git.kirsle.net/apps/doodle/render"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Level is the container format for Doodle map drawings.
|
// Base provides the common struct keys that are shared between Levels and
|
||||||
type Level struct {
|
// Doodads.
|
||||||
|
type Base struct {
|
||||||
Version int `json:"version"` // File format version spec.
|
Version int `json:"version"` // File format version spec.
|
||||||
GameVersion string `json:"gameVersion"` // Game version that created the level.
|
GameVersion string `json:"gameVersion"` // Game version that created the level.
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Author string `json:"author"`
|
Author string `json:"author"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Level is the container format for Doodle map drawings.
|
||||||
|
type Level struct {
|
||||||
|
Base
|
||||||
Password string `json:"passwd"`
|
Password string `json:"passwd"`
|
||||||
Locked bool `json:"locked"`
|
Locked bool `json:"locked"`
|
||||||
|
|
||||||
// Chunked pixel data.
|
// Chunked pixel data.
|
||||||
Chunker *Chunker `json:"chunks"`
|
Chunker *Chunker `json:"chunks"`
|
||||||
|
|
||||||
// XXX: deprecated?
|
|
||||||
Width int32 `json:"w"`
|
|
||||||
Height int32 `json:"h"`
|
|
||||||
|
|
||||||
// The Palette holds the unique "colors" used in this map file, and their
|
// The Palette holds the unique "colors" used in this map file, and their
|
||||||
// properties (solid, fire, slippery, etc.)
|
// properties (solid, fire, slippery, etc.)
|
||||||
Palette *Palette `json:"palette"`
|
Palette *Palette `json:"palette"`
|
||||||
|
|
||||||
// Pixels is a 2D array indexed by [X][Y]. The cell values are indexes into
|
|
||||||
// the Palette.
|
|
||||||
Pixels []*Pixel `json:"pixels"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a blank level object with all its members initialized.
|
// New creates a blank level object with all its members initialized.
|
||||||
func New() *Level {
|
func New() *Level {
|
||||||
return &Level{
|
return &Level{
|
||||||
|
Base: Base{
|
||||||
Version: 1,
|
Version: 1,
|
||||||
|
},
|
||||||
Chunker: NewChunker(balance.ChunkSize),
|
Chunker: NewChunker(balance.ChunkSize),
|
||||||
Pixels: []*Pixel{},
|
|
||||||
Palette: &Palette{},
|
Palette: &Palette{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"git.kirsle.net/apps/doodle/events"
|
"git.kirsle.net/apps/doodle/events"
|
||||||
"git.kirsle.net/apps/doodle/level"
|
"git.kirsle.net/apps/doodle/level"
|
||||||
"git.kirsle.net/apps/doodle/render"
|
"git.kirsle.net/apps/doodle/render"
|
||||||
|
"git.kirsle.net/apps/doodle/uix"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PlayScene manages the "Edit Level" game mode.
|
// PlayScene manages the "Edit Level" game mode.
|
||||||
|
@ -17,10 +18,10 @@ type PlayScene struct {
|
||||||
Level *level.Level
|
Level *level.Level
|
||||||
|
|
||||||
// Private variables.
|
// Private variables.
|
||||||
drawing *level.Canvas
|
drawing *uix.Canvas
|
||||||
|
|
||||||
// Player character
|
// Player character
|
||||||
Player doodads.Doodad
|
Player doodads.Actor
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name of the scene.
|
// Name of the scene.
|
||||||
|
@ -30,7 +31,7 @@ func (s *PlayScene) Name() string {
|
||||||
|
|
||||||
// Setup the play scene.
|
// Setup the play scene.
|
||||||
func (s *PlayScene) Setup(d *Doodle) error {
|
func (s *PlayScene) Setup(d *Doodle) error {
|
||||||
s.drawing = level.NewCanvas(balance.ChunkSize, false)
|
s.drawing = uix.NewCanvas(balance.ChunkSize, false)
|
||||||
s.drawing.MoveTo(render.Origin)
|
s.drawing.MoveTo(render.Origin)
|
||||||
s.drawing.Resize(render.NewRect(d.width, d.height))
|
s.drawing.Resize(render.NewRect(d.width, d.height))
|
||||||
s.drawing.Compute(d.Engine)
|
s.drawing.Compute(d.Engine)
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package level
|
package uix
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.kirsle.net/apps/doodle/balance"
|
"git.kirsle.net/apps/doodle/balance"
|
||||||
|
"git.kirsle.net/apps/doodle/doodads"
|
||||||
"git.kirsle.net/apps/doodle/events"
|
"git.kirsle.net/apps/doodle/events"
|
||||||
|
"git.kirsle.net/apps/doodle/level"
|
||||||
"git.kirsle.net/apps/doodle/render"
|
"git.kirsle.net/apps/doodle/render"
|
||||||
"git.kirsle.net/apps/doodle/ui"
|
"git.kirsle.net/apps/doodle/ui"
|
||||||
)
|
)
|
||||||
|
@ -10,32 +12,37 @@ import (
|
||||||
// Canvas is a custom ui.Widget that manages a single drawing.
|
// Canvas is a custom ui.Widget that manages a single drawing.
|
||||||
type Canvas struct {
|
type Canvas struct {
|
||||||
ui.Frame
|
ui.Frame
|
||||||
Palette *Palette
|
Palette *level.Palette
|
||||||
|
|
||||||
// Set to true to allow clicking to edit this canvas.
|
// Set to true to allow clicking to edit this canvas.
|
||||||
Editable bool
|
Editable bool
|
||||||
|
Scrollable bool
|
||||||
|
|
||||||
chunks *Chunker
|
chunks *level.Chunker
|
||||||
pixelHistory []*Pixel
|
pixelHistory []*level.Pixel
|
||||||
lastPixel *Pixel
|
lastPixel *level.Pixel
|
||||||
|
|
||||||
// We inherit the ui.Widget which manages the width and height.
|
// We inherit the ui.Widget which manages the width and height.
|
||||||
Scroll render.Point // Scroll offset for which parts of canvas are visible.
|
Scroll render.Point // Scroll offset for which parts of canvas are visible.
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCanvas initializes a Canvas widget.
|
// NewCanvas initializes a Canvas widget.
|
||||||
|
//
|
||||||
|
// If editable is true, Scrollable is also set to true, which means the arrow
|
||||||
|
// keys will scroll the canvas viewport which is desirable in Edit Mode.
|
||||||
func NewCanvas(size int, editable bool) *Canvas {
|
func NewCanvas(size int, editable bool) *Canvas {
|
||||||
w := &Canvas{
|
w := &Canvas{
|
||||||
Editable: editable,
|
Editable: editable,
|
||||||
Palette: NewPalette(),
|
Scrollable: editable,
|
||||||
chunks: NewChunker(size),
|
Palette: level.NewPalette(),
|
||||||
|
chunks: level.NewChunker(size),
|
||||||
}
|
}
|
||||||
w.setup()
|
w.setup()
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load initializes the Canvas using an existing Palette and Grid.
|
// Load initializes the Canvas using an existing Palette and Grid.
|
||||||
func (w *Canvas) Load(p *Palette, g *Chunker) {
|
func (w *Canvas) Load(p *level.Palette, g *level.Chunker) {
|
||||||
w.Palette = p
|
w.Palette = p
|
||||||
w.chunks = g
|
w.chunks = g
|
||||||
|
|
||||||
|
@ -45,12 +52,18 @@ func (w *Canvas) Load(p *Palette, g *Chunker) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadLevel initializes a Canvas from a Level object.
|
// LoadLevel initializes a Canvas from a Level object.
|
||||||
func (w *Canvas) LoadLevel(level *Level) {
|
func (w *Canvas) LoadLevel(level *level.Level) {
|
||||||
w.Load(level.Palette, level.Chunker)
|
w.Load(level.Palette, level.Chunker)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadDoodad initializes a Canvas from a Doodad object.
|
||||||
|
func (w *Canvas) LoadDoodad(d *doodads.Doodad) {
|
||||||
|
// TODO more safe
|
||||||
|
w.Load(d.Palette, d.Layers[0].Chunker)
|
||||||
|
}
|
||||||
|
|
||||||
// SetSwatch changes the currently selected swatch for editing.
|
// SetSwatch changes the currently selected swatch for editing.
|
||||||
func (w *Canvas) SetSwatch(s *Swatch) {
|
func (w *Canvas) SetSwatch(s *level.Swatch) {
|
||||||
w.Palette.ActiveSwatch = s
|
w.Palette.ActiveSwatch = s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,6 +86,7 @@ func (w *Canvas) Loop(ev *events.State) error {
|
||||||
_ = P
|
_ = P
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if w.Scrollable {
|
||||||
// Arrow keys to scroll the view.
|
// Arrow keys to scroll the view.
|
||||||
scrollBy := render.Point{}
|
scrollBy := render.Point{}
|
||||||
if ev.Right.Now {
|
if ev.Right.Now {
|
||||||
|
@ -88,6 +102,7 @@ func (w *Canvas) Loop(ev *events.State) error {
|
||||||
if !scrollBy.IsZero() {
|
if !scrollBy.IsZero() {
|
||||||
w.ScrollBy(scrollBy)
|
w.ScrollBy(scrollBy)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Only care if the cursor is over our space.
|
// Only care if the cursor is over our space.
|
||||||
cursor := render.NewPoint(ev.CursorX.Now, ev.CursorY.Now)
|
cursor := render.NewPoint(ev.CursorX.Now, ev.CursorY.Now)
|
||||||
|
@ -108,7 +123,7 @@ func (w *Canvas) Loop(ev *events.State) error {
|
||||||
X: ev.CursorX.Now - P.X + w.Scroll.X,
|
X: ev.CursorX.Now - P.X + w.Scroll.X,
|
||||||
Y: ev.CursorY.Now - P.Y + w.Scroll.Y,
|
Y: ev.CursorY.Now - P.Y + w.Scroll.Y,
|
||||||
}
|
}
|
||||||
pixel := &Pixel{
|
pixel := &level.Pixel{
|
||||||
X: cursor.X,
|
X: cursor.X,
|
||||||
Y: cursor.Y,
|
Y: cursor.Y,
|
||||||
Swatch: w.Palette.ActiveSwatch,
|
Swatch: w.Palette.ActiveSwatch,
|
||||||
|
@ -152,10 +167,16 @@ func (w *Canvas) Viewport() render.Rect {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chunker returns the underlying Chunker object.
|
// Chunker returns the underlying Chunker object.
|
||||||
func (w *Canvas) Chunker() *Chunker {
|
func (w *Canvas) Chunker() *level.Chunker {
|
||||||
return w.chunks
|
return w.chunks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ScrollTo sets the viewport scroll position.
|
||||||
|
func (w *Canvas) ScrollTo(to render.Point) {
|
||||||
|
w.Scroll.X = to.X
|
||||||
|
w.Scroll.Y = to.Y
|
||||||
|
}
|
||||||
|
|
||||||
// ScrollBy adjusts the viewport scroll position.
|
// ScrollBy adjusts the viewport scroll position.
|
||||||
func (w *Canvas) ScrollBy(by render.Point) {
|
func (w *Canvas) ScrollBy(by render.Point) {
|
||||||
w.Scroll.Add(by)
|
w.Scroll.Add(by)
|
Loading…
Reference in New Issue
Block a user