github.com/Seikaijyu/gio@v0.0.1/io/router/router.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 /* 4 Package router implements Router, a event.Queue implementation 5 that that disambiguates and routes events to handlers declared 6 in operation lists. 7 8 Router is used by app.Window and is otherwise only useful for 9 using Gio with external window implementations. 10 */ 11 package router 12 13 import ( 14 "encoding/binary" 15 "image" 16 "io" 17 "math" 18 "strings" 19 "time" 20 21 "github.com/Seikaijyu/gio/f32" 22 f32internal "github.com/Seikaijyu/gio/internal/f32" 23 "github.com/Seikaijyu/gio/internal/ops" 24 "github.com/Seikaijyu/gio/io/clipboard" 25 "github.com/Seikaijyu/gio/io/event" 26 "github.com/Seikaijyu/gio/io/key" 27 "github.com/Seikaijyu/gio/io/pointer" 28 "github.com/Seikaijyu/gio/io/profile" 29 "github.com/Seikaijyu/gio/io/semantic" 30 "github.com/Seikaijyu/gio/io/system" 31 "github.com/Seikaijyu/gio/io/transfer" 32 "github.com/Seikaijyu/gio/op" 33 ) 34 35 // Router is a Queue implementation that routes events 36 // to handlers declared in operation lists. 37 type Router struct { 38 savedTrans []f32.Affine2D 39 transStack []f32.Affine2D 40 pointer struct { 41 queue pointerQueue 42 collector pointerCollector 43 } 44 key struct { 45 queue keyQueue 46 collector keyCollector 47 } 48 cqueue clipboardQueue 49 50 handlers handlerEvents 51 52 reader ops.Reader 53 54 // InvalidateOp summary. 55 wakeup bool 56 wakeupTime time.Time 57 58 // ProfileOp summary. 59 profHandlers map[event.Tag]struct{} 60 profile profile.Event 61 } 62 63 // SemanticNode represents a node in the tree describing the components 64 // contained in a frame. 65 type SemanticNode struct { 66 ID SemanticID 67 ParentID SemanticID 68 Children []SemanticNode 69 Desc SemanticDesc 70 71 areaIdx int 72 } 73 74 // SemanticDesc provides a semantic description of a UI component. 75 type SemanticDesc struct { 76 Class semantic.ClassOp 77 Description string 78 Label string 79 Selected bool 80 Disabled bool 81 Gestures SemanticGestures 82 Bounds image.Rectangle 83 } 84 85 // SemanticGestures is a bit-set of supported gestures. 86 type SemanticGestures int 87 88 const ( 89 ClickGesture SemanticGestures = 1 << iota 90 ScrollGesture 91 ) 92 93 // SemanticID uniquely identifies a SemanticDescription. 94 // 95 // By convention, the zero value denotes the non-existent ID. 96 type SemanticID uint64 97 98 type handlerEvents struct { 99 handlers map[event.Tag][]event.Event 100 hadEvents bool 101 } 102 103 // Events returns the available events for the handler key. 104 func (q *Router) Events(k event.Tag) []event.Event { 105 events := q.handlers.Events(k) 106 if _, isprof := q.profHandlers[k]; isprof { 107 delete(q.profHandlers, k) 108 events = append(events, q.profile) 109 } 110 return events 111 } 112 113 // Frame replaces the declared handlers from the supplied 114 // operation list. The text input state, wakeup time and whether 115 // there are active profile handlers is also saved. 116 func (q *Router) Frame(frame *op.Ops) { 117 q.handlers.Clear() 118 q.wakeup = false 119 for k := range q.profHandlers { 120 delete(q.profHandlers, k) 121 } 122 var ops *ops.Ops 123 if frame != nil { 124 ops = &frame.Internal 125 } 126 q.reader.Reset(ops) 127 q.collect() 128 129 q.pointer.queue.Frame(&q.handlers) 130 q.key.queue.Frame(&q.handlers, q.key.collector) 131 if q.handlers.HadEvents() { 132 q.wakeup = true 133 q.wakeupTime = time.Time{} 134 } 135 } 136 137 // Queue key events to the topmost handler. 138 func (q *Router) QueueTopmost(events ...key.Event) bool { 139 var topmost event.Tag 140 pq := &q.pointer.queue 141 for _, h := range pq.hitTree { 142 if h.ktag != nil { 143 topmost = h.ktag 144 break 145 } 146 } 147 if topmost == nil { 148 return false 149 } 150 for _, e := range events { 151 q.handlers.Add(topmost, e) 152 } 153 return q.handlers.HadEvents() 154 } 155 156 // Queue events and report whether at least one handler had an event queued. 157 func (q *Router) Queue(events ...event.Event) bool { 158 for _, e := range events { 159 switch e := e.(type) { 160 case profile.Event: 161 q.profile = e 162 case pointer.Event: 163 q.pointer.queue.Push(e, &q.handlers) 164 case key.Event: 165 q.queueKeyEvent(e) 166 case key.SnippetEvent: 167 // Expand existing, overlapping snippet. 168 if r := q.key.queue.content.Snippet.Range; rangeOverlaps(r, key.Range(e)) { 169 if e.Start > r.Start { 170 e.Start = r.Start 171 } 172 if e.End < r.End { 173 e.End = r.End 174 } 175 } 176 if f := q.key.queue.focus; f != nil { 177 q.handlers.Add(f, e) 178 } 179 case key.EditEvent, key.FocusEvent, key.SelectionEvent: 180 if f := q.key.queue.focus; f != nil { 181 q.handlers.Add(f, e) 182 } 183 case clipboard.Event: 184 q.cqueue.Push(e, &q.handlers) 185 } 186 } 187 return q.handlers.HadEvents() 188 } 189 190 func rangeOverlaps(r1, r2 key.Range) bool { 191 r1 = rangeNorm(r1) 192 r2 = rangeNorm(r2) 193 return r1.Start <= r2.Start && r2.Start < r1.End || 194 r1.Start <= r2.End && r2.End < r1.End 195 } 196 197 func rangeNorm(r key.Range) key.Range { 198 if r.End < r.Start { 199 r.End, r.Start = r.Start, r.End 200 } 201 return r 202 } 203 204 func (q *Router) queueKeyEvent(e key.Event) { 205 kq := &q.key.queue 206 f := q.key.queue.focus 207 if f != nil && kq.Accepts(f, e) { 208 q.handlers.Add(f, e) 209 return 210 } 211 pq := &q.pointer.queue 212 idx := len(pq.hitTree) - 1 213 focused := f != nil 214 if focused { 215 // If there is a focused tag, traverse its ancestry through the 216 // hit tree to search for handlers. 217 for ; pq.hitTree[idx].ktag != f; idx-- { 218 } 219 } 220 for idx != -1 { 221 n := &pq.hitTree[idx] 222 if focused { 223 idx = n.next 224 } else { 225 idx-- 226 } 227 if n.ktag == nil { 228 continue 229 } 230 if kq.Accepts(n.ktag, e) { 231 q.handlers.Add(n.ktag, e) 232 break 233 } 234 } 235 } 236 237 func (q *Router) MoveFocus(dir FocusDirection) bool { 238 return q.key.queue.MoveFocus(dir, &q.handlers) 239 } 240 241 // RevealFocus scrolls the current focus (if any) into viewport 242 // if there are scrollable parent handlers. 243 func (q *Router) RevealFocus(viewport image.Rectangle) { 244 focus := q.key.queue.focus 245 if focus == nil { 246 return 247 } 248 bounds := q.key.queue.BoundsFor(focus) 249 area := q.key.queue.AreaFor(focus) 250 viewport = q.pointer.queue.ClipFor(area, viewport) 251 252 topleft := bounds.Min.Sub(viewport.Min) 253 topleft = max(topleft, bounds.Max.Sub(viewport.Max)) 254 topleft = min(image.Pt(0, 0), topleft) 255 bottomright := bounds.Max.Sub(viewport.Max) 256 bottomright = min(bottomright, bounds.Min.Sub(viewport.Min)) 257 bottomright = max(image.Pt(0, 0), bottomright) 258 s := topleft 259 if s.X == 0 { 260 s.X = bottomright.X 261 } 262 if s.Y == 0 { 263 s.Y = bottomright.Y 264 } 265 q.ScrollFocus(s) 266 } 267 268 // ScrollFocus scrolls the focused widget, if any, by dist. 269 func (q *Router) ScrollFocus(dist image.Point) { 270 focus := q.key.queue.focus 271 if focus == nil { 272 return 273 } 274 area := q.key.queue.AreaFor(focus) 275 q.pointer.queue.Deliver(area, pointer.Event{ 276 Kind: pointer.Scroll, 277 Source: pointer.Touch, 278 Scroll: f32internal.FPt(dist), 279 }, &q.handlers) 280 } 281 282 func max(p1, p2 image.Point) image.Point { 283 m := p1 284 if p2.X > m.X { 285 m.X = p2.X 286 } 287 if p2.Y > m.Y { 288 m.Y = p2.Y 289 } 290 return m 291 } 292 293 func min(p1, p2 image.Point) image.Point { 294 m := p1 295 if p2.X < m.X { 296 m.X = p2.X 297 } 298 if p2.Y < m.Y { 299 m.Y = p2.Y 300 } 301 return m 302 } 303 304 func (q *Router) ActionAt(p f32.Point) (system.Action, bool) { 305 return q.pointer.queue.ActionAt(p) 306 } 307 308 func (q *Router) ClickFocus() { 309 focus := q.key.queue.focus 310 if focus == nil { 311 return 312 } 313 bounds := q.key.queue.BoundsFor(focus) 314 center := bounds.Max.Add(bounds.Min).Div(2) 315 e := pointer.Event{ 316 Position: f32.Pt(float32(center.X), float32(center.Y)), 317 Source: pointer.Touch, 318 } 319 area := q.key.queue.AreaFor(focus) 320 e.Kind = pointer.Press 321 q.pointer.queue.Deliver(area, e, &q.handlers) 322 e.Kind = pointer.Release 323 q.pointer.queue.Deliver(area, e, &q.handlers) 324 } 325 326 // TextInputState returns the input state from the most recent 327 // call to Frame. 328 func (q *Router) TextInputState() TextInputState { 329 return q.key.queue.InputState() 330 } 331 332 // TextInputHint returns the input mode from the most recent key.InputOp. 333 func (q *Router) TextInputHint() (key.InputHint, bool) { 334 return q.key.queue.InputHint() 335 } 336 337 // WriteClipboard returns the most recent text to be copied 338 // to the clipboard, if any. 339 func (q *Router) WriteClipboard() (string, bool) { 340 return q.cqueue.WriteClipboard() 341 } 342 343 // ReadClipboard reports if any new handler is waiting 344 // to read the clipboard. 345 func (q *Router) ReadClipboard() bool { 346 return q.cqueue.ReadClipboard() 347 } 348 349 // Cursor returns the last cursor set. 350 func (q *Router) Cursor() pointer.Cursor { 351 return q.pointer.queue.cursor 352 } 353 354 // SemanticAt returns the first semantic description under pos, if any. 355 func (q *Router) SemanticAt(pos f32.Point) (SemanticID, bool) { 356 return q.pointer.queue.SemanticAt(pos) 357 } 358 359 // AppendSemantics appends the semantic tree to nodes, and returns the result. 360 // The root node is the first added. 361 func (q *Router) AppendSemantics(nodes []SemanticNode) []SemanticNode { 362 q.pointer.collector.q = &q.pointer.queue 363 q.pointer.collector.ensureRoot() 364 return q.pointer.queue.AppendSemantics(nodes) 365 } 366 367 // EditorState returns the editor state for the focused handler, or the 368 // zero value if there is none. 369 func (q *Router) EditorState() EditorState { 370 return q.key.queue.content 371 } 372 373 func (q *Router) collect() { 374 q.transStack = q.transStack[:0] 375 pc := &q.pointer.collector 376 pc.q = &q.pointer.queue 377 pc.reset() 378 kc := &q.key.collector 379 *kc = keyCollector{q: &q.key.queue} 380 q.key.queue.Reset() 381 var t f32.Affine2D 382 bo := binary.LittleEndian 383 for encOp, ok := q.reader.Decode(); ok; encOp, ok = q.reader.Decode() { 384 switch ops.OpType(encOp.Data[0]) { 385 case ops.TypeInvalidate: 386 op := decodeInvalidateOp(encOp.Data) 387 if !q.wakeup || op.At.Before(q.wakeupTime) { 388 q.wakeup = true 389 q.wakeupTime = op.At 390 } 391 case ops.TypeProfile: 392 op := decodeProfileOp(encOp.Data, encOp.Refs) 393 if q.profHandlers == nil { 394 q.profHandlers = make(map[event.Tag]struct{}) 395 } 396 q.profHandlers[op.Tag] = struct{}{} 397 case ops.TypeClipboardRead: 398 q.cqueue.ProcessReadClipboard(encOp.Refs) 399 case ops.TypeClipboardWrite: 400 q.cqueue.ProcessWriteClipboard(encOp.Refs) 401 case ops.TypeSave: 402 id := ops.DecodeSave(encOp.Data) 403 if extra := id - len(q.savedTrans) + 1; extra > 0 { 404 q.savedTrans = append(q.savedTrans, make([]f32.Affine2D, extra)...) 405 } 406 q.savedTrans[id] = t 407 case ops.TypeLoad: 408 id := ops.DecodeLoad(encOp.Data) 409 t = q.savedTrans[id] 410 pc.resetState() 411 pc.setTrans(t) 412 413 case ops.TypeClip: 414 var op ops.ClipOp 415 op.Decode(encOp.Data) 416 pc.clip(op) 417 case ops.TypePopClip: 418 pc.popArea() 419 case ops.TypeTransform: 420 t2, push := ops.DecodeTransform(encOp.Data) 421 if push { 422 q.transStack = append(q.transStack, t) 423 } 424 t = t.Mul(t2) 425 pc.setTrans(t) 426 case ops.TypePopTransform: 427 n := len(q.transStack) 428 t = q.transStack[n-1] 429 q.transStack = q.transStack[:n-1] 430 pc.setTrans(t) 431 432 // Pointer ops. 433 case ops.TypePass: 434 pc.pass() 435 case ops.TypePopPass: 436 pc.popPass() 437 case ops.TypePointerInput: 438 op := pointer.InputOp{ 439 Tag: encOp.Refs[0].(event.Tag), 440 Grab: encOp.Data[1] != 0, 441 Kinds: pointer.Kind(bo.Uint16(encOp.Data[2:])), 442 ScrollBounds: image.Rectangle{ 443 Min: image.Point{ 444 X: int(int32(bo.Uint32(encOp.Data[4:]))), 445 Y: int(int32(bo.Uint32(encOp.Data[8:]))), 446 }, 447 Max: image.Point{ 448 X: int(int32(bo.Uint32(encOp.Data[12:]))), 449 Y: int(int32(bo.Uint32(encOp.Data[16:]))), 450 }, 451 }, 452 } 453 pc.inputOp(op, &q.handlers) 454 case ops.TypeCursor: 455 name := pointer.Cursor(encOp.Data[1]) 456 pc.cursor(name) 457 case ops.TypeSource: 458 op := transfer.SourceOp{ 459 Tag: encOp.Refs[0].(event.Tag), 460 Type: encOp.Refs[1].(string), 461 } 462 pc.sourceOp(op, &q.handlers) 463 case ops.TypeTarget: 464 op := transfer.TargetOp{ 465 Tag: encOp.Refs[0].(event.Tag), 466 Type: encOp.Refs[1].(string), 467 } 468 pc.targetOp(op, &q.handlers) 469 case ops.TypeOffer: 470 op := transfer.OfferOp{ 471 Tag: encOp.Refs[0].(event.Tag), 472 Type: encOp.Refs[1].(string), 473 Data: encOp.Refs[2].(io.ReadCloser), 474 } 475 pc.offerOp(op, &q.handlers) 476 case ops.TypeActionInput: 477 act := system.Action(encOp.Data[1]) 478 pc.actionInputOp(act) 479 480 // Key ops. 481 case ops.TypeKeyFocus: 482 tag, _ := encOp.Refs[0].(event.Tag) 483 op := key.FocusOp{ 484 Tag: tag, 485 } 486 kc.focusOp(op.Tag) 487 case ops.TypeKeySoftKeyboard: 488 op := key.SoftKeyboardOp{ 489 Show: encOp.Data[1] != 0, 490 } 491 kc.softKeyboard(op.Show) 492 case ops.TypeKeyInput: 493 filter := key.Set(*encOp.Refs[1].(*string)) 494 op := key.InputOp{ 495 Tag: encOp.Refs[0].(event.Tag), 496 Hint: key.InputHint(encOp.Data[1]), 497 Keys: filter, 498 } 499 a := pc.currentArea() 500 b := pc.currentAreaBounds() 501 pc.keyInputOp(op) 502 kc.inputOp(op, a, b) 503 case ops.TypeSnippet: 504 op := key.SnippetOp{ 505 Tag: encOp.Refs[0].(event.Tag), 506 Snippet: key.Snippet{ 507 Range: key.Range{ 508 Start: int(int32(bo.Uint32(encOp.Data[1:]))), 509 End: int(int32(bo.Uint32(encOp.Data[5:]))), 510 }, 511 Text: *(encOp.Refs[1].(*string)), 512 }, 513 } 514 kc.snippetOp(op) 515 case ops.TypeSelection: 516 op := key.SelectionOp{ 517 Tag: encOp.Refs[0].(event.Tag), 518 Range: key.Range{ 519 Start: int(int32(bo.Uint32(encOp.Data[1:]))), 520 End: int(int32(bo.Uint32(encOp.Data[5:]))), 521 }, 522 Caret: key.Caret{ 523 Pos: f32.Point{ 524 X: math.Float32frombits(bo.Uint32(encOp.Data[9:])), 525 Y: math.Float32frombits(bo.Uint32(encOp.Data[13:])), 526 }, 527 Ascent: math.Float32frombits(bo.Uint32(encOp.Data[17:])), 528 Descent: math.Float32frombits(bo.Uint32(encOp.Data[21:])), 529 }, 530 } 531 kc.selectionOp(t, op) 532 533 // Semantic ops. 534 case ops.TypeSemanticLabel: 535 lbl := *encOp.Refs[0].(*string) 536 pc.semanticLabel(lbl) 537 case ops.TypeSemanticDesc: 538 desc := *encOp.Refs[0].(*string) 539 pc.semanticDesc(desc) 540 case ops.TypeSemanticClass: 541 class := semantic.ClassOp(encOp.Data[1]) 542 pc.semanticClass(class) 543 case ops.TypeSemanticSelected: 544 if encOp.Data[1] != 0 { 545 pc.semanticSelected(true) 546 } else { 547 pc.semanticSelected(false) 548 } 549 case ops.TypeSemanticEnabled: 550 if encOp.Data[1] != 0 { 551 pc.semanticEnabled(true) 552 } else { 553 pc.semanticEnabled(false) 554 } 555 } 556 } 557 } 558 559 // Profiling reports whether there was profile handlers in the 560 // most recent Frame call. 561 func (q *Router) Profiling() bool { 562 return len(q.profHandlers) > 0 563 } 564 565 // WakeupTime returns the most recent time for doing another frame, 566 // as determined from the last call to Frame. 567 func (q *Router) WakeupTime() (time.Time, bool) { 568 return q.wakeupTime, q.wakeup 569 } 570 571 func (h *handlerEvents) init() { 572 if h.handlers == nil { 573 h.handlers = make(map[event.Tag][]event.Event) 574 } 575 } 576 577 func (h *handlerEvents) AddNoRedraw(k event.Tag, e event.Event) { 578 h.init() 579 h.handlers[k] = append(h.handlers[k], e) 580 } 581 582 func (h *handlerEvents) Add(k event.Tag, e event.Event) { 583 h.AddNoRedraw(k, e) 584 h.hadEvents = true 585 } 586 587 func (h *handlerEvents) HadEvents() bool { 588 u := h.hadEvents 589 h.hadEvents = false 590 return u 591 } 592 593 func (h *handlerEvents) Events(k event.Tag) []event.Event { 594 if events, ok := h.handlers[k]; ok { 595 h.handlers[k] = h.handlers[k][:0] 596 return events 597 } 598 return nil 599 } 600 601 func (h *handlerEvents) Clear() { 602 for k := range h.handlers { 603 delete(h.handlers, k) 604 } 605 } 606 607 func decodeProfileOp(d []byte, refs []interface{}) profile.Op { 608 if ops.OpType(d[0]) != ops.TypeProfile { 609 panic("invalid op") 610 } 611 return profile.Op{ 612 Tag: refs[0].(event.Tag), 613 } 614 } 615 616 func decodeInvalidateOp(d []byte) op.InvalidateOp { 617 bo := binary.LittleEndian 618 if ops.OpType(d[0]) != ops.TypeInvalidate { 619 panic("invalid op") 620 } 621 var o op.InvalidateOp 622 if nanos := bo.Uint64(d[1:]); nanos > 0 { 623 o.At = time.Unix(0, int64(nanos)) 624 } 625 return o 626 } 627 628 func (s SemanticGestures) String() string { 629 var gestures []string 630 if s&ClickGesture != 0 { 631 gestures = append(gestures, "Click") 632 } 633 return strings.Join(gestures, ",") 634 }