github.com/cybriq/giocore@v0.0.7-0.20210703034601-cfb9cb5f3900/io/router/pointer.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 package router 4 5 import ( 6 "encoding/binary" 7 "image" 8 9 "github.com/cybriq/giocore/f32" 10 "github.com/cybriq/giocore/internal/opconst" 11 "github.com/cybriq/giocore/internal/ops" 12 "github.com/cybriq/giocore/io/event" 13 "github.com/cybriq/giocore/io/pointer" 14 "github.com/cybriq/giocore/op" 15 ) 16 17 type pointerQueue struct { 18 hitTree []hitNode 19 areas []areaNode 20 cursors []cursorNode 21 cursor pointer.CursorName 22 handlers map[event.Tag]*pointerHandler 23 pointers []pointerInfo 24 reader ops.Reader 25 26 // states holds the storage for save/restore ops. 27 states []collectState 28 scratch []event.Tag 29 } 30 31 type hitNode struct { 32 next int 33 area int 34 // Pass tracks the most recent PassOp mode. 35 pass bool 36 37 // For handler nodes. 38 tag event.Tag 39 } 40 41 type cursorNode struct { 42 name pointer.CursorName 43 area int 44 } 45 46 type pointerInfo struct { 47 id pointer.ID 48 pressed bool 49 handlers []event.Tag 50 // last tracks the last pointer event received, 51 // used while processing frame events. 52 last pointer.Event 53 54 // entered tracks the tags that contain the pointer. 55 entered []event.Tag 56 } 57 58 type pointerHandler struct { 59 area int 60 active bool 61 wantsGrab bool 62 types pointer.Type 63 // min and max horizontal/vertical scroll 64 scrollRange image.Rectangle 65 } 66 67 type areaOp struct { 68 kind areaKind 69 rect f32.Rectangle 70 } 71 72 type areaNode struct { 73 trans f32.Affine2D 74 next int 75 area areaOp 76 } 77 78 type areaKind uint8 79 80 // collectState represents the state for collectHandlers 81 type collectState struct { 82 t f32.Affine2D 83 area int 84 node int 85 pass bool 86 } 87 88 const ( 89 areaRect areaKind = iota 90 areaEllipse 91 ) 92 93 func (q *pointerQueue) save(id int, state collectState) { 94 if extra := id - len(q.states) + 1; extra > 0 { 95 q.states = append(q.states, make([]collectState, extra)...) 96 } 97 q.states[id] = state 98 } 99 100 func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents) { 101 state := collectState{ 102 area: -1, 103 node: -1, 104 } 105 q.save(opconst.InitialStateID, state) 106 for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() { 107 switch opconst.OpType(encOp.Data[0]) { 108 case opconst.TypeSave: 109 id := ops.DecodeSave(encOp.Data) 110 q.save(id, state) 111 case opconst.TypeLoad: 112 id, mask := ops.DecodeLoad(encOp.Data) 113 s := q.states[id] 114 if mask&opconst.TransformState != 0 { 115 state.t = s.t 116 } 117 if mask&^opconst.TransformState != 0 { 118 state = s 119 } 120 case opconst.TypePass: 121 state.pass = encOp.Data[1] != 0 122 case opconst.TypeArea: 123 var op areaOp 124 op.Decode(encOp.Data) 125 q.areas = append(q.areas, areaNode{trans: state.t, next: state.area, area: op}) 126 state.area = len(q.areas) - 1 127 q.hitTree = append(q.hitTree, hitNode{ 128 next: state.node, 129 area: state.area, 130 pass: state.pass, 131 }) 132 state.node = len(q.hitTree) - 1 133 case opconst.TypeTransform: 134 dop := ops.DecodeTransform(encOp.Data) 135 state.t = state.t.Mul(dop) 136 case opconst.TypePointerInput: 137 op := pointer.InputOp{ 138 Tag: encOp.Refs[0].(event.Tag), 139 Grab: encOp.Data[1] != 0, 140 Types: pointer.Type(encOp.Data[2]), 141 } 142 q.hitTree = append(q.hitTree, hitNode{ 143 next: state.node, 144 area: state.area, 145 pass: state.pass, 146 tag: op.Tag, 147 }) 148 state.node = len(q.hitTree) - 1 149 h, ok := q.handlers[op.Tag] 150 if !ok { 151 h = new(pointerHandler) 152 q.handlers[op.Tag] = h 153 // Cancel handlers on (each) first appearance, but don't 154 // trigger redraw. 155 events.AddNoRedraw(op.Tag, pointer.Event{Type: pointer.Cancel}) 156 } 157 h.active = true 158 h.area = state.area 159 h.wantsGrab = h.wantsGrab || op.Grab 160 h.types = h.types | op.Types 161 bo := binary.LittleEndian.Uint32 162 h.scrollRange = image.Rectangle{ 163 Min: image.Point{ 164 X: int(int32(bo(encOp.Data[3:]))), 165 Y: int(int32(bo(encOp.Data[7:]))), 166 }, 167 Max: image.Point{ 168 X: int(int32(bo(encOp.Data[11:]))), 169 Y: int(int32(bo(encOp.Data[15:]))), 170 }, 171 } 172 case opconst.TypeCursor: 173 q.cursors = append(q.cursors, cursorNode{ 174 name: encOp.Refs[0].(pointer.CursorName), 175 area: len(q.areas) - 1, 176 }) 177 } 178 } 179 } 180 181 func (q *pointerQueue) opHit(handlers *[]event.Tag, pos f32.Point) { 182 // Track whether we're passing through hits. 183 pass := true 184 idx := len(q.hitTree) - 1 185 for idx >= 0 { 186 n := &q.hitTree[idx] 187 if !q.hit(n.area, pos) { 188 idx-- 189 continue 190 } 191 pass = pass && n.pass 192 if pass { 193 idx-- 194 } else { 195 idx = n.next 196 } 197 if n.tag != nil { 198 if _, exists := q.handlers[n.tag]; exists { 199 *handlers = append(*handlers, n.tag) 200 } 201 } 202 } 203 } 204 205 func (q *pointerQueue) invTransform(areaIdx int, p f32.Point) f32.Point { 206 if areaIdx == -1 { 207 return p 208 } 209 return q.areas[areaIdx].trans.Invert().Transform(p) 210 } 211 212 func (q *pointerQueue) hit(areaIdx int, p f32.Point) bool { 213 for areaIdx != -1 { 214 a := &q.areas[areaIdx] 215 p := a.trans.Invert().Transform(p) 216 if !a.area.Hit(p) { 217 return false 218 } 219 areaIdx = a.next 220 } 221 return true 222 } 223 224 func (q *pointerQueue) reset() { 225 if q.handlers == nil { 226 q.handlers = make(map[event.Tag]*pointerHandler) 227 } 228 } 229 230 func (q *pointerQueue) Frame(root *op.Ops, events *handlerEvents) { 231 q.reset() 232 for _, h := range q.handlers { 233 // Reset handler. 234 h.active = false 235 h.wantsGrab = false 236 h.types = 0 237 } 238 q.hitTree = q.hitTree[:0] 239 q.areas = q.areas[:0] 240 q.cursors = q.cursors[:0] 241 q.reader.Reset(root) 242 q.collectHandlers(&q.reader, events) 243 for k, h := range q.handlers { 244 if !h.active { 245 q.dropHandlers(events, k) 246 delete(q.handlers, k) 247 } 248 if h.wantsGrab { 249 for _, p := range q.pointers { 250 if !p.pressed { 251 continue 252 } 253 for i, k2 := range p.handlers { 254 if k2 == k { 255 // Drop other handlers that lost their grab. 256 dropped := make([]event.Tag, 0, len(p.handlers)-1) 257 dropped = append(dropped, p.handlers[:i]...) 258 dropped = append(dropped, p.handlers[i+1:]...) 259 cancelHandlers(events, dropped...) 260 q.dropHandlers(events, dropped...) 261 break 262 } 263 } 264 } 265 } 266 } 267 for i := range q.pointers { 268 p := &q.pointers[i] 269 q.deliverEnterLeaveEvents(p, events, p.last) 270 } 271 } 272 273 func cancelHandlers(events *handlerEvents, tags ...event.Tag) { 274 for _, k := range tags { 275 events.Add(k, pointer.Event{Type: pointer.Cancel}) 276 } 277 } 278 279 func (q *pointerQueue) dropHandlers(events *handlerEvents, tags ...event.Tag) { 280 for _, k := range tags { 281 for i := range q.pointers { 282 p := &q.pointers[i] 283 for i := len(p.handlers) - 1; i >= 0; i-- { 284 if p.handlers[i] == k { 285 p.handlers = append(p.handlers[:i], p.handlers[i+1:]...) 286 } 287 } 288 for i := len(p.entered) - 1; i >= 0; i-- { 289 if p.entered[i] == k { 290 p.entered = append(p.entered[:i], p.entered[i+1:]...) 291 } 292 } 293 } 294 } 295 } 296 297 func (q *pointerQueue) Push(e pointer.Event, events *handlerEvents) { 298 q.reset() 299 if e.Type == pointer.Cancel { 300 q.pointers = q.pointers[:0] 301 for k := range q.handlers { 302 cancelHandlers(events, k) 303 q.dropHandlers(events, k) 304 } 305 return 306 } 307 pidx := -1 308 for i, p := range q.pointers { 309 if p.id == e.PointerID { 310 pidx = i 311 break 312 } 313 } 314 if pidx == -1 { 315 q.pointers = append(q.pointers, pointerInfo{id: e.PointerID}) 316 pidx = len(q.pointers) - 1 317 } 318 p := &q.pointers[pidx] 319 p.last = e 320 321 if e.Type == pointer.Move && p.pressed { 322 e.Type = pointer.Drag 323 } 324 325 if e.Type == pointer.Release { 326 q.deliverEvent(p, events, e) 327 p.pressed = false 328 } 329 q.deliverEnterLeaveEvents(p, events, e) 330 331 if !p.pressed { 332 p.handlers = append(p.handlers[:0], q.scratch...) 333 } 334 if e.Type == pointer.Press { 335 p.pressed = true 336 } 337 switch e.Type { 338 case pointer.Release: 339 case pointer.Scroll: 340 q.deliverScrollEvent(p, events, e) 341 default: 342 q.deliverEvent(p, events, e) 343 } 344 if !p.pressed && len(p.entered) == 0 { 345 // No longer need to track pointer. 346 q.pointers = append(q.pointers[:pidx], q.pointers[pidx+1:]...) 347 } 348 } 349 350 func (q *pointerQueue) deliverEvent(p *pointerInfo, events *handlerEvents, e pointer.Event) { 351 foremost := true 352 if p.pressed && len(p.handlers) == 1 { 353 e.Priority = pointer.Grabbed 354 foremost = false 355 } 356 for _, k := range p.handlers { 357 h := q.handlers[k] 358 if e.Type&h.types == 0 { 359 continue 360 } 361 e := e 362 if foremost { 363 foremost = false 364 e.Priority = pointer.Foremost 365 } 366 e.Position = q.invTransform(h.area, e.Position) 367 events.Add(k, e) 368 } 369 } 370 371 func (q *pointerQueue) deliverScrollEvent(p *pointerInfo, events *handlerEvents, e pointer.Event) { 372 foremost := true 373 if p.pressed && len(p.handlers) == 1 { 374 e.Priority = pointer.Grabbed 375 foremost = false 376 } 377 var sx, sy = e.Scroll.X, e.Scroll.Y 378 for _, k := range p.handlers { 379 if sx == 0 && sy == 0 { 380 return 381 } 382 h := q.handlers[k] 383 // Distribute the scroll to the handler based on its ScrollRange. 384 sx, e.Scroll.X = setScrollEvent(sx, h.scrollRange.Min.X, h.scrollRange.Max.X) 385 sy, e.Scroll.Y = setScrollEvent(sy, h.scrollRange.Min.Y, h.scrollRange.Max.Y) 386 e := e 387 if foremost { 388 foremost = false 389 e.Priority = pointer.Foremost 390 } 391 e.Position = q.invTransform(h.area, e.Position) 392 events.Add(k, e) 393 } 394 } 395 396 func (q *pointerQueue) deliverEnterLeaveEvents(p *pointerInfo, events *handlerEvents, e pointer.Event) { 397 q.scratch = q.scratch[:0] 398 q.opHit(&q.scratch, e.Position) 399 if p.pressed { 400 // Filter out non-participating handlers. 401 for i := len(q.scratch) - 1; i >= 0; i-- { 402 if _, found := searchTag(p.handlers, q.scratch[i]); !found { 403 q.scratch = append(q.scratch[:i], q.scratch[i+1:]...) 404 } 405 } 406 } 407 hits := q.scratch 408 if e.Source != pointer.Mouse && !p.pressed && e.Type != pointer.Press { 409 // Consider non-mouse pointers leaving when they're released. 410 hits = nil 411 } 412 // Deliver Leave events. 413 for _, k := range p.entered { 414 if _, found := searchTag(hits, k); found { 415 continue 416 } 417 h := q.handlers[k] 418 e.Type = pointer.Leave 419 420 if e.Type&h.types != 0 { 421 e.Position = q.invTransform(h.area, e.Position) 422 events.Add(k, e) 423 } 424 } 425 // Deliver Enter events and update cursor. 426 q.cursor = pointer.CursorDefault 427 for _, k := range hits { 428 h := q.handlers[k] 429 for i := len(q.cursors) - 1; i >= 0; i-- { 430 if c := q.cursors[i]; c.area == h.area { 431 q.cursor = c.name 432 break 433 } 434 } 435 if _, found := searchTag(p.entered, k); found { 436 continue 437 } 438 e.Type = pointer.Enter 439 440 if e.Type&h.types != 0 { 441 e.Position = q.invTransform(h.area, e.Position) 442 events.Add(k, e) 443 } 444 } 445 p.entered = append(p.entered[:0], hits...) 446 } 447 448 func searchTag(tags []event.Tag, tag event.Tag) (int, bool) { 449 for i, t := range tags { 450 if t == tag { 451 return i, true 452 } 453 } 454 return 0, false 455 } 456 457 func opDecodeFloat32(d []byte) float32 { 458 return float32(int32(binary.LittleEndian.Uint32(d))) 459 } 460 461 func (op *areaOp) Decode(d []byte) { 462 if opconst.OpType(d[0]) != opconst.TypeArea { 463 panic("invalid op") 464 } 465 rect := f32.Rectangle{ 466 Min: f32.Point{ 467 X: opDecodeFloat32(d[2:]), 468 Y: opDecodeFloat32(d[6:]), 469 }, 470 Max: f32.Point{ 471 X: opDecodeFloat32(d[10:]), 472 Y: opDecodeFloat32(d[14:]), 473 }, 474 } 475 *op = areaOp{ 476 kind: areaKind(d[1]), 477 rect: rect, 478 } 479 } 480 481 func (op *areaOp) Hit(pos f32.Point) bool { 482 pos = pos.Sub(op.rect.Min) 483 size := op.rect.Size() 484 switch op.kind { 485 case areaRect: 486 return 0 <= pos.X && pos.X < size.X && 487 0 <= pos.Y && pos.Y < size.Y 488 case areaEllipse: 489 rx := size.X / 2 490 ry := size.Y / 2 491 xh := pos.X - rx 492 yk := pos.Y - ry 493 // The ellipse function works in all cases because 494 // 0/0 is not <= 1. 495 return (xh*xh)/(rx*rx)+(yk*yk)/(ry*ry) <= 1 496 default: 497 panic("invalid area kind") 498 } 499 } 500 501 func setScrollEvent(scroll float32, min, max int) (left, scrolled float32) { 502 if v := float32(max); scroll > v { 503 return scroll - v, v 504 } 505 if v := float32(min); scroll < v { 506 return scroll - v, v 507 } 508 return 0, scroll 509 }