~eliasnaur/gio-patches

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
4 3

[PATCH gio] io/pointer: support nested scrollables

~pierrec
Details
Message ID
<161717202830.20982.4893106447422555032-0@git.sr.ht>
DKIM signature
missing
Download raw message
Patch: +159 -20
From: pierre <pierre.curto@gmail.com>

Fixes #185.

Signed-off-by: pierre <pierre.curto@gmail.com>
---
Sorry for my misunderstanding about what you and Egon meant!

 gesture/gesture.go        | 13 +++++----
 internal/opconst/ops.go   |  2 +-
 io/pointer/pointer.go     | 26 ++++++++++++++----
 io/router/pointer.go      | 55 ++++++++++++++++++++++++++++++++++++++-
 io/router/pointer_test.go | 50 ++++++++++++++++++++++++++++++++---
 layout/list.go            | 21 ++++++++++++++-
 widget/editor.go          | 12 ++++++++-
 7 files changed, 159 insertions(+), 20 deletions(-)

diff --git a/gesture/gesture.go b/gesture/gesture.go
index c43b321..6f1c04b 100644
--- a/gesture/gesture.go
+++ b/gesture/gesture.go
@@ -10,6 +10,7 @@ and scrolling.
package gesture

import (
	"image"
	"math"
	"runtime"
	"time"
@@ -205,11 +206,12 @@ 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, bounds image.Rectangle) {
	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,
		ScrollBounds: bounds,
	}
	oph.Add(ops)
	if s.flinger.Active() {
@@ -265,9 +267,6 @@ func (s *Scroll) Scroll(cfg unit.Metric, q event.Queue, t time.Time, axis Axis)
			s.dragging = false
			s.grab = false
		case pointer.Scroll:
			if e.Priority < pointer.Foremost {
				continue
			}
			switch s.axis {
			case Horizontal:
				s.scroll += e.Scroll.X
diff --git a/internal/opconst/ops.go b/internal/opconst/ops.go
index 3c2ab62..db9dd8d 100644
--- a/internal/opconst/ops.go
+++ b/internal/opconst/ops.go
@@ -46,7 +46,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 bc36387..b495f06 100644
--- a/io/pointer/pointer.go
+++ b/io/pointer/pointer.go
@@ -4,6 +4,7 @@ package pointer

import (
	"encoding/binary"
	"fmt"
	"image"
	"strings"
	"time"
@@ -63,6 +64,12 @@ type InputOp struct {
	Grab bool
	// Types is a bitwise-or of event types to receive.
	Types Type
	// ScrollBounds describe the maximum scrollable distances in both
	// axes. Specifically, any Event e delivered to Tag will satisfy
	//
	// ScrollBounds.Min.X <= e.Scroll.X <= ScrollBounds.Max.X (horizontal axis)
	// ScrollBounds.Min.Y <= e.Scroll.Y <= ScrollBounds.Max.Y (vertical axis)
	ScrollBounds image.Rectangle
}

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

func (h InputOp) Add(o *op.Ops) {
	if h.Tag == nil {
// Add panics if the scroll range does not contain zero.
func (op InputOp) Add(o *op.Ops) {
	if op.Tag == nil {
		panic("Tag must be non-nil")
	}
	data := o.Write1(opconst.TypePointerInputLen, h.Tag)
	if b := op.ScrollBounds; b.Min.X > 0 || b.Max.X < 0 || b.Min.Y > 0 || b.Max.Y < 0 {
		panic(fmt.Errorf("invalid scroll range value %v", b))
	}
	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.ScrollBounds.Min.X))
	bo.PutUint32(data[7:], uint32(op.ScrollBounds.Min.Y))
	bo.PutUint32(data[11:], uint32(op.ScrollBounds.Max.X))
	bo.PutUint32(data[15:], uint32(op.ScrollBounds.Max.Y))
}

func (op PassOp) Add(o *op.Ops) {
diff --git a/io/router/pointer.go b/io/router/pointer.go
index 591a92f..2fb203e 100644
--- a/io/router/pointer.go
+++ b/io/router/pointer.go
@@ -4,6 +4,7 @@ package router

import (
	"encoding/binary"
	"image"

	"gioui.org/f32"
	"gioui.org/internal/opconst"
@@ -59,6 +60,8 @@ type pointerHandler struct {
	active    bool
	wantsGrab bool
	types     pointer.Type
	// min and max horizontal/vertical scroll
	scrollRange image.Rectangle
}

type areaOp struct {
@@ -155,6 +158,17 @@ func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents) {
			h.area = state.area
			h.wantsGrab = h.wantsGrab || op.Grab
			h.types = h.types | op.Types
			bo := binary.LittleEndian.Uint32
			h.scrollRange = image.Rectangle{
				Min: image.Point{
					X: int(int32(bo(encOp.Data[3:]))),
					Y: int(int32(bo(encOp.Data[7:]))),
				},
				Max: image.Point{
					X: int(int32(bo(encOp.Data[11:]))),
					Y: int(int32(bo(encOp.Data[15:]))),
				},
			}
		case opconst.TypeCursor:
			q.cursors = append(q.cursors, cursorNode{
				name: encOp.Refs[0].(pointer.CursorName),
@@ -319,7 +333,11 @@ func (q *pointerQueue) Push(e pointer.Event, events *handlerEvents) {
	if e.Type == pointer.Press {
		p.pressed = true
	}
	if e.Type != pointer.Release {
	switch e.Type {
	case pointer.Release:
	case pointer.Scroll:
		q.deliverScrollEvent(p, events, e)
	default:
		q.deliverEvent(p, events, e)
	}
	if !p.pressed && len(p.entered) == 0 {
@@ -349,6 +367,31 @@ func (q *pointerQueue) deliverEvent(p *pointerInfo, events *handlerEvents, e poi
	}
}

func (q *pointerQueue) deliverScrollEvent(p *pointerInfo, events *handlerEvents, e pointer.Event) {
	foremost := true
	if p.pressed && len(p.handlers) == 1 {
		e.Priority = pointer.Grabbed
		foremost = false
	}
	var sx, sy = e.Scroll.X, e.Scroll.Y
	for _, k := range p.handlers {
		if sx == 0 && sy == 0 {
			return
		}
		h := q.handlers[k]
		// Distribute the scroll to the handler based on its ScrollRange.
		sx, e.Scroll.X = setScrollEvent(sx, h.scrollRange.Min.X, h.scrollRange.Max.X)
		sy, e.Scroll.Y = setScrollEvent(sy, h.scrollRange.Min.Y, h.scrollRange.Max.Y)
		e := e
		if foremost {
			foremost = false
			e.Priority = pointer.Foremost
		}
		e.Position = q.invTransform(h.area, e.Position)
		events.Add(k, e)
	}
}

func (q *pointerQueue) deliverEnterLeaveEvents(p *pointerInfo, events *handlerEvents, e pointer.Event) {
	q.scratch = q.scratch[:0]
	q.opHit(&q.scratch, e.Position)
@@ -453,3 +496,13 @@ func (op *areaOp) Hit(pos f32.Point) bool {
		panic("invalid area kind")
	}
}

func setScrollEvent(scroll float32, min, max int) (left, scrolled float32) {
	if v := float32(max); scroll > v {
		return scroll - v, v
	}
	if v := float32(min); scroll < v {
		return scroll - v, v
	}
	return 0, scroll
}
diff --git a/io/router/pointer_test.go b/io/router/pointer_test.go
index c0738b4..1c60f36 100644
--- a/io/router/pointer_test.go
+++ b/io/router/pointer_test.go
@@ -150,39 +150,73 @@ func TestPointerTypes(t *testing.T) {
func TestPointerPriority(t *testing.T) {
	handler1 := new(int)
	handler2 := new(int)
	handler3 := new(int)
	var ops op.Ops

	st := op.Save(&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,
		ScrollBounds: image.Rectangle{Max: image.Point{X: 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,
		ScrollBounds: image.Rectangle{Max: image.Point{X: 20}},
	}.Add(&ops)
	st.Load()

	pointer.Rect(image.Rect(0, 100, 100, 200)).Add(&ops)
	pointer.InputOp{
		Tag:          handler3,
		Types:        pointer.Scroll,
		ScrollBounds: image.Rectangle{Min: image.Point{X: -20, Y: -40}},
	}.Add(&ops)

	var r Router
	r.Frame(&ops)
	r.Queue(
		// Hit both handlers.
		// Hit handler 1 and 2.
		pointer.Event{
			Type:     pointer.Scroll,
			Position: f32.Pt(50, 25),
			Scroll:   f32.Pt(50, 0),
		},
		// Hit handler 1.
		pointer.Event{
			Type:     pointer.Scroll,
			Position: f32.Pt(50, 75),
			Scroll:   f32.Pt(50, 50),
		},
		// Hit handler 3.
		pointer.Event{
			Type:     pointer.Scroll,
			Position: f32.Pt(50, 150),
			Scroll:   f32.Pt(-30, -30),
		},
		// Hit no handlers.
		pointer.Event{
			Type:     pointer.Scroll,
			Position: f32.Pt(50, 125),
			Position: f32.Pt(50, 225),
		},
	)

	hev1 := r.Events(handler1)
	hev2 := r.Events(handler2)
	hev3 := r.Events(handler3)
	assertEventSequence(t, hev1, pointer.Cancel, pointer.Scroll, pointer.Scroll)
	assertEventSequence(t, hev2, pointer.Cancel, pointer.Scroll)
	assertEventSequence(t, hev3, pointer.Cancel, pointer.Scroll)
	assertEventPriorities(t, hev1, pointer.Shared, pointer.Shared, pointer.Foremost)
	assertEventPriorities(t, hev2, pointer.Shared, pointer.Foremost)
	assertEventPriorities(t, hev3, pointer.Shared, pointer.Foremost)
	assertScrollEvent(t, hev1[1], f32.Pt(30, 0))
	assertScrollEvent(t, hev2[1], f32.Pt(20, 0))
	assertScrollEvent(t, hev1[2], f32.Pt(50, 0))
	assertScrollEvent(t, hev3[1], f32.Pt(-20, -30))
}

func TestPointerEnterLeave(t *testing.T) {
@@ -628,6 +662,14 @@ func assertEventPriorities(t *testing.T, events []event.Event, prios ...pointer.
	}
}

// assertScrollEvent checks that the event scrolling amount matches the supplied value.
func assertScrollEvent(t *testing.T, ev event.Event, scroll f32.Point) {
	t.Helper()
	if got, want := ev.(pointer.Event).Scroll, scroll; got != want {
		t.Errorf("got %v; want %v", got, want)
	}
}

func BenchmarkRouterAdd(b *testing.B) {
	// Set this to the number of overlapping handlers that you want to
	// evaluate performance for. Typical values for the example applications
diff --git a/layout/list.go b/layout/list.go
index 1614ea1..91b2e79 100644
--- a/layout/list.go
+++ b/layout/list.go
@@ -285,7 +285,26 @@ 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 min, max int
	if o := l.Position.Offset; o > 0 {
		// Use the undisplayed size as scroll boundary.
		min = -o
	} else if l.Position.First > 0 {
		min = -int(inf)
	}
	if o := l.Position.OffsetLast; o < 0 {
		// Use the undisplayed size as scroll boundary.
		max = -o
	} else if l.Position.First+l.Position.Count < l.len {
		max = int(inf)
	}
	scrollRange := image.Rectangle{
		Min: l.Axis.Convert(image.Pt(min, 0)),
		Max: l.Axis.Convert(image.Pt(max, 0)),
	}
	l.scroll.Add(ops, scrollRange)

	call.Add(ops)
	return Dimensions{Size: dims}
}
diff --git a/widget/editor.go b/widget/editor.go
index 99c66b9..19aef46 100644
--- a/widget/editor.go
+++ b/widget/editor.go
@@ -547,7 +547,17 @@ 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 scrollRange image.Rectangle
	if e.SingleLine {
		scrollRange.Min.X = -e.scrollOff.X
		scrollRange.Max.X = max(0, e.dims.Size.X-(e.scrollOff.X+e.viewSize.X))
	} else {
		scrollRange.Min.Y = -e.scrollOff.Y
		scrollRange.Max.Y = max(0, e.dims.Size.Y-(e.scrollOff.Y+e.viewSize.Y))
	}
	e.scroller.Add(gtx.Ops, scrollRange)

	e.clicker.Add(gtx.Ops)
	e.dragger.Add(gtx.Ops)
	e.caret.on = false
-- 
2.30.2

[gio/patches] build success

builds.sr.ht
Details
Message ID
<CABC2AKKE5SD.B6O7I5PF6XX7@cirno>
In-Reply-To
<161717202830.20982.4893106447422555032-0@git.sr.ht> (view parent)
DKIM signature
missing
Download raw message
gio/patches: SUCCESS in 19m52s

[io/pointer: support nested scrollables][0] from [~pierrec][1]

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

✓ #473749 SUCCESS gio/patches/openbsd.yml https://builds.sr.ht/~eliasnaur/job/473749
✓ #473748 SUCCESS gio/patches/linux.yml   https://builds.sr.ht/~eliasnaur/job/473748
✓ #473747 SUCCESS gio/patches/freebsd.yml https://builds.sr.ht/~eliasnaur/job/473747
✓ #473746 SUCCESS gio/patches/apple.yml   https://builds.sr.ht/~eliasnaur/job/473746
Details
Message ID
<CABDLKVEYBK8.25K80ODLDK5FT@testmac>
In-Reply-To
<161717202830.20982.4893106447422555032-0@git.sr.ht> (view parent)
DKIM signature
fail
Download raw message
DKIM signature: fail
Merged, with the nits below applied. Thank you very much for fixing this
important issue!

Elias

On Wed Mar 31, 2021 at 08:25 CEST, ~pierrec wrote:
> From: pierre <pierre.curto@gmail.com>
>
> Fixes #185.
>
> Signed-off-by: pierre <pierre.curto@gmail.com>
> ---
> Sorry for my misunderstanding about what you and Egon meant!
>

You don't need this cover letter anymore :)

> diff --git a/layout/list.go b/layout/list.go
> index 1614ea1..91b2e79 100644
> --- a/layout/list.go
> +++ b/layout/list.go
> @@ -285,7 +285,26 @@ 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 min, max int
> +	if o := l.Position.Offset; o > 0 {
> +		// Use the undisplayed size as scroll boundary.

Nit:

// Use the size of the invisible part as scroll boundary.

> +		min = -o
> +	} else if l.Position.First > 0 {
> +		min = -int(inf)

Nit: the cast is redundant.

> +	}
> +	if o := l.Position.OffsetLast; o < 0 {
> +		// Use the undisplayed size as scroll boundary.

Nit: redundant comment.

> +		max = -o
> +	} else if l.Position.First+l.Position.Count < l.len {
> +		max = int(inf)
> +	}
> +	scrollRange := image.Rectangle{
> +		Min: l.Axis.Convert(image.Pt(min, 0)),
> +		Max: l.Axis.Convert(image.Pt(max, 0)),
> +	}
> +	l.scroll.Add(ops, scrollRange)
> +
>  	call.Add(ops)
>  	return Dimensions{Size: dims}
>  }
Details
Message ID
<CAG3idSeT=tBd6seGce7pXVxBEVsqhzTH2fzx8PRANruRBjaRow@mail.gmail.com>
In-Reply-To
<CABDLKVEYBK8.25K80ODLDK5FT@testmac> (view parent)
DKIM signature
pass
Download raw message
Thanks a lot for your patience Elias :)!

Le mer. 31 mars 2021 à 09:59, Elias Naur <mail@eliasnaur.com> a écrit :
>
> Merged, with the nits below applied. Thank you very much for fixing this
> important issue!
>
> Elias
>
> On Wed Mar 31, 2021 at 08:25 CEST, ~pierrec wrote:
> > From: pierre <pierre.curto@gmail.com>
> >
> > Fixes #185.
> >
> > Signed-off-by: pierre <pierre.curto@gmail.com>
> > ---
> > Sorry for my misunderstanding about what you and Egon meant!
> >
>
> You don't need this cover letter anymore :)

I dont know why srchut keep adding it :/.

>
> > diff --git a/layout/list.go b/layout/list.go
> > index 1614ea1..91b2e79 100644
> > --- a/layout/list.go
> > +++ b/layout/list.go
> > @@ -285,7 +285,26 @@ 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 min, max int
> > +     if o := l.Position.Offset; o > 0 {
> > +             // Use the undisplayed size as scroll boundary.
>
> Nit:
>
> // Use the size of the invisible part as scroll boundary.
>
> > +             min = -o
> > +     } else if l.Position.First > 0 {
> > +             min = -int(inf)
>
> Nit: the cast is redundant.
>
> > +     }
> > +     if o := l.Position.OffsetLast; o < 0 {
> > +             // Use the undisplayed size as scroll boundary.
>
> Nit: redundant comment.
>
> > +             max = -o
> > +     } else if l.Position.First+l.Position.Count < l.len {
> > +             max = int(inf)
> > +     }
> > +     scrollRange := image.Rectangle{
> > +             Min: l.Axis.Convert(image.Pt(min, 0)),
> > +             Max: l.Axis.Convert(image.Pt(max, 0)),
> > +     }
> > +     l.scroll.Add(ops, scrollRange)
> > +
> >       call.Add(ops)
> >       return Dimensions{Size: dims}
> >  }
Details
Message ID
<CABDPQP4QSL4.M0O453MV8I2V@testmac>
In-Reply-To
<CAG3idSeT=tBd6seGce7pXVxBEVsqhzTH2fzx8PRANruRBjaRow@mail.gmail.com> (view parent)
DKIM signature
pass
Download raw message
On Wed Mar 31, 2021 at 10:03 CEST, Pierre Curto wrote:
> Thanks a lot for your patience Elias :)!
>
> Le mer. 31 mars 2021 à 09:59, Elias Naur <mail@eliasnaur.com> a écrit :
> >
> > Merged, with the nits below applied. Thank you very much for fixing this
> > important issue!
> >
> > Elias
> >
> > On Wed Mar 31, 2021 at 08:25 CEST, ~pierrec wrote:
> > > From: pierre <pierre.curto@gmail.com>
> > >
> > > Fixes #185.
> > >
> > > Signed-off-by: pierre <pierre.curto@gmail.com>
> > > ---
> > > Sorry for my misunderstanding about what you and Egon meant!
> > >
> >
> > You don't need this cover letter anymore :)
>
> I dont know why srchut keep adding it :/.
>

I believe the cover letter is an option on the first page of the github
send email webflow.
Reply to thread Export thread (mbox)