~eliasnaur/gio-patches

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
3 2

[PATCH gio] widget: add methods Press, Release and Click to Clickable to simulate button presses or button clicks. This can be used to implement keyboard shortcuts for buttons.

Raffaele Sena
Details
Message ID
<20201221211510.48231-1-raff367@gmail.com>
DKIM signature
pass
Download raw message
Patch: +50 -1
Note that when called outside the processing of a system.FrameEvent, you will need
to explicitly call window.Invalidate in order to generate a new FrameEvent with the
button UI changes.

Also added method Pressed to Clickable (similar to Clicked) to check if the button is
currently being pressed.

Also fixed method String in key.Event (missing "{").

Signed-off-by: Raffaele Sena <raff367@gmail.com>
---
 io/key/key.go    |  2 +-
 widget/button.go | 49 ++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 50 insertions(+), 1 deletion(-)

diff --git a/io/key/key.go b/io/key/key.go
index f3cda60..3f49bf4 100644
--- a/io/key/key.go
+++ b/io/key/key.go
@@ -144,7 +144,7 @@ func (Event) ImplementsEvent()      {}
func (FocusEvent) ImplementsEvent() {}

func (e Event) String() string {
	return fmt.Sprintf("%v %v %v}", e.Name, e.Modifiers, e.State)
	return fmt.Sprintf("{%v %v %v}", e.Name, e.Modifiers, e.State)
}

