switch.go raw

   1  package gel
   2  
   3  import (
   4  	"image"
   5  	"image/color"
   6  	
   7  	"github.com/p9c/gio/f32"
   8  	"github.com/p9c/gio/io/pointer"
   9  	l "github.com/p9c/gio/layout"
  10  	"github.com/p9c/gio/op"
  11  	"github.com/p9c/gio/op/clip"
  12  	"github.com/p9c/gio/op/paint"
  13  	"github.com/p9c/gio/unit"
  14  	
  15  	"github.com/p9c/gel/f32color"
  16  )
  17  
  18  type Switch struct {
  19  	*Window
  20  	color struct {
  21  		enabled  color.NRGBA
  22  		disabled color.NRGBA
  23  	}
  24  	swtch *Bool
  25  }
  26  
  27  // Switch creates a boolean switch widget (basically a checkbox but looks like a switch)
  28  func (w *Window) Switch(swtch *Bool) *Switch {
  29  	sw := &Switch{
  30  		Window: w,
  31  		swtch:  swtch,
  32  	}
  33  	sw.color.enabled = w.Colors.GetNRGBAFromName("Primary")
  34  	sw.color.disabled = w.Colors.GetNRGBAFromName("scrim")
  35  	return sw
  36  }
  37  
  38  // EnabledColor sets the color to draw for the enabled state
  39  func (s *Switch) EnabledColor(color string) *Switch {
  40  	s.color.enabled = s.Theme.Colors.GetNRGBAFromName(color)
  41  	return s
  42  }
  43  
  44  // DisabledColor sets the color to draw for the disabled state
  45  func (s *Switch) DisabledColor(color string) *Switch {
  46  	s.color.disabled = s.Theme.Colors.GetNRGBAFromName(color)
  47  	return s
  48  }
  49  
  50  func (s *Switch) SetHook(fn func(b bool)) *Switch {
  51  	s.swtch.SetOnChange(fn)
  52  	return s
  53  }
  54  
  55  // Fn updates the switch and displays it.
  56  func (s *Switch) Fn(gtx l.Context) l.Dimensions {
  57  	return s.Inset(0.25, func(gtx l.Context) l.Dimensions {
  58  		trackWidth := gtx.Px(s.Theme.TextSize.Scale(2.5))
  59  		trackHeight := gtx.Px(unit.Dp(16))
  60  		thumbSize := gtx.Px(unit.Dp(20))
  61  		trackOff := float32(thumbSize-trackHeight) * .5
  62  		
  63  		// Draw track.
  64  		stack := op.Save(gtx.Ops)
  65  		trackCorner := float32(trackHeight) / 2
  66  		trackRect := f32.Rectangle{Max: f32.Point{
  67  			X: float32(trackWidth),
  68  			Y: float32(trackHeight),
  69  		}}
  70  		col := s.color.disabled
  71  		if s.swtch.value {
  72  			col = s.color.enabled
  73  		}
  74  		if gtx.Queue == nil {
  75  			col = f32color.MulAlpha(col, 150)
  76  		}
  77  		trackColor := f32color.MulAlpha(col, 200)
  78  		op.Offset(f32.Point{Y: trackOff}).Add(gtx.Ops)
  79  		clip.RRect{
  80  			Rect: trackRect,
  81  			NE:   trackCorner, NW: trackCorner, SE: trackCorner, SW: trackCorner,
  82  		}.Add(gtx.Ops)
  83  		paint.ColorOp{Color: trackColor}.Add(gtx.Ops)
  84  		paint.PaintOp{}.Add(gtx.Ops)
  85  		stack.Load()
  86  		
  87  		// Draw thumb ink.
  88  		stack = op.Save(gtx.Ops)
  89  		inkSize := gtx.Px(unit.Dp(44))
  90  		rr := float32(inkSize) * .5
  91  		inkOff := f32.Point{
  92  			X: float32(trackWidth)*.5 - rr,
  93  			Y: -rr + float32(trackHeight)*.5 + trackOff,
  94  		}
  95  		op.Offset(inkOff).Add(gtx.Ops)
  96  		gtx.Constraints.Min = image.Pt(inkSize, inkSize)
  97  		clip.RRect{
  98  			Rect: f32.Rectangle{
  99  				Max: l.FPt(gtx.Constraints.Min),
 100  			},
 101  			NE: rr, NW: rr, SE: rr, SW: rr,
 102  		}.Add(gtx.Ops)
 103  		for _, p := range s.swtch.History() {
 104  			drawInk(gtx, p)
 105  		}
 106  		stack.Load()
 107  		
 108  		// Compute thumb offset and color.
 109  		stack = op.Save(gtx.Ops)
 110  		if s.swtch.value {
 111  			off := trackWidth - thumbSize
 112  			op.Offset(f32.Point{X: float32(off)}).Add(gtx.Ops)
 113  		}
 114  		
 115  		// Draw thumb shadow, a translucent disc slightly larger than the
 116  		// thumb itself.
 117  		shadowStack := op.Save(gtx.Ops)
 118  		shadowSize := float32(2)
 119  		// Center shadow horizontally and slightly adjust its Y.
 120  		op.Offset(f32.Point{X: -shadowSize / 2, Y: -.75}).Add(gtx.Ops)
 121  		drawDisc(gtx.Ops, float32(thumbSize)+shadowSize, color.NRGBA(argb(0x55000000)))
 122  		shadowStack.Load()
 123  		
 124  		// Draw thumb.
 125  		drawDisc(gtx.Ops, float32(thumbSize), col)
 126  		stack.Load()
 127  		
 128  		// Set up click area.
 129  		stack = op.Save(gtx.Ops)
 130  		clickSize := gtx.Px(unit.Dp(40))
 131  		clickOff := f32.Point{
 132  			X: (float32(trackWidth) - float32(clickSize)) * .5,
 133  			Y: (float32(trackHeight)-float32(clickSize))*.5 + trackOff,
 134  		}
 135  		op.Offset(clickOff).Add(gtx.Ops)
 136  		sz := image.Pt(clickSize, clickSize)
 137  		pointer.Ellipse(image.Rectangle{Max: sz}).Add(gtx.Ops)
 138  		gtx.Constraints.Min = sz
 139  		s.swtch.Fn(gtx)
 140  		stack.Load()
 141  		
 142  		dims := image.Point{X: trackWidth, Y: thumbSize}
 143  		return l.Dimensions{Size: dims}
 144  	}).Fn(gtx)
 145  }
 146  
 147  func drawDisc(ops *op.Ops, sz float32, col color.NRGBA) {
 148  	defer op.Save(ops).Load()
 149  	rr := sz / 2
 150  	r := f32.Rectangle{Max: f32.Point{X: sz, Y: sz}}
 151  	clip.RRect{
 152  		Rect: r,
 153  		NE:   rr, NW: rr, SE: rr, SW: rr,
 154  	}.Add(ops)
 155  	paint.ColorOp{Color: col}.Add(ops)
 156  	paint.PaintOp{}.Add(ops)
 157  }
 158