slider.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/layout"
  12  	"github.com/p9c/p9/pkg/gel/gio/op"
  13  	"github.com/p9c/p9/pkg/gel/gio/op/clip"
  14  	"github.com/p9c/p9/pkg/gel/gio/op/paint"
  15  	"github.com/p9c/p9/pkg/gel/gio/unit"
  16  	"github.com/p9c/p9/pkg/gel/gio/widget"
  17  )
  18  
  19  // Slider is for selecting a value in a range.
  20  func Slider(th *Theme, float *widget.Float, min, max float32) SliderStyle {
  21  	return SliderStyle{
  22  		Min:        min,
  23  		Max:        max,
  24  		Color:      th.Palette.ContrastBg,
  25  		Float:      float,
  26  		FingerSize: th.FingerSize,
  27  	}
  28  }
  29  
  30  type SliderStyle struct {
  31  	Min, Max float32
  32  	Color    color.NRGBA
  33  	Float    *widget.Float
  34  
  35  	FingerSize unit.Value
  36  }
  37  
  38  func (s SliderStyle) Layout(gtx layout.Context) layout.Dimensions {
  39  	thumbRadius := gtx.Px(unit.Dp(6))
  40  	trackWidth := gtx.Px(unit.Dp(2))
  41  
  42  	axis := s.Float.Axis
  43  	// Keep a minimum length so that the track is always visible.
  44  	minLength := thumbRadius + 3*thumbRadius + thumbRadius
  45  	// Try to expand to finger size, but only if the constraints
  46  	// allow for it.
  47  	touchSizePx := min(gtx.Px(s.FingerSize), axis.Convert(gtx.Constraints.Max).Y)
  48  	sizeMain := max(axis.Convert(gtx.Constraints.Min).X, minLength)
  49  	sizeCross := max(2*thumbRadius, touchSizePx)
  50  	size := axis.Convert(image.Pt(sizeMain, sizeCross))
  51  
  52  	st := op.Save(gtx.Ops)
  53  	o := axis.Convert(image.Pt(thumbRadius, 0))
  54  	op.Offset(layout.FPt(o)).Add(gtx.Ops)
  55  	gtx.Constraints.Min = axis.Convert(image.Pt(sizeMain-2*thumbRadius, sizeCross))
  56  	s.Float.Layout(gtx, thumbRadius, s.Min, s.Max)
  57  	gtx.Constraints.Min = gtx.Constraints.Min.Add(axis.Convert(image.Pt(0, sizeCross)))
  58  	thumbPos := thumbRadius + int(s.Float.Pos())
  59  	st.Load()
  60  
  61  	color := s.Color
  62  	if gtx.Queue == nil {
  63  		color = f32color.Disabled(color)
  64  	}
  65  
  66  	// Draw track before thumb.
  67  	st = op.Save(gtx.Ops)
  68  	track := image.Rectangle{
  69  		Min: axis.Convert(image.Pt(thumbRadius, sizeCross/2-trackWidth/2)),
  70  		Max: axis.Convert(image.Pt(thumbPos, sizeCross/2+trackWidth/2)),
  71  	}
  72  	clip.Rect(track).Add(gtx.Ops)
  73  	paint.Fill(gtx.Ops, color)
  74  	st.Load()
  75  
  76  	// Draw track after thumb.
  77  	st = op.Save(gtx.Ops)
  78  	track = image.Rectangle{
  79  		Min: axis.Convert(image.Pt(thumbPos, axis.Convert(track.Min).Y)),
  80  		Max: axis.Convert(image.Pt(sizeMain-thumbRadius, axis.Convert(track.Max).Y)),
  81  	}
  82  	clip.Rect(track).Add(gtx.Ops)
  83  	paint.Fill(gtx.Ops, f32color.MulAlpha(color, 96))
  84  	st.Load()
  85  
  86  	// Draw thumb.
  87  	pt := axis.Convert(image.Pt(thumbPos, sizeCross/2))
  88  	paint.FillShape(gtx.Ops, color,
  89  		clip.Circle{
  90  			Center: f32.Point{X: float32(pt.X), Y: float32(pt.Y)},
  91  			Radius: float32(thumbRadius),
  92  		}.Op(gtx.Ops))
  93  
  94  	return layout.Dimensions{Size: size}
  95  }
  96  
  97  func max(a, b int) int {
  98  	if a > b {
  99  		return a
 100  	}
 101  	return b
 102  }
 103  
 104  func min(a, b int) int {
 105  	if a < b {
 106  		return a
 107  	}
 108  	return b
 109  }
 110