mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-06 18:05:35 +00:00
io/pointer: [API] split scroll bounds into two separate axes
A single image.Rectangle for the scroll bounds introduced a subtle issue with zero area rectangles (see #572). To avoid that and similar issues, split the bounds into two separate one-dimensional ranges. Fixes: https://todo.sr.ht/~eliasnaur/gio/572 Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
+5
-4
@@ -271,12 +271,13 @@ func (s *Scroll) Stop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update state and report the scroll distance along axis.
|
// Update state and report the scroll distance along axis.
|
||||||
func (s *Scroll) Update(cfg unit.Metric, q input.Source, t time.Time, axis Axis, bounds image.Rectangle) int {
|
func (s *Scroll) Update(cfg unit.Metric, q input.Source, t time.Time, axis Axis, scrollx, scrolly pointer.ScrollRange) int {
|
||||||
total := 0
|
total := 0
|
||||||
f := pointer.Filter{
|
f := pointer.Filter{
|
||||||
Target: s,
|
Target: s,
|
||||||
Kinds: pointer.Press | pointer.Drag | pointer.Release | pointer.Scroll | pointer.Cancel,
|
Kinds: pointer.Press | pointer.Drag | pointer.Release | pointer.Scroll | pointer.Cancel,
|
||||||
ScrollBounds: bounds,
|
ScrollX: scrollx,
|
||||||
|
ScrollY: scrolly,
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
evt, ok := q.Event(f)
|
evt, ok := q.Event(f)
|
||||||
|
|||||||
@@ -251,9 +251,10 @@ func TestFocusScroll(t *testing.T) {
|
|||||||
filters := []event.Filter{
|
filters := []event.Filter{
|
||||||
key.FocusFilter{Target: h},
|
key.FocusFilter{Target: h},
|
||||||
pointer.Filter{
|
pointer.Filter{
|
||||||
Target: h,
|
Target: h,
|
||||||
Kinds: pointer.Scroll,
|
Kinds: pointer.Scroll,
|
||||||
ScrollBounds: image.Rect(-100, -100, 100, 100),
|
ScrollX: pointer.ScrollRange{Min: -100, Max: +100},
|
||||||
|
ScrollY: pointer.ScrollRange{Min: -100, Max: +100},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
events(r, -1, filters...)
|
events(r, -1, filters...)
|
||||||
|
|||||||
+7
-5
@@ -72,7 +72,7 @@ type pointerHandler struct {
|
|||||||
type pointerFilter struct {
|
type pointerFilter struct {
|
||||||
kinds pointer.Kind
|
kinds pointer.Kind
|
||||||
// min and max horizontal/vertical scroll
|
// min and max horizontal/vertical scroll
|
||||||
scrollRange image.Rectangle
|
scrollX, scrollY pointer.ScrollRange
|
||||||
|
|
||||||
sourceMimes []string
|
sourceMimes []string
|
||||||
targetMimes []string
|
targetMimes []string
|
||||||
@@ -297,7 +297,8 @@ func (p *pointerFilter) Add(f event.Filter) {
|
|||||||
p.targetMimes = append(p.targetMimes, f.Type)
|
p.targetMimes = append(p.targetMimes, f.Type)
|
||||||
case pointer.Filter:
|
case pointer.Filter:
|
||||||
p.kinds = p.kinds | f.Kinds
|
p.kinds = p.kinds | f.Kinds
|
||||||
p.scrollRange = p.scrollRange.Union(f.ScrollBounds)
|
p.scrollX = p.scrollX.Union(f.ScrollX)
|
||||||
|
p.scrollY = p.scrollY.Union(f.ScrollY)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,7 +326,8 @@ func (p *pointerFilter) Matches(e event.Event) bool {
|
|||||||
|
|
||||||
func (p *pointerFilter) Merge(p2 pointerFilter) {
|
func (p *pointerFilter) Merge(p2 pointerFilter) {
|
||||||
p.kinds = p.kinds | p2.kinds
|
p.kinds = p.kinds | p2.kinds
|
||||||
p.scrollRange = p.scrollRange.Union(p2.scrollRange)
|
p.scrollX = p.scrollX.Union(p2.scrollX)
|
||||||
|
p.scrollY = p.scrollY.Union(p2.scrollY)
|
||||||
p.sourceMimes = append(p.sourceMimes, p2.sourceMimes...)
|
p.sourceMimes = append(p.sourceMimes, p2.sourceMimes...)
|
||||||
p.targetMimes = append(p.targetMimes, p2.targetMimes...)
|
p.targetMimes = append(p.targetMimes, p2.targetMimes...)
|
||||||
}
|
}
|
||||||
@@ -333,8 +335,8 @@ func (p *pointerFilter) Merge(p2 pointerFilter) {
|
|||||||
// clampScroll splits a scroll distance in the remaining scroll and the
|
// clampScroll splits a scroll distance in the remaining scroll and the
|
||||||
// scroll accepted by the filter.
|
// scroll accepted by the filter.
|
||||||
func (p *pointerFilter) clampScroll(scroll f32.Point) (left, scrolled f32.Point) {
|
func (p *pointerFilter) clampScroll(scroll f32.Point) (left, scrolled f32.Point) {
|
||||||
left.X, scrolled.X = clampSplit(scroll.X, p.scrollRange.Min.X, p.scrollRange.Max.X)
|
left.X, scrolled.X = clampSplit(scroll.X, p.scrollX.Min, p.scrollX.Max)
|
||||||
left.Y, scrolled.Y = clampSplit(scroll.Y, p.scrollRange.Min.Y, p.scrollRange.Max.Y)
|
left.Y, scrolled.Y = clampSplit(scroll.Y, p.scrollY.Min, p.scrollY.Max)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -300,9 +300,9 @@ func TestPointerPriority(t *testing.T) {
|
|||||||
r1 := clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops)
|
r1 := clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops)
|
||||||
f1 := func(t event.Tag) event.Filter {
|
f1 := func(t event.Tag) event.Filter {
|
||||||
return pointer.Filter{
|
return pointer.Filter{
|
||||||
Target: t,
|
Target: t,
|
||||||
Kinds: pointer.Scroll,
|
Kinds: pointer.Scroll,
|
||||||
ScrollBounds: image.Rectangle{Max: image.Point{X: 100}},
|
ScrollX: pointer.ScrollRange{Max: 100},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
events(&r, -1, f1(handler1))
|
events(&r, -1, f1(handler1))
|
||||||
@@ -311,9 +311,9 @@ func TestPointerPriority(t *testing.T) {
|
|||||||
r2 := clip.Rect(image.Rect(0, 0, 100, 50)).Push(&ops)
|
r2 := clip.Rect(image.Rect(0, 0, 100, 50)).Push(&ops)
|
||||||
f2 := func(t event.Tag) event.Filter {
|
f2 := func(t event.Tag) event.Filter {
|
||||||
return pointer.Filter{
|
return pointer.Filter{
|
||||||
Target: t,
|
Target: t,
|
||||||
Kinds: pointer.Scroll,
|
Kinds: pointer.Scroll,
|
||||||
ScrollBounds: image.Rectangle{Max: image.Point{X: 20}},
|
ScrollX: pointer.ScrollRange{Max: 20},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
events(&r, -1, f2(handler2))
|
events(&r, -1, f2(handler2))
|
||||||
@@ -324,9 +324,10 @@ func TestPointerPriority(t *testing.T) {
|
|||||||
r3 := clip.Rect(image.Rect(0, 100, 100, 200)).Push(&ops)
|
r3 := clip.Rect(image.Rect(0, 100, 100, 200)).Push(&ops)
|
||||||
f3 := func(t event.Tag) event.Filter {
|
f3 := func(t event.Tag) event.Filter {
|
||||||
return pointer.Filter{
|
return pointer.Filter{
|
||||||
Target: t,
|
Target: t,
|
||||||
Kinds: pointer.Scroll,
|
Kinds: pointer.Scroll,
|
||||||
ScrollBounds: image.Rectangle{Min: image.Point{X: -20, Y: -40}},
|
ScrollX: pointer.ScrollRange{Min: -20},
|
||||||
|
ScrollY: pointer.ScrollRange{Min: -40},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
events(&r, -1, f3(handler3))
|
events(&r, -1, f3(handler3))
|
||||||
|
|||||||
+19
-6
@@ -3,7 +3,6 @@
|
|||||||
package pointer
|
package pointer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"image"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -61,12 +60,19 @@ type Filter struct {
|
|||||||
Target event.Tag
|
Target event.Tag
|
||||||
// Kinds is a bitwise-or of event types to match.
|
// Kinds is a bitwise-or of event types to match.
|
||||||
Kinds Kind
|
Kinds Kind
|
||||||
// ScrollBounds describe the maximum scrollable distances in both
|
// ScrollX and ScrollY constrain the range of scrolling events delivered
|
||||||
// axes. Specifically, any Event e delivered to Tag will satisfy
|
// to Target. Specifically, any Event e delivered to Tag will satisfy
|
||||||
//
|
//
|
||||||
// ScrollBounds.Min.X <= e.Scroll.X <= ScrollBounds.Max.X (horizontal axis)
|
// ScrollX.Min <= e.Scroll.X <= ScrollX.Max (horizontal axis)
|
||||||
// ScrollBounds.Min.Y <= e.Scroll.Y <= ScrollBounds.Max.Y (vertical axis)
|
// ScrollY.Min <= e.Scroll.Y <= ScrollY.Max (vertical axis)
|
||||||
ScrollBounds image.Rectangle
|
ScrollX ScrollRange
|
||||||
|
ScrollY ScrollRange
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScrollRange describes the range of scrolling distances in an
|
||||||
|
// axis.
|
||||||
|
type ScrollRange struct {
|
||||||
|
Min, Max int
|
||||||
}
|
}
|
||||||
|
|
||||||
// GrabCmd requests a pointer grab on the pointer identified by ID.
|
// GrabCmd requests a pointer grab on the pointer identified by ID.
|
||||||
@@ -219,6 +225,13 @@ const (
|
|||||||
ButtonTertiary
|
ButtonTertiary
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (s ScrollRange) Union(s2 ScrollRange) ScrollRange {
|
||||||
|
return ScrollRange{
|
||||||
|
Min: min(s.Min, s2.Min),
|
||||||
|
Max: max(s.Max, s2.Max),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Push the current pass mode to the pass stack and set the pass mode.
|
// Push the current pass mode to the pass stack and set the pass mode.
|
||||||
func (p PassOp) Push(o *op.Ops) PassStack {
|
func (p PassOp) Push(o *op.Ops) PassStack {
|
||||||
id, mid := ops.PushOp(&o.Internal, ops.PassStack)
|
id, mid := ops.PushOp(&o.Internal, ops.PassStack)
|
||||||
|
|||||||
+6
-4
@@ -7,6 +7,7 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
|
|
||||||
"gioui.org/gesture"
|
"gioui.org/gesture"
|
||||||
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
"gioui.org/op/clip"
|
"gioui.org/op/clip"
|
||||||
)
|
)
|
||||||
@@ -158,11 +159,12 @@ func (l *List) update(gtx Context) {
|
|||||||
max = 0
|
max = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scrollRange := image.Rectangle{
|
xrange := pointer.ScrollRange{Min: min, Max: max}
|
||||||
Min: l.Axis.Convert(image.Pt(min, 0)),
|
yrange := pointer.ScrollRange{}
|
||||||
Max: l.Axis.Convert(image.Pt(max, 0)),
|
if l.Axis == Vertical {
|
||||||
|
xrange, yrange = yrange, xrange
|
||||||
}
|
}
|
||||||
d := l.scroll.Update(gtx.Metric, gtx.Source, gtx.Now, gesture.Axis(l.Axis), scrollRange)
|
d := l.scroll.Update(gtx.Metric, gtx.Source, gtx.Now, gesture.Axis(l.Axis), xrange, yrange)
|
||||||
l.scrollDelta = d
|
l.scrollDelta = d
|
||||||
l.Position.Offset += d
|
l.Position.Offset += d
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-6
@@ -228,19 +228,19 @@ func (e *Editor) processPointer(gtx layout.Context) (EditorEvent, bool) {
|
|||||||
axis = gesture.Vertical
|
axis = gesture.Vertical
|
||||||
smin, smax = sbounds.Min.Y, sbounds.Max.Y
|
smin, smax = sbounds.Min.Y, sbounds.Max.Y
|
||||||
}
|
}
|
||||||
var scrollRange image.Rectangle
|
var scrollX, scrollY pointer.ScrollRange
|
||||||
textDims := e.text.FullDimensions()
|
textDims := e.text.FullDimensions()
|
||||||
visibleDims := e.text.Dimensions()
|
visibleDims := e.text.Dimensions()
|
||||||
if e.SingleLine {
|
if e.SingleLine {
|
||||||
scrollOffX := e.text.ScrollOff().X
|
scrollOffX := e.text.ScrollOff().X
|
||||||
scrollRange.Min.X = min(-scrollOffX, 0)
|
scrollX.Min = min(-scrollOffX, 0)
|
||||||
scrollRange.Max.X = max(0, textDims.Size.X-(scrollOffX+visibleDims.Size.X))
|
scrollX.Max = max(0, textDims.Size.X-(scrollOffX+visibleDims.Size.X))
|
||||||
} else {
|
} else {
|
||||||
scrollOffY := e.text.ScrollOff().Y
|
scrollOffY := e.text.ScrollOff().Y
|
||||||
scrollRange.Min.Y = -scrollOffY
|
scrollY.Min = -scrollOffY
|
||||||
scrollRange.Max.Y = max(0, textDims.Size.Y-(scrollOffY+visibleDims.Size.Y))
|
scrollY.Max = max(0, textDims.Size.Y-(scrollOffY+visibleDims.Size.Y))
|
||||||
}
|
}
|
||||||
sdist := e.scroller.Update(gtx.Metric, gtx.Source, gtx.Now, axis, scrollRange)
|
sdist := e.scroller.Update(gtx.Metric, gtx.Source, gtx.Now, axis, scrollX, scrollY)
|
||||||
var soff int
|
var soff int
|
||||||
if e.SingleLine {
|
if e.SingleLine {
|
||||||
e.text.ScrollRel(sdist, 0)
|
e.text.ScrollRel(sdist, 0)
|
||||||
|
|||||||
Reference in New Issue
Block a user