clickable.go raw

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