From 09a6c1db5234ea181ffb904d085966553722ca7d Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Wed, 6 Oct 2021 19:35:24 -0700 Subject: [PATCH] Add support for SDL2 MultiGestureEvents --- README.md | 18 ++++++ color.go | 24 ++++++++ event/event.go | 12 +++- interface.go | 146 ------------------------------------------------- rect.go | 102 ++++++++++++++++++++++++++++++++++ sdl/events.go | 14 +++++ text.go | 25 +++++++++ 7 files changed, 194 insertions(+), 147 deletions(-) create mode 100644 rect.go create mode 100644 text.go diff --git a/README.md b/README.md index 6a50c16..d9e21e3 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,24 @@ for pt := range render.IterLine(A, B) { * IterEllipse(A Point, B Point): draw an elipse fitting inside the rectangle bounded by points A and B. +## Multitouch Gesture Notes + +Support for SDL2's MultiGestureEvent is added on October 6 2021. +The event.State will have the property `Touching=true` while the engine +believes multitouch gestures are afoot. This begins when the user touches +the screen with two fingers, and _then_ motion is detected. + +SDL2 spams us with gesture events for each tiny change detected, and then +just stops. The SDL driver in this repo doesn't set ev.State.Touching=false. +One heuristic you may use in your program to detect when multitouch has +ended is this: + +SDL2 always emulates the mouse Button1 click for one of the fingers. +Record the position at the first Touching=true event, and monitor for +delta changes in position as the "mouse cursor" moves. When delta +movements become stale and don't update, you can set State.Touching=false +in your program. + ## License MIT. diff --git a/color.go b/color.go index bc11222..733c454 100644 --- a/color.go +++ b/color.go @@ -19,6 +19,30 @@ var ( reHexColor8 = regexp.MustCompile(`^([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})$`) ) +// Common color names. +var ( + Invisible = Color{} + White = RGBA(255, 255, 255, 255) + Grey = RGBA(153, 153, 153, 255) + DarkGrey = RGBA(64, 64, 64, 255) + Black = RGBA(0, 0, 0, 255) + SkyBlue = RGBA(0, 153, 255, 255) + Blue = RGBA(0, 0, 255, 255) + DarkBlue = RGBA(0, 0, 153, 255) + Red = RGBA(255, 0, 0, 255) + DarkRed = RGBA(153, 0, 0, 255) + Green = RGBA(0, 255, 0, 255) + DarkGreen = RGBA(0, 153, 0, 255) + Cyan = RGBA(0, 255, 255, 255) + DarkCyan = RGBA(0, 153, 153, 255) + Yellow = RGBA(255, 255, 0, 255) + Orange = RGBA(255, 153, 0, 255) + DarkYellow = RGBA(153, 153, 0, 255) + Magenta = RGBA(255, 0, 255, 255) + Purple = RGBA(153, 0, 153, 255) + Pink = RGBA(255, 153, 255, 255) +) + // Color holds an RGBA color value. type Color struct { Red uint8 diff --git a/event/event.go b/event/event.go index 1ed072b..82b2622 100644 --- a/event/event.go +++ b/event/event.go @@ -1,6 +1,8 @@ package event -import "strings" +import ( + "strings" +) // State holds the current state of key/mouse events. type State struct { @@ -30,6 +32,14 @@ type State struct { // Window resized WindowResized bool + + // Touch state + Touching bool + TouchNumFingers int + TouchCenterX int + TouchCenterY int + GestureRotated float64 + GesturePinched float64 } // NewState creates a new event.State. diff --git a/interface.go b/interface.go index e1e7eca..1a62576 100644 --- a/interface.go +++ b/interface.go @@ -1,7 +1,6 @@ package render import ( - "fmt" "image" "git.kirsle.net/go/render/event" @@ -51,148 +50,3 @@ type Texturer interface { Size() Rect Image() image.Image } - -// Rect has a coordinate and a width and height. -type Rect struct { - X int - Y int - W int - H int -} - -// NewRect creates a rectangle of size `width` and `height`. The X,Y values -// are initialized to zero. -func NewRect(width, height int) Rect { - return Rect{ - W: width, - H: height, - } -} - -func (r Rect) String() string { - return fmt.Sprintf("Rect<%d,%d,%d,%d>", - r.X, r.Y, r.W, r.H, - ) -} - -// Point returns the rectangle's X,Y values as a Point. -func (r Rect) Point() Point { - return Point{ - X: r.X, - Y: r.Y, - } -} - -// Bigger returns if the given rect is larger than the current one. -func (r Rect) Bigger(other Rect) bool { - // TODO: don't know why this is ! - return !(other.X < r.X || // Lefter - other.Y < r.Y || // Higher - other.W > r.W || // Wider - other.H > r.H) // Taller -} - -// Intersects with the other rectangle in any way. -func (r Rect) Intersects(other Rect) bool { - // Do a bidirectional compare. - compare := func(a, b Rect) bool { - var corners = []Point{ - NewPoint(b.X, b.Y), - NewPoint(b.X, b.Y+b.H), - NewPoint(b.X+b.W, b.Y), - NewPoint(b.X+b.W, b.Y+b.H), - } - for _, pt := range corners { - if pt.Inside(a) { - return true - } - } - return false - } - - return compare(r, other) || compare(other, r) || false -} - -// IsZero returns if the Rect is uninitialized. -func (r Rect) IsZero() bool { - return r.X == 0 && r.Y == 0 && r.W == 0 && r.H == 0 -} - -// Add another rect. -func (r Rect) Add(other Rect) Rect { - return Rect{ - X: r.X + other.X, - Y: r.Y + other.Y, - W: r.W + other.W, - H: r.H + other.H, - } -} - -// Add a point to move the rect. -func (r Rect) AddPoint(other Point) Rect { - return Rect{ - X: r.X + other.X, - Y: r.Y + other.Y, - W: r.W, - H: r.H, - } -} - -// SubtractPoint is the inverse of AddPoint. Use this only if you need to invert -// the Point being added. -// -// This does r.X - other.X, r.Y - other.Y and keeps the width/height the same. -func (r Rect) SubtractPoint(other Point) Rect { - return Rect{ - X: r.X - other.X, - Y: r.Y - other.Y, - W: r.W, - H: r.H, - } -} - -// Text holds information for drawing text. -type Text struct { - Text string - Size int - Color Color - Padding int - PadX int - PadY int - Stroke Color // Stroke color (if not zero) - Shadow Color // Drop shadow color (if not zero) - FontFilename string // Path to *.ttf file on disk -} - -func (t Text) String() string { - return fmt.Sprintf(`Text<"%s" %dpx %s>`, t.Text, t.Size, t.Color) -} - -// IsZero returns if the Text is the zero value. -func (t Text) IsZero() bool { - return t.Text == "" && t.Size == 0 && t.Color == Invisible && t.Padding == 0 && t.PadX == 0 && t.PadY == 0 && t.Stroke == Invisible && t.Shadow == Invisible -} - -// Common color names. -var ( - Invisible = Color{} - White = RGBA(255, 255, 255, 255) - Grey = RGBA(153, 153, 153, 255) - DarkGrey = RGBA(64, 64, 64, 255) - Black = RGBA(0, 0, 0, 255) - SkyBlue = RGBA(0, 153, 255, 255) - Blue = RGBA(0, 0, 255, 255) - DarkBlue = RGBA(0, 0, 153, 255) - Red = RGBA(255, 0, 0, 255) - DarkRed = RGBA(153, 0, 0, 255) - Green = RGBA(0, 255, 0, 255) - DarkGreen = RGBA(0, 153, 0, 255) - Cyan = RGBA(0, 255, 255, 255) - DarkCyan = RGBA(0, 153, 153, 255) - Yellow = RGBA(255, 255, 0, 255) - Orange = RGBA(255, 153, 0, 255) - DarkYellow = RGBA(153, 153, 0, 255) - Magenta = RGBA(255, 0, 255, 255) - Purple = RGBA(153, 0, 153, 255) - Pink = RGBA(255, 153, 255, 255) -) diff --git a/rect.go b/rect.go new file mode 100644 index 0000000..7a8d49b --- /dev/null +++ b/rect.go @@ -0,0 +1,102 @@ +package render + +import "fmt" + +// Rect has a coordinate and a width and height. +type Rect struct { + X int + Y int + W int + H int +} + +// NewRect creates a rectangle of size `width` and `height`. The X,Y values +// are initialized to zero. +func NewRect(width, height int) Rect { + return Rect{ + W: width, + H: height, + } +} + +func (r Rect) String() string { + return fmt.Sprintf("Rect<%d,%d,%d,%d>", + r.X, r.Y, r.W, r.H, + ) +} + +// Point returns the rectangle's X,Y values as a Point. +func (r Rect) Point() Point { + return Point{ + X: r.X, + Y: r.Y, + } +} + +// Bigger returns if the given rect is larger than the current one. +func (r Rect) Bigger(other Rect) bool { + // TODO: don't know why this is ! + return !(other.X < r.X || // Lefter + other.Y < r.Y || // Higher + other.W > r.W || // Wider + other.H > r.H) // Taller +} + +// Intersects with the other rectangle in any way. +func (r Rect) Intersects(other Rect) bool { + // Do a bidirectional compare. + compare := func(a, b Rect) bool { + var corners = []Point{ + NewPoint(b.X, b.Y), + NewPoint(b.X, b.Y+b.H), + NewPoint(b.X+b.W, b.Y), + NewPoint(b.X+b.W, b.Y+b.H), + } + for _, pt := range corners { + if pt.Inside(a) { + return true + } + } + return false + } + + return compare(r, other) || compare(other, r) || false +} + +// IsZero returns if the Rect is uninitialized. +func (r Rect) IsZero() bool { + return r.X == 0 && r.Y == 0 && r.W == 0 && r.H == 0 +} + +// Add another rect. +func (r Rect) Add(other Rect) Rect { + return Rect{ + X: r.X + other.X, + Y: r.Y + other.Y, + W: r.W + other.W, + H: r.H + other.H, + } +} + +// Add a point to move the rect. +func (r Rect) AddPoint(other Point) Rect { + return Rect{ + X: r.X + other.X, + Y: r.Y + other.Y, + W: r.W, + H: r.H, + } +} + +// SubtractPoint is the inverse of AddPoint. Use this only if you need to invert +// the Point being added. +// +// This does r.X - other.X, r.Y - other.Y and keeps the width/height the same. +func (r Rect) SubtractPoint(other Point) Rect { + return Rect{ + X: r.X - other.X, + Y: r.Y - other.Y, + W: r.W, + H: r.H, + } +} diff --git a/sdl/events.go b/sdl/events.go index c745ff5..e88fa20 100644 --- a/sdl/events.go +++ b/sdl/events.go @@ -11,6 +11,7 @@ import ( // Debug certain SDL events var ( DebugWindowEvents = false + DebugTouchEvents = false DebugMouseEvents = false DebugClickEvents = false DebugKeyEvents = false @@ -22,6 +23,7 @@ func (r *Renderer) Poll() (*event.State, error) { // Reset some events. s.WindowResized = false + // s.Touching = false // helper function to push keyboard key names on keyDown events only. pushKey := func(name string, state uint8) { @@ -105,6 +107,18 @@ func (r *Renderer) Poll() (*event.State, error) { t.Timestamp, r.ticks, t.Type, t.Which, t.X, t.Y, ) } + case *sdl.MultiGestureEvent: + if DebugTouchEvents { + fmt.Printf("[%d ms] tick:%d MultiGesture type:%d Num=%d TouchID=%+v Dt=%f Dd=%f XY=%f,%f\n", + t.Timestamp, r.ticks, t.Type, t.NumFingers, t.TouchID, t.DTheta, t.DDist, t.X, t.Y, + ) + } + s.Touching = true + s.TouchNumFingers = int(t.NumFingers) + s.TouchCenterX = int(t.X) + s.TouchCenterY = int(t.Y) + s.GesturePinched = float64(t.DDist) + s.GestureRotated = float64(t.DTheta) case *sdl.KeyboardEvent: if DebugKeyEvents { fmt.Printf("[%d ms] tick:%d Keyboard type:%d sym:%c modifiers:%d state:%d repeat:%d\n", diff --git a/text.go b/text.go new file mode 100644 index 0000000..4b67810 --- /dev/null +++ b/text.go @@ -0,0 +1,25 @@ +package render + +import "fmt" + +// Text holds information for drawing text. +type Text struct { + Text string + Size int + Color Color + Padding int + PadX int + PadY int + Stroke Color // Stroke color (if not zero) + Shadow Color // Drop shadow color (if not zero) + FontFilename string // Path to *.ttf file on disk +} + +func (t Text) String() string { + return fmt.Sprintf(`Text<"%s" %dpx %s>`, t.Text, t.Size, t.Color) +} + +// IsZero returns if the Text is the zero value. +func (t Text) IsZero() bool { + return t.Text == "" && t.Size == 0 && t.Color == Invisible && t.Padding == 0 && t.PadX == 0 && t.PadY == 0 && t.Stroke == Invisible && t.Shadow == Invisible +}