~eliasnaur/gio-patches

gio: io/router: add support for ScrollRange v1 PROPOSED

~pierrec
~pierrec: 1
 io/router: add support for ScrollRange

 7 files changed, 139 insertions(+), 19 deletions(-)
#438268 apple.yml success
#438269 freebsd.yml success
#438270 linux.yml success
#438271 openbsd.yml success
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~eliasnaur/gio-patches/patches/20512/mbox | git am -3
Learn more about email & git
View this thread in the archives

[PATCH gio] io/router: add support for ScrollRange Export this patch

~pierrec
From: pierre <pierre.curto@gmail.com>

ScrollRange allows specifying how far scroll events should be considered in widgets.
A List displaying Editors can now be scrolled while outside of its focused editors.

Fixes #185.

Signed-off-by: pierre <pierre.curto@gmail.com>
---
 gesture/gesture.go        | 10 ++++---
 internal/opconst/ops.go   |  2 +-
 io/pointer/pointer.go     | 27 +++++++++++++++----
 io/router/pointer.go      | 51 +++++++++++++++++++++++++++++++----
 io/router/pointer_test.go | 56 +++++++++++++++++++++++++++++++++++++--
 layout/list.go            |  5 +++-
 widget/editor.go          |  7 ++++-
 7 files changed, 139 insertions(+), 19 deletions(-)

