github.com/Seikaijyu/gio@v0.0.1/io/router/pointer_test.go (about)

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