widget,widget/material: add Float and Slider

Signed-off-by: Gordon Klaus <gordon.klaus@gmail.com>
This commit is contained in:
Gordon Klaus
2020-06-17 19:24:25 +02:00
committed by Elias Naur
parent 817e0fa9c3
commit 5368743478
3 changed files with 266 additions and 0 deletions
+69
View File
@@ -62,6 +62,14 @@ type ClickEvent struct {
type ClickType uint8
// Drag detects drag gestures in the form of pointer.Drag events.
type Drag struct {
dragging bool
pid pointer.ID
start f32.Point
grab bool
}
// Scroll detects scroll gestures and reduces them to
// scroll distances. Scroll recognizes mouse wheel
// movements as well as drag and fling touch gestures.
@@ -301,6 +309,67 @@ func (s *Scroll) State() ScrollState {
}
}
// Add the handler to the operation list to receive drag events.
func (d *Drag) Add(ops *op.Ops) {
op := pointer.InputOp{
Tag: d,
Grab: d.grab,
Types: pointer.Press | pointer.Drag | pointer.Release,
}
op.Add(ops)
}
// Events returns the next drag events, if any.
func (d *Drag) Events(cfg unit.Metric, q event.Queue, axis Axis) []pointer.Event {
var events []pointer.Event
for _, e := range q.Events(d) {
e, ok := e.(pointer.Event)
if !ok {
continue
}
switch e.Type {
case pointer.Press:
if !(e.Buttons == pointer.ButtonLeft || e.Source == pointer.Touch) {
continue
}
if d.dragging {
continue
}
d.dragging = true
d.pid = e.PointerID
d.start = e.Position
case pointer.Drag:
if !d.dragging || e.PointerID != d.pid {
continue
}
switch axis {
case Horizontal:
e.Position.Y = d.start.Y
case Vertical:
e.Position.X = d.start.X
}
if e.Priority < pointer.Grabbed {
diff := e.Position.Sub(d.start)
slop := cfg.Px(touchSlop)
if diff.X*diff.X+diff.Y*diff.Y > float32(slop*slop) {
d.grab = true
}
}
case pointer.Release, pointer.Cancel:
if !d.dragging || e.PointerID != d.pid {
continue
}
d.dragging = false
d.grab = false
}
events = append(events, e)
}
return events
}
func (a Axis) String() string {
switch a {
case Horizontal:
+88
View File
@@ -0,0 +1,88 @@
// SPDX-License-Identifier: Unlicense OR MIT
package widget
import (
"image"
"gioui.org/gesture"
"gioui.org/io/pointer"
"gioui.org/layout"
"gioui.org/op"
)
// Float is for selecting a value in a range.
type Float struct {
Value float32
drag gesture.Drag
pos float32 // position normalized to [0, 1]
length float32
changed bool
}
// Layout processes events.
func (f *Float) Layout(gtx layout.Context, pointerMargin int, min, max float32) layout.Dimensions {
size := gtx.Constraints.Min
f.length = float32(size.X)
var de *pointer.Event
for _, e := range f.drag.Events(gtx.Metric, gtx, gesture.Horizontal) {
if e.Type == pointer.Press || e.Type == pointer.Drag {
de = &e
}
}
value := f.Value
if de != nil {
f.pos = de.Position.X / f.length
value = min + (max-min)*f.pos
} else if min != max {
f.pos = value/(max-min) - 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
}
defer op.Push(gtx.Ops).Pop()
rect := image.Rectangle{Max: size}
rect.Min.X -= pointerMargin
rect.Max.X += pointerMargin
pointer.Rect(rect).Add(gtx.Ops)
f.drag.Add(gtx.Ops)
return layout.Dimensions{Size: size}
}
func (f *Float) setValue(value, min, max float32) {
if min > max {
min, max = max, min
}
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
}
+109
View File
@@ -0,0 +1,109 @@
// SPDX-License-Identifier: Unlicense OR MIT
package material
import (
"image"
"image/color"
"gioui.org/f32"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip"
"gioui.org/op/paint"
"gioui.org/unit"
"gioui.org/widget"
)
// Slider is for selecting a value in a range.
func Slider(th *Theme, float *widget.Float, min, max float32) SliderStyle {
return SliderStyle{
Min: min,
Max: max,
Color: th.Color.Primary,
Float: float,
}
}
type SliderStyle struct {
Min, Max float32
Color color.RGBA
Float *widget.Float
}
func (s SliderStyle) Layout(gtx layout.Context) layout.Dimensions {
thumbRadiusInt := gtx.Px(unit.Dp(6))
trackWidth := float32(gtx.Px(unit.Dp(2)))
thumbRadius := float32(thumbRadiusInt)
halfWidthInt := 2 * thumbRadiusInt
halfWidth := float32(halfWidthInt)
size := gtx.Constraints.Min
// Keep a minimum length so that the track is always visible.
minLength := halfWidthInt + 3*thumbRadiusInt + halfWidthInt
if size.X < minLength {
size.X = minLength
}
size.Y = 2 * halfWidthInt
st := op.Push(gtx.Ops)
op.Offset(f32.Pt(halfWidth, 0)).Add(gtx.Ops)
gtx.Constraints.Min = image.Pt(size.X-2*halfWidthInt, size.Y)
s.Float.Layout(gtx, halfWidthInt, s.Min, s.Max)
thumbPos := halfWidth + s.Float.Pos()
st.Pop()
color := s.Color
if gtx.Queue == nil {
color = mulAlpha(color, 150)
}
// Draw track before thumb.
st = op.Push(gtx.Ops)
track := f32.Rectangle{
Min: f32.Point{
X: halfWidth,
Y: halfWidth - trackWidth/2,
},
Max: f32.Point{
X: thumbPos,
Y: halfWidth + trackWidth/2,
},
}
clip.Rect{Rect: track}.Op(gtx.Ops).Add(gtx.Ops)
paint.ColorOp{Color: color}.Add(gtx.Ops)
paint.PaintOp{Rect: track}.Add(gtx.Ops)
st.Pop()
// Draw track after thumb.
st = op.Push(gtx.Ops)
track.Min.X = thumbPos
track.Max.X = float32(size.X) - halfWidth
clip.Rect{Rect: track}.Op(gtx.Ops).Add(gtx.Ops)
paint.ColorOp{Color: mulAlpha(color, 96)}.Add(gtx.Ops)
paint.PaintOp{Rect: track}.Add(gtx.Ops)
st.Pop()
// Draw thumb.
st = op.Push(gtx.Ops)
thumb := f32.Rectangle{
Min: f32.Point{
X: thumbPos - thumbRadius,
Y: halfWidth - thumbRadius,
},
Max: f32.Point{
X: thumbPos + thumbRadius,
Y: halfWidth + thumbRadius,
},
}
rr := thumbRadius
clip.Rect{
Rect: thumb,
NE: rr, NW: rr, SE: rr, SW: rr,
}.Op(gtx.Ops).Add(gtx.Ops)
paint.ColorOp{Color: color}.Add(gtx.Ops)
paint.PaintOp{Rect: thumb}.Add(gtx.Ops)
st.Pop()
return layout.Dimensions{Size: size}
}