button.go raw

   1  package gel
   2  
   3  import (
   4  	"image/color"
   5  	"math"
   6  	"strings"
   7  	
   8  	"github.com/p9c/gio/f32"
   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/text"
  14  	"github.com/p9c/gio/unit"
  15  	
  16  	"github.com/p9c/gel/f32color"
  17  )
  18  
  19  // Button is a material text label icon with options to change all features
  20  type Button struct {
  21  	*Window
  22  	background   color.NRGBA
  23  	color        color.NRGBA
  24  	cornerRadius unit.Value
  25  	font         text.Font
  26  	inset        *l.Inset
  27  	text         string
  28  	textSize     unit.Value
  29  	button       *Clickable
  30  	shaper       text.Shaper
  31  }
  32  
  33  // Button is a regular material text button where all the dimensions, colors, corners and font can be changed
  34  func (w *Window) Button(btn *Clickable) *Button {
  35  	var font text.Font
  36  	var e error
  37  	if font, e = w.collection.Font("plan9"); E.Chk(e) {
  38  	}
  39  	return &Button{
  40  		Window: w,
  41  		text:   strings.ToUpper("text unset"),
  42  		// default sets
  43  		font:         font,
  44  		color:        w.Colors.GetNRGBAFromName("DocBg"),
  45  		cornerRadius: w.TextSize.Scale(0.25),
  46  		background:   w.Colors.GetNRGBAFromName("Primary"),
  47  		textSize:     w.TextSize,
  48  		inset: &l.Inset{
  49  			Top:    w.TextSize.Scale(0.5),
  50  			Bottom: w.TextSize.Scale(0.5),
  51  			Left:   w.TextSize.Scale(0.5),
  52  			Right:  w.TextSize.Scale(0.5),
  53  		},
  54  		button: btn,
  55  		shaper: w.shaper,
  56  	}
  57  }
  58  
  59  // Background sets the background color
  60  func (b *Button) Background(background string) *Button {
  61  	b.background = b.Theme.Colors.GetNRGBAFromName(background)
  62  	return b
  63  }
  64  
  65  // Color sets the text color
  66  func (b *Button) Color(color string) *Button {
  67  	b.color = b.Theme.Colors.GetNRGBAFromName(color)
  68  	return b
  69  }
  70  
  71  // CornerRadius sets the corner radius (all measurements are scaled from the base text size)
  72  func (b *Button) CornerRadius(cornerRadius float32) *Button {
  73  	b.cornerRadius = b.TextSize.Scale(cornerRadius)
  74  	return b
  75  }
  76  
  77  // Font sets the font style
  78  func (b *Button) Font(font string) *Button {
  79  	var fon text.Font
  80  	var e error
  81  	if fon, e = b.collection.Font(font); !E.Chk(e) {
  82  		b.font = fon
  83  	} else {
  84  		panic(e)
  85  	}
  86  	return b
  87  }
  88  
  89  // Inset sets the inset between the button border and the text
  90  func (b *Button) Inset(scale float32) *Button {
  91  	b.inset = &l.Inset{
  92  		Top:    b.TextSize.Scale(scale),
  93  		Right:  b.TextSize.Scale(scale),
  94  		Bottom: b.TextSize.Scale(scale),
  95  		Left:   b.TextSize.Scale(scale),
  96  	}
  97  	return b
  98  }
  99  
 100  // Text sets the text on the button
 101  func (b *Button) Text(text string) *Button {
 102  	b.text = text
 103  	return b
 104  }
 105  
 106  // TextScale sets the dimensions of the text as a fraction of the base text size
 107  func (b *Button) TextScale(scale float32) *Button {
 108  	b.textSize = b.Theme.TextSize.Scale(scale)
 109  	return b
 110  }
 111  
 112  // SetClick defines the callback to run on a click (mouse up) event
 113  func (b *Button) SetClick(fn func()) *Button {
 114  	b.button.SetClick(fn)
 115  	return b
 116  }
 117  
 118  // SetCancel sets the callback to run when the user presses down over the button
 119  // but then moves out of its hitbox before release (click)
 120  func (b *Button) SetCancel(fn func()) *Button {
 121  	b.button.SetCancel(fn)
 122  	return b
 123  }
 124  
 125  func (b *Button) SetPress(fn func()) *Button {
 126  	b.button.SetPress(fn)
 127  	return b
 128  }
 129  
 130  // Fn renders the button
 131  func (b *Button) Fn(gtx l.Context) l.Dimensions {
 132  	bl := &ButtonLayout{
 133  		background:   b.background,
 134  		cornerRadius: b.cornerRadius,
 135  		button:       b.button,
 136  	}
 137  	fn := func(gtx l.Context) l.Dimensions {
 138  		return b.inset.Layout(
 139  			gtx, func(gtx l.Context) l.Dimensions {
 140  				// paint.ColorOp{Color: b.color}.Add(gtx.Ops)
 141  				return b.Flex().Rigid(b.Label().Text(b.text).TextScale(b.textSize.V / b.TextSize.V).Fn).Fn(gtx)
 142  				// b.Window.Text().
 143  				// Alignment(text.Middle).
 144  				// Fn(gtx, b.shaper, b.font, b.textSize, b.text)
 145  			},
 146  		)
 147  	}
 148  	bl.Embed(fn)
 149  	return bl.Fn(gtx)
 150  }
 151  
 152  func drawInk(c l.Context, p press) {
 153  	// duration is the number of seconds for the completed animation: expand while
 154  	// fading in, then out.
 155  	const (
 156  		expandDuration = float32(0.5)
 157  		fadeDuration   = float32(0.9)
 158  	)
 159  	now := c.Now
 160  	t := float32(now.Sub(p.Start).Seconds())
 161  	end := p.End
 162  	if end.IsZero() {
 163  		// If the press hasn't ended, don't fade-out.
 164  		end = now
 165  	}
 166  	endt := float32(end.Sub(p.Start).Seconds())
 167  	// Compute the fade-in/out position in [0;1].
 168  	var alphat float32
 169  	{
 170  		var haste float32
 171  		if p.Cancelled {
 172  			// If the press was cancelled before the inkwell was fully faded in, fast
 173  			// forward the animation to match the fade-out.
 174  			if h := 0.5 - endt/fadeDuration; h > 0 {
 175  				haste = h
 176  			}
 177  		}
 178  		// Fade in.
 179  		half1 := t/fadeDuration + haste
 180  		if half1 > 0.5 {
 181  			half1 = 0.5
 182  		}
 183  		// Fade out.
 184  		half2 := float32(now.Sub(end).Seconds())
 185  		half2 /= fadeDuration
 186  		half2 += haste
 187  		if half2 > 0.5 {
 188  			// Too old.
 189  			return
 190  		}
 191  		
 192  		alphat = half1 + half2
 193  	}
 194  	// Compute the expand position in [0;1].
 195  	sizet := t
 196  	if p.Cancelled {
 197  		// Freeze expansion of cancelled presses.
 198  		sizet = endt
 199  	}
 200  	sizet /= expandDuration
 201  	// Animate only ended presses, and presses that are fading in.
 202  	if !p.End.IsZero() || sizet <= 1.0 {
 203  		op.InvalidateOp{}.Add(c.Ops)
 204  	}
 205  	if sizet > 1.0 {
 206  		sizet = 1.0
 207  	}
 208  	if alphat > .5 {
 209  		// Start fadeout after half the animation.
 210  		alphat = 1.0 - alphat
 211  	}
 212  	// Twice the speed to attain fully faded in at 0.5.
 213  	t2 := alphat * 2
 214  	// BeziƩr ease-in curve.
 215  	alphaBezier := t2 * t2 * (3.0 - 2.0*t2)
 216  	sizeBezier := sizet * sizet * (3.0 - 2.0*sizet)
 217  	size := float32(c.Constraints.Min.X)
 218  	if h := float32(c.Constraints.Min.Y); h > size {
 219  		size = h
 220  	}
 221  	// Cover the entire constraints min rectangle.
 222  	size *= 2 * float32(math.Sqrt(2))
 223  	// Apply curve values to size and color.
 224  	size *= sizeBezier
 225  	alpha := 0.7 * alphaBezier
 226  	const col = 0.8
 227  	ba, bc := byte(alpha*0xff), byte(col*0xff)
 228  	defer op.Save(c.Ops).Load()
 229  	rgba := f32color.MulAlpha(color.NRGBA{A: 0xff, R: bc, G: bc, B: bc}, ba)
 230  	ink := paint.ColorOp{Color: rgba}
 231  	ink.Add(c.Ops)
 232  	rr := size * .5
 233  	op.Offset(
 234  		p.Position.Add(
 235  			f32.Point{
 236  				X: -rr,
 237  				Y: -rr,
 238  			},
 239  		),
 240  	).Add(c.Ops)
 241  	clip.RRect{
 242  		Rect: f32.Rectangle{
 243  			Max: f32.Point{
 244  				X: size,
 245  				Y: size,
 246  			},
 247  		},
 248  		NE: rr, NW: rr, SE: rr, SW: rr,
 249  	}.Add(c.Ops)
 250  	paint.PaintOp{}.Add(c.Ops)
 251  }
 252