From 7355778a39d1967b54460f9126c1783584db9af6 Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Sun, 22 Dec 2019 14:11:01 -0800 Subject: [PATCH] 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. --- lib/render/README.md | 7 +++ lib/render/canvas/engine.go | 6 +- lib/render/canvas/events.go | 53 +++++++----------- lib/render/color.go | 48 ---------------- lib/render/event/event.go | 109 ++++++++++++++++++++++++++++++++++++ lib/render/interface.go | 4 +- lib/render/sdl/events.go | 105 +++++++++++++++++----------------- lib/render/sdl/sdl.go | 6 +- lib/render/sdl/text.go | 16 ------ lib/ui/main_window.go | 2 +- lib/ui/supervisor.go | 12 ++-- pkg/doodads/drawing.go | 4 +- pkg/doodle.go | 22 ++++---- pkg/editor_scene.go | 22 ++++---- pkg/editor_ui.go | 10 ++-- pkg/fps.go | 2 +- pkg/guitest_scene.go | 4 +- pkg/level/actors.go | 4 +- pkg/level/chunk.go | 4 +- pkg/main_scene.go | 6 +- pkg/menu_scene.go | 4 +- pkg/play_scene.go | 18 +++--- pkg/scene.go | 4 +- pkg/scripting/events.go | 4 +- pkg/shell.go | 19 ++++--- pkg/sprites/sprites.go | 22 ++++++++ pkg/uix/actor.go | 4 +- pkg/uix/canvas.go | 6 +- pkg/uix/canvas_editable.go | 24 ++++---- pkg/uix/canvas_scrolling.go | 14 ++--- 30 files changed, 318 insertions(+), 247 deletions(-) create mode 100644 lib/render/README.md create mode 100644 lib/render/event/event.go diff --git a/lib/render/README.md b/lib/render/README.md new file mode 100644 index 0000000..fde0e4b --- /dev/null +++ b/lib/render/README.md @@ -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. diff --git a/lib/render/canvas/engine.go b/lib/render/canvas/engine.go index 5f7f49e..ab458cf 100644 --- a/lib/render/canvas/engine.go +++ b/lib/render/canvas/engine.go @@ -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), diff --git a/lib/render/canvas/events.go b/lib/render/canvas/events.go index 4cc4905..7b20f91 100644 --- a/lib/render/canvas/events.go +++ b/lib/render/canvas/events.go @@ -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) } } } diff --git a/lib/render/color.go b/lib/render/color.go index e6b045d..70de13b 100644 --- a/lib/render/color.go +++ b/lib/render/color.go @@ -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 diff --git a/lib/render/event/event.go b/lib/render/event/event.go new file mode 100644 index 0000000..1787e4c --- /dev/null +++ b/lib/render/event/event.go @@ -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": ")", + "-": "_", + "=": "+", + "[": "{", + "]": "}", + `\`: "|", + ";": ":", + `'`: `"`, + ",": "<", + ".": ">", + "/": "?", +} diff --git a/lib/render/interface.go b/lib/render/interface.go index c30948a..b97e289 100644 --- a/lib/render/interface.go +++ b/lib/render/interface.go @@ -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) diff --git a/lib/render/sdl/events.go b/lib/render/sdl/events.go index 152c906..a5b8014 100644 --- a/lib/render/sdl/events.go +++ b/lib/render/sdl/events.go @@ -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) } } } diff --git a/lib/render/sdl/sdl.go b/lib/render/sdl/sdl.go index c54767f..35799ef 100644 --- a/lib/render/sdl/sdl.go +++ b/lib/render/sdl/sdl.go @@ -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), diff --git a/lib/render/sdl/text.go b/lib/render/sdl/text.go index efe59fd..1347254 100644 --- a/lib/render/sdl/text.go +++ b/lib/render/sdl/text.go @@ -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) { diff --git a/lib/ui/main_window.go b/lib/ui/main_window.go index b62450f..5cf5487 100644 --- a/lib/ui/main_window.go +++ b/lib/ui/main_window.go @@ -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 diff --git a/lib/ui/supervisor.go b/lib/ui/supervisor.go index ac78a51..b5fda2f 100644 --- a/lib/ui/supervisor.go +++ b/lib/ui/supervisor.go @@ -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 diff --git a/pkg/doodads/drawing.go b/pkg/doodads/drawing.go index 9fcf401..1347162 100644 --- a/pkg/doodads/drawing.go +++ b/pkg/doodads/drawing.go @@ -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, diff --git a/pkg/doodle.go b/pkg/doodle.go index 12cd953..7155f30 100644 --- a/pkg/doodle.go +++ b/pkg/doodle.go @@ -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...") diff --git a/pkg/editor_scene.go b/pkg/editor_scene.go index b5a0f77..5dd0e6d 100644 --- a/pkg/editor_scene.go +++ b/pkg/editor_scene.go @@ -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() diff --git a/pkg/editor_ui.go b/pkg/editor_ui.go index 8ea8232..0f1f108 100644 --- a/pkg/editor_ui.go +++ b/pkg/editor_ui.go @@ -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", diff --git a/pkg/fps.go b/pkg/fps.go index 3179c38..dc63422 100644 --- a/pkg/fps.go +++ b/pkg/fps.go @@ -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), } ) diff --git a/pkg/guitest_scene.go b/pkg/guitest_scene.go index 0df3865..dd780d0 100644 --- a/pkg/guitest_scene.go +++ b/pkg/guitest_scene.go @@ -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 } diff --git a/pkg/level/actors.go b/pkg/level/actors.go index ffa1ee5..ad89bde 100644 --- a/pkg/level/actors.go +++ b/pkg/level/actors.go @@ -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 } diff --git a/pkg/level/chunk.go b/pkg/level/chunk.go index 5dac9ae..cc103f8 100644 --- a/pkg/level/chunk.go +++ b/pkg/level/chunk.go @@ -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() diff --git a/pkg/main_scene.go b/pkg/main_scene.go index 0e25912..eeaf801 100644 --- a/pkg/main_scene.go +++ b/pkg/main_scene.go @@ -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 diff --git a/pkg/menu_scene.go b/pkg/menu_scene.go index 91587f5..3d39d14 100644 --- a/pkg/menu_scene.go +++ b/pkg/menu_scene.go @@ -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 } diff --git a/pkg/play_scene.go b/pkg/play_scene.go index 22033da..002de6a 100644 --- a/pkg/play_scene.go +++ b/pkg/play_scene.go @@ -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() { diff --git a/pkg/scene.go b/pkg/scene.go index 545aa8a..f9b43c4 100644 --- a/pkg/scene.go +++ b/pkg/scene.go @@ -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. diff --git a/pkg/scripting/events.go b/pkg/scripting/events.go index 4172010..d1a03a0 100644 --- a/pkg/scripting/events.go +++ b/pkg/scripting/events.go @@ -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) } diff --git a/pkg/shell.go b/pkg/shell.go index 10461d7..a1cdfd9 100644 --- a/pkg/shell.go +++ b/pkg/shell.go @@ -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? diff --git a/pkg/sprites/sprites.go b/pkg/sprites/sprites.go index 6ddbf88..5750d33 100644 --- a/pkg/sprites/sprites.go +++ b/pkg/sprites/sprites.go @@ -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) diff --git a/pkg/uix/actor.go b/pkg/uix/actor.go index ddb3ab3..e8fd36f 100644 --- a/pkg/uix/actor.go +++ b/pkg/uix/actor.go @@ -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) diff --git a/pkg/uix/canvas.go b/pkg/uix/canvas.go index 8220c9e..51d734a 100644 --- a/pkg/uix/canvas.go +++ b/pkg/uix/canvas.go @@ -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) } diff --git a/pkg/uix/canvas_editable.go b/pkg/uix/canvas_editable.go index ebf78e2..32e6da5 100644 --- a/pkg/uix/canvas_editable.go +++ b/pkg/uix/canvas_editable.go @@ -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 } diff --git a/pkg/uix/canvas_scrolling.go b/pkg/uix/canvas_scrolling.go index c42eaab..e17e03b 100644 --- a/pkg/uix/canvas_scrolling.go +++ b/pkg/uix/canvas_scrolling.go @@ -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