// SPDX-License-Identifier: Unlicense OR MIT package material import ( "image" "image/color" "math" "gioui.org/f32" "gioui.org/internal/f32color" "gioui.org/io/pointer" "gioui.org/layout" "gioui.org/op/clip" "gioui.org/op/paint" "gioui.org/text" "gioui.org/unit" "gioui.org/widget" ) type checkable struct { Label string Color color.NRGBA Font text.Font TextSize unit.Value IconColor color.NRGBA Size unit.Value shaper text.Shaper checkedStateIcon *widget.Icon uncheckedStateIcon *widget.Icon } func (c *checkable) layout(gtx layout.Context, checked, hovered bool) layout.Dimensions { var icon *widget.Icon if checked { icon = c.checkedStateIcon } else { icon = c.uncheckedStateIcon } min := gtx.Constraints.Min dims := layout.Flex{Alignment: layout.Middle}.Layout(gtx, layout.Rigid(func(gtx layout.Context) layout.Dimensions { return layout.Stack{Alignment: layout.Center}.Layout(gtx, layout.Stacked(func(gtx layout.Context) layout.Dimensions { size := gtx.Px(c.Size) * 4 / 3 dims := layout.Dimensions{ Size: image.Point{X: size, Y: size}, } if !hovered { return dims } background := f32color.MulAlpha(c.IconColor, 70) var p clip.Path p.Begin(gtx.Ops) addCircle(&p, float32(size)/2) paint.FillShape(gtx.Ops, background, clip.Outline{Path: p.End()}.Op()) return dims }), layout.Stacked(func(gtx layout.Context) layout.Dimensions { return layout.UniformInset(unit.Dp(2)).Layout(gtx, func(gtx layout.Context) layout.Dimensions { size := gtx.Px(c.Size) icon.Color = c.IconColor if gtx.Queue == nil { icon.Color = f32color.Disabled(icon.Color) } icon.Layout(gtx, unit.Px(float32(size))) return layout.Dimensions{ Size: image.Point{X: size, Y: size}, } }) }), ) }), layout.Rigid(func(gtx layout.Context) layout.Dimensions { gtx.Constraints.Min = min return layout.W.Layout(gtx, func(gtx layout.Context) layout.Dimensions { return layout.UniformInset(unit.Dp(2)).Layout(gtx, func(gtx layout.Context) layout.Dimensions { paint.ColorOp{Color: c.Color}.Add(gtx.Ops) return widget.Label{}.Layout(gtx, c.shaper, c.Font, c.TextSize, c.Label) }) }) }), ) pointer.Rect(image.Rectangle{Max: dims.Size}).Add(gtx.Ops) return dims } // addCircle adds the outline of a circle to a path. func addCircle(p *clip.Path, r float32) { // https://pomax.github.io/bezierinfo/#circles_cubic. const c = 4 * (math.Sqrt2 - 1) / 3 // 4*(sqrt(2)-1)/3 p.Move(f32.Point{X: 2 * r, Y: 2*r - r}) p.Cube(f32.Point{X: 0, Y: r * c}, f32.Point{X: -r + r*c, Y: r}, f32.Point{X: -r, Y: r}) p.Cube(f32.Point{X: -r * c, Y: 0}, f32.Point{X: -r, Y: -r + r*c}, f32.Point{X: -r, Y: -r}) p.Cube(f32.Point{X: 0, Y: -r * c}, f32.Point{X: r - r*c, Y: -r}, f32.Point{X: r, Y: -r}) p.Cube(f32.Point{X: r * c, Y: 0}, f32.Point{X: r, Y: r - r*c}, f32.Point{X: r, Y: r}) }