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:
parent
857e5ec098
commit
5c803f6a88
7
README.md
Normal file
7
README.md
Normal 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.
|
|
@ -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),
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
48
color.go
48
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
|
||||
|
|
109
event/event.go
Normal file
109
event/event.go
Normal 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": ")",
|
||||
"-": "_",
|
||||
"=": "+",
|
||||
"[": "{",
|
||||
"]": "}",
|
||||
`\`: "|",
|
||||
";": ":",
|
||||
`'`: `"`,
|
||||
",": "<",
|
||||
".": ">",
|
||||
"/": "?",
|
||||
}
|
|
@ -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)
|
||||
|
||||
|
|
105
sdl/events.go
105
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
16
sdl/text.go
16
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) {
|
||||
|
|
Loading…
Reference in New Issue
Block a user