render: Refactor Events System to Make Module Standalone

* Refactor the events used in lib/render/sdl to be more general-purpose
  to make librender a stand-alone library separate from Doodle.
This commit is contained in:
Noah 2019-12-22 14:11:01 -08:00
parent 3634577f19
commit 7355778a39
30 changed files with 318 additions and 247 deletions

7
lib/render/README.md Normal file
View File

@ -0,0 +1,7 @@
# Render: Go Graphics Library
Render is a graphics library written in Go which targets desktop applications on
Windows, MacOS and Linux as well as WebAssembly to run in the browser.
For desktop systems it uses SDL2 under the hood, and in WebAssembly it interacts
with an HTML Canvas element for drawing.

View File

@ -4,7 +4,7 @@ import (
"syscall/js"
"time"
"git.kirsle.net/apps/doodle/lib/events"
"git.kirsle.net/apps/doodle/lib/render/event"
)
// Engine implements a rendering engine targeting an HTML canvas for
@ -17,7 +17,7 @@ type Engine struct {
ticks uint32
// Private fields.
events *events.State
events *event.State
running bool
textures map[string]*Texture // cached texture PNG images
@ -34,7 +34,7 @@ func New(canvasID string) (*Engine, error) {
engine := &Engine{
canvas: canvas,
startTime: time.Now(),
events: events.New(),
events: event.NewState(),
width: canvas.ClientW(),
height: canvas.ClientH(),
queue: make(chan Event, 1024),

View File

@ -3,7 +3,7 @@ package canvas
import (
"syscall/js"
"git.kirsle.net/apps/doodle/lib/events"
"git.kirsle.net/apps/doodle/lib/render/event"
)
// EventClass to categorize JavaScript events.
@ -158,21 +158,21 @@ func (e *Engine) PollEvent() *Event {
}
// Poll for events.
func (e *Engine) Poll() (*events.State, error) {
func (e *Engine) Poll() (*event.State, error) {
s := e.events
for event := e.PollEvent(); event != nil; event = e.PollEvent() {
switch event.Class {
case WindowEvent:
s.Resized.Push(true)
s.WindowResized = true
case MouseEvent:
s.CursorX.Push(int32(event.X))
s.CursorY.Push(int32(event.Y))
s.CursorX = event.X
s.CursorY = event.Y
case ClickEvent:
s.CursorX.Push(int32(event.X))
s.CursorY.Push(int32(event.Y))
s.Button1.Push(event.LeftClick)
s.Button2.Push(event.RightClick)
s.CursorX = event.X
s.CursorY = event.Y
s.Button1 = event.LeftClick
s.Button2 = event.RightClick
case KeyEvent:
switch event.KeyName {
case "Escape":
@ -180,45 +180,34 @@ func (e *Engine) Poll() (*events.State, error) {
continue
}
if event.State {
s.EscapeKey.Push(true)
}
s.Escape = event.State
case "Enter":
if event.Repeat {
continue
}
if event.State {
s.EnterKey.Push(true)
}
s.Enter = event.State
case "F3":
if event.State {
s.KeyName.Push("F3")
}
s.SetKeyDown("F3", event.State)
case "ArrowUp":
s.Up.Push(event.State)
s.Up = event.State
case "ArrowLeft":
s.Left.Push(event.State)
s.Left = event.State
case "ArrowRight":
s.Right.Push(event.State)
s.Right = event.State
case "ArrowDown":
s.Down.Push(event.State)
s.Down = event.State
case "Shift":
s.ShiftActive.Push(event.State)
s.Shift = event.State
continue
case "Alt":
s.Alt = event.State
case "Control":
continue
s.Ctrl = event.State
case "Backspace":
if event.State {
s.KeyName.Push(`\b`)
}
s.SetKeyDown(`\b`, event.State)
default:
if event.State {
s.KeyName.Push(event.KeyName)
} else {
s.KeyName.Push("")
}
s.SetKeyDown(event.KeyName, event.State)
}
}
}

View File

@ -7,8 +7,6 @@ import (
"image/color"
"regexp"
"strconv"
"github.com/vmihailenco/msgpack"
)
var (
@ -165,52 +163,6 @@ func (c *Color) UnmarshalJSON(b []byte) error {
return nil
}
func (c Color) EncodeMsgpack(enc *msgpack.Encoder) error {
return enc.EncodeString(fmt.Sprintf(
`"#%02x%02x%02x"`,
c.Red, c.Green, c.Blue,
))
}
func (c Color) DecodeMsgpack(dec *msgpack.Decoder) error {
hex, err := dec.DecodeString()
if err != nil {
return fmt.Errorf("Color.DecodeMsgpack: %s", err)
}
parsed, err := HexColor(hex)
if err != nil {
return fmt.Errorf("Color.DecodeMsgpack: HexColor: %s", err)
}
c.Red = parsed.Red
c.Blue = parsed.Blue
c.Green = parsed.Green
c.Alpha = parsed.Alpha
return nil
}
// // MarshalMsgpack serializes the Color for msgpack.
// func (c Color) MarshalMsgpack() ([]byte, error) {
// data := []uint8{
// c.Red, c.Green, c.Blue, c.Alpha,
// }
// return msgpack.Marshal(data)
// }
//
// // UnmarshalMsgpack decodes a Color from msgpack format.
// func (c *Color) UnmarshalMsgpack(b []byte) error {
// var data []uint8
// if err := msgpack.Unmarshal(data, b); err != nil {
// return err
// }
// c.Red = 255
// c.Green = data[1]
// c.Blue = data[2]
// c.Alpha = data[3]
// return nil
// }
// IsZero returns if the color is all zeroes (invisible).
func (c Color) IsZero() bool {
return c.Red+c.Green+c.Blue+c.Alpha == 0

109
lib/render/event/event.go Normal file
View File

@ -0,0 +1,109 @@
package event
import "strings"
// State holds the current state of key/mouse events.
type State struct {
// Mouse buttons.
Button1 bool // Left
Button2 bool // Middle
Button3 bool // Right
// Special keys
Escape bool
Enter bool
Shift bool
Ctrl bool
Alt bool
Up bool
Left bool
Right bool
Down bool
// Pressed keys.
keydown map[string]interface{}
// Cursor position
CursorX int
CursorY int
// Window resized
WindowResized bool
}
// NewState creates a new event.State.
func NewState() *State {
return &State{
keydown: map[string]interface{}{},
}
}
// SetKeyDown sets that a named key is pressed down.
func (s *State) SetKeyDown(name string, down bool) {
if down {
s.keydown[name] = nil
} else {
delete(s.keydown, name)
}
}
// KeyDown returns whether a named key is currently pressed.
func (s *State) KeyDown(name string) bool {
_, ok := s.keydown[name]
return ok
}
// KeysDown returns a list of all key names currently pressed down.
// Set shifted to True to return the key symbols correctly shifted
// (uppercase, or symbols on number keys, etc.)
func (s *State) KeysDown(shifted bool) []string {
var (
result = make([]string, len(s.keydown))
i = 0
)
for key := range s.keydown {
if shifted && s.Shift {
if symbol, ok := shiftMap[key]; ok {
result[i] = symbol
} else {
result[i] = strings.ToUpper(key)
}
} else {
result[i] = key
}
i++
}
return result
}
// ResetKeyDown clears all key-down states.
func (s *State) ResetKeyDown() {
s.keydown = map[string]interface{}{}
}
// shiftMap maps keys to their Shift versions.
var shiftMap = map[string]string{
"`": "~",
"1": "!",
"2": "@",
"3": "#",
"4": "$",
"5": "%",
"6": "^",
"7": "&",
"8": "*",
"9": "(",
"0": ")",
"-": "_",
"=": "+",
"[": "{",
"]": "}",
`\`: "|",
";": ":",
`'`: `"`,
",": "<",
".": ">",
"/": "?",
}

View File

@ -4,7 +4,7 @@ import (
"fmt"
"image"
"git.kirsle.net/apps/doodle/lib/events"
"git.kirsle.net/apps/doodle/lib/render/event"
)
// Engine is the interface for the rendering engine, keeping SDL-specific stuff
@ -13,7 +13,7 @@ type Engine interface {
Setup() error
// Poll for events like keypresses and mouse clicks.
Poll() (*events.State, error)
Poll() (*event.State, error)
GetTicks() uint32
WindowSize() (w, h int)

View File

@ -4,7 +4,7 @@ import (
"errors"
"fmt"
"git.kirsle.net/apps/doodle/lib/events"
"git.kirsle.net/apps/doodle/lib/render/event"
"github.com/veandco/go-sdl2/sdl"
)
@ -12,19 +12,17 @@ import (
var (
DebugWindowEvents = false
DebugMouseEvents = false
DebugClickEvents = false
DebugClickEvents = true
DebugKeyEvents = false
)
// Poll for events.
func (r *Renderer) Poll() (*events.State, error) {
func (r *Renderer) Poll() (*event.State, error) {
s := r.events
// helper function to push keyboard key names on keyDown events only.
pushKey := func(name string, state uint8) {
if state == 1 {
s.KeyName.Push(name)
}
s.SetKeyDown(name, state == 1)
}
for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
@ -42,7 +40,10 @@ func (r *Renderer) Poll() (*events.State, error) {
)
}
}
s.Resized.Push(true)
if t.Event == sdl.WINDOWEVENT_RESIZED {
s.WindowResized = true
}
case *sdl.MouseMotionEvent:
if DebugMouseEvents {
fmt.Printf("[%d ms] tick:%d MouseMotion type:%d id:%d x:%d y:%d xrel:%d yrel:%d",
@ -51,42 +52,50 @@ func (r *Renderer) Poll() (*events.State, error) {
}
// Push the cursor position.
s.CursorX.Push(t.X)
s.CursorY.Push(t.Y)
s.Button1.Push(t.State == 1)
s.CursorX = int(t.X)
s.CursorY = int(t.Y)
case *sdl.MouseButtonEvent:
if DebugClickEvents {
fmt.Printf("[%d ms] tick:%d MouseButton type:%d id:%d x:%d y:%d button:%d state:%d",
fmt.Printf("[%d ms] tick:%d MouseButton type:%d id:%d x:%d y:%d button:%d state:%d\n",
t.Timestamp, r.ticks, t.Type, t.Which, t.X, t.Y, t.Button, t.State,
)
}
// Push the cursor position.
s.CursorX.Push(t.X)
s.CursorY.Push(t.Y)
s.CursorX = int(t.X)
s.CursorY = int(t.Y)
// Is a mouse button pressed down?
checkDown := func(number uint8, target *events.BoolTick) bool {
if t.Button == number {
var eventName string
if t.State == 1 && target.Now == false {
eventName = "DOWN"
} else if t.State == 0 && target.Now == true {
eventName = "UP"
}
if eventName != "" {
target.Push(eventName == "DOWN")
}
return true
}
return false
}
if checkDown(1, s.Button1) || checkDown(3, s.Button2) || checkDown(2, s.Button3) {
// Return the event immediately.
return s, nil
// Store the clicked state of the mouse button.
if t.Button == 1 {
s.Button1 = t.State == 1
} else if t.Button == 2 {
s.Button2 = t.State == 1
} else if t.Button == 3 {
s.Button3 = t.State == 1
}
//
// // Is a mouse button pressed down?
// checkDown := func(number uint8, target *events.BoolTick) bool {
// if t.Button == number {
// var eventName string
// if t.State == 1 && target.Now == false {
// eventName = "DOWN"
// } else if t.State == 0 && target.Now == true {
// eventName = "UP"
// }
//
// if eventName != "" {
// target.Push(eventName == "DOWN")
// }
// return true
// }
// return false
// }
//
// if checkDown(1, s.Button1) || checkDown(3, s.Button2) || checkDown(2, s.Button3) {
// // Return the event immediately.
// return s, nil
// }
case *sdl.MouseWheelEvent:
if DebugMouseEvents {
fmt.Printf("[%d ms] tick:%d MouseWheel type:%d id:%d x:%d y:%d",
@ -105,12 +114,12 @@ func (r *Renderer) Poll() (*events.State, error) {
if t.Repeat == 1 {
continue
}
s.EscapeKey.Push(t.State == 1)
s.Escape = t.State == 1
case sdl.SCANCODE_RETURN:
if t.Repeat == 1 {
continue
}
s.EnterKey.Push(t.State == 1)
s.Enter = t.State == 1
case sdl.SCANCODE_F1:
pushKey("F1", t.State)
case sdl.SCANCODE_F2:
@ -136,33 +145,29 @@ func (r *Renderer) Poll() (*events.State, error) {
case sdl.SCANCODE_F12:
pushKey("F12", t.State)
case sdl.SCANCODE_UP:
s.Up.Push(t.State == 1)
s.Up = t.State == 1
case sdl.SCANCODE_LEFT:
s.Left.Push(t.State == 1)
s.Left = t.State == 1
case sdl.SCANCODE_RIGHT:
s.Right.Push(t.State == 1)
s.Right = t.State == 1
case sdl.SCANCODE_DOWN:
s.Down.Push(t.State == 1)
s.Down = t.State == 1
case sdl.SCANCODE_LSHIFT:
case sdl.SCANCODE_RSHIFT:
s.ShiftActive.Push(t.State == 1)
s.Shift = t.State == 1
case sdl.SCANCODE_LALT:
case sdl.SCANCODE_RALT:
continue
s.Alt = t.State == 1
case sdl.SCANCODE_LCTRL:
s.ControlActive.Push(t.State == 1)
s.Ctrl = t.State == 1
case sdl.SCANCODE_RCTRL:
s.ControlActive.Push(t.State == 1)
s.Ctrl = t.State == 1
case sdl.SCANCODE_BACKSPACE:
// Make it a key event with "\b" as the sequence.
if t.State == 1 || t.Repeat == 1 {
s.KeyName.Push(`\b`)
}
s.SetKeyDown(`\b`, t.State == 1 || t.Repeat == 1)
default:
// Push the string value of the key.
if t.State == 1 {
s.KeyName.Push(string(t.Keysym.Sym))
}
s.SetKeyDown(string(t.Keysym.Sym), t.State == 1)
}
}
}

View File

@ -5,8 +5,8 @@ import (
"fmt"
"time"
"git.kirsle.net/apps/doodle/lib/events"
"git.kirsle.net/apps/doodle/lib/render"
"git.kirsle.net/apps/doodle/lib/render/event"
"github.com/veandco/go-sdl2/sdl"
"github.com/veandco/go-sdl2/ttf"
)
@ -20,7 +20,7 @@ type Renderer struct {
startTime time.Time
// Private fields.
events *events.State
events *event.State
window *sdl.Window
renderer *sdl.Renderer
running bool
@ -34,7 +34,7 @@ type Renderer struct {
// New creates the SDL renderer.
func New(title string, width, height int) *Renderer {
return &Renderer{
events: events.New(),
events: event.NewState(),
title: title,
width: int32(width),
height: int32(height),

View File

@ -2,10 +2,8 @@ package sdl
import (
"fmt"
"strings"
"sync"
"git.kirsle.net/apps/doodle/lib/events"
"git.kirsle.net/apps/doodle/lib/render"
"github.com/veandco/go-sdl2/sdl"
"github.com/veandco/go-sdl2/ttf"
@ -77,20 +75,6 @@ func LoadFont(filename string, size int) (*ttf.Font, error) {
return font, nil
}
// Keysym returns the current key pressed, taking into account the Shift
// key modifier.
func (r *Renderer) Keysym(ev *events.State) string {
if key := ev.KeyName.Read(); key != "" {
if ev.ShiftActive.Pressed() {
if symbol, ok := shiftMap[key]; ok {
return symbol
}
return strings.ToUpper(key)
}
}
return ""
}
// ComputeTextRect computes and returns a Rect for how large the text would
// appear if rendered.
func (r *Renderer) ComputeTextRect(text render.Text) (render.Rect, error) {

View File

@ -100,7 +100,7 @@ func (mw *MainWindow) Loop() error {
return fmt.Errorf("event poll error: %s", err)
}
if ev.Resized.Now {
if ev.WindowResized {
w, h := mw.engine.WindowSize()
if w != mw.w || h != mw.h {
mw.w = w

View File

@ -4,8 +4,8 @@ import (
"errors"
"sync"
"git.kirsle.net/apps/doodle/lib/events"
"git.kirsle.net/apps/doodle/lib/render"
"git.kirsle.net/apps/doodle/lib/render/event"
)
// Event is a named event that the supervisor will send.
@ -79,11 +79,11 @@ var (
//
// Useful errors returned by this may be:
// - ErrStopPropagation
func (s *Supervisor) Loop(ev *events.State) error {
func (s *Supervisor) Loop(ev *event.State) error {
var (
XY = render.Point{
X: ev.CursorX.Now,
Y: ev.CursorY.Now,
X: int32(ev.CursorX),
Y: int32(ev.CursorY),
}
)
@ -93,7 +93,7 @@ func (s *Supervisor) Loop(ev *events.State) error {
// If we are dragging something around, do not trigger any mouse events
// to other widgets but DO notify any widget we dropped on top of!
if s.dd.IsDragging() {
if !ev.Button1.Now && !ev.Button2.Now {
if !ev.Button1 && !ev.Button3 {
// The mouse has been released. TODO: make mouse button important?
for _, child := range hovering {
child.widget.Event(Drop, XY)
@ -121,7 +121,7 @@ func (s *Supervisor) Loop(ev *events.State) error {
}
_, isClicked := s.clicked[id]
if ev.Button1.Now {
if ev.Button1 {
if !isClicked {
w.Event(MouseDown, XY)
s.clicked[id] = nil

View File

@ -2,7 +2,7 @@ package doodads
import (
"git.kirsle.net/apps/doodle/lib/render"
uuid "github.com/satori/go.uuid"
"github.com/google/uuid"
)
// Drawing is a Doodad Actor that is based on drawings made inside the game.
@ -22,7 +22,7 @@ type Drawing struct {
// an empty ID string, it will make a random UUIDv4 ID.
func NewDrawing(id string, doodad *Doodad) Drawing {
if id == "" {
id = uuid.Must(uuid.NewV4()).String()
id = uuid.Must(uuid.NewRandom()).String()
}
return Drawing{
id: id,

View File

@ -6,8 +6,8 @@ import (
"strings"
"time"
"git.kirsle.net/apps/doodle/lib/events"
"git.kirsle.net/apps/doodle/lib/render"
"git.kirsle.net/apps/doodle/lib/render/event"
"git.kirsle.net/apps/doodle/pkg/balance"
"git.kirsle.net/apps/doodle/pkg/branding"
"git.kirsle.net/apps/doodle/pkg/enum"
@ -32,7 +32,7 @@ type Doodle struct {
// Easy access to the event state, for the debug overlay to use.
// Might not be thread safe.
event *events.State
event *event.State
startTime time.Time
running bool
@ -113,7 +113,8 @@ func (d *Doodle) Run() error {
// Poll for events.
ev, err := d.Engine.Poll()
shmem.Cursor = render.NewPoint(ev.CursorX.Now, ev.CursorY.Now)
// log.Error("Button1 is: %+v", ev.Button1)
shmem.Cursor = render.NewPoint(int32(ev.CursorX), int32(ev.CursorY))
if err != nil {
log.Error("event poll error: %s", err)
d.running = false
@ -123,23 +124,24 @@ func (d *Doodle) Run() error {
// Command line shell.
if d.shell.Open {
} else if ev.EnterKey.Read() {
} else if ev.Enter {
log.Debug("Shell: opening shell")
d.shell.Open = true
ev.Enter = false
} else {
// Global event handlers.
if ev.EscapeKey.Read() {
if ev.Escape {
log.Error("Escape key pressed, shutting down")
d.running = false
break
}
if ev.KeyName.Now == "F3" {
if ev.KeyDown("F3") {
DebugOverlay = !DebugOverlay
ev.KeyName.Read()
} else if ev.KeyName.Now == "F4" {
ev.SetKeyDown("F3", false)
} else if ev.KeyDown("F4") {
DebugCollision = !DebugCollision
ev.KeyName.Read()
ev.SetKeyDown("F4", false)
}
// Run the scene's logic.
@ -186,7 +188,7 @@ func (d *Doodle) Run() error {
d.TrackFPS(delay)
// Consume any lingering key sym.
ev.KeyName.Read()
ev.ResetKeyDown()
}
log.Warn("Main Loop Exited! Shutting down...")

View File

@ -6,8 +6,8 @@ import (
"os"
"strings"
"git.kirsle.net/apps/doodle/lib/events"
"git.kirsle.net/apps/doodle/lib/render"
"git.kirsle.net/apps/doodle/lib/render/event"
"git.kirsle.net/apps/doodle/pkg/balance"
"git.kirsle.net/apps/doodle/pkg/doodads"
"git.kirsle.net/apps/doodle/pkg/drawtool"
@ -161,14 +161,14 @@ func (s *EditorScene) Playtest() {
}
// Loop the editor scene.
func (s *EditorScene) Loop(d *Doodle, ev *events.State) error {
func (s *EditorScene) Loop(d *Doodle, ev *event.State) error {
// Update debug overlay values.
*s.debTool = s.UI.Canvas.Tool.String()
*s.debSwatch = s.UI.Canvas.Palette.ActiveSwatch.Name
*s.debWorldIndex = s.UI.Canvas.WorldIndexAt(s.UI.cursor).String()
// Has the window been resized?
if resized := ev.Resized.Read(); resized {
if ev.WindowResized {
w, h := d.Engine.WindowSize()
if w != d.width || h != d.height {
// Not a false alarm.
@ -180,11 +180,10 @@ func (s *EditorScene) Loop(d *Doodle, ev *events.State) error {
}
// Undo/Redo key bindings.
if ev.ControlActive.Now {
key := ev.KeyName.Read()
if key == "z" {
if ev.Ctrl {
if ev.KeyDown("z") {
s.UI.Canvas.UndoStroke()
} else if key == "y" {
} else if ev.KeyDown("y") {
s.UI.Canvas.RedoStroke()
}
}
@ -192,18 +191,17 @@ func (s *EditorScene) Loop(d *Doodle, ev *events.State) error {
s.UI.Loop(ev)
// Switching to Play Mode?
switch ev.KeyName.Read() {
case "p":
if ev.KeyDown("p") {
s.Playtest()
case "l":
} else if ev.KeyDown("l") {
d.Flash("Line Tool selected.")
s.UI.Canvas.Tool = drawtool.LineTool
s.UI.activeTool = s.UI.Canvas.Tool.String()
case "f":
} else if ev.KeyDown("f") {
d.Flash("Pencil Tool selected.")
s.UI.Canvas.Tool = drawtool.PencilTool
s.UI.activeTool = s.UI.Canvas.Tool.String()
case "r":
} else if ev.KeyDown("r") {
d.Flash("Rectangle Tool selected.")
s.UI.Canvas.Tool = drawtool.RectTool
s.UI.activeTool = s.UI.Canvas.Tool.String()

View File

@ -5,8 +5,8 @@ import (
"path/filepath"
"strconv"
"git.kirsle.net/apps/doodle/lib/events"
"git.kirsle.net/apps/doodle/lib/render"
"git.kirsle.net/apps/doodle/lib/render/event"
"git.kirsle.net/apps/doodle/lib/ui"
"git.kirsle.net/apps/doodle/pkg/balance"
"git.kirsle.net/apps/doodle/pkg/branding"
@ -214,8 +214,8 @@ func (u *EditorUI) Resized(d *Doodle) {
}
// Loop to process events and update the UI.
func (u *EditorUI) Loop(ev *events.State) error {
u.cursor = render.NewPoint(ev.CursorX.Now, ev.CursorY.Now)
func (u *EditorUI) Loop(ev *event.State) error {
u.cursor = render.NewPoint(int32(ev.CursorX), int32(ev.CursorY))
// Loop the UI and see whether we're told to stop event propagation.
var stopPropagation bool
@ -230,8 +230,8 @@ func (u *EditorUI) Loop(ev *events.State) error {
// Update status bar labels.
{
u.StatusMouseText = fmt.Sprintf("Rel:(%d,%d) Abs:(%s)",
ev.CursorX.Now,
ev.CursorY.Now,
ev.CursorX,
ev.CursorY,
*u.Scene.debWorldIndex,
)
u.StatusPaletteText = fmt.Sprintf("%s Tool",

View File

@ -68,7 +68,7 @@ func (d *Doodle) DrawDebugOverlay() {
values = []string{
fmt.Sprintf("%d %s", fpsCurrent, framesSkipped),
d.Scene.Name(),
fmt.Sprintf("%d,%d", d.event.CursorX.Now, d.event.CursorY.Now),
fmt.Sprintf("%d,%d", d.event.CursorX, d.event.CursorY),
}
)

View File

@ -3,8 +3,8 @@ package doodle
import (
"fmt"
"git.kirsle.net/apps/doodle/lib/events"
"git.kirsle.net/apps/doodle/lib/render"
"git.kirsle.net/apps/doodle/lib/render/event"
"git.kirsle.net/apps/doodle/lib/ui"
"git.kirsle.net/apps/doodle/pkg/balance"
"git.kirsle.net/apps/doodle/pkg/branding"
@ -248,7 +248,7 @@ func (s *GUITestScene) Setup(d *Doodle) error {
}
// Loop the editor scene.
func (s *GUITestScene) Loop(d *Doodle, ev *events.State) error {
func (s *GUITestScene) Loop(d *Doodle, ev *event.State) error {
s.Supervisor.Loop(ev)
return nil
}

View File

@ -2,7 +2,7 @@ package level
import (
"git.kirsle.net/apps/doodle/lib/render"
uuid "github.com/satori/go.uuid"
"github.com/google/uuid"
)
// ActorMap holds the doodad information by their ID in the level data.
@ -19,7 +19,7 @@ func (m ActorMap) Inflate() {
// given a random UUIDv4 ID.
func (m ActorMap) Add(a *Actor) {
if a.id == "" {
a.id = uuid.Must(uuid.NewV4()).String()
a.id = uuid.Must(uuid.NewRandom()).String()
}
m[a.id] = a
}

View File

@ -10,7 +10,7 @@ import (
"git.kirsle.net/apps/doodle/pkg/balance"
"git.kirsle.net/apps/doodle/pkg/log"
"git.kirsle.net/apps/doodle/pkg/shmem"
"github.com/satori/go.uuid"
"github.com/google/uuid"
"github.com/vmihailenco/msgpack"
)
@ -109,7 +109,7 @@ func (c *Chunk) toBitmap(mask render.Color) (render.Texturer, error) {
// Generate a unique name for this chunk cache.
var name string
if c.uuid == uuid.Nil {
c.uuid = uuid.Must(uuid.NewV4())
c.uuid = uuid.Must(uuid.NewRandom())
}
name = c.uuid.String()

View File

@ -1,8 +1,8 @@
package doodle
import (
"git.kirsle.net/apps/doodle/lib/events"
"git.kirsle.net/apps/doodle/lib/render"
"git.kirsle.net/apps/doodle/lib/render/event"
"git.kirsle.net/apps/doodle/lib/ui"
"git.kirsle.net/apps/doodle/pkg/balance"
"git.kirsle.net/apps/doodle/pkg/branding"
@ -113,7 +113,7 @@ func (s *MainScene) SetupDemoLevel(d *Doodle) error {
}
// Loop the editor scene.
func (s *MainScene) Loop(d *Doodle, ev *events.State) error {
func (s *MainScene) Loop(d *Doodle, ev *event.State) error {
s.Supervisor.Loop(ev)
if err := s.scripting.Loop(); err != nil {
@ -122,7 +122,7 @@ func (s *MainScene) Loop(d *Doodle, ev *events.State) error {
s.canvas.Loop(ev)
if resized := ev.Resized.Read(); resized {
if ev.WindowResized {
w, h := d.Engine.WindowSize()
d.width = w
d.height = h

View File

@ -3,8 +3,8 @@ package doodle
import (
"fmt"
"git.kirsle.net/apps/doodle/lib/events"
"git.kirsle.net/apps/doodle/lib/render"
"git.kirsle.net/apps/doodle/lib/render/event"
"git.kirsle.net/apps/doodle/lib/ui"
"git.kirsle.net/apps/doodle/pkg/balance"
"git.kirsle.net/apps/doodle/pkg/enum"
@ -493,7 +493,7 @@ func (s *MenuScene) setupLoadWindow(d *Doodle) error {
}
// Loop the editor scene.
func (s *MenuScene) Loop(d *Doodle, ev *events.State) error {
func (s *MenuScene) Loop(d *Doodle, ev *event.State) error {
s.Supervisor.Loop(ev)
return nil
}

View File

@ -3,8 +3,8 @@ package doodle
import (
"fmt"
"git.kirsle.net/apps/doodle/lib/events"
"git.kirsle.net/apps/doodle/lib/render"
"git.kirsle.net/apps/doodle/lib/render/event"
"git.kirsle.net/apps/doodle/lib/ui"
"git.kirsle.net/apps/doodle/pkg/balance"
"git.kirsle.net/apps/doodle/pkg/collision"
@ -303,9 +303,9 @@ func (s *PlayScene) DieByFire() {
}
// Loop the editor scene.
func (s *PlayScene) Loop(d *Doodle, ev *events.State) error {
func (s *PlayScene) Loop(d *Doodle, ev *event.State) error {
// Update debug overlay values.
*s.debWorldIndex = s.drawing.WorldIndexAt(render.NewPoint(ev.CursorX.Now, ev.CursorY.Now)).String()
*s.debWorldIndex = s.drawing.WorldIndexAt(render.NewPoint(int32(ev.CursorX), int32(ev.CursorY))).String()
*s.debPosition = s.Player.Position().String() + " vel " + s.Player.Velocity().String()
*s.debViewport = s.drawing.Viewport().String()
*s.debScroll = s.drawing.Scroll.String()
@ -313,7 +313,7 @@ func (s *PlayScene) Loop(d *Doodle, ev *events.State) error {
s.supervisor.Loop(ev)
// Has the window been resized?
if resized := ev.Resized.Now; resized {
if ev.WindowResized {
w, h := d.Engine.WindowSize()
if w != d.width || h != d.height {
d.width = w
@ -324,7 +324,7 @@ func (s *PlayScene) Loop(d *Doodle, ev *events.State) error {
}
// Switching to Edit Mode?
if s.CanEdit && ev.KeyName.Read() == "e" {
if s.CanEdit && ev.KeyDown("e") {
s.EditLevel()
return nil
}
@ -381,19 +381,19 @@ func (s *PlayScene) Draw(d *Doodle) error {
}
// movePlayer updates the player's X,Y coordinate based on key pressed.
func (s *PlayScene) movePlayer(ev *events.State) {
func (s *PlayScene) movePlayer(ev *event.State) {
var playerSpeed = int32(balance.PlayerMaxVelocity)
// var gravity = int32(balance.Gravity)
var velocity render.Point
if ev.Left.Now {
if ev.Left {
velocity.X = -playerSpeed
}
if ev.Right.Now {
if ev.Right {
velocity.X = playerSpeed
}
if ev.Up.Now && (s.Player.Grounded() || s.playerJumpCounter >= 0) {
if ev.Up && (s.Player.Grounded() || s.playerJumpCounter >= 0) {
velocity.Y = -playerSpeed
if s.Player.Grounded() {

View File

@ -1,7 +1,7 @@
package doodle
import (
"git.kirsle.net/apps/doodle/lib/events"
"git.kirsle.net/apps/doodle/lib/render/event"
"git.kirsle.net/apps/doodle/pkg/log"
)
@ -14,7 +14,7 @@ type Scene interface {
Destroy() error
// Loop should update the scene's state but not draw anything.
Loop(*Doodle, *events.State) error
Loop(*Doodle, *event.State) error
// Draw should use the scene's state to figure out what pixels need
// to draw to the screen.

View File

@ -3,7 +3,7 @@ package scripting
import (
"errors"
"git.kirsle.net/apps/doodle/lib/events"
"git.kirsle.net/apps/doodle/lib/render/event"
"github.com/robertkrimen/otto"
)
@ -60,7 +60,7 @@ func (e *Events) OnKeypress(call otto.FunctionCall) otto.Value {
}
// RunKeypress invokes the OnCollide handler function.
func (e *Events) RunKeypress(ev *events.State) error {
func (e *Events) RunKeypress(ev *event.State) error {
return e.run(KeypressEvent, ev)
}

View File

@ -5,8 +5,8 @@ import (
"fmt"
"strings"
"git.kirsle.net/apps/doodle/lib/events"
"git.kirsle.net/apps/doodle/lib/render"
"git.kirsle.net/apps/doodle/lib/render/event"
"git.kirsle.net/apps/doodle/lib/ui"
"git.kirsle.net/apps/doodle/pkg/balance"
"git.kirsle.net/apps/doodle/pkg/log"
@ -206,16 +206,16 @@ func (s *Shell) Parse(input string) Command {
}
// Draw the shell.
func (s *Shell) Draw(d *Doodle, ev *events.State) error {
func (s *Shell) Draw(d *Doodle, ev *event.State) error {
// Compute the line height we can draw.
lineHeight := balance.ShellFontSize + int(balance.ShellPadding)
// If the console is open, draw the console.
if s.Open {
if ev.EscapeKey.Read() {
if ev.Escape {
s.Close()
return nil
} else if ev.EnterKey.Read() || ev.EscapeKey.Read() {
} else if ev.Enter {
s.Execute(s.Text)
// Auto-close the console unless in REPL mode.
@ -223,8 +223,9 @@ func (s *Shell) Draw(d *Doodle, ev *events.State) error {
s.Close()
}
ev.Enter = false
return nil
} else if (ev.Up.Now || ev.Down.Now) && len(s.History) > 0 {
} else if (ev.Up || ev.Down) && len(s.History) > 0 {
// Paging through history.
if !s.historyPaging {
s.historyPaging = true
@ -232,8 +233,9 @@ func (s *Shell) Draw(d *Doodle, ev *events.State) error {
}
// Consume the inputs and make convenient variables.
ev.Down.Read()
isUp := ev.Up.Read()
isUp := ev.Up
ev.Down = false
ev.Up = false
// Scroll through the input history.
if isUp {
@ -263,7 +265,7 @@ func (s *Shell) Draw(d *Doodle, ev *events.State) error {
}
// Read a character from the keyboard.
if key := ev.ReadKey(); key != "" {
for _, key := range ev.KeysDown(true) {
// Backspace?
if key == `\b` {
if len(s.Text) > 0 {
@ -272,6 +274,7 @@ func (s *Shell) Draw(d *Doodle, ev *events.State) error {
} else {
s.Text += key
}
ev.SetKeyDown(key, false)
}
// How tall is the box?

View File

@ -6,11 +6,13 @@ import (
"image/png"
"io/ioutil"
"os"
"runtime"
"git.kirsle.net/apps/doodle/lib/render"
"git.kirsle.net/apps/doodle/lib/ui"
"git.kirsle.net/apps/doodle/pkg/bindata"
"git.kirsle.net/apps/doodle/pkg/log"
"git.kirsle.net/apps/doodle/pkg/wasm"
)
// LoadImage loads a sprite as a ui.Image object. It checks Doodle's embedded
@ -35,6 +37,26 @@ func LoadImage(e render.Engine, filename string) (*ui.Image, error) {
return ui.ImageFromTexture(tex), nil
}
// WASM: try the file over HTTP ajax request.
if runtime.GOOS == "js" {
data, err := wasm.HTTPGet(filename)
if err != nil {
return nil, err
}
img, err := png.Decode(bytes.NewBuffer(data))
if err != nil {
return nil, err
}
tex, err := e.StoreTexture(filename, img)
if err != nil {
return nil, err
}
return ui.ImageFromTexture(tex), nil
}
// Then try the file system.
if _, err := os.Stat(filename); !os.IsNotExist(err) {
log.Debug("sprites.LoadImage: %s from filesystem", filename)

View File

@ -7,8 +7,8 @@ import (
"git.kirsle.net/apps/doodle/lib/render"
"git.kirsle.net/apps/doodle/pkg/doodads"
"git.kirsle.net/apps/doodle/pkg/level"
"github.com/google/uuid"
"github.com/robertkrimen/otto"
uuid "github.com/satori/go.uuid"
)
// Actor is an object that marries together the three things that make a
@ -42,7 +42,7 @@ type Actor struct {
// If the id is blank, a new UUIDv4 is generated.
func NewActor(id string, levelActor *level.Actor, doodad *doodads.Doodad) *Actor {
if id == "" {
id = uuid.Must(uuid.NewV4()).String()
id = uuid.Must(uuid.NewRandom()).String()
}
size := int32(doodad.Layers[0].Chunker.Size)

View File

@ -6,8 +6,8 @@ import (
"runtime"
"strings"
"git.kirsle.net/apps/doodle/lib/events"
"git.kirsle.net/apps/doodle/lib/render"
"git.kirsle.net/apps/doodle/lib/render/event"
"git.kirsle.net/apps/doodle/lib/ui"
"git.kirsle.net/apps/doodle/pkg/balance"
"git.kirsle.net/apps/doodle/pkg/bindata"
@ -192,7 +192,7 @@ func (w *Canvas) setup() {
// 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 {
func (w *Canvas) Loop(ev *event.State) error {
// Process the arrow keys scrolling the level in Edit Mode.
// canvas_scrolling.go
w.loopEditorScroll(ev)
@ -225,7 +225,7 @@ func (w *Canvas) Loop(ev *events.State) error {
// If the canvas is editable, only care if it's over our space.
if w.Editable {
cursor := render.NewPoint(ev.CursorX.Now, ev.CursorY.Now)
cursor := render.NewPoint(int32(ev.CursorX), int32(ev.CursorY))
if cursor.Inside(ui.AbsoluteRect(w)) {
return w.loopEditable(ev)
}

View File

@ -1,8 +1,8 @@
package uix
import (
"git.kirsle.net/apps/doodle/lib/events"
"git.kirsle.net/apps/doodle/lib/render"
"git.kirsle.net/apps/doodle/lib/render/event"
"git.kirsle.net/apps/doodle/lib/ui"
"git.kirsle.net/apps/doodle/pkg/drawtool"
"git.kirsle.net/apps/doodle/pkg/level"
@ -96,14 +96,14 @@ func (w *Canvas) commitStroke(tool drawtool.Tool, addHistory bool) {
}
// loopEditable handles the Loop() part for editable canvases.
func (w *Canvas) loopEditable(ev *events.State) error {
func (w *Canvas) loopEditable(ev *event.State) error {
// 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{
X: ev.CursorX.Now - P.X - w.Scroll.X,
Y: ev.CursorY.Now - P.Y - w.Scroll.Y,
X: int32(ev.CursorX) - P.X - w.Scroll.X,
Y: int32(ev.CursorY) - P.Y - w.Scroll.Y,
}
)
@ -123,7 +123,7 @@ func (w *Canvas) loopEditable(ev *events.State) error {
}
// Clicking? Log all the pixels while doing so.
if ev.Button1.Now {
if ev.Button1 {
// Initialize a new Stroke for this atomic drawing operation?
if w.currentStroke == nil {
w.currentStroke = drawtool.NewStroke(drawtool.Freehand, w.Palette.ActiveSwatch.Color)
@ -175,7 +175,7 @@ func (w *Canvas) loopEditable(ev *events.State) error {
}
// Clicking? Log all the pixels while doing so.
if ev.Button1.Now {
if ev.Button1 {
// Initialize a new Stroke for this atomic drawing operation?
if w.currentStroke == nil {
w.currentStroke = drawtool.NewStroke(drawtool.Line, w.Palette.ActiveSwatch.Color)
@ -196,7 +196,7 @@ func (w *Canvas) loopEditable(ev *events.State) error {
}
// Clicking? Log all the pixels while doing so.
if ev.Button1.Now {
if ev.Button1 {
// Initialize a new Stroke for this atomic drawing operation?
if w.currentStroke == nil {
w.currentStroke = drawtool.NewStroke(drawtool.Rectangle, w.Palette.ActiveSwatch.Color)
@ -215,7 +215,7 @@ func (w *Canvas) loopEditable(ev *events.State) error {
return nil
}
if ev.Button1.Now {
if ev.Button1 {
if w.currentStroke == nil {
w.currentStroke = drawtool.NewStroke(drawtool.Ellipse, w.Palette.ActiveSwatch.Color)
w.currentStroke.Thickness = w.BrushSize
@ -230,7 +230,7 @@ func (w *Canvas) loopEditable(ev *events.State) error {
}
case drawtool.EraserTool:
// Clicking? Log all the pixels while doing so.
if ev.Button1.Now {
if ev.Button1 {
// 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
@ -296,14 +296,14 @@ func (w *Canvas) loopEditable(ev *events.State) error {
// Check for a mouse down event to begin dragging this
// canvas around.
if ev.Button1.Read() {
if ev.Button1 {
// Pop this canvas out for the drag/drop.
if w.OnDragStart != nil {
deleteActors = append(deleteActors, actor.Actor)
w.OnDragStart(actor.Actor)
}
break
} else if ev.Button2.Read() {
} else if ev.Button2 {
// Right click to delete an actor.
deleteActors = append(deleteActors, actor.Actor)
}
@ -347,7 +347,7 @@ func (w *Canvas) loopEditable(ev *events.State) error {
})
// Click handler to start linking this actor.
if ev.Button1.Read() {
if ev.Button1 {
if err := w.LinkAdd(actor); err != nil {
return err
}

View File

@ -4,8 +4,8 @@ import (
"errors"
"fmt"
"git.kirsle.net/apps/doodle/lib/events"
"git.kirsle.net/apps/doodle/lib/render"
"git.kirsle.net/apps/doodle/lib/render/event"
"git.kirsle.net/apps/doodle/pkg/balance"
"git.kirsle.net/apps/doodle/pkg/level"
)
@ -22,21 +22,21 @@ constrained to fit the bounds of the level.
The debug boolean `NoLimitScroll=true` will override the bounded level scroll
restriction and allow scrolling into out-of-bounds areas of the level.
*/
func (w *Canvas) loopEditorScroll(ev *events.State) error {
func (w *Canvas) loopEditorScroll(ev *event.State) error {
if !w.Scrollable {
return errors.New("canvas not scrollable")
}
// Arrow keys to scroll the view.
scrollBy := render.Point{}
if ev.Right.Now {
if ev.Right {
scrollBy.X -= balance.CanvasScrollSpeed
} else if ev.Left.Now {
} else if ev.Left {
scrollBy.X += balance.CanvasScrollSpeed
}
if ev.Down.Now {
if ev.Down {
scrollBy.Y -= balance.CanvasScrollSpeed
} else if ev.Up.Now {
} else if ev.Up {
scrollBy.Y += balance.CanvasScrollSpeed
}
if !scrollBy.IsZero() {
@ -103,7 +103,7 @@ Does nothing if w.FollowActor is an empty string. Set it to the ID of an Actor
to follow. If the actor exists, the Canvas will scroll to keep it on the
screen.
*/
func (w *Canvas) loopFollowActor(ev *events.State) error {
func (w *Canvas) loopFollowActor(ev *event.State) error {
// Are we following an actor?
if w.FollowActor == "" {
return nil