github.com/cybriq/giocore@v0.0.7-0.20210703034601-cfb9cb5f3900/io/router/pointer.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  package router
     4  
     5  import (
     6  	"encoding/binary"
     7  	"image"
     8  
     9  	"github.com/cybriq/giocore/f32"
    10  	"github.com/cybriq/giocore/internal/opconst"
    11  	"github.com/cybriq/giocore/internal/ops"
    12  	"github.com/cybriq/giocore/io/event"
    13  	"github.com/cybriq/giocore/io/pointer"
    14  	"github.com/cybriq/giocore/op"
    15  )
    16  
    17  type pointerQueue struct {
    18  	hitTree  []hitNode
    19  	areas    []areaNode
    20  	cursors  []cursorNode
    21  	cursor   pointer.CursorName
    22  	handlers map[event.Tag]*pointerHandler
    23  	pointers []pointerInfo
    24  	reader   ops.Reader
    25  
    26  	// states holds the storage for save/restore ops.
    27  	states  []collectState
    28  	scratch []event.Tag
    29  }
    30  
    31  type hitNode struct {
    32  	next int
    33  	area int
    34  	// Pass tracks the most recent PassOp mode.
    35  	pass bool
    36  
    37  	// For handler nodes.
    38  	tag event.Tag
    39  }
    40  
    41  type cursorNode struct {
    42  	name pointer.CursorName
    43  	area int
    44  }
    45  
    46  type pointerInfo struct {
    47  	id       pointer.ID
    48  	pressed  bool
    49  	handlers []event.Tag
    50  	// last tracks the last pointer event received,
    51  	// used while processing frame events.
    52  	last pointer.Event
    53  
    54  	// entered tracks the tags that contain the pointer.
    55  	entered []event.Tag
    56  }
    57  
    58  type pointerHandler struct {
    59  	area      int
    60  	active    bool
    61  	wantsGrab bool
    62  	types     pointer.Type
    63  	// min and max horizontal/vertical scroll
    64  	scrollRange image.Rectangle
    65  }
    66  
    67  type areaOp struct {
    68  	kind areaKind
    69  	rect f32.Rectangle
    70  }
    71  
    72  type areaNode struct {
    73  	trans f32.Affine2D
    74  	next  int
    75  	area  areaOp
    76  }
    77  
    78  type areaKind uint8
    79  
    80  // collectState represents the state for collectHandlers
    81  type collectState struct {
    82  	t    f32.Affine2D
    83  	area int
    84  	node int
    85  	pass bool
    86  }
    87  
    88  const (
    89  	areaRect areaKind = iota
    90  	areaEllipse
    91  )
    92  
    93  func (q *pointerQueue) save(id int, state collectState) {
    94  	if extra := id - len(q.states) + 1; extra > 0 {
    95  		q.states = append(q.states, make([]collectState, extra)...)
    96  	}
    97  	q.states[id] = state
    98  }
    99  
   100  func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents) {
   101  	state := collectState{
   102  		area: -1,
   103  		node: -1,
   104  	}
   105  	q.save(opconst.InitialStateID, state)
   106  	for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
   107  		switch opconst.OpType(encOp.Data[0]) {
   108  		case opconst.TypeSave:
   109  			id := ops.DecodeSave(encOp.Data)
   110  			q.save(id, state)
   111  		case opconst.TypeLoad:
   112  			id, mask := ops.DecodeLoad(encOp.Data)
   113  			s := q.states[id]
   114  			if mask&opconst.TransformState != 0 {
   115  				state.t = s.t
   116  			}
   117  			if mask&^opconst.TransformState != 0 {
   118  				state = s
   119  			}
   120  		case opconst.TypePass:
   121  			state.pass = encOp.Data[1] != 0
   122  		case opconst.TypeArea:
   123  			var op areaOp
   124  			op.Decode(encOp.Data)
   125  			q.areas = append(q.areas, areaNode{trans: state.t, next: state.area, area: op})
   126  			state.area = len(q.areas) - 1
   127  			q.hitTree = append(q.hitTree, hitNode{
   128  				next: state.node,
   129  				area: state.area,
   130  				pass: state.pass,
   131  			})
   132  			state.node = len(q.hitTree) - 1
   133  		case opconst.TypeTransform:
   134  			dop := ops.DecodeTransform(encOp.Data)
   135  			state.t = state.t.Mul(dop)
   136  		case opconst.TypePointerInput:
   137  			op := pointer.InputOp{
   138  				Tag:   encOp.Refs[0].(event.Tag),
   139  				Grab:  encOp.Data[1] != 0,
   140  				Types: pointer.Type(encOp.Data[2]),
   141  			}
   142  			q.hitTree = append(q.hitTree, hitNode{
   143  				next: state.node,
   144  				area: state.area,
   145  				pass: state.pass,
   146  				tag:  op.Tag,
   147  			})
   148  			state.node = len(q.hitTree) - 1
   149  			h, ok := q.handlers[op.Tag]
   150  			if !ok {
   151  				h = new(pointerHandler)
   152  				q.handlers[op.Tag] = h
   153  				// Cancel handlers on (each) first appearance, but don't
   154  				// trigger redraw.
   155  				events.AddNoRedraw(op.Tag, pointer.Event{Type: pointer.Cancel})
   156  			}
   157  			h.active = true
   158  			h.area = state.area
   159  			h.wantsGrab = h.wantsGrab || op.Grab
   160  			h.types = h.types | op.Types
   161  			bo := binary.LittleEndian.Uint32
   162  			h.scrollRange = image.Rectangle{
   163  				Min: image.Point{
   164  					X: int(int32(bo(encOp.Data[3:]))),
   165  					Y: int(int32(bo(encOp.Data[7:]))),
   166  				},
   167  				Max: image.Point{
   168  					X: int(int32(bo(encOp.Data[11:]))),
   169  					Y: int(int32(bo(encOp.Data[15:]))),
   170  				},
   171  			}
   172  		case opconst.TypeCursor:
   173  			q.cursors = append(q.cursors, cursorNode{
   174  				name: encOp.Refs[0].(pointer.CursorName),
   175  				area: len(q.areas) - 1,
   176  			})
   177  		}
   178  	}
   179  }
   180  
   181  func (q *pointerQueue) opHit(handlers *[]event.Tag, pos f32.Point) {
   182  	// Track whether we're passing through hits.
   183  	pass := true
   184  	idx := len(q.hitTree) - 1
   185  	for idx >= 0 {
   186  		n := &q.hitTree[idx]
   187  		if !q.hit(n.area, pos) {
   188  			idx--
   189  			continue
   190  		}
   191  		pass = pass && n.pass
   192  		if pass {
   193  			idx--
   194  		} else {
   195  			idx = n.next
   196  		}
   197  		if n.tag != nil {
   198  			if _, exists := q.handlers[n.tag]; exists {
   199  				*handlers = append(*handlers, n.tag)
   200  			}
   201  		}
   202  	}
   203  }
   204  
   205  func (q *pointerQueue) invTransform(areaIdx int, p f32.Point) f32.Point {
   206  	if areaIdx == -1 {
   207  		return p
   208  	}
   209  	return q.areas[areaIdx].trans.Invert().Transform(p)
   210  }
   211  
   212  func (q *pointerQueue) hit(areaIdx int, p f32.Point) bool {
   213  	for areaIdx != -1 {
   214  		a := &q.areas[areaIdx]
   215  		p := a.trans.Invert().Transform(p)
   216  		if !a.area.Hit(p) {
   217  			return false
   218  		}
   219  		areaIdx = a.next
   220  	}
   221  	return true
   222  }
   223  
   224  func (q *pointerQueue) reset() {
   225  	if q.handlers == nil {
   226  		q.handlers = make(map[event.Tag]*pointerHandler)
   227  	}
   228  }
   229  
   230  func (q *pointerQueue) Frame(root *op.Ops, events *handlerEvents) {
   231  	q.reset()
   232  	for _, h := range q.handlers {
   233  		// Reset handler.
   234  		h.active = false
   235  		h.wantsGrab = false
   236  		h.types = 0
   237  	}
   238  	q.hitTree = q.hitTree[:0]
   239  	q.areas = q.areas[:0]
   240  	q.cursors = q.cursors[:0]
   241  	q.reader.Reset(root)
   242  	q.collectHandlers(&q.reader, events)
   243  	for k, h := range q.handlers {
   244  		if !h.active {
   245  			q.dropHandlers(events, k)
   246  			delete(q.handlers, k)
   247  		}
   248  		if h.wantsGrab {
   249  			for _, p := range q.pointers {
   250  				if !p.pressed {
   251  					continue
   252  				}
   253  				for i, k2 := range p.handlers {
   254  					if k2 == k {
   255  						// Drop other handlers that lost their grab.
   256  						dropped := make([]event.Tag, 0, len(p.handlers)-1)
   257  						dropped = append(dropped, p.handlers[:i]...)
   258  						dropped = append(dropped, p.handlers[i+1:]...)
   259  						cancelHandlers(events, dropped...)
   260  						q.dropHandlers(events, dropped...)
   261  						break
   262  					}
   263  				}
   264  			}
   265  		}
   266  	}
   267  	for i := range q.pointers {
   268  		p := &q.pointers[i]
   269  		q.deliverEnterLeaveEvents(p, events, p.last)
   270  	}
   271  }
   272  
   273  func cancelHandlers(events *handlerEvents, tags ...event.Tag) {
   274  	for _, k := range tags {
   275  		events.Add(k, pointer.Event{Type: pointer.Cancel})
   276  	}
   277  }
   278  
   279  func (q *pointerQueue) dropHandlers(events *handlerEvents, tags ...event.Tag) {
   280  	for _, k := range tags {
   281  		for i := range q.pointers {
   282  			p := &q.pointers[i]
   283  			for i := len(p.handlers) - 1; i >= 0; i-- {
   284  				if p.handlers[i] == k {
   285  					p.handlers = append(p.handlers[:i], p.handlers[i+1:]...)
   286  				}
   287  			}
   288  			for i := len(p.entered) - 1; i >= 0; i-- {
   289  				if p.entered[i] == k {
   290  					p.entered = append(p.entered[:i], p.entered[i+1:]...)
   291  				}
   292  			}
   293  		}
   294  	}
   295  }
   296  
   297  func (q *pointerQueue) Push(e pointer.Event, events *handlerEvents) {
   298  	q.reset()
   299  	if e.Type == pointer.Cancel {
   300  		q.pointers = q.pointers[:0]
   301  		for k := range q.handlers {
   302  			cancelHandlers(events, k)
   303  			q.dropHandlers(events, k)
   304  		}
   305  		return
   306  	}
   307  	pidx := -1
   308  	for i, p := range q.pointers {
   309  		if p.id == e.PointerID {
   310  			pidx = i
   311  			break
   312  		}
   313  	}
   314  	if pidx == -1 {
   315  		q.pointers = append(q.pointers, pointerInfo{id: e.PointerID})
   316  		pidx = len(q.pointers) - 1
   317  	}
   318  	p := &q.pointers[pidx]
   319  	p.last = e
   320  
   321  	if e.Type == pointer.Move && p.pressed {
   322  		e.Type = pointer.Drag
   323  	}
   324  
   325  	if e.Type == pointer.Release {
   326  		q.deliverEvent(p, events, e)
   327  		p.pressed = false
   328  	}
   329  	q.deliverEnterLeaveEvents(p, events, e)
   330  
   331  	if !p.pressed {
   332  		p.handlers = append(p.handlers[:0], q.scratch...)
   333  	}
   334  	if e.Type == pointer.Press {
   335  		p.pressed = true
   336  	}
   337  	switch e.Type {
   338  	case pointer.Release:
   339  	case pointer.Scroll:
   340  		q.deliverScrollEvent(p, events, e)
   341  	default:
   342  		q.deliverEvent(p, events, e)
   343  	}
   344  	if !p.pressed && len(p.entered) == 0 {
   345  		// No longer need to track pointer.
   346  		q.pointers = append(q.pointers[:pidx], q.pointers[pidx+1:]...)
   347  	}
   348  }
   349  
   350  func (q *pointerQueue) deliverEvent(p *pointerInfo, events *handlerEvents, e pointer.Event) {
   351  	foremost := true
   352  	if p.pressed && len(p.handlers) == 1 {
   353  		e.Priority = pointer.Grabbed
   354  		foremost = false
   355  	}
   356  	for _, k := range p.handlers {
   357  		h := q.handlers[k]
   358  		if e.Type&h.types == 0 {
   359  			continue
   360  		}
   361  		e := e
   362  		if foremost {
   363  			foremost = false
   364  			e.Priority = pointer.Foremost
   365  		}
   366  		e.Position = q.invTransform(h.area, e.Position)
   367  		events.Add(k, e)
   368  	}
   369  }
   370  
   371  func (q *pointerQueue) deliverScrollEvent(p *pointerInfo, events *handlerEvents, e pointer.Event) {
   372  	foremost := true
   373  	if p.pressed && len(p.handlers) == 1 {
   374  		e.Priority = pointer.Grabbed
   375  		foremost = false
   376  	}
   377  	var sx, sy = e.Scroll.X, e.Scroll.Y
   378  	for _, k := range p.handlers {
   379  		if sx == 0 && sy == 0 {
   380  			return
   381  		}
   382  		h := q.handlers[k]
   383  		// Distribute the scroll to the handler based on its ScrollRange.
   384  		sx, e.Scroll.X = setScrollEvent(sx, h.scrollRange.Min.X, h.scrollRange.Max.X)
   385  		sy, e.Scroll.Y = setScrollEvent(sy, h.scrollRange.Min.Y, h.scrollRange.Max.Y)
   386  		e := e
   387  		if foremost {
   388  			foremost = false
   389  			e.Priority = pointer.Foremost
   390  		}
   391  		e.Position = q.invTransform(h.area, e.Position)
   392  		events.Add(k, e)
   393  	}
   394  }
   395  
   396  func (q *pointerQueue) deliverEnterLeaveEvents(p *pointerInfo, events *handlerEvents, e pointer.Event) {
   397  	q.scratch = q.scratch[:0]
   398  	q.opHit(&q.scratch, e.Position)
   399  	if p.pressed {
   400  		// Filter out non-participating handlers.
   401  		for i := len(q.scratch) - 1; i >= 0; i-- {
   402  			if _, found := searchTag(p.handlers, q.scratch[i]); !found {
   403  				q.scratch = append(q.scratch[:i], q.scratch[i+1:]...)
   404  			}
   405  		}
   406  	}
   407  	hits := q.scratch
   408  	if e.Source != pointer.Mouse && !p.pressed && e.Type != pointer.Press {
   409  		// Consider non-mouse pointers leaving when they're released.
   410  		hits = nil
   411  	}
   412  	// Deliver Leave events.
   413  	for _, k := range p.entered {
   414  		if _, found := searchTag(hits, k); found {
   415  			continue
   416  		}
   417  		h := q.handlers[k]
   418  		e.Type = pointer.Leave
   419  
   420  		if e.Type&h.types != 0 {
   421  			e.Position = q.invTransform(h.area, e.Position)
   422  			events.Add(k, e)
   423  		}
   424  	}
   425  	// Deliver Enter events and update cursor.
   426  	q.cursor = pointer.CursorDefault
   427  	for _, k := range hits {
   428  		h := q.handlers[k]
   429  		for i := len(q.cursors) - 1; i >= 0; i-- {
   430  			if c := q.cursors[i]; c.area == h.area {
   431  				q.cursor = c.name
   432  				break
   433  			}
   434  		}
   435  		if _, found := searchTag(p.entered, k); found {
   436  			continue
   437  		}
   438  		e.Type = pointer.Enter
   439  
   440  		if e.Type&h.types != 0 {
   441  			e.Position = q.invTransform(h.area, e.Position)
   442  			events.Add(k, e)
   443  		}
   444  	}
   445  	p.entered = append(p.entered[:0], hits...)
   446  }
   447  
   448  func searchTag(tags []event.Tag, tag event.Tag) (int, bool) {
   449  	for i, t := range tags {
   450  		if t == tag {
   451  			return i, true
   452  		}
   453  	}
   454  	return 0, false
   455  }
   456  
   457  func opDecodeFloat32(d []byte) float32 {
   458  	return float32(int32(binary.LittleEndian.Uint32(d)))
   459  }
   460  
   461  func (op *areaOp) Decode(d []byte) {
   462  	if opconst.OpType(d[0]) != opconst.TypeArea {
   463  		panic("invalid op")
   464  	}
   465  	rect := f32.Rectangle{
   466  		Min: f32.Point{
   467  			X: opDecodeFloat32(d[2:]),
   468  			Y: opDecodeFloat32(d[6:]),
   469  		},
   470  		Max: f32.Point{
   471  			X: opDecodeFloat32(d[10:]),
   472  			Y: opDecodeFloat32(d[14:]),
   473  		},
   474  	}
   475  	*op = areaOp{
   476  		kind: areaKind(d[1]),
   477  		rect: rect,
   478  	}
   479  }
   480  
   481  func (op *areaOp) Hit(pos f32.Point) bool {
   482  	pos = pos.Sub(op.rect.Min)
   483  	size := op.rect.Size()
   484  	switch op.kind {
   485  	case areaRect:
   486  		return 0 <= pos.X && pos.X < size.X &&
   487  			0 <= pos.Y && pos.Y < size.Y
   488  	case areaEllipse:
   489  		rx := size.X / 2
   490  		ry := size.Y / 2
   491  		xh := pos.X - rx
   492  		yk := pos.Y - ry
   493  		// The ellipse function works in all cases because
   494  		// 0/0 is not <= 1.
   495  		return (xh*xh)/(rx*rx)+(yk*yk)/(ry*ry) <= 1
   496  	default:
   497  		panic("invalid area kind")
   498  	}
   499  }
   500  
   501  func setScrollEvent(scroll float32, min, max int) (left, scrolled float32) {
   502  	if v := float32(max); scroll > v {
   503  		return scroll - v, v
   504  	}
   505  	if v := float32(min); scroll < v {
   506  		return scroll - v, v
   507  	}
   508  	return 0, scroll
   509  }