github.com/cybriq/giocore@v0.0.7-0.20210703034601-cfb9cb5f3900/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  	"testing"
    10  
    11  	"github.com/cybriq/giocore/f32"
    12  	"github.com/cybriq/giocore/io/event"
    13  	"github.com/cybriq/giocore/io/key"
    14  	"github.com/cybriq/giocore/io/pointer"
    15  	"github.com/cybriq/giocore/op"
    16  )
    17  
    18  func TestPointerWakeup(t *testing.T) {
    19  	handler := new(int)
    20  	var ops op.Ops
    21  	addPointerHandler(&ops, handler, image.Rect(0, 0, 100, 100))
    22  
    23  	var r Router
    24  	// Test that merely adding a handler doesn't trigger redraw.
    25  	r.Frame(&ops)
    26  	if _, wake := r.WakeupTime(); wake {
    27  		t.Errorf("adding pointer.InputOp triggered a redraw")
    28  	}
    29  	// However, adding a handler queues a Cancel event.
    30  	assertEventSequence(t, r.Events(handler), pointer.Cancel)
    31  	// Verify that r.Events does trigger a redraw.
    32  	r.Frame(&ops)
    33  	if _, wake := r.WakeupTime(); !wake {
    34  		t.Errorf("pointer.Cancel event didn't trigger a redraw")
    35  	}
    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  			Type:     pointer.Press,
    49  			Position: f32.Pt(50, 50),
    50  		},
    51  		// Move outside the area.
    52  		pointer.Event{
    53  			Type:     pointer.Move,
    54  			Position: f32.Pt(150, 150),
    55  		},
    56  	)
    57  	assertEventSequence(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  			Type:     pointer.Press,
    71  			Position: f32.Pt(-50, -50),
    72  		},
    73  		// Move outside the area.
    74  		pointer.Event{
    75  			Type:     pointer.Move,
    76  			Position: f32.Pt(-150, -150),
    77  		},
    78  	)
    79  	assertEventSequence(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, Types: types, Grab: true}.Add(&ops)
    91  	pointer.InputOp{Tag: handler2, Types: types}.Add(&ops)
    92  	pointer.InputOp{Tag: handler3, Types: types}.Add(&ops)
    93  
    94  	var r Router
    95  	r.Frame(&ops)
    96  	r.Queue(
    97  		pointer.Event{
    98  			Type:     pointer.Press,
    99  			Position: f32.Pt(50, 50),
   100  		},
   101  	)
   102  	assertEventSequence(t, r.Events(handler1), pointer.Cancel, pointer.Press)
   103  	assertEventSequence(t, r.Events(handler2), pointer.Cancel, pointer.Press)
   104  	assertEventSequence(t, r.Events(handler3), pointer.Cancel, pointer.Press)
   105  	r.Frame(&ops)
   106  	r.Queue(
   107  		pointer.Event{
   108  			Type:     pointer.Release,
   109  			Position: f32.Pt(50, 50),
   110  		},
   111  	)
   112  	assertEventSequence(t, r.Events(handler1), pointer.Release)
   113  	assertEventSequence(t, r.Events(handler2), pointer.Cancel)
   114  	assertEventSequence(t, r.Events(handler3), pointer.Cancel)
   115  }
   116  
   117  func TestPointerMove(t *testing.T) {
   118  	handler1 := new(int)
   119  	handler2 := new(int)
   120  	var ops op.Ops
   121  
   122  	types := pointer.Move | pointer.Enter | pointer.Leave
   123  
   124  	// Handler 1 area: (0, 0) - (100, 100)
   125  	pointer.Rect(image.Rect(0, 0, 100, 100)).Add(&ops)
   126  	pointer.InputOp{Tag: handler1, Types: types}.Add(&ops)
   127  	// Handler 2 area: (50, 50) - (100, 100) (areas intersect).
   128  	pointer.Rect(image.Rect(50, 50, 200, 200)).Add(&ops)
   129  	pointer.InputOp{Tag: handler2, Types: types}.Add(&ops)
   130  
   131  	var r Router
   132  	r.Frame(&ops)
   133  	r.Queue(
   134  		// Hit both handlers.
   135  		pointer.Event{
   136  			Type:     pointer.Move,
   137  			Position: f32.Pt(50, 50),
   138  		},
   139  		// Hit handler 1.
   140  		pointer.Event{
   141  			Type:     pointer.Move,
   142  			Position: f32.Pt(49, 50),
   143  		},
   144  		// Hit no handlers.
   145  		pointer.Event{
   146  			Type:     pointer.Move,
   147  			Position: f32.Pt(100, 50),
   148  		},
   149  		pointer.Event{
   150  			Type: pointer.Cancel,
   151  		},
   152  	)
   153  	assertEventSequence(t, r.Events(handler1), pointer.Cancel, pointer.Enter, pointer.Move, pointer.Move, pointer.Leave, pointer.Cancel)
   154  	assertEventSequence(t, r.Events(handler2), pointer.Cancel, pointer.Enter, pointer.Move, pointer.Leave, pointer.Cancel)
   155  }
   156  
   157  func TestPointerTypes(t *testing.T) {
   158  	handler := new(int)
   159  	var ops op.Ops
   160  	pointer.Rect(image.Rect(0, 0, 100, 100)).Add(&ops)
   161  	pointer.InputOp{
   162  		Tag:   handler,
   163  		Types: pointer.Press | pointer.Release,
   164  	}.Add(&ops)
   165  
   166  	var r Router
   167  	r.Frame(&ops)
   168  	r.Queue(
   169  		pointer.Event{
   170  			Type:     pointer.Press,
   171  			Position: f32.Pt(50, 50),
   172  		},
   173  		pointer.Event{
   174  			Type:     pointer.Move,
   175  			Position: f32.Pt(150, 150),
   176  		},
   177  		pointer.Event{
   178  			Type:     pointer.Release,
   179  			Position: f32.Pt(150, 150),
   180  		},
   181  	)
   182  	assertEventSequence(t, r.Events(handler), pointer.Cancel, pointer.Press, pointer.Release)
   183  }
   184  
   185  func TestPointerPriority(t *testing.T) {
   186  	handler1 := new(int)
   187  	handler2 := new(int)
   188  	handler3 := new(int)
   189  	var ops op.Ops
   190  
   191  	st := op.Save(&ops)
   192  	pointer.Rect(image.Rect(0, 0, 100, 100)).Add(&ops)
   193  	pointer.InputOp{
   194  		Tag:          handler1,
   195  		Types:        pointer.Scroll,
   196  		ScrollBounds: image.Rectangle{Max: image.Point{X: 100}},
   197  	}.Add(&ops)
   198  
   199  	pointer.Rect(image.Rect(0, 0, 100, 50)).Add(&ops)
   200  	pointer.InputOp{
   201  		Tag:          handler2,
   202  		Types:        pointer.Scroll,
   203  		ScrollBounds: image.Rectangle{Max: image.Point{X: 20}},
   204  	}.Add(&ops)
   205  	st.Load()
   206  
   207  	pointer.Rect(image.Rect(0, 100, 100, 200)).Add(&ops)
   208  	pointer.InputOp{
   209  		Tag:          handler3,
   210  		Types:        pointer.Scroll,
   211  		ScrollBounds: image.Rectangle{Min: image.Point{X: -20, Y: -40}},
   212  	}.Add(&ops)
   213  
   214  	var r Router
   215  	r.Frame(&ops)
   216  	r.Queue(
   217  		// Hit handler 1 and 2.
   218  		pointer.Event{
   219  			Type:     pointer.Scroll,
   220  			Position: f32.Pt(50, 25),
   221  			Scroll:   f32.Pt(50, 0),
   222  		},
   223  		// Hit handler 1.
   224  		pointer.Event{
   225  			Type:     pointer.Scroll,
   226  			Position: f32.Pt(50, 75),
   227  			Scroll:   f32.Pt(50, 50),
   228  		},
   229  		// Hit handler 3.
   230  		pointer.Event{
   231  			Type:     pointer.Scroll,
   232  			Position: f32.Pt(50, 150),
   233  			Scroll:   f32.Pt(-30, -30),
   234  		},
   235  		// Hit no handlers.
   236  		pointer.Event{
   237  			Type:     pointer.Scroll,
   238  			Position: f32.Pt(50, 225),
   239  		},
   240  	)
   241  
   242  	hev1 := r.Events(handler1)
   243  	hev2 := r.Events(handler2)
   244  	hev3 := r.Events(handler3)
   245  	assertEventSequence(t, hev1, pointer.Cancel, pointer.Scroll, pointer.Scroll)
   246  	assertEventSequence(t, hev2, pointer.Cancel, pointer.Scroll)
   247  	assertEventSequence(t, hev3, pointer.Cancel, pointer.Scroll)
   248  	assertEventPriorities(t, hev1, pointer.Shared, pointer.Shared, pointer.Foremost)
   249  	assertEventPriorities(t, hev2, pointer.Shared, pointer.Foremost)
   250  	assertEventPriorities(t, hev3, pointer.Shared, pointer.Foremost)
   251  	assertScrollEvent(t, hev1[1], f32.Pt(30, 0))
   252  	assertScrollEvent(t, hev2[1], f32.Pt(20, 0))
   253  	assertScrollEvent(t, hev1[2], f32.Pt(50, 0))
   254  	assertScrollEvent(t, hev3[1], f32.Pt(-20, -30))
   255  }
   256  
   257  func TestPointerEnterLeave(t *testing.T) {
   258  	handler1 := new(int)
   259  	handler2 := new(int)
   260  	var ops op.Ops
   261  
   262  	// Handler 1 area: (0, 0) - (100, 100)
   263  	addPointerHandler(&ops, handler1, image.Rect(0, 0, 100, 100))
   264  
   265  	// Handler 2 area: (50, 50) - (200, 200) (areas overlap).
   266  	addPointerHandler(&ops, handler2, image.Rect(50, 50, 200, 200))
   267  
   268  	var r Router
   269  	r.Frame(&ops)
   270  	// Hit both handlers.
   271  	r.Queue(
   272  		pointer.Event{
   273  			Type:     pointer.Move,
   274  			Position: f32.Pt(50, 50),
   275  		},
   276  	)
   277  	// First event for a handler is always a Cancel.
   278  	// Only handler2 should receive the enter/move events because it is on top
   279  	// and handler1 is not an ancestor in the hit tree.
   280  	assertEventSequence(t, r.Events(handler1), pointer.Cancel)
   281  	assertEventSequence(t, r.Events(handler2), pointer.Cancel, pointer.Enter, pointer.Move)
   282  
   283  	// Leave the second area by moving into the first.
   284  	r.Queue(
   285  		pointer.Event{
   286  			Type:     pointer.Move,
   287  			Position: f32.Pt(45, 45),
   288  		},
   289  	)
   290  	// The cursor leaves handler2 and enters handler1.
   291  	assertEventSequence(t, r.Events(handler1), pointer.Enter, pointer.Move)
   292  	assertEventSequence(t, r.Events(handler2), pointer.Leave)
   293  
   294  	// Move, but stay within the same hit area.
   295  	r.Queue(
   296  		pointer.Event{
   297  			Type:     pointer.Move,
   298  			Position: f32.Pt(40, 40),
   299  		},
   300  	)
   301  	assertEventSequence(t, r.Events(handler1), pointer.Move)
   302  	assertEventSequence(t, r.Events(handler2))
   303  
   304  	// Move outside of both inputs.
   305  	r.Queue(
   306  		pointer.Event{
   307  			Type:     pointer.Move,
   308  			Position: f32.Pt(300, 300),
   309  		},
   310  	)
   311  	assertEventSequence(t, r.Events(handler1), pointer.Leave)
   312  	assertEventSequence(t, r.Events(handler2))
   313  
   314  	// Check that a Press event generates Enter Events.
   315  	r.Queue(
   316  		pointer.Event{
   317  			Type:     pointer.Press,
   318  			Position: f32.Pt(125, 125),
   319  		},
   320  	)
   321  	assertEventSequence(t, r.Events(handler1))
   322  	assertEventSequence(t, r.Events(handler2), pointer.Enter, pointer.Press)
   323  
   324  	// Check that a drag only affects the participating handlers.
   325  	r.Queue(
   326  		// Leave
   327  		pointer.Event{
   328  			Type:     pointer.Move,
   329  			Position: f32.Pt(25, 25),
   330  		},
   331  		// Enter
   332  		pointer.Event{
   333  			Type:     pointer.Move,
   334  			Position: f32.Pt(50, 50),
   335  		},
   336  	)
   337  	assertEventSequence(t, r.Events(handler1))
   338  	assertEventSequence(t, r.Events(handler2), pointer.Leave, pointer.Drag, pointer.Enter, pointer.Drag)
   339  
   340  	// Check that a Release event generates Enter/Leave Events.
   341  	r.Queue(
   342  		pointer.Event{
   343  			Type: pointer.Release,
   344  			Position: f32.Pt(25,
   345  				25),
   346  		},
   347  	)
   348  	assertEventSequence(t, r.Events(handler1), pointer.Enter)
   349  	// The second handler gets the release event because the press started inside it.
   350  	assertEventSequence(t, r.Events(handler2), pointer.Release, pointer.Leave)
   351  
   352  }
   353  
   354  func TestMultipleAreas(t *testing.T) {
   355  	handler := new(int)
   356  
   357  	var ops op.Ops
   358  
   359  	addPointerHandler(&ops, handler, image.Rect(0, 0, 100, 100))
   360  	st := op.Save(&ops)
   361  	pointer.Rect(image.Rect(50, 50, 200, 200)).Add(&ops)
   362  	// Second area has no Types set, yet should receive events because
   363  	// Types for the same handles are or-ed together.
   364  	pointer.InputOp{Tag: handler}.Add(&ops)
   365  	st.Load()
   366  
   367  	var r Router
   368  	r.Frame(&ops)
   369  	// Hit first area, then second area, then both.
   370  	r.Queue(
   371  		pointer.Event{
   372  			Type:     pointer.Move,
   373  			Position: f32.Pt(25, 25),
   374  		},
   375  		pointer.Event{
   376  			Type:     pointer.Move,
   377  			Position: f32.Pt(150, 150),
   378  		},
   379  		pointer.Event{
   380  			Type:     pointer.Move,
   381  			Position: f32.Pt(50, 50),
   382  		},
   383  	)
   384  	assertEventSequence(t, r.Events(handler), pointer.Cancel, pointer.Enter, pointer.Move, pointer.Move, pointer.Move)
   385  }
   386  
   387  func TestPointerEnterLeaveNested(t *testing.T) {
   388  	handler1 := new(int)
   389  	handler2 := new(int)
   390  	var ops op.Ops
   391  
   392  	types := pointer.Press | pointer.Move | pointer.Release | pointer.Enter | pointer.Leave
   393  
   394  	// Handler 1 area: (0, 0) - (100, 100)
   395  	pointer.Rect(image.Rect(0, 0, 100, 100)).Add(&ops)
   396  	pointer.InputOp{Tag: handler1, Types: types}.Add(&ops)
   397  
   398  	// Handler 2 area: (25, 25) - (75, 75) (nested within first).
   399  	pointer.Rect(image.Rect(25, 25, 75, 75)).Add(&ops)
   400  	pointer.InputOp{Tag: handler2, Types: types}.Add(&ops)
   401  
   402  	var r Router
   403  	r.Frame(&ops)
   404  	// Hit both handlers.
   405  	r.Queue(
   406  		pointer.Event{
   407  			Type:     pointer.Move,
   408  			Position: f32.Pt(50, 50),
   409  		},
   410  	)
   411  	// First event for a handler is always a Cancel.
   412  	// Both handlers should receive the Enter and Move events because handler2 is a child of handler1.
   413  	assertEventSequence(t, r.Events(handler1), pointer.Cancel, pointer.Enter, pointer.Move)
   414  	assertEventSequence(t, r.Events(handler2), pointer.Cancel, pointer.Enter, pointer.Move)
   415  
   416  	// Leave the second area by moving into the first.
   417  	r.Queue(
   418  		pointer.Event{
   419  			Type:     pointer.Move,
   420  			Position: f32.Pt(20, 20),
   421  		},
   422  	)
   423  	assertEventSequence(t, r.Events(handler1), pointer.Move)
   424  	assertEventSequence(t, r.Events(handler2), pointer.Leave)
   425  
   426  	// Move, but stay within the same hit area.
   427  	r.Queue(
   428  		pointer.Event{
   429  			Type:     pointer.Move,
   430  			Position: f32.Pt(10, 10),
   431  		},
   432  	)
   433  	assertEventSequence(t, r.Events(handler1), pointer.Move)
   434  	assertEventSequence(t, r.Events(handler2))
   435  
   436  	// Move outside of both inputs.
   437  	r.Queue(
   438  		pointer.Event{
   439  			Type:     pointer.Move,
   440  			Position: f32.Pt(200, 200),
   441  		},
   442  	)
   443  	assertEventSequence(t, r.Events(handler1), pointer.Leave)
   444  	assertEventSequence(t, r.Events(handler2))
   445  
   446  	// Check that a Press event generates Enter Events.
   447  	r.Queue(
   448  		pointer.Event{
   449  			Type:     pointer.Press,
   450  			Position: f32.Pt(50, 50),
   451  		},
   452  	)
   453  	assertEventSequence(t, r.Events(handler1), pointer.Enter, pointer.Press)
   454  	assertEventSequence(t, r.Events(handler2), pointer.Enter, pointer.Press)
   455  
   456  	// Check that a Release event generates Enter/Leave Events.
   457  	r.Queue(
   458  		pointer.Event{
   459  			Type:     pointer.Release,
   460  			Position: f32.Pt(20, 20),
   461  		},
   462  	)
   463  	assertEventSequence(t, r.Events(handler1), pointer.Release)
   464  	assertEventSequence(t, r.Events(handler2), pointer.Release, pointer.Leave)
   465  }
   466  
   467  func TestPointerActiveInputDisappears(t *testing.T) {
   468  	handler1 := new(int)
   469  	var ops op.Ops
   470  	var r Router
   471  
   472  	// Draw handler.
   473  	ops.Reset()
   474  	addPointerHandler(&ops, handler1, image.Rect(0, 0, 100, 100))
   475  	r.Frame(&ops)
   476  	r.Queue(
   477  		pointer.Event{
   478  			Type:     pointer.Move,
   479  			Position: f32.Pt(25, 25),
   480  		},
   481  	)
   482  	assertEventSequence(t, r.Events(handler1), pointer.Cancel, pointer.Enter, pointer.Move)
   483  
   484  	// Re-render with handler missing.
   485  	ops.Reset()
   486  	r.Frame(&ops)
   487  	r.Queue(
   488  		pointer.Event{
   489  			Type:     pointer.Move,
   490  			Position: f32.Pt(25, 25),
   491  		},
   492  	)
   493  	assertEventSequence(t, r.Events(handler1))
   494  }
   495  
   496  func TestMultitouch(t *testing.T) {
   497  	var ops op.Ops
   498  
   499  	// Add two separate handlers.
   500  	h1, h2 := new(int), new(int)
   501  	addPointerHandler(&ops, h1, image.Rect(0, 0, 100, 100))
   502  	addPointerHandler(&ops, h2, image.Rect(0, 100, 100, 200))
   503  
   504  	h1pt, h2pt := f32.Pt(0, 0), f32.Pt(0, 100)
   505  	var p1, p2 pointer.ID = 0, 1
   506  
   507  	var r Router
   508  	r.Frame(&ops)
   509  	r.Queue(
   510  		pointer.Event{
   511  			Type:      pointer.Press,
   512  			Position:  h1pt,
   513  			PointerID: p1,
   514  		},
   515  	)
   516  	r.Queue(
   517  		pointer.Event{
   518  			Type:      pointer.Press,
   519  			Position:  h2pt,
   520  			PointerID: p2,
   521  		},
   522  	)
   523  	r.Queue(
   524  		pointer.Event{
   525  			Type:      pointer.Release,
   526  			Position:  h2pt,
   527  			PointerID: p2,
   528  		},
   529  	)
   530  	assertEventSequence(t, r.Events(h1), pointer.Cancel, pointer.Enter, pointer.Press)
   531  	assertEventSequence(t, r.Events(h2), pointer.Cancel, pointer.Enter, pointer.Press, pointer.Release)
   532  }
   533  
   534  func TestCursorNameOp(t *testing.T) {
   535  	ops := new(op.Ops)
   536  	var r Router
   537  	var h, h2 int
   538  	var widget2 func()
   539  	widget := func() {
   540  		// This is the area where the cursor is changed to CursorPointer.
   541  		pointer.Rect(image.Rectangle{Max: image.Pt(100, 100)}).Add(ops)
   542  		// The cursor is checked and changed upon cursor movement.
   543  		pointer.InputOp{Tag: &h}.Add(ops)
   544  		pointer.CursorNameOp{Name: pointer.CursorPointer}.Add(ops)
   545  		if widget2 != nil {
   546  			widget2()
   547  		}
   548  	}
   549  	// Register the handlers.
   550  	widget()
   551  	// No cursor change as the mouse has not moved yet.
   552  	if got, want := r.Cursor(), pointer.CursorDefault; got != want {
   553  		t.Errorf("got %q; want %q", got, want)
   554  	}
   555  
   556  	_at := func(x, y float32) pointer.Event {
   557  		return pointer.Event{
   558  			Type:     pointer.Move,
   559  			Source:   pointer.Mouse,
   560  			Buttons:  pointer.ButtonPrimary,
   561  			Position: f32.Pt(x, y),
   562  		}
   563  	}
   564  	for _, tc := range []struct {
   565  		label string
   566  		event interface{}
   567  		want  pointer.CursorName
   568  	}{
   569  		{label: "move inside",
   570  			event: _at(50, 50),
   571  			want:  pointer.CursorPointer,
   572  		},
   573  		{label: "move outside",
   574  			event: _at(200, 200),
   575  			want:  pointer.CursorDefault,
   576  		},
   577  		{label: "move back inside",
   578  			event: _at(50, 50),
   579  			want:  pointer.CursorPointer,
   580  		},
   581  		{label: "send key events while inside",
   582  			event: []event.Event{
   583  				key.Event{Name: "A", State: key.Press},
   584  				key.Event{Name: "A", State: key.Release},
   585  			},
   586  			want: pointer.CursorPointer,
   587  		},
   588  		{label: "send key events while outside",
   589  			event: []event.Event{
   590  				_at(200, 200),
   591  				key.Event{Name: "A", State: key.Press},
   592  				key.Event{Name: "A", State: key.Release},
   593  			},
   594  			want: pointer.CursorDefault,
   595  		},
   596  		{label: "add new input on top while inside",
   597  			event: func() []event.Event {
   598  				widget2 = func() {
   599  					pointer.InputOp{Tag: &h2}.Add(ops)
   600  					pointer.CursorNameOp{Name: pointer.CursorCrossHair}.Add(ops)
   601  				}
   602  				return []event.Event{
   603  					_at(50, 50),
   604  					key.Event{
   605  						Name:  "A",
   606  						State: key.Press,
   607  					},
   608  				}
   609  			},
   610  			want: pointer.CursorCrossHair,
   611  		},
   612  		{label: "remove input on top while inside",
   613  			event: func() []event.Event {
   614  				widget2 = nil
   615  				return []event.Event{
   616  					_at(50, 50),
   617  					key.Event{
   618  						Name:  "A",
   619  						State: key.Press,
   620  					},
   621  				}
   622  			},
   623  			want: pointer.CursorPointer,
   624  		},
   625  	} {
   626  		t.Run(tc.label, func(t *testing.T) {
   627  			ops.Reset()
   628  			widget()
   629  			r.Frame(ops)
   630  			switch ev := tc.event.(type) {
   631  			case event.Event:
   632  				r.Queue(ev)
   633  			case []event.Event:
   634  				r.Queue(ev...)
   635  			case func() event.Event:
   636  				r.Queue(ev())
   637  			case func() []event.Event:
   638  				r.Queue(ev()...)
   639  			default:
   640  				panic(fmt.Sprintf("unkown event %T", ev))
   641  			}
   642  			widget()
   643  			r.Frame(ops)
   644  			// The cursor should now have been changed if the mouse moved over the declared area.
   645  			if got, want := r.Cursor(), tc.want; got != want {
   646  				t.Errorf("got %q; want %q", got, want)
   647  			}
   648  		})
   649  	}
   650  }
   651  
   652  // addPointerHandler adds a pointer.InputOp for the tag in a
   653  // rectangular area.
   654  func addPointerHandler(ops *op.Ops, tag event.Tag, area image.Rectangle) {
   655  	defer op.Save(ops).Load()
   656  	pointer.Rect(area).Add(ops)
   657  	pointer.InputOp{
   658  		Tag:   tag,
   659  		Types: pointer.Press | pointer.Release | pointer.Move | pointer.Drag | pointer.Enter | pointer.Leave,
   660  	}.Add(ops)
   661  }
   662  
   663  // pointerTypes converts a sequence of event.Event to their pointer.Types. It assumes
   664  // that all input events are of underlying type pointer.Event, and thus will
   665  // panic if some are not.
   666  func pointerTypes(events []event.Event) []pointer.Type {
   667  	var types []pointer.Type
   668  	for _, e := range events {
   669  		if e, ok := e.(pointer.Event); ok {
   670  			types = append(types, e.Type)
   671  		}
   672  	}
   673  	return types
   674  }
   675  
   676  // assertEventSequence checks that the provided events match the expected pointer event types
   677  // in the provided order.
   678  func assertEventSequence(t *testing.T, events []event.Event, expected ...pointer.Type) {
   679  	t.Helper()
   680  	got := pointerTypes(events)
   681  	if !reflect.DeepEqual(got, expected) {
   682  		t.Errorf("expected %v events, got %v", expected, got)
   683  	}
   684  }
   685  
   686  // assertEventPriorities checks that the pointer.Event priorities of events match prios.
   687  func assertEventPriorities(t *testing.T, events []event.Event, prios ...pointer.Priority) {
   688  	t.Helper()
   689  	var got []pointer.Priority
   690  	for _, e := range events {
   691  		if e, ok := e.(pointer.Event); ok {
   692  			got = append(got, e.Priority)
   693  		}
   694  	}
   695  	if !reflect.DeepEqual(got, prios) {
   696  		t.Errorf("expected priorities %v, got %v", prios, got)
   697  	}
   698  }
   699  
   700  // assertScrollEvent checks that the event scrolling amount matches the supplied value.
   701  func assertScrollEvent(t *testing.T, ev event.Event, scroll f32.Point) {
   702  	t.Helper()
   703  	if got, want := ev.(pointer.Event).Scroll, scroll; got != want {
   704  		t.Errorf("got %v; want %v", got, want)
   705  	}
   706  }
   707  
   708  func BenchmarkRouterAdd(b *testing.B) {
   709  	// Set this to the number of overlapping handlers that you want to
   710  	// evaluate performance for. Typical values for the example applications
   711  	// are 1-3, though checking highers values helps evaluate performance for
   712  	// more complex applications.
   713  	const startingHandlerCount = 3
   714  	const maxHandlerCount = 100
   715  	for i := startingHandlerCount; i < maxHandlerCount; i *= 3 {
   716  		handlerCount := i
   717  		b.Run(fmt.Sprintf("%d-handlers", i), func(b *testing.B) {
   718  			handlers := make([]event.Tag, handlerCount)
   719  			for i := 0; i < handlerCount; i++ {
   720  				h := new(int)
   721  				*h = i
   722  				handlers[i] = h
   723  			}
   724  			var ops op.Ops
   725  
   726  			for i := range handlers {
   727  				pointer.Rect(image.Rectangle{
   728  					Max: image.Point{
   729  						X: 100,
   730  						Y: 100,
   731  					},
   732  				}).Add(&ops)
   733  				pointer.InputOp{
   734  					Tag:   handlers[i],
   735  					Types: pointer.Move,
   736  				}.Add(&ops)
   737  			}
   738  			var r Router
   739  			r.Frame(&ops)
   740  			b.ReportAllocs()
   741  			b.ResetTimer()
   742  			for i := 0; i < b.N; i++ {
   743  				r.Queue(
   744  					pointer.Event{
   745  						Type:     pointer.Move,
   746  						Position: f32.Pt(50, 50),
   747  					},
   748  				)
   749  			}
   750  		})
   751  	}
   752  }
   753  
   754  var benchAreaOp areaOp
   755  
   756  func BenchmarkAreaOp_Decode(b *testing.B) {
   757  	ops := new(op.Ops)
   758  	pointer.Rect(image.Rectangle{Max: image.Pt(100, 100)}).Add(ops)
   759  	for i := 0; i < b.N; i++ {
   760  		benchAreaOp.Decode(ops.Data())
   761  	}
   762  }
   763  
   764  func BenchmarkAreaOp_Hit(b *testing.B) {
   765  	ops := new(op.Ops)
   766  	pointer.Rect(image.Rectangle{Max: image.Pt(100, 100)}).Add(ops)
   767  	benchAreaOp.Decode(ops.Data())
   768  	for i := 0; i < b.N; i++ {
   769  		benchAreaOp.Hit(f32.Pt(50, 50))
   770  	}
   771  }