float.go raw

   1  // SPDX-License-Identifier: Unlicense OR MIT
   2  
   3  package widget
   4  
   5  import (
   6  	"image"
   7  
   8  	"github.com/p9c/p9/pkg/gel/gio/gesture"
   9  	"github.com/p9c/p9/pkg/gel/gio/io/pointer"
  10  	"github.com/p9c/p9/pkg/gel/gio/layout"
  11  	"github.com/p9c/p9/pkg/gel/gio/op"
  12  )
  13  
  14  // Float is for selecting a value in a range.
  15  type Float struct {
  16  	Value float32
  17  	Axis  layout.Axis
  18  
  19  	drag    gesture.Drag
  20  	pos     float32 // position normalized to [0, 1]
  21  	length  float32
  22  	changed bool
  23  }
  24  
  25  // Dragging returns whether the value is being interacted with.
  26  func (f *Float) Dragging() bool { return f.drag.Dragging() }
  27  
  28  // Layout updates the value according to drag events along the f's main axis.
  29  //
  30  // The range of f is set by the minimum constraints main axis value.
  31  func (f *Float) Layout(gtx layout.Context, pointerMargin int, min, max float32) layout.Dimensions {
  32  	size := gtx.Constraints.Min
  33  	f.length = float32(f.Axis.Convert(size).X)
  34  
  35  	var de *pointer.Event
  36  	for _, e := range f.drag.Events(gtx.Metric, gtx, gesture.Axis(f.Axis)) {
  37  		if e.Type == pointer.Press || e.Type == pointer.Drag {
  38  			de = &e
  39  		}
  40  	}
  41  
  42  	value := f.Value
  43  	if de != nil {
  44  		xy := de.Position.X
  45  		if f.Axis == layout.Vertical {
  46  			xy = de.Position.Y
  47  		}
  48  		f.pos = xy / f.length
  49  		value = min + (max-min)*f.pos
  50  	} else if min != max {
  51  		f.pos = (value - min) / (max - min)
  52  	}
  53  	// Unconditionally call setValue in case min, max, or value changed.
  54  	f.setValue(value, min, max)
  55  
  56  	if f.pos < 0 {
  57  		f.pos = 0
  58  	} else if f.pos > 1 {
  59  		f.pos = 1
  60  	}
  61  
  62  	defer op.Save(gtx.Ops).Load()
  63  	margin := f.Axis.Convert(image.Pt(pointerMargin, 0))
  64  	rect := image.Rectangle{
  65  		Min: margin.Mul(-1),
  66  		Max: size.Add(margin),
  67  	}
  68  	pointer.Rect(rect).Add(gtx.Ops)
  69  	f.drag.Add(gtx.Ops)
  70  
  71  	return layout.Dimensions{Size: size}
  72  }
  73  
  74  func (f *Float) setValue(value, min, max float32) {
  75  	if min > max {
  76  		min, max = max, min
  77  	}
  78  	if value < min {
  79  		value = min
  80  	} else if value > max {
  81  		value = max
  82  	}
  83  	if f.Value != value {
  84  		f.Value = value
  85  		f.changed = true
  86  	}
  87  }
  88  
  89  // Pos reports the selected position.
  90  func (f *Float) Pos() float32 {
  91  	return f.pos * f.length
  92  }
  93  
  94  // Changed reports whether the value has changed since
  95  // the last call to Changed.
  96  func (f *Float) Changed() bool {
  97  	changed := f.changed
  98  	f.changed = false
  99  	return changed
 100  }
 101