pointer.go raw

   1  // SPDX-License-Identifier: Unlicense OR MIT
   2  
   3  package router
   4  
   5  import (
   6  	"encoding/binary"
   7  	"image"
   8  
   9  	"github.com/p9c/p9/pkg/gel/gio/f32"
  10  	"github.com/p9c/p9/pkg/gel/gio/internal/opconst"
  11  	"github.com/p9c/p9/pkg/gel/gio/internal/ops"
  12  	"github.com/p9c/p9/pkg/gel/gio/io/event"
  13  	"github.com/p9c/p9/pkg/gel/gio/io/pointer"
  14  	"github.com/p9c/p9/pkg/gel/gio/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  }
 510