button.go raw

   1  // SPDX-License-Identifier: Unlicense OR MIT
   2  
   3  package widget
   4  
   5  import (
   6  	"image"
   7  	"time"
   8  
   9  	"github.com/p9c/p9/pkg/gel/gio/f32"
  10  	"github.com/p9c/p9/pkg/gel/gio/gesture"
  11  	"github.com/p9c/p9/pkg/gel/gio/io/key"
  12  	"github.com/p9c/p9/pkg/gel/gio/io/pointer"
  13  	"github.com/p9c/p9/pkg/gel/gio/layout"
  14  	"github.com/p9c/p9/pkg/gel/gio/op"
  15  )
  16  
  17  // Clickable represents a clickable area.
  18  type Clickable struct {
  19  	click  gesture.Click
  20  	clicks []Click
  21  	// prevClicks is the index into clicks that marks the clicks
  22  	// from the most recent Layout call. prevClicks is used to keep
  23  	// clicks bounded.
  24  	prevClicks int
  25  	history    []Press
  26  }
  27  
  28  // Click represents a click.
  29  type Click struct {
  30  	Modifiers key.Modifiers
  31  	NumClicks int
  32  }
  33  
  34  // Press represents a past pointer press.
  35  type Press struct {
  36  	// Position of the press.
  37  	Position f32.Point
  38  	// Start is when the press began.
  39  	Start time.Time
  40  	// End is when the press was ended by a release or cancel.
  41  	// A zero End means it hasn't ended yet.
  42  	End time.Time
  43  	// Cancelled is true for cancelled presses.
  44  	Cancelled bool
  45  }
  46  
  47  // Click executes a simple programmatic click
  48  func (b *Clickable) Click() {
  49  	b.clicks = append(b.clicks, Click{
  50  		Modifiers: 0,
  51  		NumClicks: 1,
  52  	})
  53  }
  54  
  55  // Clicked reports whether there are pending clicks as would be
  56  // reported by Clicks. If so, Clicked removes the earliest click.
  57  func (b *Clickable) Clicked() bool {
  58  	if len(b.clicks) == 0 {
  59  		return false
  60  	}
  61  	n := copy(b.clicks, b.clicks[1:])
  62  	b.clicks = b.clicks[:n]
  63  	if b.prevClicks > 0 {
  64  		b.prevClicks--
  65  	}
  66  	return true
  67  }
  68  
  69  // Hovered returns whether pointer is over the element.
  70  func (b *Clickable) Hovered() bool {
  71  	return b.click.Hovered()
  72  }
  73  
  74  // Pressed returns whether pointer is pressing the element.
  75  func (b *Clickable) Pressed() bool {
  76  	return b.click.Pressed()
  77  }
  78  
  79  // Clicks returns and clear the clicks since the last call to Clicks.
  80  func (b *Clickable) Clicks() []Click {
  81  	clicks := b.clicks
  82  	b.clicks = nil
  83  	b.prevClicks = 0
  84  	return clicks
  85  }
  86  
  87  // History is the past pointer presses useful for drawing markers.
  88  // History is retained for a short duration (about a second).
  89  func (b *Clickable) History() []Press {
  90  	return b.history
  91  }
  92  
  93  // Layout and update the button state
  94  func (b *Clickable) Layout(gtx layout.Context) layout.Dimensions {
  95  	b.update(gtx)
  96  	stack := op.Save(gtx.Ops)
  97  	pointer.Rect(image.Rectangle{Max: gtx.Constraints.Min}).Add(gtx.Ops)
  98  	b.click.Add(gtx.Ops)
  99  	stack.Load()
 100  	for len(b.history) > 0 {
 101  		c := b.history[0]
 102  		if c.End.IsZero() || gtx.Now.Sub(c.End) < 1*time.Second {
 103  			break
 104  		}
 105  		n := copy(b.history, b.history[1:])
 106  		b.history = b.history[:n]
 107  	}
 108  	return layout.Dimensions{Size: gtx.Constraints.Min}
 109  }
 110  
 111  // update the button state by processing events.
 112  func (b *Clickable) update(gtx layout.Context) {
 113  	// Flush clicks from before the last update.
 114  	n := copy(b.clicks, b.clicks[b.prevClicks:])
 115  	b.clicks = b.clicks[:n]
 116  	b.prevClicks = n
 117  
 118  	for _, e := range b.click.Events(gtx) {
 119  		switch e.Type {
 120  		case gesture.TypeClick:
 121  			b.clicks = append(b.clicks, Click{
 122  				Modifiers: e.Modifiers,
 123  				NumClicks: e.NumClicks,
 124  			})
 125  			if l := len(b.history); l > 0 {
 126  				b.history[l-1].End = gtx.Now
 127  			}
 128  		case gesture.TypeCancel:
 129  			for i := range b.history {
 130  				b.history[i].Cancelled = true
 131  				if b.history[i].End.IsZero() {
 132  					b.history[i].End = gtx.Now
 133  				}
 134  			}
 135  		case gesture.TypePress:
 136  			b.history = append(b.history, Press{
 137  				Position: e.Position,
 138  				Start:    gtx.Now,
 139  			})
 140  		}
 141  	}
 142  }
 143