gioui.org@v0.6.1-0.20240506124620-7a9ce51988ce/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 "gioui.org/f32" 13 "gioui.org/io/event" 14 "gioui.org/io/key" 15 "gioui.org/io/pointer" 16 "gioui.org/io/system" 17 "gioui.org/io/transfer" 18 "gioui.org/op" 19 "gioui.org/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 ScrollX: pointer.ScrollRange{Max: 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 ScrollX: pointer.ScrollRange{Max: 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 ScrollX: pointer.ScrollRange{Min: -20}, 330 ScrollY: pointer.ScrollRange{Min: -40}, 331 } 332 } 333 events(&r, -1, f3(handler3)) 334 event.Op(&ops, handler3) 335 r3.Pop() 336 337 r.Frame(&ops) 338 r.Queue( 339 // Hit handler 1 and 2. 340 pointer.Event{ 341 Kind: pointer.Scroll, 342 Position: f32.Pt(50, 25), 343 Scroll: f32.Pt(50, 0), 344 }, 345 // Hit handler 1. 346 pointer.Event{ 347 Kind: pointer.Scroll, 348 Position: f32.Pt(50, 75), 349 Scroll: f32.Pt(50, 50), 350 }, 351 // Hit handler 3. 352 pointer.Event{ 353 Kind: pointer.Scroll, 354 Position: f32.Pt(50, 150), 355 Scroll: f32.Pt(-30, -30), 356 }, 357 // Hit no handlers. 358 pointer.Event{ 359 Kind: pointer.Scroll, 360 Position: f32.Pt(50, 225), 361 }, 362 ) 363 364 hev1 := events(&r, -1, f1(handler1)) 365 hev2 := events(&r, -1, f2(handler2)) 366 hev3 := events(&r, -1, f3(handler3)) 367 assertEventPointerTypeSequence(t, hev1, pointer.Scroll, pointer.Scroll) 368 assertEventPointerTypeSequence(t, hev2, pointer.Scroll) 369 assertEventPointerTypeSequence(t, hev3, pointer.Scroll) 370 assertEventPriorities(t, hev1, pointer.Shared, pointer.Foremost) 371 assertEventPriorities(t, hev2, pointer.Foremost) 372 assertEventPriorities(t, hev3, pointer.Foremost) 373 assertScrollEvent(t, hev1[0], f32.Pt(30, 0)) 374 assertScrollEvent(t, hev2[0], f32.Pt(20, 0)) 375 assertScrollEvent(t, hev1[1], f32.Pt(50, 0)) 376 assertScrollEvent(t, hev3[0], f32.Pt(-20, -30)) 377 } 378 379 func TestPointerEnterLeave(t *testing.T) { 380 handler1 := new(int) 381 handler2 := new(int) 382 var ops op.Ops 383 var r Router 384 385 // Handler 1 area: (0, 0) - (100, 100) 386 f1 := addPointerHandler(&r, &ops, handler1, image.Rect(0, 0, 100, 100)) 387 388 // Handler 2 area: (50, 50) - (200, 200) (areas overlap). 389 f2 := addPointerHandler(&r, &ops, handler2, image.Rect(50, 50, 200, 200)) 390 391 r.Frame(&ops) 392 // Hit both handlers. 393 r.Queue( 394 pointer.Event{ 395 Kind: pointer.Move, 396 Position: f32.Pt(50, 50), 397 }, 398 ) 399 // First event for a handler is always a Cancel. 400 // Only handler2 should receive the enter/move events because it is on top 401 // and handler1 is not an ancestor in the hit tree. 402 assertEventPointerTypeSequence(t, events(&r, -1, f1)) 403 assertEventPointerTypeSequence(t, events(&r, -1, f2), pointer.Enter, pointer.Move) 404 405 // Leave the second area by moving into the first. 406 r.Queue( 407 pointer.Event{ 408 Kind: pointer.Move, 409 Position: f32.Pt(45, 45), 410 }, 411 ) 412 // The cursor leaves handler2 and enters handler1. 413 assertEventPointerTypeSequence(t, events(&r, -1, f1), pointer.Enter, pointer.Move) 414 assertEventPointerTypeSequence(t, events(&r, -1, f2), pointer.Leave) 415 416 // Move, but stay within the same hit area. 417 r.Queue( 418 pointer.Event{ 419 Kind: pointer.Move, 420 Position: f32.Pt(40, 40), 421 }, 422 ) 423 assertEventPointerTypeSequence(t, events(&r, -1, f1), pointer.Move) 424 assertEventPointerTypeSequence(t, events(&r, -1, f2)) 425 426 // Move outside of both inputs. 427 r.Queue( 428 pointer.Event{ 429 Kind: pointer.Move, 430 Position: f32.Pt(300, 300), 431 }, 432 ) 433 assertEventPointerTypeSequence(t, events(&r, -1, f1), pointer.Leave) 434 assertEventPointerTypeSequence(t, events(&r, -1, f2)) 435 436 // Check that a Press event generates Enter Events. 437 r.Queue( 438 pointer.Event{ 439 Kind: pointer.Press, 440 Position: f32.Pt(125, 125), 441 }, 442 ) 443 assertEventPointerTypeSequence(t, events(&r, -1, f1)) 444 assertEventPointerTypeSequence(t, events(&r, -1, f2), pointer.Enter, pointer.Press) 445 446 // Check that a drag only affects the participating handlers. 447 r.Queue( 448 // Leave 449 pointer.Event{ 450 Kind: pointer.Move, 451 Position: f32.Pt(25, 25), 452 }, 453 // Enter 454 pointer.Event{ 455 Kind: pointer.Move, 456 Position: f32.Pt(50, 50), 457 }, 458 ) 459 assertEventPointerTypeSequence(t, events(&r, -1, f1)) 460 assertEventPointerTypeSequence(t, events(&r, -1, f2), pointer.Leave, pointer.Drag, pointer.Enter, pointer.Drag) 461 462 // Check that a Release event generates Enter/Leave Events. 463 r.Queue( 464 pointer.Event{ 465 Kind: pointer.Release, 466 Position: f32.Pt(25, 467 25), 468 }, 469 ) 470 assertEventPointerTypeSequence(t, events(&r, -1, f1), pointer.Enter) 471 // The second handler gets the release event because the press started inside it. 472 assertEventPointerTypeSequence(t, events(&r, -1, f2), pointer.Release, pointer.Leave) 473 } 474 475 func TestMultipleAreas(t *testing.T) { 476 handler := new(int) 477 478 var ops op.Ops 479 var r Router 480 481 f := addPointerHandler(&r, &ops, handler, image.Rect(0, 0, 100, 100)) 482 r1 := clip.Rect(image.Rect(50, 50, 200, 200)).Push(&ops) 483 // Test that declaring a handler twice doesn't affect event handling. 484 event.Op(&ops, handler) 485 r1.Pop() 486 487 assertEventPointerTypeSequence(t, events(&r, -1, f)) 488 r.Frame(&ops) 489 // Hit first area, then second area, then both. 490 r.Queue( 491 pointer.Event{ 492 Kind: pointer.Move, 493 Position: f32.Pt(25, 25), 494 }, 495 pointer.Event{ 496 Kind: pointer.Move, 497 Position: f32.Pt(150, 150), 498 }, 499 pointer.Event{ 500 Kind: pointer.Move, 501 Position: f32.Pt(50, 50), 502 }, 503 ) 504 assertEventPointerTypeSequence(t, events(&r, -1, f), pointer.Enter, pointer.Move, pointer.Move, pointer.Move) 505 } 506 507 func TestPointerEnterLeaveNested(t *testing.T) { 508 handler1 := new(int) 509 handler2 := new(int) 510 var ops op.Ops 511 512 filter := func(t event.Tag) event.Filter { 513 return pointer.Filter{ 514 Target: t, 515 Kinds: pointer.Press | pointer.Move | pointer.Release | pointer.Enter | pointer.Leave | pointer.Cancel, 516 } 517 } 518 519 // Handler 1 area: (0, 0) - (100, 100) 520 r1 := clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops) 521 event.Op(&ops, handler1) 522 523 // Handler 2 area: (25, 25) - (75, 75) (nested within first). 524 r2 := clip.Rect(image.Rect(25, 25, 75, 75)).Push(&ops) 525 event.Op(&ops, handler2) 526 r2.Pop() 527 r1.Pop() 528 529 var r Router 530 assertEventPointerTypeSequence(t, events(&r, -1, filter(handler1)), pointer.Cancel) 531 assertEventPointerTypeSequence(t, events(&r, -1, filter(handler2)), pointer.Cancel) 532 r.Frame(&ops) 533 // Hit both handlers. 534 r.Queue( 535 pointer.Event{ 536 Kind: pointer.Move, 537 Position: f32.Pt(50, 50), 538 }, 539 ) 540 // First event for a handler is always a Cancel. 541 // Both handlers should receive the Enter and Move events because handler2 is a child of handler1. 542 assertEventPointerTypeSequence(t, events(&r, -1, filter(handler1)), pointer.Enter, pointer.Move) 543 assertEventPointerTypeSequence(t, events(&r, -1, filter(handler2)), pointer.Enter, pointer.Move) 544 545 // Leave the second area by moving into the first. 546 r.Queue( 547 pointer.Event{ 548 Kind: pointer.Move, 549 Position: f32.Pt(20, 20), 550 }, 551 ) 552 assertEventPointerTypeSequence(t, events(&r, -1, filter(handler1)), pointer.Move) 553 assertEventPointerTypeSequence(t, events(&r, -1, filter(handler2)), pointer.Leave) 554 555 // Move, but stay within the same hit area. 556 r.Queue( 557 pointer.Event{ 558 Kind: pointer.Move, 559 Position: f32.Pt(10, 10), 560 }, 561 ) 562 assertEventPointerTypeSequence(t, events(&r, -1, filter(handler1)), pointer.Move) 563 assertEventPointerTypeSequence(t, events(&r, -1, filter(handler2))) 564 565 // Move outside of both inputs. 566 r.Queue( 567 pointer.Event{ 568 Kind: pointer.Move, 569 Position: f32.Pt(200, 200), 570 }, 571 ) 572 assertEventPointerTypeSequence(t, events(&r, -1, filter(handler1)), pointer.Leave) 573 assertEventPointerTypeSequence(t, events(&r, -1, filter(handler2))) 574 575 // Check that a Press event generates Enter Events. 576 r.Queue( 577 pointer.Event{ 578 Kind: pointer.Press, 579 Position: f32.Pt(50, 50), 580 }, 581 ) 582 assertEventPointerTypeSequence(t, events(&r, -1, filter(handler1)), pointer.Enter, pointer.Press) 583 assertEventPointerTypeSequence(t, events(&r, -1, filter(handler2)), pointer.Enter, pointer.Press) 584 585 // Check that a Release event generates Enter/Leave Events. 586 r.Queue( 587 pointer.Event{ 588 Kind: pointer.Release, 589 Position: f32.Pt(20, 20), 590 }, 591 ) 592 assertEventPointerTypeSequence(t, events(&r, -1, filter(handler1)), pointer.Release) 593 assertEventPointerTypeSequence(t, events(&r, -1, filter(handler2)), pointer.Release, pointer.Leave) 594 } 595 596 func TestPointerActiveInputDisappears(t *testing.T) { 597 handler1 := new(int) 598 var ops op.Ops 599 var r Router 600 601 // Draw handler. 602 ops.Reset() 603 f := addPointerHandler(&r, &ops, handler1, image.Rect(0, 0, 100, 100)) 604 r.Frame(&ops) 605 r.Queue( 606 pointer.Event{ 607 Kind: pointer.Move, 608 Position: f32.Pt(25, 25), 609 }, 610 ) 611 assertEventPointerTypeSequence(t, events(&r, -1, f), pointer.Enter, pointer.Move) 612 r.Frame(&ops) 613 614 // Re-render with handler missing. 615 ops.Reset() 616 r.Frame(&ops) 617 r.Queue( 618 pointer.Event{ 619 Kind: pointer.Move, 620 Position: f32.Pt(25, 25), 621 }, 622 ) 623 assertEventPointerTypeSequence(t, events(&r, -1, f), pointer.Cancel) 624 } 625 626 func TestMultitouch(t *testing.T) { 627 var ops op.Ops 628 var r Router 629 630 // Add two separate handlers. 631 h1, h2 := new(int), new(int) 632 f1 := addPointerHandler(&r, &ops, h1, image.Rect(0, 0, 100, 100)) 633 f2 := addPointerHandler(&r, &ops, h2, image.Rect(0, 100, 100, 200)) 634 635 h1pt, h2pt := f32.Pt(0, 0), f32.Pt(0, 100) 636 var p1, p2 pointer.ID = 0, 1 637 638 r.Frame(&ops) 639 r.Queue( 640 pointer.Event{ 641 Kind: pointer.Press, 642 Position: h1pt, 643 PointerID: p1, 644 }, 645 ) 646 r.Queue( 647 pointer.Event{ 648 Kind: pointer.Press, 649 Position: h2pt, 650 PointerID: p2, 651 }, 652 ) 653 r.Queue( 654 pointer.Event{ 655 Kind: pointer.Release, 656 Position: h2pt, 657 PointerID: p2, 658 }, 659 ) 660 assertEventPointerTypeSequence(t, events(&r, -1, f1), pointer.Enter, pointer.Press) 661 assertEventPointerTypeSequence(t, events(&r, -1, f2), pointer.Enter, pointer.Press, pointer.Release) 662 } 663 664 func TestCursor(t *testing.T) { 665 _at := func(x, y float32) []event.Event { 666 return []event.Event{pointer.Event{ 667 Kind: pointer.Move, 668 Source: pointer.Mouse, 669 Buttons: pointer.ButtonPrimary, 670 Position: f32.Pt(x, y), 671 }} 672 } 673 ops := new(op.Ops) 674 var r Router 675 for _, tc := range []struct { 676 label string 677 events []event.Event 678 cursors []pointer.Cursor 679 want pointer.Cursor 680 }{ 681 {label: "no movement", 682 cursors: []pointer.Cursor{pointer.CursorPointer}, 683 want: pointer.CursorDefault, 684 }, 685 {label: "move inside", 686 cursors: []pointer.Cursor{pointer.CursorPointer}, 687 events: _at(50, 50), 688 want: pointer.CursorPointer, 689 }, 690 {label: "move outside", 691 cursors: []pointer.Cursor{pointer.CursorPointer}, 692 events: _at(200, 200), 693 want: pointer.CursorDefault, 694 }, 695 {label: "move back inside", 696 cursors: []pointer.Cursor{pointer.CursorPointer}, 697 events: _at(50, 50), 698 want: pointer.CursorPointer, 699 }, 700 {label: "send key events while inside", 701 cursors: []pointer.Cursor{pointer.CursorPointer}, 702 events: []event.Event{ 703 key.Event{Name: "A", State: key.Press}, 704 key.Event{Name: "A", State: key.Release}, 705 }, 706 want: pointer.CursorPointer, 707 }, 708 {label: "send key events while outside", 709 cursors: []pointer.Cursor{pointer.CursorPointer}, 710 events: append( 711 _at(200, 200), 712 key.Event{Name: "A", State: key.Press}, 713 key.Event{Name: "A", State: key.Release}, 714 ), 715 want: pointer.CursorDefault, 716 }, 717 {label: "add new input on top while inside", 718 cursors: []pointer.Cursor{pointer.CursorPointer, pointer.CursorCrosshair}, 719 events: append( 720 _at(50, 50), 721 key.Event{ 722 Name: "A", 723 State: key.Press, 724 }, 725 ), 726 want: pointer.CursorCrosshair, 727 }, 728 {label: "remove input on top while inside", 729 cursors: []pointer.Cursor{pointer.CursorPointer}, 730 events: append( 731 _at(50, 50), 732 key.Event{ 733 Name: "A", 734 State: key.Press, 735 }, 736 ), 737 want: pointer.CursorPointer, 738 }, 739 } { 740 t.Run(tc.label, func(t *testing.T) { 741 ops.Reset() 742 defer clip.Rect(image.Rectangle{Max: image.Pt(100, 100)}).Push(ops).Pop() 743 for _, c := range tc.cursors { 744 c.Add(ops) 745 } 746 r.Frame(ops) 747 r.Queue(tc.events...) 748 // The cursor should now have been changed if the mouse moved over the declared area. 749 if got, want := r.Cursor(), tc.want; got != want { 750 t.Errorf("got %q; want %q", got, want) 751 } 752 }) 753 } 754 } 755 756 func TestPassOp(t *testing.T) { 757 var ops op.Ops 758 759 h1, h2, h3, h4 := new(int), new(int), new(int), new(int) 760 area := clip.Rect(image.Rect(0, 0, 100, 100)) 761 root := area.Push(&ops) 762 event.Op(&ops, &h1) 763 event.Op(&ops, h1) 764 child1 := area.Push(&ops) 765 event.Op(&ops, h2) 766 child1.Pop() 767 child2 := area.Push(&ops) 768 pass := pointer.PassOp{}.Push(&ops) 769 event.Op(&ops, h3) 770 event.Op(&ops, h4) 771 pass.Pop() 772 child2.Pop() 773 root.Pop() 774 775 var r Router 776 filter := func(t event.Tag) event.Filter { 777 return pointer.Filter{Target: t, Kinds: pointer.Press | pointer.Cancel} 778 } 779 assertEventPointerTypeSequence(t, events(&r, -1, filter(h1)), pointer.Cancel) 780 assertEventPointerTypeSequence(t, events(&r, -1, filter(h2)), pointer.Cancel) 781 assertEventPointerTypeSequence(t, events(&r, -1, filter(h3)), pointer.Cancel) 782 assertEventPointerTypeSequence(t, events(&r, -1, filter(h4)), pointer.Cancel) 783 r.Frame(&ops) 784 r.Queue( 785 pointer.Event{ 786 Kind: pointer.Press, 787 }, 788 ) 789 assertEventPointerTypeSequence(t, events(&r, -1, filter(h1)), pointer.Press) 790 assertEventPointerTypeSequence(t, events(&r, -1, filter(h2)), pointer.Press) 791 assertEventPointerTypeSequence(t, events(&r, -1, filter(h3)), pointer.Press) 792 assertEventPointerTypeSequence(t, events(&r, -1, filter(h4)), pointer.Press) 793 } 794 795 func TestAreaPassthrough(t *testing.T) { 796 var ops op.Ops 797 798 h := new(int) 799 event.Op(&ops, h) 800 clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops).Pop() 801 var r Router 802 f := pointer.Filter{ 803 Target: h, 804 Kinds: pointer.Press | pointer.Cancel, 805 } 806 assertEventPointerTypeSequence(t, events(&r, -1, f), pointer.Cancel) 807 r.Frame(&ops) 808 r.Queue( 809 pointer.Event{ 810 Kind: pointer.Press, 811 }, 812 ) 813 assertEventPointerTypeSequence(t, events(&r, -1, f), pointer.Press) 814 } 815 816 func TestEllipse(t *testing.T) { 817 var ops op.Ops 818 819 h := new(int) 820 cl := clip.Ellipse(image.Rect(0, 0, 100, 100)).Push(&ops) 821 event.Op(&ops, h) 822 cl.Pop() 823 var r Router 824 f := pointer.Filter{ 825 Target: h, 826 Kinds: pointer.Press | pointer.Cancel, 827 } 828 assertEventPointerTypeSequence(t, events(&r, -1, f), pointer.Cancel) 829 r.Frame(&ops) 830 r.Queue( 831 // Outside ellipse. 832 pointer.Event{ 833 Position: f32.Pt(10, 10), 834 Kind: pointer.Press, 835 }, 836 pointer.Event{ 837 Kind: pointer.Release, 838 }, 839 // Inside ellipse. 840 pointer.Event{ 841 Position: f32.Pt(50, 50), 842 Kind: pointer.Press, 843 }, 844 ) 845 assertEventPointerTypeSequence(t, events(&r, -1, f), pointer.Press) 846 } 847 848 func TestTransfer(t *testing.T) { 849 srcArea := image.Rect(0, 0, 20, 20) 850 tgtArea := srcArea.Add(image.Pt(40, 0)) 851 setup := func(r *Router, ops *op.Ops, srcType, tgtType string) (src, tgt event.Tag) { 852 src, tgt = new(int), new(int) 853 events(r, -1, transfer.SourceFilter{Target: src, Type: srcType}) 854 events(r, -1, transfer.TargetFilter{Target: tgt, Type: tgtType}) 855 856 srcStack := clip.Rect(srcArea).Push(ops) 857 event.Op(ops, src) 858 srcStack.Pop() 859 860 tgt1Stack := clip.Rect(tgtArea).Push(ops) 861 event.Op(ops, tgt) 862 tgt1Stack.Pop() 863 864 return src, tgt 865 } 866 867 t.Run("drop on no target", func(t *testing.T) { 868 ops := new(op.Ops) 869 var r Router 870 src, tgt := setup(&r, ops, "file", "file") 871 r.Frame(ops) 872 // Initiate a drag. 873 r.Queue( 874 pointer.Event{ 875 Position: f32.Pt(10, 10), 876 Kind: pointer.Press, 877 }, 878 pointer.Event{ 879 Position: f32.Pt(10, 10), 880 Kind: pointer.Move, 881 }, 882 ) 883 assertEventSequence(t, events(&r, -1, transfer.SourceFilter{Target: src, Type: "file"})) 884 assertEventSequence(t, events(&r, -1, transfer.TargetFilter{Target: tgt, Type: "file"}), transfer.InitiateEvent{}) 885 886 // Drop. 887 r.Queue( 888 pointer.Event{ 889 Position: f32.Pt(30, 10), 890 Kind: pointer.Move, 891 }, 892 pointer.Event{ 893 Position: f32.Pt(30, 10), 894 Kind: pointer.Release, 895 }, 896 ) 897 assertEventSequence(t, events(&r, -1, transfer.SourceFilter{Target: src, Type: "file"}), transfer.CancelEvent{}) 898 assertEventSequence(t, events(&r, -1, transfer.TargetFilter{Target: tgt, Type: "file"}), transfer.CancelEvent{}) 899 }) 900 901 t.Run("drag with valid and invalid targets", func(t *testing.T) { 902 ops := new(op.Ops) 903 var r Router 904 src, tgt1 := setup(&r, ops, "file", "file") 905 tgt2 := new(int) 906 events(&r, -1, transfer.TargetFilter{Target: tgt2, Type: "nofile"}) 907 stack := clip.Rect(tgtArea).Push(ops) 908 event.Op(ops, tgt2) 909 stack.Pop() 910 r.Frame(ops) 911 // Initiate a drag. 912 r.Queue( 913 pointer.Event{ 914 Position: f32.Pt(10, 10), 915 Kind: pointer.Press, 916 }, 917 pointer.Event{ 918 Position: f32.Pt(10, 10), 919 Kind: pointer.Move, 920 }, 921 ) 922 assertEventSequence(t, events(&r, -1, transfer.SourceFilter{Target: src, Type: "file"})) 923 assertEventSequence(t, events(&r, -1, transfer.TargetFilter{Target: tgt1, Type: "file"}), transfer.InitiateEvent{}) 924 assertEventSequence(t, events(&r, -1, transfer.TargetFilter{Target: tgt2, Type: "nofile"})) 925 }) 926 927 t.Run("drop on invalid target", func(t *testing.T) { 928 ops := new(op.Ops) 929 var r Router 930 src, tgt := setup(&r, ops, "file", "nofile") 931 r.Frame(ops) 932 // Drag. 933 r.Queue( 934 pointer.Event{ 935 Position: f32.Pt(10, 10), 936 Kind: pointer.Press, 937 }, 938 pointer.Event{ 939 Position: f32.Pt(10, 10), 940 Kind: pointer.Move, 941 }, 942 ) 943 assertEventSequence(t, events(&r, -1, transfer.SourceFilter{Target: src, Type: "file"})) 944 assertEventSequence(t, events(&r, -1, transfer.TargetFilter{Target: tgt, Type: "nofile"})) 945 946 // Drop. 947 r.Queue( 948 pointer.Event{ 949 Position: f32.Pt(40, 10), 950 Kind: pointer.Release, 951 }, 952 ) 953 assertEventSequence(t, events(&r, -1, transfer.SourceFilter{Target: src, Type: "file"}), transfer.CancelEvent{}) 954 assertEventSequence(t, events(&r, -1, transfer.TargetFilter{Target: tgt, Type: "nofile"})) 955 }) 956 957 t.Run("drop on valid target", func(t *testing.T) { 958 ops := new(op.Ops) 959 var r Router 960 src, tgt := setup(&r, ops, "file", "file") 961 // Make the target also a source. This should have no effect. 962 events(&r, -1, transfer.SourceFilter{Target: tgt, Type: "file"}) 963 r.Frame(ops) 964 // Drag. 965 r.Queue( 966 pointer.Event{ 967 Position: f32.Pt(10, 10), 968 Kind: pointer.Press, 969 }, 970 pointer.Event{ 971 Position: f32.Pt(10, 10), 972 Kind: pointer.Move, 973 }, 974 ) 975 assertEventSequence(t, events(&r, 1, transfer.TargetFilter{Target: tgt, Type: "file"}), transfer.InitiateEvent{}) 976 977 // Drop. 978 r.Queue( 979 pointer.Event{ 980 Position: f32.Pt(40, 10), 981 Kind: pointer.Release, 982 }, 983 ) 984 assertEventSequence(t, events(&r, 1, transfer.SourceFilter{Target: src, Type: "file"}), transfer.RequestEvent{Type: "file"}) 985 986 // Offer valid type and data. 987 ofr := &offer{data: "hello"} 988 r.Source().Execute(transfer.OfferCmd{Tag: src, Type: "file", Data: ofr}) 989 assertEventSequence(t, events(&r, -1, transfer.SourceFilter{Target: src, Type: "file"}), transfer.CancelEvent{}) 990 evs := events(&r, -1, transfer.TargetFilter{Target: tgt, Type: "file"}) 991 if len(evs) != 2 { 992 t.Fatalf("unexpected number of events: %d, want 2", len(evs)) 993 } 994 assertEventSequence(t, evs[1:], transfer.CancelEvent{}) 995 dataEvent, ok := evs[0].(transfer.DataEvent) 996 if !ok { 997 t.Fatalf("unexpected event type: %T, want %T", dataEvent, transfer.DataEvent{}) 998 } 999 if got, want := dataEvent.Type, "file"; got != want { 1000 t.Fatalf("got %s; want %s", got, want) 1001 } 1002 if got, want := dataEvent.Open(), ofr; got != want { 1003 t.Fatalf("got %v; want %v", got, want) 1004 } 1005 1006 // Drag and drop complete. 1007 if ofr.closed { 1008 t.Error("offer closed prematurely") 1009 } 1010 }) 1011 1012 t.Run("drop on valid target, DataEvent not used", func(t *testing.T) { 1013 ops := new(op.Ops) 1014 var r Router 1015 src, tgt := setup(&r, ops, "file", "file") 1016 // Make the target also a source. This should have no effect. 1017 events(&r, -1, transfer.SourceFilter{Target: tgt, Type: "file"}) 1018 r.Frame(ops) 1019 // Drag. 1020 r.Queue( 1021 pointer.Event{ 1022 Position: f32.Pt(10, 10), 1023 Kind: pointer.Press, 1024 }, 1025 pointer.Event{ 1026 Position: f32.Pt(10, 10), 1027 Kind: pointer.Move, 1028 }, 1029 pointer.Event{ 1030 Position: f32.Pt(40, 10), 1031 Kind: pointer.Release, 1032 }, 1033 ) 1034 ofr := &offer{data: "hello"} 1035 events(&r, -1, transfer.SourceFilter{Target: src, Type: "file"}) 1036 events(&r, -1, transfer.TargetFilter{Target: tgt, Type: "file"}) 1037 r.Frame(ops) 1038 r.Source().Execute(transfer.OfferCmd{Tag: src, Type: "file", Data: ofr}) 1039 assertEventSequence(t, events(&r, -1, transfer.SourceFilter{Target: src, Type: "file"}), transfer.CancelEvent{}) 1040 // Ignore DataEvent and verify that the next frame closes it as unused. 1041 assertEventSequence(t, events(&r, -1, transfer.TargetFilter{Target: tgt, Type: "file"})[1:], transfer.CancelEvent{}) 1042 r.Frame(ops) 1043 if !ofr.closed { 1044 t.Error("offer was not closed") 1045 } 1046 }) 1047 } 1048 1049 func TestDeferredInputOp(t *testing.T) { 1050 var ops op.Ops 1051 1052 var r Router 1053 m := op.Record(&ops) 1054 event.Op(&ops, new(int)) 1055 call := m.Stop() 1056 1057 op.Defer(&ops, call) 1058 r.Frame(&ops) 1059 } 1060 1061 func TestPassCursor(t *testing.T) { 1062 var ops op.Ops 1063 var r Router 1064 1065 rect := clip.Rect(image.Rect(0, 0, 100, 100)) 1066 background := rect.Push(&ops) 1067 event.Op(&ops, 1) 1068 pointer.CursorDefault.Add(&ops) 1069 background.Pop() 1070 1071 overlayPass := pointer.PassOp{}.Push(&ops) 1072 overlay := rect.Push(&ops) 1073 event.Op(&ops, 2) 1074 want := pointer.CursorPointer 1075 want.Add(&ops) 1076 overlay.Pop() 1077 overlayPass.Pop() 1078 r.Frame(&ops) 1079 r.Queue(pointer.Event{ 1080 Position: f32.Pt(10, 10), 1081 Kind: pointer.Move, 1082 }) 1083 r.Frame(&ops) 1084 if got := r.Cursor(); want != got { 1085 t.Errorf("got cursor %v, want %v", got, want) 1086 } 1087 } 1088 1089 // offer satisfies io.ReadCloser for use in data transfers. 1090 type offer struct { 1091 data string 1092 closed bool 1093 } 1094 1095 func (offer) Read([]byte) (int, error) { return 0, nil } 1096 func (o *offer) Close() error { 1097 o.closed = true 1098 return nil 1099 } 1100 1101 // addPointerHandler adds a pointer.InputOp for the tag in a 1102 // rectangular area. 1103 func addPointerHandler(r *Router, ops *op.Ops, tag event.Tag, area image.Rectangle) pointer.Filter { 1104 f := pointer.Filter{ 1105 Target: tag, 1106 Kinds: pointer.Press | pointer.Release | pointer.Move | pointer.Drag | pointer.Enter | pointer.Leave | pointer.Cancel, 1107 } 1108 events(r, -1, f) 1109 defer clip.Rect(area).Push(ops).Pop() 1110 event.Op(ops, tag) 1111 return f 1112 } 1113 1114 // pointerTypes converts a sequence of event.Event to their pointer.Types. It assumes 1115 // that all input events are of underlying type pointer.Event, and thus will 1116 // panic if some are not. 1117 func pointerTypes(events []event.Event) []pointer.Kind { 1118 var types []pointer.Kind 1119 for _, e := range events { 1120 if e, ok := e.(pointer.Event); ok { 1121 types = append(types, e.Kind) 1122 } 1123 } 1124 return types 1125 } 1126 1127 // assertEventPointerTypeSequence checks that the provided events match the expected pointer event types 1128 // in the provided order. 1129 func assertEventPointerTypeSequence(t *testing.T, events []event.Event, expected ...pointer.Kind) { 1130 t.Helper() 1131 got := pointerTypes(events) 1132 if !reflect.DeepEqual(got, expected) { 1133 t.Errorf("expected %v events, got %v", expected, got) 1134 } 1135 } 1136 1137 // assertEventSequence checks that the provided events match the expected ones 1138 // in the provided order. 1139 func assertEventSequence(t *testing.T, got []event.Event, expected ...event.Event) { 1140 t.Helper() 1141 if len(expected) == 0 { 1142 if len(got) > 0 { 1143 t.Errorf("unexpected events: %v", eventsToString(got)) 1144 } 1145 return 1146 } 1147 if !reflect.DeepEqual(got, expected) { 1148 t.Errorf("expected %s events, got %s", eventsToString(expected), eventsToString(got)) 1149 } 1150 } 1151 1152 // assertEventTypeSequence checks that the provided event types match expected. 1153 func assertEventTypeSequence(t *testing.T, got []event.Event, expected ...event.Event) { 1154 t.Helper() 1155 match := len(expected) == len(got) 1156 if match { 1157 for i, ge := range got { 1158 exp := expected[i] 1159 match = match && reflect.TypeOf(ge) == reflect.TypeOf(exp) 1160 } 1161 } 1162 if !match { 1163 t.Errorf("expected event types %s, got %s", eventTypesToString(expected), eventTypesToString(got)) 1164 } 1165 } 1166 1167 func eventTypesToString(evs []event.Event) string { 1168 var s []string 1169 for _, e := range evs { 1170 s = append(s, fmt.Sprintf("%T", e)) 1171 } 1172 return "[" + strings.Join(s, ",") + "]" 1173 } 1174 1175 func eventsToString(evs []event.Event) string { 1176 var s []string 1177 for _, ev := range evs { 1178 switch e := ev.(type) { 1179 case pointer.Event: 1180 s = append(s, fmt.Sprintf("%T{%s}", e, e.Kind.String())) 1181 default: 1182 s = append(s, fmt.Sprintf("{%T}", e)) 1183 } 1184 } 1185 return "[" + strings.Join(s, ",") + "]" 1186 } 1187 1188 // assertEventPriorities checks that the pointer.Event priorities of events match prios. 1189 func assertEventPriorities(t *testing.T, events []event.Event, prios ...pointer.Priority) { 1190 t.Helper() 1191 var got []pointer.Priority 1192 for _, e := range events { 1193 if e, ok := e.(pointer.Event); ok { 1194 got = append(got, e.Priority) 1195 } 1196 } 1197 if !reflect.DeepEqual(got, prios) { 1198 t.Errorf("expected priorities %v, got %v", prios, got) 1199 } 1200 } 1201 1202 // assertScrollEvent checks that the event scrolling amount matches the supplied value. 1203 func assertScrollEvent(t *testing.T, ev event.Event, scroll f32.Point) { 1204 t.Helper() 1205 if got, want := ev.(pointer.Event).Scroll, scroll; got != want { 1206 t.Errorf("got %v; want %v", got, want) 1207 } 1208 } 1209 1210 // assertActionAt checks that the router has a system action of the expected type at point. 1211 func assertActionAt(t *testing.T, q Router, point f32.Point, expected system.Action) { 1212 t.Helper() 1213 action, ok := q.ActionAt(point) 1214 if !ok { 1215 t.Errorf("expected action %v at %v, got no action", expected, point) 1216 } else if action != expected { 1217 t.Errorf("expected action %v at %v, got %v", expected, point, action) 1218 } 1219 } 1220 1221 func BenchmarkRouterAdd(b *testing.B) { 1222 // Set this to the number of overlapping handlers that you want to 1223 // evaluate performance for. Typical values for the example applications 1224 // are 1-3, though checking highers values helps evaluate performance for 1225 // more complex applications. 1226 const startingHandlerCount = 3 1227 const maxHandlerCount = 100 1228 for i := startingHandlerCount; i < maxHandlerCount; i *= 3 { 1229 handlerCount := i 1230 b.Run(fmt.Sprintf("%d-handlers", i), func(b *testing.B) { 1231 handlers := make([]event.Tag, handlerCount) 1232 for i := 0; i < handlerCount; i++ { 1233 h := new(int) 1234 *h = i 1235 handlers[i] = h 1236 } 1237 var ops op.Ops 1238 1239 var r Router 1240 for i := range handlers { 1241 clip.Rect(image.Rectangle{ 1242 Max: image.Point{ 1243 X: 100, 1244 Y: 100, 1245 }, 1246 }). 1247 Push(&ops) 1248 events(&r, -1, pointer.Filter{Target: handlers[i], Kinds: pointer.Move}) 1249 event.Op(&ops, handlers[i]) 1250 } 1251 r.Frame(&ops) 1252 b.ReportAllocs() 1253 b.ResetTimer() 1254 for i := 0; i < b.N; i++ { 1255 r.Queue( 1256 pointer.Event{ 1257 Kind: pointer.Move, 1258 Position: f32.Pt(50, 50), 1259 }, 1260 ) 1261 } 1262 }) 1263 } 1264 } 1265 1266 func events(r *Router, n int, filters ...event.Filter) []event.Event { 1267 var events []event.Event 1268 for { 1269 if n != -1 && len(events) == n { 1270 break 1271 } 1272 e, ok := r.Event(filters...) 1273 if !ok { 1274 break 1275 } 1276 events = append(events, e) 1277 } 1278 return events 1279 }