github.com/Seikaijyu/gio@v0.0.1/io/router/pointer_test.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 package router 4 5 import ( 6 "fmt" 7 "image" 8 "reflect" 9 "strings" 10 "testing" 11 12 "github.com/Seikaijyu/gio/f32" 13 "github.com/Seikaijyu/gio/gesture" 14 "github.com/Seikaijyu/gio/io/event" 15 "github.com/Seikaijyu/gio/io/key" 16 "github.com/Seikaijyu/gio/io/pointer" 17 "github.com/Seikaijyu/gio/io/system" 18 "github.com/Seikaijyu/gio/io/transfer" 19 "github.com/Seikaijyu/gio/op" 20 "github.com/Seikaijyu/gio/op/clip" 21 ) 22 23 func TestPointerWakeup(t *testing.T) { 24 handler := new(int) 25 var ops op.Ops 26 addPointerHandler(&ops, handler, image.Rect(0, 0, 100, 100)) 27 28 var r Router 29 // Test that merely adding a handler doesn't trigger redraw. 30 r.Frame(&ops) 31 if _, wake := r.WakeupTime(); wake { 32 t.Errorf("adding pointer.InputOp triggered a redraw") 33 } 34 // However, adding a handler queues a Cancel event. 35 assertEventPointerTypeSequence(t, r.Events(handler), pointer.Cancel) 36 } 37 38 func TestPointerDrag(t *testing.T) { 39 handler := new(int) 40 var ops op.Ops 41 addPointerHandler(&ops, handler, image.Rect(0, 0, 100, 100)) 42 43 var r Router 44 r.Frame(&ops) 45 r.Queue( 46 // Press. 47 pointer.Event{ 48 Kind: pointer.Press, 49 Position: f32.Pt(50, 50), 50 }, 51 // Move outside the area. 52 pointer.Event{ 53 Kind: pointer.Move, 54 Position: f32.Pt(150, 150), 55 }, 56 ) 57 assertEventPointerTypeSequence(t, r.Events(handler), pointer.Cancel, pointer.Enter, pointer.Press, pointer.Leave, pointer.Drag) 58 } 59 60 func TestPointerDragNegative(t *testing.T) { 61 handler := new(int) 62 var ops op.Ops 63 addPointerHandler(&ops, handler, image.Rect(-100, -100, 0, 0)) 64 65 var r Router 66 r.Frame(&ops) 67 r.Queue( 68 // Press. 69 pointer.Event{ 70 Kind: pointer.Press, 71 Position: f32.Pt(-50, -50), 72 }, 73 // Move outside the area. 74 pointer.Event{ 75 Kind: pointer.Move, 76 Position: f32.Pt(-150, -150), 77 }, 78 ) 79 assertEventPointerTypeSequence(t, r.Events(handler), pointer.Cancel, pointer.Enter, pointer.Press, pointer.Leave, pointer.Drag) 80 } 81 82 func TestPointerGrab(t *testing.T) { 83 handler1 := new(int) 84 handler2 := new(int) 85 handler3 := new(int) 86 var ops op.Ops 87 88 types := pointer.Press | pointer.Release 89 90 pointer.InputOp{Tag: handler1, Kinds: types, Grab: true}.Add(&ops) 91 pointer.InputOp{Tag: handler2, Kinds: types}.Add(&ops) 92 pointer.InputOp{Tag: handler3, Kinds: types}.Add(&ops) 93 94 var r Router 95 r.Frame(&ops) 96 r.Queue( 97 pointer.Event{ 98 Kind: pointer.Press, 99 Position: f32.Pt(50, 50), 100 }, 101 ) 102 assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Cancel, pointer.Press) 103 assertEventPointerTypeSequence(t, r.Events(handler2), pointer.Cancel, pointer.Press) 104 assertEventPointerTypeSequence(t, r.Events(handler3), pointer.Cancel, pointer.Press) 105 r.Frame(&ops) 106 r.Queue( 107 pointer.Event{ 108 Kind: pointer.Release, 109 Position: f32.Pt(50, 50), 110 }, 111 ) 112 assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Release) 113 assertEventPointerTypeSequence(t, r.Events(handler2), pointer.Cancel) 114 assertEventPointerTypeSequence(t, r.Events(handler3), pointer.Cancel) 115 } 116 117 func TestPointerGrabSameHandlerTwice(t *testing.T) { 118 handler1 := new(int) 119 handler2 := new(int) 120 var ops op.Ops 121 122 types := pointer.Press | pointer.Release 123 124 pointer.InputOp{Tag: handler1, Kinds: types, Grab: true}.Add(&ops) 125 pointer.InputOp{Tag: handler1, Kinds: types}.Add(&ops) 126 pointer.InputOp{Tag: handler2, Kinds: types}.Add(&ops) 127 128 var r Router 129 r.Frame(&ops) 130 r.Queue( 131 pointer.Event{ 132 Kind: pointer.Press, 133 Position: f32.Pt(50, 50), 134 }, 135 ) 136 assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Cancel, pointer.Press) 137 assertEventPointerTypeSequence(t, r.Events(handler2), pointer.Cancel, pointer.Press) 138 r.Frame(&ops) 139 r.Queue( 140 pointer.Event{ 141 Kind: pointer.Release, 142 Position: f32.Pt(50, 50), 143 }, 144 ) 145 assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Release) 146 assertEventPointerTypeSequence(t, r.Events(handler2), pointer.Cancel) 147 } 148 149 func TestPointerMove(t *testing.T) { 150 handler1 := new(int) 151 handler2 := new(int) 152 var ops op.Ops 153 154 types := pointer.Move | pointer.Enter | pointer.Leave 155 156 // Handler 1 area: (0, 0) - (100, 100) 157 r1 := clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops) 158 pointer.InputOp{Tag: handler1, Kinds: types}.Add(&ops) 159 // Handler 2 area: (50, 50) - (100, 100) (areas intersect). 160 r2 := clip.Rect(image.Rect(50, 50, 200, 200)).Push(&ops) 161 pointer.InputOp{Tag: handler2, Kinds: types}.Add(&ops) 162 r2.Pop() 163 r1.Pop() 164 165 var r Router 166 r.Frame(&ops) 167 r.Queue( 168 // Hit both handlers. 169 pointer.Event{ 170 Kind: pointer.Move, 171 Position: f32.Pt(50, 50), 172 }, 173 // Hit handler 1. 174 pointer.Event{ 175 Kind: pointer.Move, 176 Position: f32.Pt(49, 50), 177 }, 178 // Hit no handlers. 179 pointer.Event{ 180 Kind: pointer.Move, 181 Position: f32.Pt(100, 50), 182 }, 183 pointer.Event{ 184 Kind: pointer.Cancel, 185 }, 186 ) 187 assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Cancel, pointer.Enter, pointer.Move, pointer.Move, pointer.Leave, pointer.Cancel) 188 assertEventPointerTypeSequence(t, r.Events(handler2), pointer.Cancel, pointer.Enter, pointer.Move, pointer.Leave, pointer.Cancel) 189 } 190 191 func TestPointerTypes(t *testing.T) { 192 handler := new(int) 193 var ops op.Ops 194 r1 := clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops) 195 pointer.InputOp{ 196 Tag: handler, 197 Kinds: pointer.Press | pointer.Release, 198 }.Add(&ops) 199 r1.Pop() 200 201 var r Router 202 r.Frame(&ops) 203 r.Queue( 204 pointer.Event{ 205 Kind: pointer.Press, 206 Position: f32.Pt(50, 50), 207 }, 208 pointer.Event{ 209 Kind: pointer.Move, 210 Position: f32.Pt(150, 150), 211 }, 212 pointer.Event{ 213 Kind: pointer.Release, 214 Position: f32.Pt(150, 150), 215 }, 216 ) 217 assertEventPointerTypeSequence(t, r.Events(handler), pointer.Cancel, pointer.Press, pointer.Release) 218 } 219 220 func TestPointerSystemAction(t *testing.T) { 221 t.Run("simple", func(t *testing.T) { 222 var ops op.Ops 223 r1 := clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops) 224 system.ActionInputOp(system.ActionMove).Add(&ops) 225 r1.Pop() 226 227 var r Router 228 r.Frame(&ops) 229 assertActionAt(t, r, f32.Pt(50, 50), system.ActionMove) 230 }) 231 t.Run("covered by another clip", func(t *testing.T) { 232 var ops op.Ops 233 r1 := clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops) 234 system.ActionInputOp(system.ActionMove).Add(&ops) 235 clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops).Pop() 236 r1.Pop() 237 238 var r Router 239 r.Frame(&ops) 240 assertActionAt(t, r, f32.Pt(50, 50), system.ActionMove) 241 }) 242 t.Run("uses topmost action op", func(t *testing.T) { 243 var ops op.Ops 244 r1 := clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops) 245 system.ActionInputOp(system.ActionMove).Add(&ops) 246 r2 := clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops) 247 system.ActionInputOp(system.ActionClose).Add(&ops) 248 r2.Pop() 249 r1.Pop() 250 251 var r Router 252 r.Frame(&ops) 253 assertActionAt(t, r, f32.Pt(50, 50), system.ActionClose) 254 }) 255 } 256 257 func TestPointerPriority(t *testing.T) { 258 handler1 := new(int) 259 handler2 := new(int) 260 handler3 := new(int) 261 var ops op.Ops 262 263 r1 := clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops) 264 pointer.InputOp{ 265 Tag: handler1, 266 Kinds: pointer.Scroll, 267 ScrollBounds: image.Rectangle{Max: image.Point{X: 100}}, 268 }.Add(&ops) 269 270 r2 := clip.Rect(image.Rect(0, 0, 100, 50)).Push(&ops) 271 pointer.InputOp{ 272 Tag: handler2, 273 Kinds: pointer.Scroll, 274 ScrollBounds: image.Rectangle{Max: image.Point{X: 20}}, 275 }.Add(&ops) 276 r2.Pop() 277 r1.Pop() 278 279 r3 := clip.Rect(image.Rect(0, 100, 100, 200)).Push(&ops) 280 pointer.InputOp{ 281 Tag: handler3, 282 Kinds: pointer.Scroll, 283 ScrollBounds: image.Rectangle{Min: image.Point{X: -20, Y: -40}}, 284 }.Add(&ops) 285 r3.Pop() 286 287 var r Router 288 r.Frame(&ops) 289 r.Queue( 290 // Hit handler 1 and 2. 291 pointer.Event{ 292 Kind: pointer.Scroll, 293 Position: f32.Pt(50, 25), 294 Scroll: f32.Pt(50, 0), 295 }, 296 // Hit handler 1. 297 pointer.Event{ 298 Kind: pointer.Scroll, 299 Position: f32.Pt(50, 75), 300 Scroll: f32.Pt(50, 50), 301 }, 302 // Hit handler 3. 303 pointer.Event{ 304 Kind: pointer.Scroll, 305 Position: f32.Pt(50, 150), 306 Scroll: f32.Pt(-30, -30), 307 }, 308 // Hit no handlers. 309 pointer.Event{ 310 Kind: pointer.Scroll, 311 Position: f32.Pt(50, 225), 312 }, 313 ) 314 315 hev1 := r.Events(handler1) 316 hev2 := r.Events(handler2) 317 hev3 := r.Events(handler3) 318 assertEventPointerTypeSequence(t, hev1, pointer.Cancel, pointer.Scroll, pointer.Scroll) 319 assertEventPointerTypeSequence(t, hev2, pointer.Cancel, pointer.Scroll) 320 assertEventPointerTypeSequence(t, hev3, pointer.Cancel, pointer.Scroll) 321 assertEventPriorities(t, hev1, pointer.Shared, pointer.Shared, pointer.Foremost) 322 assertEventPriorities(t, hev2, pointer.Shared, pointer.Foremost) 323 assertEventPriorities(t, hev3, pointer.Shared, pointer.Foremost) 324 assertScrollEvent(t, hev1[1], f32.Pt(30, 0)) 325 assertScrollEvent(t, hev2[1], f32.Pt(20, 0)) 326 assertScrollEvent(t, hev1[2], f32.Pt(50, 0)) 327 assertScrollEvent(t, hev3[1], f32.Pt(-20, -30)) 328 } 329 330 func TestPointerEnterLeave(t *testing.T) { 331 handler1 := new(int) 332 handler2 := new(int) 333 var ops op.Ops 334 335 // Handler 1 area: (0, 0) - (100, 100) 336 addPointerHandler(&ops, handler1, image.Rect(0, 0, 100, 100)) 337 338 // Handler 2 area: (50, 50) - (200, 200) (areas overlap). 339 addPointerHandler(&ops, handler2, image.Rect(50, 50, 200, 200)) 340 341 var r Router 342 r.Frame(&ops) 343 // Hit both handlers. 344 r.Queue( 345 pointer.Event{ 346 Kind: pointer.Move, 347 Position: f32.Pt(50, 50), 348 }, 349 ) 350 // First event for a handler is always a Cancel. 351 // Only handler2 should receive the enter/move events because it is on top 352 // and handler1 is not an ancestor in the hit tree. 353 assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Cancel) 354 assertEventPointerTypeSequence(t, r.Events(handler2), pointer.Cancel, pointer.Enter, pointer.Move) 355 356 // Leave the second area by moving into the first. 357 r.Queue( 358 pointer.Event{ 359 Kind: pointer.Move, 360 Position: f32.Pt(45, 45), 361 }, 362 ) 363 // The cursor leaves handler2 and enters handler1. 364 assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Enter, pointer.Move) 365 assertEventPointerTypeSequence(t, r.Events(handler2), pointer.Leave) 366 367 // Move, but stay within the same hit area. 368 r.Queue( 369 pointer.Event{ 370 Kind: pointer.Move, 371 Position: f32.Pt(40, 40), 372 }, 373 ) 374 assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Move) 375 assertEventPointerTypeSequence(t, r.Events(handler2)) 376 377 // Move outside of both inputs. 378 r.Queue( 379 pointer.Event{ 380 Kind: pointer.Move, 381 Position: f32.Pt(300, 300), 382 }, 383 ) 384 assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Leave) 385 assertEventPointerTypeSequence(t, r.Events(handler2)) 386 387 // Check that a Press event generates Enter Events. 388 r.Queue( 389 pointer.Event{ 390 Kind: pointer.Press, 391 Position: f32.Pt(125, 125), 392 }, 393 ) 394 assertEventPointerTypeSequence(t, r.Events(handler1)) 395 assertEventPointerTypeSequence(t, r.Events(handler2), pointer.Enter, pointer.Press) 396 397 // Check that a drag only affects the participating handlers. 398 r.Queue( 399 // Leave 400 pointer.Event{ 401 Kind: pointer.Move, 402 Position: f32.Pt(25, 25), 403 }, 404 // Enter 405 pointer.Event{ 406 Kind: pointer.Move, 407 Position: f32.Pt(50, 50), 408 }, 409 ) 410 assertEventPointerTypeSequence(t, r.Events(handler1)) 411 assertEventPointerTypeSequence(t, r.Events(handler2), pointer.Leave, pointer.Drag, pointer.Enter, pointer.Drag) 412 413 // Check that a Release event generates Enter/Leave Events. 414 r.Queue( 415 pointer.Event{ 416 Kind: pointer.Release, 417 Position: f32.Pt(25, 418 25), 419 }, 420 ) 421 assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Enter) 422 // The second handler gets the release event because the press started inside it. 423 assertEventPointerTypeSequence(t, r.Events(handler2), pointer.Release, pointer.Leave) 424 425 } 426 427 func TestMultipleAreas(t *testing.T) { 428 handler := new(int) 429 430 var ops op.Ops 431 432 addPointerHandler(&ops, handler, image.Rect(0, 0, 100, 100)) 433 r1 := clip.Rect(image.Rect(50, 50, 200, 200)).Push(&ops) 434 // Second area has no Types set, yet should receive events because 435 // Types for the same handles are or-ed together. 436 pointer.InputOp{Tag: handler}.Add(&ops) 437 r1.Pop() 438 439 var r Router 440 r.Frame(&ops) 441 // Hit first area, then second area, then both. 442 r.Queue( 443 pointer.Event{ 444 Kind: pointer.Move, 445 Position: f32.Pt(25, 25), 446 }, 447 pointer.Event{ 448 Kind: pointer.Move, 449 Position: f32.Pt(150, 150), 450 }, 451 pointer.Event{ 452 Kind: pointer.Move, 453 Position: f32.Pt(50, 50), 454 }, 455 ) 456 assertEventPointerTypeSequence(t, r.Events(handler), pointer.Cancel, pointer.Enter, pointer.Move, pointer.Move, pointer.Move) 457 } 458 459 func TestPointerEnterLeaveNested(t *testing.T) { 460 handler1 := new(int) 461 handler2 := new(int) 462 var ops op.Ops 463 464 types := pointer.Press | pointer.Move | pointer.Release | pointer.Enter | pointer.Leave 465 466 // Handler 1 area: (0, 0) - (100, 100) 467 r1 := clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops) 468 pointer.InputOp{Tag: handler1, Kinds: types}.Add(&ops) 469 470 // Handler 2 area: (25, 25) - (75, 75) (nested within first). 471 r2 := clip.Rect(image.Rect(25, 25, 75, 75)).Push(&ops) 472 pointer.InputOp{Tag: handler2, Kinds: types}.Add(&ops) 473 r2.Pop() 474 r1.Pop() 475 476 var r Router 477 r.Frame(&ops) 478 // Hit both handlers. 479 r.Queue( 480 pointer.Event{ 481 Kind: pointer.Move, 482 Position: f32.Pt(50, 50), 483 }, 484 ) 485 // First event for a handler is always a Cancel. 486 // Both handlers should receive the Enter and Move events because handler2 is a child of handler1. 487 assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Cancel, pointer.Enter, pointer.Move) 488 assertEventPointerTypeSequence(t, r.Events(handler2), pointer.Cancel, pointer.Enter, pointer.Move) 489 490 // Leave the second area by moving into the first. 491 r.Queue( 492 pointer.Event{ 493 Kind: pointer.Move, 494 Position: f32.Pt(20, 20), 495 }, 496 ) 497 assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Move) 498 assertEventPointerTypeSequence(t, r.Events(handler2), pointer.Leave) 499 500 // Move, but stay within the same hit area. 501 r.Queue( 502 pointer.Event{ 503 Kind: pointer.Move, 504 Position: f32.Pt(10, 10), 505 }, 506 ) 507 assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Move) 508 assertEventPointerTypeSequence(t, r.Events(handler2)) 509 510 // Move outside of both inputs. 511 r.Queue( 512 pointer.Event{ 513 Kind: pointer.Move, 514 Position: f32.Pt(200, 200), 515 }, 516 ) 517 assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Leave) 518 assertEventPointerTypeSequence(t, r.Events(handler2)) 519 520 // Check that a Press event generates Enter Events. 521 r.Queue( 522 pointer.Event{ 523 Kind: pointer.Press, 524 Position: f32.Pt(50, 50), 525 }, 526 ) 527 assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Enter, pointer.Press) 528 assertEventPointerTypeSequence(t, r.Events(handler2), pointer.Enter, pointer.Press) 529 530 // Check that a Release event generates Enter/Leave Events. 531 r.Queue( 532 pointer.Event{ 533 Kind: pointer.Release, 534 Position: f32.Pt(20, 20), 535 }, 536 ) 537 assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Release) 538 assertEventPointerTypeSequence(t, r.Events(handler2), pointer.Release, pointer.Leave) 539 } 540 541 func TestPointerActiveInputDisappears(t *testing.T) { 542 handler1 := new(int) 543 var ops op.Ops 544 var r Router 545 546 // Draw handler. 547 ops.Reset() 548 addPointerHandler(&ops, handler1, image.Rect(0, 0, 100, 100)) 549 r.Frame(&ops) 550 r.Queue( 551 pointer.Event{ 552 Kind: pointer.Move, 553 Position: f32.Pt(25, 25), 554 }, 555 ) 556 assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Cancel, pointer.Enter, pointer.Move) 557 558 // Re-render with handler missing. 559 ops.Reset() 560 r.Frame(&ops) 561 r.Queue( 562 pointer.Event{ 563 Kind: pointer.Move, 564 Position: f32.Pt(25, 25), 565 }, 566 ) 567 assertEventPointerTypeSequence(t, r.Events(handler1)) 568 } 569 570 func TestMultitouch(t *testing.T) { 571 var ops op.Ops 572 573 // Add two separate handlers. 574 h1, h2 := new(int), new(int) 575 addPointerHandler(&ops, h1, image.Rect(0, 0, 100, 100)) 576 addPointerHandler(&ops, h2, image.Rect(0, 100, 100, 200)) 577 578 h1pt, h2pt := f32.Pt(0, 0), f32.Pt(0, 100) 579 var p1, p2 pointer.ID = 0, 1 580 581 var r Router 582 r.Frame(&ops) 583 r.Queue( 584 pointer.Event{ 585 Kind: pointer.Press, 586 Position: h1pt, 587 PointerID: p1, 588 }, 589 ) 590 r.Queue( 591 pointer.Event{ 592 Kind: pointer.Press, 593 Position: h2pt, 594 PointerID: p2, 595 }, 596 ) 597 r.Queue( 598 pointer.Event{ 599 Kind: pointer.Release, 600 Position: h2pt, 601 PointerID: p2, 602 }, 603 ) 604 assertEventPointerTypeSequence(t, r.Events(h1), pointer.Cancel, pointer.Enter, pointer.Press) 605 assertEventPointerTypeSequence(t, r.Events(h2), pointer.Cancel, pointer.Enter, pointer.Press, pointer.Release) 606 } 607 608 func TestCursor(t *testing.T) { 609 ops := new(op.Ops) 610 var r Router 611 var h, h2 int 612 var widget2 func() 613 widget := func() { 614 // This is the area where the cursor is changed to CursorPointer. 615 defer clip.Rect(image.Rectangle{Max: image.Pt(100, 100)}).Push(ops).Pop() 616 // The cursor is checked and changed upon cursor movement. 617 pointer.InputOp{Tag: &h}.Add(ops) 618 pointer.CursorPointer.Add(ops) 619 if widget2 != nil { 620 widget2() 621 } 622 } 623 // Register the handlers. 624 widget() 625 // No cursor change as the mouse has not moved yet. 626 if got, want := r.Cursor(), pointer.CursorDefault; got != want { 627 t.Errorf("got %q; want %q", got, want) 628 } 629 630 _at := func(x, y float32) pointer.Event { 631 return pointer.Event{ 632 Kind: pointer.Move, 633 Source: pointer.Mouse, 634 Buttons: pointer.ButtonPrimary, 635 Position: f32.Pt(x, y), 636 } 637 } 638 for _, tc := range []struct { 639 label string 640 event interface{} 641 want pointer.Cursor 642 }{ 643 {label: "move inside", 644 event: _at(50, 50), 645 want: pointer.CursorPointer, 646 }, 647 {label: "move outside", 648 event: _at(200, 200), 649 want: pointer.CursorDefault, 650 }, 651 {label: "move back inside", 652 event: _at(50, 50), 653 want: pointer.CursorPointer, 654 }, 655 {label: "send key events while inside", 656 event: []event.Event{ 657 key.Event{Name: "A", State: key.Press}, 658 key.Event{Name: "A", State: key.Release}, 659 }, 660 want: pointer.CursorPointer, 661 }, 662 {label: "send key events while outside", 663 event: []event.Event{ 664 _at(200, 200), 665 key.Event{Name: "A", State: key.Press}, 666 key.Event{Name: "A", State: key.Release}, 667 }, 668 want: pointer.CursorDefault, 669 }, 670 {label: "add new input on top while inside", 671 event: func() []event.Event { 672 widget2 = func() { 673 pointer.InputOp{Tag: &h2}.Add(ops) 674 pointer.CursorCrosshair.Add(ops) 675 } 676 return []event.Event{ 677 _at(50, 50), 678 key.Event{ 679 Name: "A", 680 State: key.Press, 681 }, 682 } 683 }, 684 want: pointer.CursorCrosshair, 685 }, 686 {label: "remove input on top while inside", 687 event: func() []event.Event { 688 widget2 = nil 689 return []event.Event{ 690 _at(50, 50), 691 key.Event{ 692 Name: "A", 693 State: key.Press, 694 }, 695 } 696 }, 697 want: pointer.CursorPointer, 698 }, 699 } { 700 t.Run(tc.label, func(t *testing.T) { 701 ops.Reset() 702 widget() 703 r.Frame(ops) 704 switch ev := tc.event.(type) { 705 case event.Event: 706 r.Queue(ev) 707 case []event.Event: 708 r.Queue(ev...) 709 case func() event.Event: 710 r.Queue(ev()) 711 case func() []event.Event: 712 r.Queue(ev()...) 713 default: 714 panic(fmt.Sprintf("unknown event %T", ev)) 715 } 716 widget() 717 r.Frame(ops) 718 // The cursor should now have been changed if the mouse moved over the declared area. 719 if got, want := r.Cursor(), tc.want; got != want { 720 t.Errorf("got %q; want %q", got, want) 721 } 722 }) 723 } 724 } 725 726 func TestPassOp(t *testing.T) { 727 var ops op.Ops 728 729 h1, h2, h3, h4 := new(int), new(int), new(int), new(int) 730 area := clip.Rect(image.Rect(0, 0, 100, 100)) 731 root := area.Push(&ops) 732 pointer.InputOp{Tag: h1, Kinds: pointer.Press}.Add(&ops) 733 child1 := area.Push(&ops) 734 pointer.InputOp{Tag: h2, Kinds: pointer.Press}.Add(&ops) 735 child1.Pop() 736 child2 := area.Push(&ops) 737 pass := pointer.PassOp{}.Push(&ops) 738 pointer.InputOp{Tag: h3, Kinds: pointer.Press}.Add(&ops) 739 pointer.InputOp{Tag: h4, Kinds: pointer.Press}.Add(&ops) 740 pass.Pop() 741 child2.Pop() 742 root.Pop() 743 744 var r Router 745 r.Frame(&ops) 746 r.Queue( 747 pointer.Event{ 748 Kind: pointer.Press, 749 }, 750 ) 751 assertEventPointerTypeSequence(t, r.Events(h1), pointer.Cancel, pointer.Press) 752 assertEventPointerTypeSequence(t, r.Events(h2), pointer.Cancel, pointer.Press) 753 assertEventPointerTypeSequence(t, r.Events(h3), pointer.Cancel, pointer.Press) 754 assertEventPointerTypeSequence(t, r.Events(h4), pointer.Cancel, pointer.Press) 755 } 756 757 func TestAreaPassthrough(t *testing.T) { 758 var ops op.Ops 759 760 h := new(int) 761 pointer.InputOp{Tag: h, Kinds: pointer.Press}.Add(&ops) 762 clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops).Pop() 763 var r Router 764 r.Frame(&ops) 765 r.Queue( 766 pointer.Event{ 767 Kind: pointer.Press, 768 }, 769 ) 770 assertEventPointerTypeSequence(t, r.Events(h), pointer.Cancel, pointer.Press) 771 } 772 773 func TestEllipse(t *testing.T) { 774 var ops op.Ops 775 776 h := new(int) 777 cl := clip.Ellipse(image.Rect(0, 0, 100, 100)).Push(&ops) 778 pointer.InputOp{Tag: h, Kinds: pointer.Press}.Add(&ops) 779 cl.Pop() 780 var r Router 781 r.Frame(&ops) 782 r.Queue( 783 // Outside ellipse. 784 pointer.Event{ 785 Position: f32.Pt(10, 10), 786 Kind: pointer.Press, 787 }, 788 pointer.Event{ 789 Kind: pointer.Release, 790 }, 791 // Inside ellipse. 792 pointer.Event{ 793 Position: f32.Pt(50, 50), 794 Kind: pointer.Press, 795 }, 796 ) 797 assertEventPointerTypeSequence(t, r.Events(h), pointer.Cancel, pointer.Press) 798 } 799 800 func TestTransfer(t *testing.T) { 801 srcArea := image.Rect(0, 0, 20, 20) 802 tgtArea := srcArea.Add(image.Pt(40, 0)) 803 setup := func(ops *op.Ops, srcType, tgtType string) (src, tgt event.Tag) { 804 src, tgt = new(int), new(int) 805 806 srcStack := clip.Rect(srcArea).Push(ops) 807 transfer.SourceOp{ 808 Tag: src, 809 Type: srcType, 810 }.Add(ops) 811 srcStack.Pop() 812 813 tgt1Stack := clip.Rect(tgtArea).Push(ops) 814 transfer.TargetOp{ 815 Tag: tgt, 816 Type: tgtType, 817 }.Add(ops) 818 tgt1Stack.Pop() 819 820 return src, tgt 821 } 822 // Cancel is received when the pointer is first seen. 823 cancel := pointer.Event{Kind: pointer.Cancel} 824 825 t.Run("transfer.Offer should panic on nil Data", func(t *testing.T) { 826 defer func() { 827 if recover() == nil { 828 t.Error("expected panic upon invalid data") 829 } 830 }() 831 transfer.OfferOp{}.Add(new(op.Ops)) 832 }) 833 834 t.Run("drop on no target", func(t *testing.T) { 835 ops := new(op.Ops) 836 src, tgt := setup(ops, "file", "file") 837 var r Router 838 r.Frame(ops) 839 // Initiate a drag. 840 r.Queue( 841 pointer.Event{ 842 Position: f32.Pt(10, 10), 843 Kind: pointer.Press, 844 }, 845 pointer.Event{ 846 Position: f32.Pt(10, 10), 847 Kind: pointer.Move, 848 }, 849 ) 850 assertEventSequence(t, r.Events(src), cancel) 851 assertEventSequence(t, r.Events(tgt), cancel, transfer.InitiateEvent{}) 852 853 // Drop. 854 r.Queue( 855 pointer.Event{ 856 Position: f32.Pt(30, 10), 857 Kind: pointer.Move, 858 }, 859 pointer.Event{ 860 Position: f32.Pt(30, 10), 861 Kind: pointer.Release, 862 }, 863 ) 864 assertEventSequence(t, r.Events(src), transfer.CancelEvent{}) 865 assertEventSequence(t, r.Events(tgt), transfer.CancelEvent{}) 866 }) 867 868 t.Run("drag with valid and invalid targets", func(t *testing.T) { 869 ops := new(op.Ops) 870 src, tgt1 := setup(ops, "file", "file") 871 tgt2 := new(int) 872 stack := clip.Rect(tgtArea).Push(ops) 873 transfer.TargetOp{ 874 Tag: tgt2, 875 Type: "nofile", 876 }.Add(ops) 877 stack.Pop() 878 var r Router 879 r.Frame(ops) 880 // Initiate a drag. 881 r.Queue( 882 pointer.Event{ 883 Position: f32.Pt(10, 10), 884 Kind: pointer.Press, 885 }, 886 pointer.Event{ 887 Position: f32.Pt(10, 10), 888 Kind: pointer.Move, 889 }, 890 ) 891 assertEventSequence(t, r.Events(src), cancel) 892 assertEventSequence(t, r.Events(tgt1), cancel, transfer.InitiateEvent{}) 893 assertEventSequence(t, r.Events(tgt2), cancel) 894 }) 895 896 t.Run("drop on invalid target", func(t *testing.T) { 897 ops := new(op.Ops) 898 src, tgt := setup(ops, "file", "nofile") 899 var r Router 900 r.Frame(ops) 901 // Drag. 902 r.Queue( 903 pointer.Event{ 904 Position: f32.Pt(10, 10), 905 Kind: pointer.Press, 906 }, 907 pointer.Event{ 908 Position: f32.Pt(10, 10), 909 Kind: pointer.Move, 910 }, 911 ) 912 assertEventSequence(t, r.Events(src), cancel) 913 assertEventSequence(t, r.Events(tgt), cancel) 914 915 // Drop. 916 r.Queue( 917 pointer.Event{ 918 Position: f32.Pt(40, 10), 919 Kind: pointer.Release, 920 }, 921 ) 922 assertEventSequence(t, r.Events(src), transfer.CancelEvent{}) 923 assertEventSequence(t, r.Events(tgt)) 924 }) 925 926 t.Run("drop on valid target", func(t *testing.T) { 927 ops := new(op.Ops) 928 src, tgt := setup(ops, "file", "file") 929 // Make the target also a source. This should have no effect. 930 stack := clip.Rect(tgtArea).Push(ops) 931 transfer.SourceOp{ 932 Tag: tgt, 933 Type: "file", 934 }.Add(ops) 935 stack.Pop() 936 var r Router 937 r.Frame(ops) 938 // Drag. 939 r.Queue( 940 pointer.Event{ 941 Position: f32.Pt(10, 10), 942 Kind: pointer.Press, 943 }, 944 pointer.Event{ 945 Position: f32.Pt(10, 10), 946 Kind: pointer.Move, 947 }, 948 ) 949 assertEventSequence(t, r.Events(src), cancel) 950 assertEventSequence(t, r.Events(tgt), cancel, transfer.InitiateEvent{}) 951 952 // Drop. 953 r.Queue( 954 pointer.Event{ 955 Position: f32.Pt(40, 10), 956 Kind: pointer.Release, 957 }, 958 ) 959 assertEventSequence(t, r.Events(src), transfer.RequestEvent{Type: "file"}) 960 961 // Offer valid type and data. 962 ofr := &offer{data: "hello"} 963 transfer.OfferOp{ 964 Tag: src, 965 Type: "file", 966 Data: ofr, 967 }.Add(ops) 968 r.Frame(ops) 969 evs := r.Events(tgt) 970 if len(evs) != 1 { 971 t.Fatalf("unexpected number of events: %d, want 1", len(evs)) 972 } 973 dataEvent, ok := evs[0].(transfer.DataEvent) 974 if !ok { 975 t.Fatalf("unexpected event type: %T, want %T", dataEvent, transfer.DataEvent{}) 976 } 977 if got, want := dataEvent.Type, "file"; got != want { 978 t.Fatalf("got %s; want %s", got, want) 979 } 980 if got, want := dataEvent.Open(), ofr; got != want { 981 t.Fatalf("got %v; want %v", got, want) 982 } 983 984 // Drag and drop complete. 985 if ofr.closed { 986 t.Error("offer closed prematurely") 987 } 988 r.Frame(ops) 989 assertEventSequence(t, r.Events(src), transfer.CancelEvent{}) 990 assertEventSequence(t, r.Events(tgt), transfer.CancelEvent{}) 991 }) 992 993 t.Run("drop on valid target, DataEvent not used", func(t *testing.T) { 994 ops := new(op.Ops) 995 src, tgt := setup(ops, "file", "file") 996 // Make the target also a source. This should have no effect. 997 stack := clip.Rect(tgtArea).Push(ops) 998 transfer.SourceOp{ 999 Tag: tgt, 1000 Type: "file", 1001 }.Add(ops) 1002 stack.Pop() 1003 var r Router 1004 r.Frame(ops) 1005 // Drag. 1006 r.Queue( 1007 pointer.Event{ 1008 Position: f32.Pt(10, 10), 1009 Kind: pointer.Press, 1010 }, 1011 pointer.Event{ 1012 Position: f32.Pt(10, 10), 1013 Kind: pointer.Move, 1014 }, 1015 pointer.Event{ 1016 Position: f32.Pt(40, 10), 1017 Kind: pointer.Release, 1018 }, 1019 ) 1020 ofr := &offer{data: "hello"} 1021 transfer.OfferOp{ 1022 Tag: src, 1023 Type: "file", 1024 Data: ofr, 1025 }.Add(ops) 1026 r.Frame(ops) 1027 // DataEvent should be used here. The next frame should close it as unused. 1028 r.Frame(ops) 1029 assertEventSequence(t, r.Events(src), transfer.CancelEvent{}) 1030 assertEventSequence(t, r.Events(tgt), transfer.CancelEvent{}) 1031 if !ofr.closed { 1032 t.Error("offer was not closed") 1033 } 1034 }) 1035 1036 t.Run("valid target enter/leave events", func(t *testing.T) { 1037 ops := new(op.Ops) 1038 src, _ := setup(ops, "file", "file") 1039 var hover gesture.Hover 1040 pass := pointer.PassOp{}.Push(ops) 1041 stack := clip.Rect(tgtArea).Push(ops) 1042 hover.Add(ops) 1043 stack.Pop() 1044 pass.Pop() 1045 1046 var r Router 1047 r.Frame(ops) 1048 // Drag. 1049 r.Queue( 1050 pointer.Event{ 1051 Position: f32.Pt(10, 10), 1052 Kind: pointer.Press, 1053 }, 1054 pointer.Event{ 1055 Position: f32.Pt(10, 10), 1056 Kind: pointer.Move, 1057 }, 1058 pointer.Event{ 1059 Position: f32.Pt(40, 10), 1060 Kind: pointer.Move, 1061 }, 1062 ) 1063 assertEventPointerTypeSequence(t, r.Events(&hover), pointer.Cancel, pointer.Enter) 1064 1065 // Drop. 1066 r.Queue( 1067 pointer.Event{ 1068 Position: f32.Pt(40, 10), 1069 Kind: pointer.Release, 1070 }, 1071 ) 1072 1073 // Offer valid type and data. 1074 ofr := &offer{data: "hello"} 1075 transfer.OfferOp{ 1076 Tag: src, 1077 Type: "file", 1078 Data: ofr, 1079 }.Add(ops) 1080 r.Frame(ops) 1081 assertEventPointerTypeSequence(t, r.Events(&hover), pointer.Leave) 1082 }) 1083 1084 t.Run("invalid target NO enter/leave events", func(t *testing.T) { 1085 ops := new(op.Ops) 1086 src, _ := setup(ops, "file", "nofile") 1087 var hover gesture.Hover 1088 pass := pointer.PassOp{}.Push(ops) 1089 stack := clip.Rect(tgtArea).Push(ops) 1090 hover.Add(ops) 1091 stack.Pop() 1092 pass.Pop() 1093 1094 var r Router 1095 r.Frame(ops) 1096 // Drag. 1097 r.Queue( 1098 pointer.Event{ 1099 Position: f32.Pt(10, 10), 1100 Kind: pointer.Press, 1101 }, 1102 pointer.Event{ 1103 Position: f32.Pt(10, 10), 1104 Kind: pointer.Move, 1105 }, 1106 pointer.Event{ 1107 Position: f32.Pt(40, 10), 1108 Kind: pointer.Move, 1109 }, 1110 ) 1111 assertEventPointerTypeSequence(t, r.Events(&hover), pointer.Cancel) 1112 1113 // Drop. 1114 r.Queue( 1115 pointer.Event{ 1116 Position: f32.Pt(40, 10), 1117 Kind: pointer.Release, 1118 }, 1119 ) 1120 1121 // Offer valid type and data. 1122 ofr := &offer{data: "hello"} 1123 transfer.OfferOp{ 1124 Tag: src, 1125 Type: "file", 1126 Data: ofr, 1127 }.Add(ops) 1128 r.Frame(ops) 1129 assertEventPointerTypeSequence(t, r.Events(&hover), pointer.Leave) 1130 }) 1131 } 1132 1133 func TestDeferredInputOp(t *testing.T) { 1134 var ops op.Ops 1135 1136 var r Router 1137 m := op.Record(&ops) 1138 key.InputOp{Tag: new(int)}.Add(&ops) 1139 call := m.Stop() 1140 1141 op.Defer(&ops, call) 1142 r.Frame(&ops) 1143 } 1144 1145 func TestPassCursor(t *testing.T) { 1146 var ops op.Ops 1147 var r Router 1148 1149 rect := clip.Rect(image.Rect(0, 0, 100, 100)) 1150 background := rect.Push(&ops) 1151 pointer.InputOp{Tag: 1}.Add(&ops) 1152 pointer.CursorDefault.Add(&ops) 1153 background.Pop() 1154 1155 overlayPass := pointer.PassOp{}.Push(&ops) 1156 overlay := rect.Push(&ops) 1157 pointer.InputOp{Tag: 2}.Add(&ops) 1158 want := pointer.CursorPointer 1159 want.Add(&ops) 1160 overlay.Pop() 1161 overlayPass.Pop() 1162 r.Frame(&ops) 1163 r.Queue(pointer.Event{ 1164 Position: f32.Pt(10, 10), 1165 Kind: pointer.Move, 1166 }) 1167 if got := r.Cursor(); want != got { 1168 t.Errorf("got cursor %v, want %v", got, want) 1169 } 1170 } 1171 1172 // offer satisfies io.ReadCloser for use in data transfers. 1173 type offer struct { 1174 data string 1175 closed bool 1176 } 1177 1178 func (offer) Read([]byte) (int, error) { return 0, nil } 1179 func (o *offer) Close() error { 1180 o.closed = true 1181 return nil 1182 } 1183 1184 // addPointerHandler adds a pointer.InputOp for the tag in a 1185 // rectangular area. 1186 func addPointerHandler(ops *op.Ops, tag event.Tag, area image.Rectangle) { 1187 defer clip.Rect(area).Push(ops).Pop() 1188 pointer.InputOp{ 1189 Tag: tag, 1190 Kinds: pointer.Press | pointer.Release | pointer.Move | pointer.Drag | pointer.Enter | pointer.Leave, 1191 }.Add(ops) 1192 } 1193 1194 // pointerTypes converts a sequence of event.Event to their pointer.Types. It assumes 1195 // that all input events are of underlying type pointer.Event, and thus will 1196 // panic if some are not. 1197 func pointerTypes(events []event.Event) []pointer.Kind { 1198 var types []pointer.Kind 1199 for _, e := range events { 1200 if e, ok := e.(pointer.Event); ok { 1201 types = append(types, e.Kind) 1202 } 1203 } 1204 return types 1205 } 1206 1207 // assertEventPointerTypeSequence checks that the provided events match the expected pointer event types 1208 // in the provided order. 1209 func assertEventPointerTypeSequence(t *testing.T, events []event.Event, expected ...pointer.Kind) { 1210 t.Helper() 1211 got := pointerTypes(events) 1212 if !reflect.DeepEqual(got, expected) { 1213 t.Errorf("expected %v events, got %v", expected, got) 1214 } 1215 } 1216 1217 // assertEventSequence checks that the provided events match the expected ones 1218 // in the provided order. 1219 func assertEventSequence(t *testing.T, got []event.Event, expected ...event.Event) { 1220 t.Helper() 1221 if len(expected) == 0 { 1222 if len(got) > 0 { 1223 t.Errorf("unexpected events: %v", eventsToString(got)) 1224 } 1225 return 1226 } 1227 if !reflect.DeepEqual(got, expected) { 1228 t.Errorf("expected %s events, got %s", eventsToString(expected), eventsToString(got)) 1229 } 1230 } 1231 1232 func eventsToString(evs []event.Event) string { 1233 var s []string 1234 for _, ev := range evs { 1235 switch e := ev.(type) { 1236 case pointer.Event: 1237 s = append(s, fmt.Sprintf("%T{%s}", e, e.Kind.String())) 1238 default: 1239 s = append(s, fmt.Sprintf("{%T}", e)) 1240 } 1241 } 1242 return "[" + strings.Join(s, ",") + "]" 1243 } 1244 1245 // assertEventPriorities checks that the pointer.Event priorities of events match prios. 1246 func assertEventPriorities(t *testing.T, events []event.Event, prios ...pointer.Priority) { 1247 t.Helper() 1248 var got []pointer.Priority 1249 for _, e := range events { 1250 if e, ok := e.(pointer.Event); ok { 1251 got = append(got, e.Priority) 1252 } 1253 } 1254 if !reflect.DeepEqual(got, prios) { 1255 t.Errorf("expected priorities %v, got %v", prios, got) 1256 } 1257 } 1258 1259 // assertScrollEvent checks that the event scrolling amount matches the supplied value. 1260 func assertScrollEvent(t *testing.T, ev event.Event, scroll f32.Point) { 1261 t.Helper() 1262 if got, want := ev.(pointer.Event).Scroll, scroll; got != want { 1263 t.Errorf("got %v; want %v", got, want) 1264 } 1265 } 1266 1267 // assertActionAt checks that the router has a system action of the expected type at point. 1268 func assertActionAt(t *testing.T, q Router, point f32.Point, expected system.Action) { 1269 t.Helper() 1270 action, ok := q.ActionAt(point) 1271 if !ok { 1272 t.Errorf("expected action %v at %v, got no action", expected, point) 1273 } else if action != expected { 1274 t.Errorf("expected action %v at %v, got %v", expected, point, action) 1275 } 1276 } 1277 1278 func BenchmarkRouterAdd(b *testing.B) { 1279 // Set this to the number of overlapping handlers that you want to 1280 // evaluate performance for. Typical values for the example applications 1281 // are 1-3, though checking highers values helps evaluate performance for 1282 // more complex applications. 1283 const startingHandlerCount = 3 1284 const maxHandlerCount = 100 1285 for i := startingHandlerCount; i < maxHandlerCount; i *= 3 { 1286 handlerCount := i 1287 b.Run(fmt.Sprintf("%d-handlers", i), func(b *testing.B) { 1288 handlers := make([]event.Tag, handlerCount) 1289 for i := 0; i < handlerCount; i++ { 1290 h := new(int) 1291 *h = i 1292 handlers[i] = h 1293 } 1294 var ops op.Ops 1295 1296 for i := range handlers { 1297 clip.Rect(image.Rectangle{ 1298 Max: image.Point{ 1299 X: 100, 1300 Y: 100, 1301 }, 1302 }). 1303 Push(&ops) 1304 pointer.InputOp{ 1305 Tag: handlers[i], 1306 Kinds: pointer.Move, 1307 }.Add(&ops) 1308 } 1309 var r Router 1310 r.Frame(&ops) 1311 b.ReportAllocs() 1312 b.ResetTimer() 1313 for i := 0; i < b.N; i++ { 1314 r.Queue( 1315 pointer.Event{ 1316 Kind: pointer.Move, 1317 Position: f32.Pt(50, 50), 1318 }, 1319 ) 1320 } 1321 }) 1322 } 1323 }