~eliasnaur/gio

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch

[PATCH] widgets, widgets/material: add RadioButton & RadioButtonsGroup

Alexander Arin
Details
Message ID
<20191106094330.9676-1-fralx@yandex.ru>
DKIM signature
pass
Download raw message
Patch: +198 -73
Signed-off-by: Alexander Arin <fralx@yandex.ru>
---
 example/kitchen/kitchen.go     | 22 ++++++++--
 widget/enum.go                 | 51 ++++++++++++++++++++++
 widget/material/checkbox.go    | 79 ++++++----------------------------
 widget/material/chekable.go    | 65 ++++++++++++++++++++++++++++
 widget/material/material.go    | 12 ++++--
 widget/material/radiobutton.go | 42 ++++++++++++++++++
 6 files changed, 198 insertions(+), 73 deletions(-)
 create mode 100644 widget/enum.go
 create mode 100644 widget/material/chekable.go
 create mode 100644 widget/material/radiobutton.go

diff --git a/example/kitchen/kitchen.go b/example/kitchen/kitchen.go
index e390761..486924e 100644
--- a/example/kitchen/kitchen.go
+++ b/example/kitchen/kitchen.go
@@ -62,10 +62,11 @@ var (
 		SingleLine: true,
 		Submit:     true,
 	}