func (m Modifiers) String() string {
diff --git a/widget/button.go b/widget/button.go
index f534920..ea42416 100644
--- a/widget/button.go
+++ b/widget/button.go
@@ -66,6 +66,13 @@ func (b *Clickable) Clicks() []Click {
	return clicks
}

// Pressed reports if the button is currently pressed
// (the has been a Press event without a release).
func (b *Clickable) Pressed() bool {
	l := len(b.history)
	return l > 0 && b.history[l-1].End.IsZero()
}

// History is the past pointer presses useful for drawing markers.
// History is retained for a short duration (about a second).
func (b *Clickable) History() []Press {
@@ -86,9 +93,51 @@ func (b *Clickable) Layout(gtx layout.Context) layout.Dimensions {
		n := copy(b.history, b.history[1:])
		b.history = b.history[:n]
	}

	return layout.Dimensions{Size: gtx.Constraints.Min}
}

func (b *Clickable) addClickEvent(press bool, m key.Modifiers) {
	// Flush clicks from before the last update.
	n := copy(b.clicks, b.clicks[b.prevClicks:])
	b.clicks = b.clicks[:n]
	b.prevClicks = n

	if press {
		b.history = append(b.history, Press{
			Position: f32.Point{X: 1, Y: 1},
			Start:    time.Now(),
		})
	} else {
		b.clicks = append(b.clicks, Click{
			Modifiers: m,
			NumClicks: 1,
		})
		if l := len(b.history); l > 0 {
			b.history[l-1].End = time.Now()
		}
	}
}

// Press adds a button press event to the Clickable.
func (b *Clickable) Press() {
	b.addClickEvent(true, 0)
}

// Release adds a button release event to the Clickable.
func (b *Clickable) Release(m key.Modifiers) {
	b.addClickEvent(false, m)
}

// Click add a button press and later button release to the Clickable.
func (b *Clickable) Click(m key.Modifiers) {
	b.addClickEvent(true, 0)

	time.AfterFunc(200*time.Millisecond, func() {
		b.addClickEvent(false, m)
	})
}

// update the button state by processing events.
func (b *Clickable) update(gtx layout.Context) {
	// Flush clicks from before the last update.
-- 
2.29.2
Details
Message ID
<C7Z4JX0XYVC0.2BZ83K93BG044@testmac>
In-Reply-To
<20201221211510.48231-1-raff367@gmail.com> (view parent)
DKIM signature
fail
Download raw message
DKIM signature: fail
On Mon Dec 21, 2020 at 10:15 PM CET, Raffaele Sena wrote:
> Note that when called outside the processing of a system.FrameEvent, you
> will need
> to explicitly call window.Invalidate in order to generate a new
> FrameEvent with the
> button UI changes.
>
> Also added method Pressed to Clickable (similar to Clicked) to check if
> the button is
> currently being pressed.
>
> Also fixed method String in key.Event (missing "{").
>
> Signed-off-by: Raffaele Sena <raff367@gmail.com>
> diff --git a/widget/button.go b/widget/button.go
> index f534920..ea42416 100644
> --- a/widget/button.go
> +++ b/widget/button.go
> @@ -86,9 +93,51 @@ func (b *Clickable) Layout(gtx layout.Context)
> layout.Dimensions {
> n := copy(b.history, b.history[1:])
> b.history = b.history[:n]
> }
> +
> return layout.Dimensions{Size: gtx.Constraints.Min}
> }
>  
> +func (b *Clickable) addClickEvent(press bool, m key.Modifiers) {

Why not call this method from Clickable.update, to not duplicate the
subtle click logic?

> +
> +// Click add a button press and later button release to the Clickable.
> +func (b *Clickable) Click(m key.Modifiers) {
> + b.addClickEvent(true, 0)
> +
> + time.AfterFunc(200*time.Millisecond, func() {

AfterFunc calls its callback from a separate goroutine, so this is racy.
Can't you add the release event immediately?

> + b.addClickEvent(false, m)
> + })
> +}
> +
> // update the button state by processing events.
> func (b *Clickable) update(gtx layout.Context) {
> // Flush clicks from before the last update.
> --
> 2.29.2
Raffaele Sena
Details
Message ID
<CANKfucYo6YiZvQt7s8DcvvN8hag3GjR4SCje7TfDRyMD-E0q-w@mail.gmail.com>
In-Reply-To
<C7Z4JX0XYVC0.2BZ83K93BG044@testmac> (view parent)
DKIM signature
pass
Download raw message
Elias, I struggled with re-using the code in addClickEvent also in
Clickable.update, but I didn't want to change the logic in
Clickable.update, but if you want this is a possible reimplementation:

func (b *Clickable) processEvent(e gesture.ClickEvent, now time.Time,
flush bool) {
        // Flush clicks from before the last update.
        if flush {
                n := copy(b.clicks, b.clicks[b.prevClicks:])
                b.clicks = b.clicks[:n]
                b.prevClicks = n
        }

        switch e.Type {
        case gesture.TypeClick:
                b.clicks = append(b.clicks, Click{
                        Modifiers: e.Modifiers,
                        NumClicks: e.NumClicks,
                })
                if l := len(b.history); l > 0 {
                        b.history[l-1].End = now
                }
        case gesture.TypeCancel:
                for i := range b.history {
                        b.history[i].Cancelled = true
                        if b.history[i].End.IsZero() {
                                b.history[i].End = now
                        }
                }
        case gesture.TypePress:
                b.history = append(b.history, Press{
                        Position: e.Position,
                        Start:    now,
                })
        }
}

// update the button state by processing events.
func (b *Clickable) update(gtx layout.Context) {
        flush := true

        for _, e := range b.click.Events(gtx) {
                b.processEvent(e, gtx.Now, flush)
                flush = false
        }
}

But at this point given the content of Clickable.update, it may be
worth moving the code directly in Clickable.Layout.

On Mon, Dec 21, 2020 at 11:10 PM Elias Naur <mail@eliasnaur.com> wrote:
>
> On Mon Dec 21, 2020 at 10:15 PM CET, Raffaele Sena wrote:
> > Note that when called outside the processing of a system.FrameEvent, you
> > will need
> > to explicitly call window.Invalidate in order to generate a new
> > FrameEvent with the
> > button UI changes.
> >
> > Also added method Pressed to Clickable (similar to Clicked) to check if
> > the button is
> > currently being pressed.
> >
> > Also fixed method String in key.Event (missing "{").
> >
> > Signed-off-by: Raffaele Sena <raff367@gmail.com>
> > diff --git a/widget/button.go b/widget/button.go
> > index f534920..ea42416 100644
> > --- a/widget/button.go
> > +++ b/widget/button.go
> > @@ -86,9 +93,51 @@ func (b *Clickable) Layout(gtx layout.Context)
> > layout.Dimensions {
> > n := copy(b.history, b.history[1:])
> > b.history = b.history[:n]
> > }
> > +
> > return layout.Dimensions{Size: gtx.Constraints.Min}
> > }
> >
> > +func (b *Clickable) addClickEvent(press bool, m key.Modifiers) {
>
> Why not call this method from Clickable.update, to not duplicate the
> subtle click logic?
>
> > +
> > +// Click add a button press and later button release to the Clickable.
> > +func (b *Clickable) Click(m key.Modifiers) {
> > + b.addClickEvent(true, 0)
> > +
> > + time.AfterFunc(200*time.Millisecond, func() {
>
> AfterFunc calls its callback from a separate goroutine, so this is racy.
> Can't you add the release event immediately?
>
> > + b.addClickEvent(false, m)
> > + })
> > +}
> > +
> > // update the button state by processing events.
> > func (b *Clickable) update(gtx layout.Context) {
> > // Flush clicks from before the last update.
> > --
> > 2.29.2
>
Details
Message ID
<C81SYVYAHL54.1Z7FS23KQFOO2@testmac>
In-Reply-To
<CANKfucYo6YiZvQt7s8DcvvN8hag3GjR4SCje7TfDRyMD-E0q-w@mail.gmail.com> (view parent)
DKIM signature
pass
Download raw message
Add a newline between a short patch subject and the patch message.

On Wed Dec 23, 2020 at 2:35 AM CET, Raffaele Sena wrote:
> Elias, I struggled with re-using the code in addClickEvent also in
> Clickable.update, but I didn't want to change the logic in
> Clickable.update, but if you want this is a possible reimplementation:
>
> func (b *Clickable) processEvent(e gesture.ClickEvent, now time.Time,
> flush bool) {
> // Flush clicks from before the last update.
> if flush {
> n := copy(b.clicks, b.clicks[b.prevClicks:])
> b.clicks = b.clicks[:n]
> b.prevClicks = n
> }
>
> switch e.Type {
> case gesture.TypeClick:
> b.clicks = append(b.clicks, Click{
> Modifiers: e.Modifiers,
> NumClicks: e.NumClicks,
> })
> if l := len(b.history); l > 0 {
> b.history[l-1].End = now
> }
> case gesture.TypeCancel:
> for i := range b.history {
> b.history[i].Cancelled = true
> if b.history[i].End.IsZero() {
> b.history[i].End = now
> }
> }
> case gesture.TypePress:
> b.history = append(b.history, Press{
> Position: e.Position,
> Start: now,
> })
> }
> }
>
> // update the button state by processing events.
> func (b *Clickable) update(gtx layout.Context) {
> flush := true
>
> for _, e := range b.click.Events(gtx) {
> b.processEvent(e, gtx.Now, flush)
> flush = false
> }
> }
>
> But at this point given the content of Clickable.update, it may be
> worth moving the code directly in Clickable.Layout.
>

You're absolutely free to change Clickable.update. In fact, I'd expect
it to change in response to exposing Press, Release, Click.

It seems the right change is to change update (or Layout) to call Press,
Release, Click, leaving the new methods the source of truth for event
handling.

Elias
Reply to thread Export thread (mbox)