widget: [API] separate Float state update; remove min, max, invert parameters

This change allows users of Float to determine its state before Layout
by calling Update.

While here, remove the value transformation represented by the min, max,
invert parameters; they're too many arguments for a computation that
may as well be done by the user.

Remove Float.Pos; it is better to compute its value from the dimensions
returned by Float.Layout.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2023-10-05 17:05:00 -05:00
parent 23e44292bb
commit d42dae73f0
2 changed files with 46 additions and 83 deletions
+29 -63
View File
@@ -9,59 +9,29 @@ import (
"gioui.org/io/pointer"
"gioui.org/layout"
"gioui.org/op/clip"
"gioui.org/unit"
)
// Float is for selecting a value in a range.
type Float struct {
// Value is the value of the Float, in the [0; 1] range.
Value float32
drag gesture.Drag
pos float32 // position normalized to [0, 1]
length float32
changed bool
drag gesture.Drag
axis layout.Axis
length float32
}
// Dragging returns whether the value is being interacted with.
func (f *Float) Dragging() bool { return f.drag.Dragging() }
// Layout updates the value according to drag events along the f's main axis.
//
// The range of f is set by the minimum constraints main axis value.
func (f *Float) Layout(gtx layout.Context, axis layout.Axis, min, max float32, invert bool, pointerMargin int) layout.Dimensions {
func (f *Float) Layout(gtx layout.Context, axis layout.Axis, pointerMargin unit.Dp) layout.Dimensions {
f.Update(gtx)
size := gtx.Constraints.Min
f.length = float32(axis.Convert(size).X)
f.axis = axis
var de *pointer.Event
for _, e := range f.drag.Events(gtx.Metric, gtx, gesture.Axis(axis)) {
if e.Kind == pointer.Press || e.Kind == pointer.Drag {
de = &e
}
}
value := f.Value
if de != nil {
xy := de.Position.X
if axis == layout.Vertical {
xy = f.length - de.Position.Y
}
if invert {
xy = f.length - xy
}
f.pos = xy / f.length
value = min + (max-min)*f.pos
} else if min != max {
f.pos = (value - min) / (max - min)
}
// Unconditionally call setValue in case min, max, or value changed.
f.setValue(value, min, max)
if f.pos < 0 {
f.pos = 0
} else if f.pos > 1 {
f.pos = 1
}
margin := axis.Convert(image.Pt(pointerMargin, 0))
margin := axis.Convert(image.Pt(gtx.Dp(pointerMargin), 0))
rect := image.Rectangle{
Min: margin.Mul(-1),
Max: size.Add(margin),
@@ -72,30 +42,26 @@ func (f *Float) Layout(gtx layout.Context, axis layout.Axis, min, max float32, i
return layout.Dimensions{Size: size}
}
func (f *Float) setValue(value, min, max float32) {
if min > max {
min, max = max, min
// Update the Value according to drag events along the f's main axis.
// The return value reports whether the value was changed.
//
// The range of f is set by the minimum constraints main axis value.
func (f *Float) Update(gtx layout.Context) bool {
changed := false
for _, e := range f.drag.Events(gtx.Metric, gtx, gesture.Axis(f.axis)) {
if f.length > 0 && (e.Kind == pointer.Press || e.Kind == pointer.Drag) {
pos := e.Position.X
if f.axis == layout.Vertical {
pos = f.length - e.Position.Y
}
f.Value = pos / f.length
if f.Value < 0 {
f.Value = 0
} else if f.Value > 1 {
f.Value = 1
}
changed = true
}
}
if value < min {
value = min
} else if value > max {
value = max
}
if f.Value != value {
f.Value = value
f.changed = true
}
}
// Pos reports the selected position.
func (f *Float) Pos() float32 {
return f.pos * f.length
}
// Changed reports whether the value has changed since
// the last call to Changed.
func (f *Float) Changed() bool {
changed := f.changed
f.changed = false
return changed
}
+17 -20
View File
@@ -16,10 +16,8 @@ import (
)
// Slider is for selecting a value in a range.
func Slider(th *Theme, float *widget.Float, min, max float32) SliderStyle {
func Slider(th *Theme, float *widget.Float) SliderStyle {
return SliderStyle{
Min: min,
Max: max,
Color: th.Palette.ContrastBg,
Float: float,
FingerSize: th.FingerSize,
@@ -27,35 +25,34 @@ func Slider(th *Theme, float *widget.Float, min, max float32) SliderStyle {
}
type SliderStyle struct {
Axis layout.Axis
Min, Max float32
Invert bool
Color color.NRGBA
Float *widget.Float
Axis layout.Axis
Color color.NRGBA
Float *widget.Float
FingerSize unit.Dp
}
func (s SliderStyle) Layout(gtx layout.Context) layout.Dimensions {
thumbRadius := gtx.Dp(6)
const thumbRadius unit.Dp = 6
tr := gtx.Dp(thumbRadius)
trackWidth := gtx.Dp(2)
axis := s.Axis
// Keep a minimum length so that the track is always visible.
minLength := thumbRadius + 3*thumbRadius + thumbRadius
minLength := tr + 3*tr + tr
// Try to expand to finger size, but only if the constraints
// allow for it.
touchSizePx := min(gtx.Dp(s.FingerSize), axis.Convert(gtx.Constraints.Max).Y)
sizeMain := max(axis.Convert(gtx.Constraints.Min).X, minLength)
sizeCross := max(2*thumbRadius, touchSizePx)
sizeCross := max(2*tr, touchSizePx)
size := axis.Convert(image.Pt(sizeMain, sizeCross))
o := axis.Convert(image.Pt(thumbRadius, 0))
o := axis.Convert(image.Pt(tr, 0))
trans := op.Offset(o).Push(gtx.Ops)
gtx.Constraints.Min = axis.Convert(image.Pt(sizeMain-2*thumbRadius, sizeCross))
s.Float.Layout(gtx, axis, s.Min, s.Max, s.Invert, thumbRadius)
gtx.Constraints.Min = axis.Convert(image.Pt(sizeMain-2*tr, sizeCross))
dims := s.Float.Layout(gtx, axis, thumbRadius)
gtx.Constraints.Min = gtx.Constraints.Min.Add(axis.Convert(image.Pt(0, sizeCross)))
thumbPos := thumbRadius + int(s.Float.Pos())
thumbPos := tr + int(s.Float.Value*float32(axis.Convert(dims.Size).X))
trans.Pop()
color := s.Color
@@ -65,7 +62,7 @@ func (s SliderStyle) Layout(gtx layout.Context) layout.Dimensions {
rect := func(minx, miny, maxx, maxy int) image.Rectangle {
r := image.Rect(minx, miny, maxx, maxy)
if s.Invert != (axis == layout.Vertical) {
if axis == layout.Vertical {
r.Max.X, r.Min.X = sizeMain-r.Min.X, sizeMain-r.Max.X
}
r.Min = axis.Convert(r.Min)
@@ -75,7 +72,7 @@ func (s SliderStyle) Layout(gtx layout.Context) layout.Dimensions {
// Draw track before thumb.
track := rect(
thumbRadius, sizeCross/2-trackWidth/2,
tr, sizeCross/2-trackWidth/2,
thumbPos, sizeCross/2+trackWidth/2,
)
paint.FillShape(gtx.Ops, color, clip.Rect(track).Op())
@@ -83,15 +80,15 @@ func (s SliderStyle) Layout(gtx layout.Context) layout.Dimensions {
// Draw track after thumb.
track = rect(
thumbPos, axis.Convert(track.Min).Y,
sizeMain-thumbRadius, axis.Convert(track.Max).Y,
sizeMain-tr, axis.Convert(track.Max).Y,
)
paint.FillShape(gtx.Ops, f32color.MulAlpha(color, 96), clip.Rect(track).Op())
// Draw thumb.
pt := image.Pt(thumbPos, sizeCross/2)
thumb := rect(
pt.X-thumbRadius, pt.Y-thumbRadius,
pt.X+thumbRadius, pt.Y+thumbRadius,
pt.X-tr, pt.Y-tr,
pt.X+tr, pt.Y+tr,
)
paint.FillShape(gtx.Ops, color, clip.Ellipse(thumb).Op(gtx.Ops))