-	button      = new(widget.Button)
-	greenButton = new(widget.Button)
-	iconButton  = new(widget.Button)
-	list        = &layout.List{
+	button            = new(widget.Button)
+	greenButton       = new(widget.Button)
+	iconButton        = new(widget.Button)
+	radioButtonsGroup = new(widget.Enum)
+	list              = &layout.List{
 		Axis: layout.Vertical,
 	}
 	green    = true
@@ -125,6 +126,19 @@ func kitchen(gtx *layout.Context, th *material.Theme) {
 		func() {
 			th.CheckBox("Checkbox").Layout(gtx, checkbox)
 		},
+		func() {
+			group := layout.Flex{}
+			r1 := group.Rigid(gtx, func() {
+				th.RadioButton("r1", "RadioButton1").Layout(gtx, radioButtonsGroup)
+			})
+			r2 := group.Rigid(gtx, func() {
+				th.RadioButton("r2", "RadioButton2").Layout(gtx, radioButtonsGroup)
+			})
+			r3 := group.Rigid(gtx, func() {
+				th.RadioButton("r3", "RadioButton3").Layout(gtx, radioButtonsGroup)
+			})
+			group.Layout(gtx, r1, r2, r3)
+		},
 	}
 	list.Layout(gtx, len(widgets), func(i int) {
 		layout.UniformInset(unit.Dp(16)).Layout(gtx, widgets[i])
diff --git a/widget/enum.go b/widget/enum.go
new file mode 100644
index 0000000..63e35cb
--- /dev/null
+++ b/widget/enum.go
@@ -0,0 +1,51 @@
+package widget
+
+import (
+	"gioui.org/gesture"
+	"gioui.org/layout"
+)
+
+type Enum struct {
+	clicks []gesture.Click
+	values []string
+	value  string
+}
+
+func index(vs []string, t string) int {
+	for i, v := range vs {
+		if v == t {
+			return i
+		}
+	}
+	return -1
+}
+
+// Value processes events and returns the last selected value, or
+// the empty string.
+func (e *Enum) Value(gtx *layout.Context) string {
+	for i := range e.clicks {
+		for _, ev := range e.clicks[i].Events(gtx) {
+			switch ev.Type {
+			case gesture.TypeClick:
+				e.value = e.values[i]
+			}
+		}
+	}
+	return e.value
+}
+
+// Layout adds the event handler for key.
+func (rg *Enum) Layout(gtx *layout.Context, key string) {
+	if index(rg.values, key) == -1 {
+		rg.values = append(rg.values, key)
+		rg.clicks = append(rg.clicks, gesture.Click{})
+		rg.clicks[len(rg.clicks)-1].Add(gtx.Ops)
+	} else {
+		idx := index(rg.values, key)
+		rg.clicks[idx].Add(gtx.Ops)
+	}
+}
+
+func (rg *Enum) SetValue(value string) {
+	rg.value = value
+}
diff --git a/widget/material/checkbox.go b/widget/material/checkbox.go
index 1648891..4d1537d 100644
--- a/widget/material/checkbox.go
+++ b/widget/material/checkbox.go
@@ -4,85 +4,34 @@
 package material
 
 import (
-	"gioui.org/io/pointer"
 	"gioui.org/layout"
-	"gioui.org/op/paint"
 	"gioui.org/text"
 	"gioui.org/unit"
 	"gioui.org/widget"
-	"image"
-	"image/color"
 )
 
 type CheckBox struct {
-	Text string
-	// Color is the text color.
-	Color              color.RGBA
-	Font               text.Font
-	IconColor          color.RGBA
-	Size               unit.Value
-	shaper             *text.Shaper
-	checkedStateIcon   *Icon
-	uncheckedStateIcon *Icon
+	checkable
 }
 
-func (t *Theme) CheckBox(txt string) CheckBox {
+func (t *Theme) CheckBox(label string) CheckBox {
 	return CheckBox{
-		Text:      txt,
-		Color:     t.Color.Text,
-		IconColor: t.Color.Primary,
-		Font: text.Font{
-			Size: t.TextSize.Scale(14.0 / 16.0),
+		checkable{
+			Label:     label,
+			Color:     t.Color.Text,
+			IconColor: t.Color.Primary,
+			Font: text.Font{
+				Size: t.TextSize.Scale(14.0 / 16.0),
+			},
+			Size:               unit.Dp(26),
+			shaper:             t.Shaper,
+			checkedStateIcon:   t.checkBoxCheckedIcon,
+			uncheckedStateIcon: t.checkBoxUncheckedIcon,
 		},
-		Size:               unit.Dp(26),
-		shaper:             t.Shaper,
-		checkedStateIcon:   t.checkedStateIcon,
-		uncheckedStateIcon: t.uncheckedStateIcon,
 	}
 }
 
 func (c CheckBox) Layout(gtx *layout.Context, checkBox *widget.CheckBox) {
-
-	textColor := c.Color
-	iconColor := c.IconColor
-
-	var icon *Icon
-	if checkBox.Checked(gtx) {
-		icon = c.checkedStateIcon
-	} else {
-		icon = c.uncheckedStateIcon
-	}
-
-	hmin := gtx.Constraints.Width.Min
-	vmin := gtx.Constraints.Height.Min
-
-	flex := layout.Flex{Alignment: layout.Middle}
-
-	ico := flex.Rigid(gtx, func() {
-		layout.Align(layout.Center).Layout(gtx, func() {
-			layout.UniformInset(unit.Dp(2)).Layout(gtx, func() {
-				size := gtx.Px(c.Size)
-				icon.Color = iconColor
-				icon.Layout(gtx, unit.Px(float32(size)))
-				gtx.Dimensions = layout.Dimensions{
-					Size: image.Point{X: size, Y: size},
-				}
-			})
-		})
-	})
-
-	lbl := flex.Rigid(gtx, func() {
-		gtx.Constraints.Width.Min = hmin
-		gtx.Constraints.Height.Min = vmin
-		layout.Align(layout.Start).Layout(gtx, func() {
-			layout.UniformInset(unit.Dp(2)).Layout(gtx, func() {
-				paint.ColorOp{Color: textColor}.Add(gtx.Ops)
-				widget.Label{}.Layout(gtx, c.shaper, c.Font, c.Text)
-			})
-		})
-	})
-
-	flex.Layout(gtx, ico, lbl)
-	pointer.RectAreaOp{Rect: image.Rectangle{Max: gtx.Dimensions.Size}}.Add(gtx.Ops)
+	c.layout(gtx, checkBox.Checked(gtx))
 	checkBox.Layout(gtx)
 }
diff --git a/widget/material/chekable.go b/widget/material/chekable.go
new file mode 100644
index 0000000..278b8d3
--- /dev/null
+++ b/widget/material/chekable.go
@@ -0,0 +1,65 @@
+package material
+
+import (
+	"image"
+	"image/color"
+
+	"gioui.org/io/pointer"
+	"gioui.org/layout"
+	"gioui.org/op/paint"
+	"gioui.org/text"
+	"gioui.org/unit"
+	"gioui.org/widget"
+)
+
+type checkable struct {
+	Label              string
+	Color              color.RGBA
+	Font               text.Font
+	IconColor          color.RGBA
+	Size               unit.Value
+	shaper             *text.Shaper
+	checkedStateIcon   *Icon
+	uncheckedStateIcon *Icon
+}
+
+func (c *checkable) layout(gtx *layout.Context, checked bool) {
+
+	var icon *Icon
+	if checked {
+		icon = c.checkedStateIcon
+	} else {
+		icon = c.uncheckedStateIcon
+	}
+
+	hmin := gtx.Constraints.Width.Min
+	vmin := gtx.Constraints.Height.Min
+	flex := layout.Flex{Alignment: layout.Middle}
+
+	ico := flex.Rigid(gtx, func() {
+		layout.Align(layout.Center).Layout(gtx, func() {
+			layout.UniformInset(unit.Dp(2)).Layout(gtx, func() {
+				size := gtx.Px(c.Size)
+				icon.Color = c.IconColor
+				icon.Layout(gtx, unit.Px(float32(size)))
+				gtx.Dimensions = layout.Dimensions{
+					Size: image.Point{X: size, Y: size},
+				}
+			})
+		})
+	})
+
+	lbl := flex.Rigid(gtx, func() {
+		gtx.Constraints.Width.Min = hmin
+		gtx.Constraints.Height.Min = vmin
+		layout.Align(layout.Start).Layout(gtx, func() {
+			layout.UniformInset(unit.Dp(2)).Layout(gtx, func() {
+				paint.ColorOp{Color: c.Color}.Add(gtx.Ops)
+				widget.Label{}.Layout(gtx, c.shaper, c.Font, c.Label)
+			})
+		})
+	})
+
+	flex.Layout(gtx, ico, lbl)
+	pointer.RectAreaOp{Rect: image.Rectangle{Max: gtx.Dimensions.Size}}.Add(gtx.Ops)
+}
diff --git a/widget/material/material.go b/widget/material/material.go
index d5b70db..1b73af0 100644
--- a/widget/material/material.go
+++ b/widget/material/material.go
@@ -26,8 +26,10 @@ type Theme struct {
 		InvText color.RGBA
 	}
 	TextSize           unit.Value
-	checkedStateIcon   *Icon
-	uncheckedStateIcon *Icon
+	checkBoxCheckedIcon   *Icon
+	checkBoxUncheckedIcon *Icon
+	radioCheckedIcon   *Icon
+	radioUncheckedIcon *Icon
 }
 
 func NewTheme() *Theme {
@@ -40,8 +42,10 @@ func NewTheme() *Theme {
 	t.Color.InvText = rgb(0xffffff)
 	t.TextSize = unit.Sp(16)
 
-	t.checkedStateIcon = mustIcon(NewIcon(icons.ToggleCheckBox))
-	t.uncheckedStateIcon = mustIcon(NewIcon(icons.ToggleCheckBoxOutlineBlank))
+	t.checkBoxCheckedIcon = mustIcon(NewIcon(icons.ToggleCheckBox))
+	t.checkBoxUncheckedIcon = mustIcon(NewIcon(icons.ToggleCheckBoxOutlineBlank))
+	t.radioCheckedIcon = mustIcon(NewIcon(icons.ToggleRadioButtonChecked))
+	t.radioUncheckedIcon = mustIcon(NewIcon(icons.ToggleRadioButtonUnchecked))
 
 	return t
 }
diff --git a/widget/material/radiobutton.go b/widget/material/radiobutton.go
new file mode 100644
index 0000000..9a9a008
--- /dev/null
+++ b/widget/material/radiobutton.go
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+// Package material implements the Material design.
+package material
+
+import (
+	"gioui.org/layout"
+	"gioui.org/text"
+	"gioui.org/unit"
+	"gioui.org/widget"
+)
+
+type RadioButton struct {
+	checkable
+	Key string
+}
+
+// RadioButton returns a RadioButton with a label. The key specifies
+// the value for the Enum.
+func (t *Theme) RadioButton(key, label string) RadioButton {
+	return RadioButton{
+		checkable: checkable{
+			Label: label,
+
+			Color:     t.Color.Text,
+			IconColor: t.Color.Primary,
+			Font: text.Font{
+				Size: t.TextSize.Scale(14.0 / 16.0),
+			},
+			Size:               unit.Dp(26),
+			shaper:             t.Shaper,
+			checkedStateIcon:   t.radioCheckedIcon,
+			uncheckedStateIcon: t.radioUncheckedIcon,
+		},
+		Key: key,
+	}
+}
+
+func (r RadioButton) Layout(gtx *layout.Context, enum *widget.Enum) {
+	r.layout(gtx, enum.Value(gtx) == r.Key)
+	enum.Layout(gtx, r.Key)
+}
-- 
2.18.0.windows.1