This commit fixes a visual misalignment in scrollbars resulting from subtle differences
in the semantics of layout.Stack and layout.Background. layout.Stack will position expanded
children according to their minimum constraint regardless of their returned size, whereas
layout.Background uses their returned size. This means that layout.Expanded widgets returning
zero dimensions are positioned correctly, but they break when converted to use layout.Background.
This commit fixes the problem by returning correct dimensions from the scrollbar track.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This matches the convention of other state update methods. While here, remove useless
dimensions return. The update doesn't draw anything, so there are no dimensions involved.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
It's relatively common to create a widget and then add a background to
it. Using layout.Stack causes bunch of heap allocs, which we would like
to avoid whenever we can.
This adds layout.Background which is roughly the same as:
layout.Stack{Alignment: layout.C}.Layout(gtx,
layout.Expanded(background),
layout.Stacked(widget)
)
goos: windows
goarch: amd64
pkg: gioui.org/layout
cpu: AMD Ryzen Threadripper 2950X 16-Core Processor
│ Stack │ Background │
│ sec/op │ sec/op vs base │
*-32 203.80n ± 1% 83.36n ± 3% -59.09% (p=0.000 n=10)
│ Stack │ Background │
│ B/op │ B/op vs base │
*-32 48.00 ± 0% 0.00 ± 0% -100.00% (p=0.000 n=10)
│ Stack │ Background │
│ allocs/op │ allocs/op vs base │
*-32 2.000 ± 0% 0.000 ± 0% -100.00% (p=0.000 n=10)
Signed-off-by: Egon Elbre <egonelbre@gmail.com>
This commit updates the logic that computes scroll viewport coordinates to correctly
consume layout.Position.OffsetLast, which was previously ignored. The impact of ignoring
that field was that dragging on a scroll indicator could sometimes fail to reach the
end of the list.
I've updated the logic to consume that field, which increased the amount of visual
jitter in the position of the scrollbar. I then also added a mechanism for smoothing
the jitter by using both methods of deriving the viewport and synthesizing a viewport
from both.
This new strategy exhibits a lower standard deviation than the other options on each of:
- the length of the scroll indicator
- the change in the start coordinate of the viewport when scrolling smoothly
- the change in the end coordinate of the viewport when scrolling smoothly
Fixes: https://todo.sr.ht/~eliasnaur/gio/504
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
Egon pointed out that the current default is unusable on touch screens in Slack, so this
change should hopefully ensure the indicator is interactable on touch devices.
I considered expanding the minor axis dimensions as well, but I don't know what value to
use. The 38DP default would be enormous on non-mobile displays if we made that the default
width.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
The unit.Value is a struct and thus more inconvenient to use than its
underlying float32 type. In addition, most uses don't need a general
value, but rather a specific unit given by the context. This change
replaces unit.Value with two float32 units, Dp and Sp. It also changes
variables and parameters of unit.Value to a specific unit type matching
the context. That is, unit.Dp everywhere except for text sizes which are
in Sp.
Switching to typed float32s has multiple advantages
- They can be constants:
const touchSlop = unit.Dp(16)
- Casting untyped constants is no longer necessary:
insets := layout.UniformInset(16)
- Calculation with values is natural:
func (s ScrollbarStyle) Width() unit.Dp {
return s.Indicator.MinorWidth + s.Track.MinorPadding + s.Track.MinorPadding
}
The main API change is that calls to gtx.Px must be replaced with either
gtx.Dp or gtx.Sp depending on the unit.
Idea by Christophe Meessen.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
Like the change to op.Offset before this, clip.RRect and UniformRRect
is usually used with integer coordinates. Change to integer coordinates
to eliminate many useless conversions to float32.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
op.Offset is a convenience function most often used by layouts. Layouts
usually operate in integer coordinates, and the float32 version of op.Offset
needlessly force conversions from int to float32. This change makes op.Offset
take integer coordinates, to better match its intended use.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit fixes a visual-only bug in the ListStyle that could
make the scrollbar float at the edge of the maximum constraints
when the list did not occupy the full constraints. The list
would still reserve layout space for the scrollbar in the correct
position, but the scrollbar would not be displayed there.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
Previously, a bug in the ListStyle could result in items being
passed a negative value in the minimum constraints.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This comment and associated code were designed to guard against
the scrollbar failing to anchor to the proper edge of the content
when the layout.Direction was used with a zero minimum constraint.
However, they were in the wrong place to actually achieve the
desired behavior. This change simply moves the constraints
change to before the invocation of layout.Direction's Layout
method. This fixes the scrollbar appearing on the wrong edge of
content when the content is laid out with a zero minimum constraint.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
A previous change merged PassOp with AreaOp under the assumption that
the pass mode would be set on a particular area. That assumption turns
out not to hold, so this change brings back PassOp as an independent
stack operation.
This is an API change: replace AreaOp{Pass: true} with a separate
pointer.PassOp operation.
Fixes gio#288
Signed-off-by: Elias Naur <mail@eliasnaur.com>
The op.Save and Load methods exist to support the need for
transformation, clip, pointer area state to behave as stacks. For
example, layout needs to apply an offset to its children but not
subsequent operations.
Before this change, op.Save and Load were used to save and restore the
state:
ops := new(op.Ops)
// Save state.
state := op.Save(ops)
// Apply offset.
op.Offset(...).Add(ops)
// Draw with offset applied.
draw(ops)
// Restore state.
state.Load()
A drawback with the op.Save mechanism is that there is no direct
connection between the state change and the saving and loading of state.
This causes confusion as to when a Save/Load is needed and who is
responsible for performing them, which leads to subtle bugs and over-use
of Save/Loads.
This change gets rid of the general state stack and replaces it with
per-state stacks. There is now a stack for transformation, clip, pointer
areas, and they can only be restored by the code pushing state to them.
The example above now becomes:
ops := new(op.Ops)
// Push offset to the transformation stack.
stack := op.Offset(...).Push(ops)
// Draw with offset applied.
draw(ops)
// Restore state.
stack.Pop()
For convenience, transformation also be Add'ed if the stack operation is
not required.
Simple state such as the current material no longer has a way to be
restored; it is assumed the client of a PaintOp adds their desired
material operation before it.
API change: replace op.Save/Load with explicit Push/Pop scopes for
op.TransformOps, pointer.AreaOps, clip.Ops.
To ease porting, this change retains a version of op.Save/Load that
saves and restores the transformation and clip stacks. It also retains
an Add method for clip.Op.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
We're about to make operation scopes explicit, which would result in
both AreaOp and PassOp be scoped. However, PassOp seems to light to have
its separate stack, so this change instead makes pass-through a property
of an area. We're assuming that clients that want pass-through are also
aware of the affected hit area.
API change: replace PassOps with the AreaOp.PassThrough field.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is based on a patch by Elias that improved drag scrolling
on the scrollbar by locking some parameters of the math at the start
of the scroll event.
I discovered while playing with that implementation that there was
an even simpler approach within his changeset. You can actually
use no information other than the delta between the current and
previous frame's scroll position to compute the scroll distance.
By simplifying the math to rely on no other inputs, the jitter that
we've been fighting simply disappears (it came from other inputs).
Turns out my attempts to make the logic smart were the problem.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit fixes a problem that could force the scroll indicator to
lay itself out outside of its configured bounds. This occurred when
the scroll indicator size was increased to meet the minimum size
configured on the style type while the scrollbar was near the end
of the list. The increased size did not take the start position
of the scroll indicator into account, which made the indicator begin
in the correct place, but extend beyond the end of its track.
This commit alters the logic to ensure that the scroll indicator can
never extend beyond the end of its track.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
Previously, it was possible for fromListPosition to return
an end coordinate greater than 1, which would make scroll
indicators using those coordinates render beyond the
boundaries of their scroll tracks. One could argue that
this is a bug in the scroll indicator, but I don't think
this method should return data outside of its documented
range.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit ensures that the dimensions returned by material.List
include the size of the scrollbar when the scrollbar is set to
the Occupy AnchorStrategy.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This change ensures that the scrollbar anchors to the proper edge of the
content even when the list was drawn with a zero minimum constraint.
Without this, the layout.Direction used to anchor the scrollbar will choose
to use the minimum size and anchor it to the opposite edge of the content.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
The scrollbar implementation prior to this change only adjusted
list.Position.Offset. This works in all circumstances except when
list.Position.BeforeEnd=false. If the position indicates that the
scroll position is at the end of the list, the offset is ignored.
This change ensures that manually dragging the scrollbar always
causes BeforeEnd to be set to true. If the drag ends with the
scrollbar at the end of the list, BeforeEnd will be set
automatically by the next list.Layout call, so this doesn't
prevent the list from optimizing for that case in general.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
To use these lists instead of layout.List, callers simply need to
change declarations of layout.List to widget.List, and to change
calls to layout.List.Layout to material.List(th,&list).Layout.
So this:
var list layout.List
list.Layout(gtx, 10, func(gtx C, index int) D {
return material.Body1(th, fmt.Sprintf("%d", index)).Layout(gtx)
})
Becomes:
var list widget.List
material.List(th, &list).Layout(gtx, 10, func(gtx C, index int) D {
return material.Body1(th, fmt.Sprintf("%d", index)).Layout(gtx)
})
Naturally, the material.ListStyle type supports tweaking the scrollbar's
appearance and behavior.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>