From 26da49e145f37590f6927328653560af338d8534 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Mon, 4 May 2020 14:39:21 +0200 Subject: [PATCH] widget/material: add Switch widget Signed-off-by: Elias Naur --- widget/bool.go | 13 +++- widget/material/switch.go | 132 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 widget/material/switch.go diff --git a/widget/bool.go b/widget/bool.go index 75f7161f..cbef01c8 100644 --- a/widget/bool.go +++ b/widget/bool.go @@ -8,19 +8,26 @@ import ( type Bool struct { Value bool - click gesture.Click + // Last is the last registered click. + Last Click + + gesture gesture.Click } // Update the checked state according to incoming events. func (b *Bool) Update(gtx *layout.Context) { - for _, e := range b.click.Events(gtx) { + for _, e := range b.gesture.Events(gtx) { switch e.Type { case gesture.TypeClick: + b.Last = Click{ + Time: gtx.Now(), + Position: e.Position, + } b.Value = !b.Value } } } func (b *Bool) Layout(gtx *layout.Context) { - b.click.Add(gtx.Ops) + b.gesture.Add(gtx.Ops) } diff --git a/widget/material/switch.go b/widget/material/switch.go new file mode 100644 index 00000000..7b1fd6c8 --- /dev/null +++ b/widget/material/switch.go @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: Unlicense OR MIT + +package material + +import ( + "image" + "image/color" + + "gioui.org/f32" + "gioui.org/io/pointer" + "gioui.org/layout" + "gioui.org/op" + "gioui.org/op/clip" + "gioui.org/op/paint" + "gioui.org/unit" + "gioui.org/widget" +) + +type SwitchStyle struct { + Color color.RGBA +} + +func Switch(th *Theme) SwitchStyle { + return SwitchStyle{ + Color: th.Color.Primary, + } +} + +// Layout updates the checkBox and displays it. +func (s SwitchStyle) Layout(gtx *layout.Context, swtch *widget.Bool) { + swtch.Update(gtx) + + trackWidth := gtx.Px(unit.Dp(36)) + trackHeight := gtx.Px(unit.Dp(16)) + thumbSize := gtx.Px(unit.Dp(20)) + trackOff := float32(thumbSize-trackHeight) * .5 + + // Draw track. + var stack op.StackOp + stack.Push(gtx.Ops) + trackCorner := float32(trackHeight) / 2 + trackRect := f32.Rectangle{Max: f32.Point{ + X: float32(trackWidth), + Y: float32(trackHeight), + }} + op.TransformOp{}.Offset(f32.Point{Y: trackOff}).Add(gtx.Ops) + clip.Rect{ + Rect: trackRect, + NE: trackCorner, NW: trackCorner, SE: trackCorner, SW: trackCorner, + }.Op(gtx.Ops).Add(gtx.Ops) + paint.ColorOp{Color: rgb(0x9b9b9b)}.Add(gtx.Ops) + paint.PaintOp{Rect: trackRect}.Add(gtx.Ops) + stack.Pop() + + // Compute thumb offset and color. + stack.Push(gtx.Ops) + col := rgb(0xffffff) + if swtch.Value { + off := trackWidth - thumbSize + op.TransformOp{}.Offset(f32.Point{X: float32(off)}).Add(gtx.Ops) + col = s.Color + } + + // Draw thumb shadow, a translucent disc slightly larger than the + // thumb itself. + var shadowStack op.StackOp + shadowStack.Push(gtx.Ops) + shadowSize := float32(2) + // Center shadow horizontally and slightly adjust its Y. + op.TransformOp{}.Offset(f32.Point{X: -shadowSize / 2, Y: -.75}).Add(gtx.Ops) + drawDisc(gtx.Ops, float32(thumbSize)+shadowSize, argb(0x55000000)) + shadowStack.Pop() + + // Draw thumb. + drawDisc(gtx.Ops, float32(thumbSize), col) + stack.Pop() + + // Draw thumb ink. + stack.Push(gtx.Ops) + inkSize := float32(gtx.Px(unit.Dp(44))) + rr := inkSize * .5 + inkOff := f32.Point{ + X: float32(trackWidth)*.5 - rr, + Y: -rr + float32(trackHeight)*.5 + trackOff, + } + op.TransformOp{}.Offset(inkOff).Add(gtx.Ops) + clip.Rect{ + Rect: f32.Rectangle{ + Max: f32.Point{ + X: inkSize, + Y: inkSize, + }, + }, + NE: rr, NW: rr, SE: rr, SW: rr, + }.Op(gtx.Ops).Add(gtx.Ops) + drawInk(gtx, swtch.Last) + stack.Pop() + + // Set up click area. + stack.Push(gtx.Ops) + clickSize := gtx.Px(unit.Dp(40)) + clickOff := f32.Point{ + X: (float32(trackWidth) - float32(clickSize)) * .5, + Y: (float32(trackHeight)-float32(clickSize))*.5 + trackOff, + } + op.TransformOp{}.Offset(clickOff).Add(gtx.Ops) + pointer.Ellipse(image.Rectangle{ + Max: image.Point{ + X: clickSize, Y: clickSize, + }, + }).Add(gtx.Ops) + swtch.Layout(gtx) + stack.Pop() + + gtx.Dimensions = layout.Dimensions{ + Size: image.Point{X: trackWidth, Y: trackHeight}, + } +} + +func drawDisc(ops *op.Ops, sz float32, col color.RGBA) { + var stack op.StackOp + stack.Push(ops) + rr := sz / 2 + r := f32.Rectangle{Max: f32.Point{X: sz, Y: sz}} + clip.Rect{ + Rect: r, + NE: rr, NW: rr, SE: rr, SW: rr, + }.Op(ops).Add(ops) + paint.ColorOp{Color: col}.Add(ops) + paint.PaintOp{Rect: r}.Add(ops) + stack.Pop() +}