switch.go raw

   1  // SPDX-License-Identifier: Unlicense OR MIT
   2  
   3  package material
   4  
   5  import (
   6  	"image"
   7  	"image/color"
   8  
   9  	"github.com/p9c/p9/pkg/gel/gio/f32"
  10  	"github.com/p9c/p9/pkg/gel/gio/internal/f32color"
  11  	"github.com/p9c/p9/pkg/gel/gio/io/pointer"
  12  	"github.com/p9c/p9/pkg/gel/gio/layout"
  13  	"github.com/p9c/p9/pkg/gel/gio/op"
  14  	"github.com/p9c/p9/pkg/gel/gio/op/clip"
  15  	"github.com/p9c/p9/pkg/gel/gio/op/paint"
  16  	"github.com/p9c/p9/pkg/gel/gio/unit"
  17  	"github.com/p9c/p9/pkg/gel/gio/widget"
  18  )
  19  
  20  type SwitchStyle struct {
  21  	Color struct {
  22  		Enabled  color.NRGBA
  23  		Disabled color.NRGBA
  24  		Track    color.NRGBA
  25  	}
  26  	Switch *widget.Bool
  27  }
  28  
  29  // Switch is for selecting a boolean value.
  30  func Switch(th *Theme, swtch *widget.Bool) SwitchStyle {
  31  	sw := SwitchStyle{
  32  		Switch: swtch,
  33  	}
  34  	sw.Color.Enabled = th.Palette.ContrastBg
  35  	sw.Color.Disabled = th.Palette.Bg
  36  	sw.Color.Track = f32color.MulAlpha(th.Palette.Fg, 0x88)
  37  	return sw
  38  }
  39  
  40  // Layout updates the switch and displays it.
  41  func (s SwitchStyle) Layout(gtx layout.Context) layout.Dimensions {
  42  	trackWidth := gtx.Px(unit.Dp(36))
  43  	trackHeight := gtx.Px(unit.Dp(16))
  44  	thumbSize := gtx.Px(unit.Dp(20))
  45  	trackOff := float32(thumbSize-trackHeight) * .5
  46  
  47  	// Draw track.
  48  	stack := op.Save(gtx.Ops)
  49  	trackCorner := float32(trackHeight) / 2
  50  	trackRect := f32.Rectangle{Max: f32.Point{
  51  		X: float32(trackWidth),
  52  		Y: float32(trackHeight),
  53  	}}
  54  	col := s.Color.Disabled
  55  	if s.Switch.Value {
  56  		col = s.Color.Enabled
  57  	}
  58  	if gtx.Queue == nil {
  59  		col = f32color.Disabled(col)
  60  	}
  61  	trackColor := s.Color.Track
  62  	op.Offset(f32.Point{Y: trackOff}).Add(gtx.Ops)
  63  	clip.UniformRRect(trackRect, trackCorner).Add(gtx.Ops)
  64  	paint.ColorOp{Color: trackColor}.Add(gtx.Ops)
  65  	paint.PaintOp{}.Add(gtx.Ops)
  66  	stack.Load()
  67  
  68  	// Draw thumb ink.
  69  	stack = op.Save(gtx.Ops)
  70  	inkSize := gtx.Px(unit.Dp(44))
  71  	rr := float32(inkSize) * .5
  72  	inkOff := f32.Point{
  73  		X: float32(trackWidth)*.5 - rr,
  74  		Y: -rr + float32(trackHeight)*.5 + trackOff,
  75  	}
  76  	op.Offset(inkOff).Add(gtx.Ops)
  77  	gtx.Constraints.Min = image.Pt(inkSize, inkSize)
  78  	clip.UniformRRect(f32.Rectangle{Max: layout.FPt(gtx.Constraints.Min)}, rr).Add(gtx.Ops)
  79  	for _, p := range s.Switch.History() {
  80  		drawInk(gtx, p)
  81  	}
  82  	stack.Load()
  83  
  84  	// Compute thumb offset and color.
  85  	stack = op.Save(gtx.Ops)
  86  	if s.Switch.Value {
  87  		off := trackWidth - thumbSize
  88  		op.Offset(f32.Point{X: float32(off)}).Add(gtx.Ops)
  89  	}
  90  
  91  	thumbRadius := float32(thumbSize) / 2
  92  
  93  	// Draw hover.
  94  	if s.Switch.Hovered() {
  95  		r := 1.7 * thumbRadius
  96  		background := f32color.MulAlpha(s.Color.Enabled, 70)
  97  		paint.FillShape(gtx.Ops, background,
  98  			clip.Circle{
  99  				Center: f32.Point{X: thumbRadius, Y: thumbRadius},
 100  				Radius: r,
 101  			}.Op(gtx.Ops))
 102  	}
 103  
 104  	// Draw thumb shadow, a translucent disc slightly larger than the
 105  	// thumb itself.
 106  	// Center shadow horizontally and slightly adjust its Y.
 107  	paint.FillShape(gtx.Ops, argb(0x55000000),
 108  		clip.Circle{
 109  			Center: f32.Point{X: thumbRadius, Y: thumbRadius + .25},
 110  			Radius: thumbRadius + 1,
 111  		}.Op(gtx.Ops))
 112  
 113  	// Draw thumb.
 114  	paint.FillShape(gtx.Ops, col,
 115  		clip.Circle{
 116  			Center: f32.Point{X: thumbRadius, Y: thumbRadius},
 117  			Radius: thumbRadius,
 118  		}.Op(gtx.Ops))
 119  
 120  	// Set up click area.
 121  	stack = op.Save(gtx.Ops)
 122  	clickSize := gtx.Px(unit.Dp(40))
 123  	clickOff := f32.Point{
 124  		X: (float32(trackWidth) - float32(clickSize)) * .5,
 125  		Y: (float32(trackHeight)-float32(clickSize))*.5 + trackOff,
 126  	}
 127  	op.Offset(clickOff).Add(gtx.Ops)
 128  	sz := image.Pt(clickSize, clickSize)
 129  	pointer.Ellipse(image.Rectangle{Max: sz}).Add(gtx.Ops)
 130  	gtx.Constraints.Min = sz
 131  	s.Switch.Layout(gtx)
 132  	stack.Load()
 133  
 134  	dims := image.Point{X: trackWidth, Y: thumbSize}
 135  	return layout.Dimensions{Size: dims}
 136  }
 137