diff --git a/gesture/gesture.go b/gesture/gesture.go
index 763183c2..5a01ad94 100644
--- a/gesture/gesture.go
+++ b/gesture/gesture.go
@@ -205,11 +205,13 @@ func (c *Click) Events(q event.Queue) []ClickEvent {
func (ClickEvent) ImplementsEvent() {}

// Add the handler to the operation list to receive scroll events.
func (s *Scroll) Add(ops *op.Ops) {
func (s *Scroll) Add(ops *op.Ops, x, y pointer.ScrollRange) {
	oph := pointer.InputOp{
		Tag:   s,
		Grab:  s.grab,
		Types: pointer.Press | pointer.Drag | pointer.Release | pointer.Scroll,
		Tag:          s,
		Grab:         s.grab,
		Types:        pointer.Press | pointer.Drag | pointer.Release | pointer.Scroll,
		XScrollRange: x,
		YScrollRange: y,
	}
	oph.Add(ops)
	if s.flinger.Active() {
diff --git a/internal/opconst/ops.go b/internal/opconst/ops.go
index 8e9a1a35..38f7a60e 100644
--- a/internal/opconst/ops.go
+++ b/internal/opconst/ops.go
@@ -47,7 +47,7 @@ const (
	TypeColorLen           = 1 + 4
	TypeLinearGradientLen  = 1 + 8*2 + 4*2
	TypeAreaLen            = 1 + 1 + 4*4
	TypePointerInputLen    = 1 + 1 + 1
	TypePointerInputLen    = 1 + 1 + 1 + 2*4 + 2*4
	TypePassLen            = 1 + 1
	TypeClipboardReadLen   = 1
	TypeClipboardWriteLen  = 1
diff --git a/io/pointer/pointer.go b/io/pointer/pointer.go
index 3b4326f6..350ee7b6 100644
--- a/io/pointer/pointer.go
+++ b/io/pointer/pointer.go
@@ -63,6 +63,18 @@ type InputOp struct {
	Grab bool
	// Types is a bitwise-or of event types to receive.
	Types Type
	// XScrollRange defines how far the handler can scroll
	// on the horizontal axis.
	XScrollRange ScrollRange
	// YScrollRange defines how far the handler can scroll
	// on the vertical axis.
	YScrollRange ScrollRange
}

// ScrollRange represents a range of scrolling along an axis.
type ScrollRange struct {
	// Min and Max limit the range to [Min,Max] pixels.
	Min, Max int
}

// PassOp sets the pass-through mode.
@@ -190,16 +202,21 @@ func (op CursorNameOp) Add(o *op.Ops) {
	data[0] = byte(opconst.TypeCursor)
}

func (h InputOp) Add(o *op.Ops) {
	if h.Tag == nil {
func (op InputOp) Add(o *op.Ops) {
	if op.Tag == nil {
		panic("Tag must be non-nil")
	}
	data := o.Write1(opconst.TypePointerInputLen, h.Tag)
	data := o.Write1(opconst.TypePointerInputLen, op.Tag)
	data[0] = byte(opconst.TypePointerInput)
	if h.Grab {
	if op.Grab {
		data[1] = 1
	}
	data[2] = byte(h.Types)
	data[2] = byte(op.Types)
	bo := binary.LittleEndian
	bo.PutUint32(data[3:], uint32(op.XScrollRange.Min))
	bo.PutUint32(data[7:], uint32(op.XScrollRange.Max))
	bo.PutUint32(data[11:], uint32(op.YScrollRange.Min))
	bo.PutUint32(data[15:], uint32(op.YScrollRange.Max))
}

func (op PassOp) Add(o *op.Ops) {
diff --git a/io/router/pointer.go b/io/router/pointer.go
index 18d1d921..fb78d6e6 100644
--- a/io/router/pointer.go
+++ b/io/router/pointer.go
@@ -34,7 +34,8 @@ type hitNode struct {
	pass bool

	// For handler nodes.
	tag event.Tag
	tag        event.Tag
	scrollArea f32.Rectangle
}

type cursorNode struct {
@@ -136,11 +137,22 @@ func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents) {
				Grab:  encOp.Data[1] != 0,
				Types: pointer.Type(encOp.Data[2]),
			}
			scrollArea := f32.Rectangle{
				Min: f32.Point{
					X: opDecodeFloat32(encOp.Data[3:]),
					Y: opDecodeFloat32(encOp.Data[11:]),
				},
				Max: f32.Point{
					X: opDecodeFloat32(encOp.Data[7:]),
					Y: opDecodeFloat32(encOp.Data[15:]),
				},
			}
			q.hitTree = append(q.hitTree, hitNode{
				next: state.node,
				area: state.area,
				pass: state.pass,
				tag:  op.Tag,
				next:       state.node,
				area:       state.area,
				pass:       state.pass,
				tag:        op.Tag,
				scrollArea: scrollArea,
			})
			state.node = len(q.hitTree) - 1
			h, ok := q.handlers[op.Tag]
@@ -188,6 +200,32 @@ func (q *pointerQueue) opHit(handlers *[]event.Tag, pos f32.Point) {
	}
}

// Return whether or not handler at pos has a hit.
func (q *pointerQueue) opHitTag(handler event.Tag, pos f32.Point) bool {
	pass := true
	idx := len(q.hitTree) - 1
	for idx >= 0 {
		n := &q.hitTree[idx]
		if !q.hit(n.area, pos) {
			idx--
			continue
		}
		if n.tag == handler && n.scrollArea != (f32.Rectangle{}) {
			p := q.invTransform(n.area, pos)
			if p.In(n.scrollArea) {
				return true
			}
		}
		pass = pass && n.pass
		if pass {
			idx--
		} else {
			idx = n.next
		}
	}
	return false
}

func (q *pointerQueue) invTransform(areaIdx int, p f32.Point) f32.Point {
	if areaIdx == -1 {
		return p
@@ -335,6 +373,9 @@ func (q *pointerQueue) deliverEvent(p *pointerInfo, events *handlerEvents, e poi
		if e.Type&h.types == 0 {
			continue
		}
		if e.Type == pointer.Scroll && !q.opHitTag(k, e.Position) {
			continue
		}
		e := e
		if p.pressed && len(p.handlers) == 1 {
			e.Priority = pointer.Grabbed
diff --git a/io/router/pointer_test.go b/io/router/pointer_test.go
index 82c79642..4b1b8159 100644
--- a/io/router/pointer_test.go
+++ b/io/router/pointer_test.go
@@ -153,10 +153,20 @@ func TestPointerPriority(t *testing.T) {
	var ops op.Ops

	pointer.Rect(image.Rect(0, 0, 100, 100)).Add(&ops)
	pointer.InputOp{Tag: handler1, Types: pointer.Scroll}.Add(&ops)
	pointer.InputOp{
		Tag:          handler1,
		Types:        pointer.Scroll,
		XScrollRange: pointer.ScrollRange{Max: 100},
		YScrollRange: pointer.ScrollRange{Max: 100},
	}.Add(&ops)

	pointer.Rect(image.Rect(0, 0, 100, 50)).Add(&ops)
	pointer.InputOp{Tag: handler2, Types: pointer.Scroll}.Add(&ops)
	pointer.InputOp{
		Tag:          handler2,
		Types:        pointer.Scroll,
		XScrollRange: pointer.ScrollRange{Max: 100},
		YScrollRange: pointer.ScrollRange{Max: 50},
	}.Add(&ops)

	var r Router
	r.Frame(&ops)
@@ -185,6 +195,48 @@ func TestPointerPriority(t *testing.T) {
	assertEventPriorities(t, hev2, pointer.Shared, pointer.Foremost)
}

func TestPointerScroll(t *testing.T) {
	handler1 := new(int)
	handler2 := new(int)
	var ops op.Ops

	pointer.Rect(image.Rect(0, 0, 100, 100)).Add(&ops)
	pointer.InputOp{
		Tag:          handler1,
		Types:        pointer.Scroll,
		XScrollRange: pointer.ScrollRange{Max: 50},
		YScrollRange: pointer.ScrollRange{Max: 50},
	}.Add(&ops)

	pointer.Rect(image.Rect(0, 0, 100, 100)).Add(&ops)
	pointer.InputOp{
		Tag:   handler2,
		Types: pointer.Scroll,
		// handler2 discards scroll events as no range is specified.
	}.Add(&ops)

	var r Router
	r.Frame(&ops)
	r.Queue(
		// Hit handler1.
		pointer.Event{
			Type:     pointer.Scroll,
			Position: f32.Pt(25, 25),
		},
		// No hit.
		pointer.Event{
			Type:     pointer.Scroll,
			Position: f32.Pt(75, 25),
		},
	)
	hev1 := r.Events(handler1)
	hev2 := r.Events(handler2)
	assertEventSequence(t, hev1, pointer.Cancel, pointer.Scroll)
	assertEventSequence(t, hev2, pointer.Cancel)
	assertEventPriorities(t, hev1, pointer.Shared, pointer.Foremost)
	assertEventPriorities(t, hev2, pointer.Shared)
}

func TestPointerEnterLeave(t *testing.T) {
	handler1 := new(int)
	handler2 := new(int)
diff --git a/layout/list.go b/layout/list.go
index 1614ea14..72923034 100644
--- a/layout/list.go
+++ b/layout/list.go
@@ -285,7 +285,10 @@ func (l *List) layout(ops *op.Ops, macro op.MacroOp) Dimensions {
	call := macro.Stop()
	defer op.Save(ops).Load()
	pointer.Rect(image.Rectangle{Max: dims}).Add(ops)
	l.scroll.Add(ops)
	var xScroll, yScroll pointer.ScrollRange
	xScroll.Max = dims.X
	yScroll.Max = dims.Y
	l.scroll.Add(ops, xScroll, yScroll)
	call.Add(ops)
	return Dimensions{Size: dims}
}
diff --git a/widget/editor.go b/widget/editor.go
index 99c66b96..8f49d35b 100644
--- a/widget/editor.go
+++ b/widget/editor.go
@@ -547,7 +547,12 @@ func (e *Editor) layout(gtx layout.Context) layout.Dimensions {
	r.Max.X += pointerPadding
	pointer.Rect(r).Add(gtx.Ops)
	pointer.CursorNameOp{Name: pointer.CursorText}.Add(gtx.Ops)
	e.scroller.Add(gtx.Ops)
	var xScroll, yScroll pointer.ScrollRange
	if e.focused {
		xScroll.Max = e.viewSize.X
		yScroll.Max = e.viewSize.Y
	}
	e.scroller.Add(gtx.Ops, xScroll, yScroll)
	e.clicker.Add(gtx.Ops)
	e.dragger.Add(gtx.Ops)
	e.caret.on = false
-- 
2.30.1
builds.sr.ht
gio/patches: SUCCESS in 28m31s

[io/router: add support for ScrollRange][0] from [~pierrec][1]

[0]: https://lists.sr.ht/~eliasnaur/gio-patches/patches/20512
[1]: mailto:pierre.curto@gmail.com

✓ #438270 SUCCESS gio/patches/linux.yml   https://builds.sr.ht/~eliasnaur/job/438270
✓ #438271 SUCCESS gio/patches/openbsd.yml https://builds.sr.ht/~eliasnaur/job/438271
✓ #438269 SUCCESS gio/patches/freebsd.yml https://builds.sr.ht/~eliasnaur/job/438269
✓ #438268 SUCCESS gio/patches/apple.yml   https://builds.sr.ht/~eliasnaur/job/438268