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  }