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