172 lines
4.0 KiB
Go
172 lines
4.0 KiB
Go
package gel
|
|
|
|
import (
|
|
"image"
|
|
"time"
|
|
|
|
"github.com/p9c/p9/pkg/gel/gio/gesture"
|
|
"github.com/p9c/p9/pkg/gel/gio/io/key"
|
|
l "github.com/p9c/p9/pkg/gel/gio/layout"
|
|
"github.com/p9c/p9/pkg/gel/gio/op/clip"
|
|
)
|
|
|
|
// clickEvents holds callback functions for click interaction events.
|
|
type clickEvents struct {
|
|
Click, Cancel, Press func()
|
|
}
|
|
|
|
// Clickable represents a clickable area that handles pointer events.
|
|
// It tracks click history for visual feedback (ink ripples) and provides
|
|
// callbacks for click, cancel, and press events via the Events field.
|
|
type Clickable struct {
|
|
*Window
|
|
click gesture.Click
|
|
clicks []click
|
|
// prevClicks is the index into clicks that marks the clicks from the most recent Fn call. prevClicks is used to
|
|
// keep clicks bounded.
|
|
prevClicks int
|
|
history []press
|
|
Events clickEvents
|
|
}
|
|
|
|
func (w *Window) Clickable() (c *Clickable) {
|
|
c = &Clickable{
|
|
Window: w,
|
|
click: gesture.Click{},
|
|
clicks: nil,
|
|
prevClicks: 0,
|
|
history: nil,
|
|
Events: clickEvents{
|
|
Click: func() {
|
|
D.Ln("click event")
|
|
},
|
|
Cancel: func() {
|
|
D.Ln("cancel event")
|
|
},
|
|
Press: func() {
|
|
D.Ln("press event")
|
|
},
|
|
},
|
|
}
|
|
return
|
|
}
|
|
|
|
func (c *Clickable) SetClick(fn func()) *Clickable {
|
|
c.Events.Click = fn
|
|
return c
|
|
}
|
|
|
|
func (c *Clickable) SetCancel(fn func()) *Clickable {
|
|
c.Events.Cancel = fn
|
|
return c
|
|
}
|
|
|
|
func (c *Clickable) SetPress(fn func()) *Clickable {
|
|
c.Events.Press = fn
|
|
return c
|
|
}
|
|
|
|
// click represents a click.
|
|
type click struct {
|
|
Modifiers key.Modifiers
|
|
NumClicks int
|
|
}
|
|
|
|
// press represents a past pointer press.
|
|
type press struct {
|
|
// Position of the press.
|
|
Position image.Point
|
|
// Start is when the press began.
|
|
Start time.Time
|
|
// End is when the press was ended by a release or Cancel. A zero End means it hasn't ended yet.
|
|
End time.Time
|
|
// Cancelled is true for cancelled presses.
|
|
Cancelled bool
|
|
}
|
|
|
|
// Clicked reports whether there are pending clicks as would be reported by Clicks. If so, Clicked removes the earliest
|
|
// click.
|
|
func (c *Clickable) Clicked() bool {
|
|
if len(c.clicks) == 0 {
|
|
return false
|
|
}
|
|
n := copy(c.clicks, c.clicks[1:])
|
|
c.clicks = c.clicks[:n]
|
|
if c.prevClicks > 0 {
|
|
c.prevClicks--
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Clicks returns and clear the clicks since the last call to Clicks.
|
|
func (c *Clickable) Clicks() []click {
|
|
clicks := c.clicks
|
|
c.clicks = nil
|
|
c.prevClicks = 0
|
|
return clicks
|
|
}
|
|
|
|
// History is the past pointer presses useful for drawing markers. History is retained for a short duration (about a
|
|
// second).
|
|
func (c *Clickable) History() []press {
|
|
return c.history
|
|
}
|
|
|
|
func (c *Clickable) Fn(gtx l.Context) l.Dimensions {
|
|
c.update(gtx)
|
|
defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Min}).Push(gtx.Ops).Pop()
|
|
c.click.Add(gtx.Ops)
|
|
for len(c.history) > 0 {
|
|
cc := c.history[0]
|
|
if cc.End.IsZero() || gtx.Now.Sub(cc.End) < 1*time.Second {
|
|
break
|
|
}
|
|
n := copy(c.history, c.history[1:])
|
|
c.history = c.history[:n]
|
|
}
|
|
return l.Dimensions{Size: gtx.Constraints.Min}
|
|
}
|
|
|
|
// update the button changeState by processing clickEvents.
|
|
func (c *Clickable) update(gtx l.Context) {
|
|
// Flush clicks from before the last update.
|
|
n := copy(c.clicks, c.clicks[c.prevClicks:])
|
|
c.clicks = c.clicks[:n]
|
|
c.prevClicks = n
|
|
for {
|
|
ev, ok := c.click.Update(gtx.Source)
|
|
if !ok {
|
|
break
|
|
}
|
|
switch ev.Kind {
|
|
case gesture.KindClick:
|
|
clk := click{
|
|
Modifiers: ev.Modifiers,
|
|
NumClicks: ev.NumClicks,
|
|
}
|
|
c.clicks = append(c.clicks, clk)
|
|
if ll := len(c.history); ll > 0 {
|
|
c.history[ll-1].End = gtx.Now
|
|
}
|
|
c.Window.Runner <- func() (e error) { c.Events.Click(); return nil }
|
|
case gesture.KindCancel:
|
|
for i := range c.history {
|
|
c.history[i].Cancelled = true
|
|
if c.history[i].End.IsZero() {
|
|
c.history[i].End = gtx.Now
|
|
}
|
|
}
|
|
c.Window.Runner <- func() (e error) { c.Events.Cancel(); return nil }
|
|
case gesture.KindPress:
|
|
c.history = append(c.history, press{
|
|
Position: ev.Position,
|
|
Start: gtx.Now,
|
|
})
|
|
c.Window.Runner <- func() (e error) {
|
|
c.Events.Press()
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
}
|