From a28e35d415bca20e48c7f7e064a22a87ef722fc4 Mon Sep 17 00:00:00 2001 From: okratitan Date: Fri, 9 Oct 2020 15:58:20 -0500 Subject: [PATCH 01/16] Don't call Tapped until we are sure the Tapped isn't a DoubleTapped --- internal/driver/glfw/window.go | 81 +++++++++++++++++------------ internal/driver/glfw/window_test.go | 14 ++++- 2 files changed, 62 insertions(+), 33 deletions(-) diff --git a/internal/driver/glfw/window.go b/internal/driver/glfw/window.go index ec7f0c89d1..5fe0b562ac 100644 --- a/internal/driver/glfw/window.go +++ b/internal/driver/glfw/window.go @@ -3,6 +3,7 @@ package glfw import "C" import ( "bytes" + "context" "image" _ "image/png" // for the icon "runtime" @@ -73,6 +74,8 @@ type window struct { mouseClickTime time.Time mouseLastClick fyne.CanvasObject mousePressed fyne.CanvasObject + mouseClickCount int + mouseCancelFunc context.CancelFunc onClosed func() onCloseIntercepted func() @@ -656,7 +659,6 @@ func (w *window) mouseClicked(_ *glfw.Window, btn glfw.MouseButton, action glfw. co, _ = w.mouseDragged.(fyne.CanvasObject) ev.Position = w.mousePos.Subtract(w.mouseDraggedOffset).Subtract(co.Position()) } - button, modifiers := convertMouseButton(btn, mods) if wid, ok := co.(desktop.Mouseable); ok { mev := new(desktop.MouseEvent) @@ -685,54 +687,69 @@ func (w *window) mouseClicked(_ *glfw.Window, btn glfw.MouseButton, action glfw. w.mouseButton = 0 } - // Check for double click/tap - doubleTapped := false - if action == glfw.Release && button == desktop.LeftMouseButton { - now := time.Now() - // we can safely subtract the first "zero" time as it'll be much larger than doubleClickDelay - if now.Sub(w.mouseClickTime).Nanoseconds()/1e6 <= doubleClickDelay && w.mouseLastClick == co { - if wid, ok := co.(fyne.DoubleTappable); ok { - doubleTapped = true - w.queueEvent(func() { wid.DoubleTapped(ev) }) - } + if wid, ok := co.(fyne.Draggable); ok { + if action == glfw.Press { + w.mouseDragPos = w.mousePos + w.mouseDragged = wid + w.mouseDraggedOffset = w.mousePos.Subtract(co.Position()).Subtract(ev.Position) } - w.mouseClickTime = now - w.mouseLastClick = co } - + if action == glfw.Release && w.mouseDragged != nil { + if w.mouseDragStarted { + w.queueEvent(w.mouseDragged.DragEnd) + w.mouseDragStarted = false + } + if w.objIsDragged(w.mouseOver) && !w.objIsDragged(coMouse) { + w.mouseOut() + } + w.mouseDragged = nil + } _, tap := co.(fyne.Tappable) _, altTap := co.(fyne.SecondaryTappable) - // Prevent Tapped from triggering if DoubleTapped has been sent - if (tap || altTap) && !doubleTapped { + if tap || altTap { if action == glfw.Press { w.mousePressed = co } else if action == glfw.Release { if co == w.mousePressed { if button == desktop.RightMouseButton && altTap { w.queueEvent(func() { co.(fyne.SecondaryTappable).TappedSecondary(ev) }) - } else if button == desktop.LeftMouseButton && tap { - w.queueEvent(func() { co.(fyne.Tappable).Tapped(ev) }) } } - w.mousePressed = nil } } - if wid, ok := co.(fyne.Draggable); ok { - if action == glfw.Press { - w.mouseDragPos = w.mousePos - w.mouseDragged = wid - w.mouseDraggedOffset = w.mousePos.Subtract(co.Position()).Subtract(ev.Position) + + // Check for double click/tap on left mouse button + if action == glfw.Release && button == desktop.LeftMouseButton { + w.mouseClickCount++ + w.mouseLastClick = co + if w.mouseCancelFunc != nil { + w.mouseCancelFunc() + return } + go w.waitForDoubleTap(co, ev, action, button) } - if action == glfw.Release && w.mouseDragged != nil { - if w.mouseDragStarted { - w.queueEvent(w.mouseDragged.DragEnd) - w.mouseDragStarted = false - } - if w.objIsDragged(w.mouseOver) && !w.objIsDragged(coMouse) { - w.mouseOut() +} + +func (w *window) waitForDoubleTap(co fyne.CanvasObject, ev *fyne.PointEvent, action glfw.Action, button desktop.MouseButton) { + var ctx context.Context + ctx, w.mouseCancelFunc = context.WithDeadline(context.TODO(), time.Now().Add(time.Millisecond*doubleClickDelay)) + defer w.mouseCancelFunc() + select { + case <-ctx.Done(): + if w.mouseClickCount == 2 && w.mouseLastClick == co { + if wid, ok := co.(fyne.DoubleTappable); ok { + w.queueEvent(func() { wid.DoubleTapped(ev) }) + } + } else if co == w.mousePressed { + if wid, ok := co.(fyne.Tappable); ok { + w.queueEvent(func() { wid.Tapped(ev) }) + } } - w.mouseDragged = nil + w.mouseClickCount = 0 + w.mousePressed = nil + w.mouseCancelFunc = nil + w.mouseLastClick = nil + return } } diff --git a/internal/driver/glfw/window_test.go b/internal/driver/glfw/window_test.go index 1d352588cb..c07d59d56e 100644 --- a/internal/driver/glfw/window_test.go +++ b/internal/driver/glfw/window_test.go @@ -427,6 +427,7 @@ func TestWindow_Tapped(t *testing.T) { w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) w.waitForEvents() + time.Sleep(time.Second) assert.Nil(t, o.popSecondaryTapEvent(), "no secondary tap") if e, _ := o.popTapEvent().(*fyne.PointEvent); assert.NotNil(t, e, "tapped") { @@ -464,12 +465,13 @@ func TestWindow_TappedSecondary_OnPrimaryOnlyTarget(t *testing.T) { w.mousePos = fyne.NewPos(10, 25) w.mouseClicked(w.viewport, glfw.MouseButton2, glfw.Press, 0) w.mouseClicked(w.viewport, glfw.MouseButton2, glfw.Release, 0) - w.waitForEvents() + time.Sleep(time.Second) assert.False(t, tapped) w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) w.waitForEvents() + time.Sleep(time.Second) assert.True(t, tapped) } @@ -497,6 +499,8 @@ func TestWindow_TappedIgnoresScrollerClip(t *testing.T) { w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) w.waitForEvents() + time.Sleep(time.Second) + assert.False(t, tapped, "Tapped button that was clipped") w.mousePos = fyne.NewPos(10, 120) @@ -504,6 +508,8 @@ func TestWindow_TappedIgnoresScrollerClip(t *testing.T) { w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) w.waitForEvents() + time.Sleep(time.Second) + assert.True(t, tapped, "Tapped button that was clipped") } @@ -519,6 +525,8 @@ func TestWindow_TappedIgnoredWhenMovedOffOfTappable(t *testing.T) { w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) w.waitForEvents() + time.Sleep(time.Second) + assert.Equal(t, 1, tapped, "Button 1 should be tapped") tapped = 0 @@ -527,12 +535,16 @@ func TestWindow_TappedIgnoredWhenMovedOffOfTappable(t *testing.T) { w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) w.waitForEvents() + time.Sleep(time.Second) + assert.Equal(t, 0, tapped, "button was tapped without mouse press & release on it %d", tapped) w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) w.waitForEvents() + time.Sleep(time.Second) + assert.Equal(t, 2, tapped, "Button 2 should be tapped") } From b35ed8475bcc94f485b289eaeee5fea06de6927b Mon Sep 17 00:00:00 2001 From: okratitan Date: Fri, 9 Oct 2020 16:02:52 -0500 Subject: [PATCH 02/16] Oops didn't mean to remove that line --- internal/driver/glfw/window_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/driver/glfw/window_test.go b/internal/driver/glfw/window_test.go index c07d59d56e..44e93888d4 100644 --- a/internal/driver/glfw/window_test.go +++ b/internal/driver/glfw/window_test.go @@ -465,6 +465,7 @@ func TestWindow_TappedSecondary_OnPrimaryOnlyTarget(t *testing.T) { w.mousePos = fyne.NewPos(10, 25) w.mouseClicked(w.viewport, glfw.MouseButton2, glfw.Press, 0) w.mouseClicked(w.viewport, glfw.MouseButton2, glfw.Release, 0) + w.waitForEvents() time.Sleep(time.Second) assert.False(t, tapped) From 2e0b38b020a47845aed2fd0210d9ddb553d33603 Mon Sep 17 00:00:00 2001 From: okratitan Date: Fri, 9 Oct 2020 16:06:20 -0500 Subject: [PATCH 03/16] 500 ms is too long to way for a click. --- internal/driver/glfw/window.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/driver/glfw/window.go b/internal/driver/glfw/window.go index 5fe0b562ac..c7c4b9ac0d 100644 --- a/internal/driver/glfw/window.go +++ b/internal/driver/glfw/window.go @@ -22,7 +22,7 @@ import ( const ( scrollSpeed = 10 - doubleClickDelay = 500 // ms (maximum interval between clicks for double click detection) + doubleClickDelay = 300 // ms (maximum interval between clicks for double click detection) ) var ( From ed3e94d93b7da89d55a11eda25a7a69fe0a34f81 Mon Sep 17 00:00:00 2001 From: okratitan Date: Fri, 9 Oct 2020 16:23:09 -0500 Subject: [PATCH 04/16] Only wait on tap if the canvas object is double tappable --- internal/driver/glfw/window.go | 20 +++++++++++++------- internal/driver/glfw/window_test.go | 10 ++-------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/internal/driver/glfw/window.go b/internal/driver/glfw/window.go index c7c4b9ac0d..7f49bc88f9 100644 --- a/internal/driver/glfw/window.go +++ b/internal/driver/glfw/window.go @@ -720,13 +720,20 @@ func (w *window) mouseClicked(_ *glfw.Window, btn glfw.MouseButton, action glfw. // Check for double click/tap on left mouse button if action == glfw.Release && button == desktop.LeftMouseButton { - w.mouseClickCount++ - w.mouseLastClick = co - if w.mouseCancelFunc != nil { - w.mouseCancelFunc() - return + _, doubleTap := co.(fyne.DoubleTappable) + if doubleTap { + w.mouseClickCount++ + w.mouseLastClick = co + if w.mouseCancelFunc != nil { + w.mouseCancelFunc() + } + go w.waitForDoubleTap(co, ev, action, button) + } else { + if wid, ok := co.(fyne.Tappable); ok { + w.queueEvent(func() { wid.Tapped(ev) }) + } } - go w.waitForDoubleTap(co, ev, action, button) + w.mousePressed = nil } } @@ -746,7 +753,6 @@ func (w *window) waitForDoubleTap(co fyne.CanvasObject, ev *fyne.PointEvent, act } } w.mouseClickCount = 0 - w.mousePressed = nil w.mouseCancelFunc = nil w.mouseLastClick = nil return diff --git a/internal/driver/glfw/window_test.go b/internal/driver/glfw/window_test.go index 44e93888d4..042bbb1d58 100644 --- a/internal/driver/glfw/window_test.go +++ b/internal/driver/glfw/window_test.go @@ -427,7 +427,6 @@ func TestWindow_Tapped(t *testing.T) { w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) w.waitForEvents() - time.Sleep(time.Second) assert.Nil(t, o.popSecondaryTapEvent(), "no secondary tap") if e, _ := o.popTapEvent().(*fyne.PointEvent); assert.NotNil(t, e, "tapped") { @@ -466,13 +465,13 @@ func TestWindow_TappedSecondary_OnPrimaryOnlyTarget(t *testing.T) { w.mouseClicked(w.viewport, glfw.MouseButton2, glfw.Press, 0) w.mouseClicked(w.viewport, glfw.MouseButton2, glfw.Release, 0) w.waitForEvents() - time.Sleep(time.Second) + assert.False(t, tapped) w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) w.waitForEvents() - time.Sleep(time.Second) + assert.True(t, tapped) } @@ -500,7 +499,6 @@ func TestWindow_TappedIgnoresScrollerClip(t *testing.T) { w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) w.waitForEvents() - time.Sleep(time.Second) assert.False(t, tapped, "Tapped button that was clipped") @@ -509,7 +507,6 @@ func TestWindow_TappedIgnoresScrollerClip(t *testing.T) { w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) w.waitForEvents() - time.Sleep(time.Second) assert.True(t, tapped, "Tapped button that was clipped") } @@ -526,7 +523,6 @@ func TestWindow_TappedIgnoredWhenMovedOffOfTappable(t *testing.T) { w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) w.waitForEvents() - time.Sleep(time.Second) assert.Equal(t, 1, tapped, "Button 1 should be tapped") tapped = 0 @@ -536,7 +532,6 @@ func TestWindow_TappedIgnoredWhenMovedOffOfTappable(t *testing.T) { w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) w.waitForEvents() - time.Sleep(time.Second) assert.Equal(t, 0, tapped, "button was tapped without mouse press & release on it %d", tapped) @@ -544,7 +539,6 @@ func TestWindow_TappedIgnoredWhenMovedOffOfTappable(t *testing.T) { w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) w.waitForEvents() - time.Sleep(time.Second) assert.Equal(t, 2, tapped, "Button 2 should be tapped") } From 63408a0e24b9effba45fefe54600fe7c9173d94a Mon Sep 17 00:00:00 2001 From: Stephen Houston Date: Sat, 10 Oct 2020 12:55:23 -0500 Subject: [PATCH 05/16] Add test.DoubleTap for use with test objects testingn double taps --- test/test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/test.go b/test/test.go index e372de639e..e52db1f4da 100644 --- a/test/test.go +++ b/test/test.go @@ -140,6 +140,13 @@ func Scroll(c fyne.Canvas, pos fyne.Position, deltaX, deltaY int) { o.(fyne.Scrollable).Scrolled(e) } +// DoubleTap simulates a double left mouse click on the specified object. +func DoubleTap(obj fyne.DoubleTappable) { + ev, c := prepareTap(obj, fyne.NewPos(1, 1)) + handleFocusOnTap(c, obj) + obj.DoubleTapped(ev) +} + // Tap simulates a left mouse click on the specified object. func Tap(obj fyne.Tappable) { TapAt(obj, fyne.NewPos(1, 1)) From 3c05226062f5289e41445e5ed245145a753e6f85 Mon Sep 17 00:00:00 2001 From: Stephen Houston Date: Sat, 10 Oct 2020 13:38:17 -0500 Subject: [PATCH 06/16] Fix issues with single taps, add a test for issue --- internal/driver/glfw/window.go | 9 ++--- internal/driver/glfw/window_test.go | 51 +++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/internal/driver/glfw/window.go b/internal/driver/glfw/window.go index 7f49bc88f9..3226758763 100644 --- a/internal/driver/glfw/window.go +++ b/internal/driver/glfw/window.go @@ -727,17 +727,17 @@ func (w *window) mouseClicked(_ *glfw.Window, btn glfw.MouseButton, action glfw. if w.mouseCancelFunc != nil { w.mouseCancelFunc() } - go w.waitForDoubleTap(co, ev, action, button) + go w.waitForDoubleTap(co, ev) } else { - if wid, ok := co.(fyne.Tappable); ok { + if wid, ok := co.(fyne.Tappable); ok && co == w.mousePressed { w.queueEvent(func() { wid.Tapped(ev) }) } + w.mousePressed = nil } - w.mousePressed = nil } } -func (w *window) waitForDoubleTap(co fyne.CanvasObject, ev *fyne.PointEvent, action glfw.Action, button desktop.MouseButton) { +func (w *window) waitForDoubleTap(co fyne.CanvasObject, ev *fyne.PointEvent) { var ctx context.Context ctx, w.mouseCancelFunc = context.WithDeadline(context.TODO(), time.Now().Add(time.Millisecond*doubleClickDelay)) defer w.mouseCancelFunc() @@ -753,6 +753,7 @@ func (w *window) waitForDoubleTap(co fyne.CanvasObject, ev *fyne.PointEvent, act } } w.mouseClickCount = 0 + w.mousePressed = nil w.mouseCancelFunc = nil w.mouseLastClick = nil return diff --git a/internal/driver/glfw/window_test.go b/internal/driver/glfw/window_test.go index 042bbb1d58..b5c9c6d88e 100644 --- a/internal/driver/glfw/window_test.go +++ b/internal/driver/glfw/window_test.go @@ -543,6 +543,40 @@ func TestWindow_TappedIgnoredWhenMovedOffOfTappable(t *testing.T) { assert.Equal(t, 2, tapped, "Button 2 should be tapped") } +func TestWindow_TappedAndDoubleTapped(t *testing.T) { + w := createWindow("Test").(*window) + tapped := 0 + but := newDoubleTappableButton() + but.OnTapped = func () { + tapped = 1 + } + but.onDoubleTap = func () { + tapped = 2 + } + w.SetContent(fyne.NewContainerWithLayout(layout.NewBorderLayout(nil, nil, nil, nil), but)) + + w.mouseMoved(w.viewport, 15, 25) + w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) + w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + + w.waitForEvents() + time.Sleep(500 * time.Millisecond) + + assert.Equal(t, 1, tapped, "Single tap should have fired") + tapped = 0 + + w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) + w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.waitForEvents() + w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) + w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + + w.waitForEvents() + time.Sleep(500 * time.Millisecond) + + assert.Equal(t, 2, tapped, "Double tap should have fired") +} + func TestWindow_MouseEventContainsModifierKeys(t *testing.T) { w := createWindow("Test").(*window) m := &mouseableObject{Rectangle: canvas.NewRectangle(color.White)} @@ -1095,3 +1129,20 @@ func pop(s []interface{}) (interface{}, []interface{}) { } return s[0], s[1:] } + +type doubleTappableButton struct { + widget.Button + + onDoubleTap func() +} + +func (t *doubleTappableButton) DoubleTapped(_ *fyne.PointEvent) { + t.onDoubleTap() +} + +func newDoubleTappableButton() *doubleTappableButton { + but := &doubleTappableButton{} + but.ExtendBaseWidget(but) + + return but +} From 0e6d6a06045e42c5077e23b7838216f3e07fb423 Mon Sep 17 00:00:00 2001 From: Stephen Houston Date: Sat, 10 Oct 2020 14:04:29 -0500 Subject: [PATCH 07/16] Check for double tappable in finding object --- internal/driver/glfw/window.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/driver/glfw/window.go b/internal/driver/glfw/window.go index 3226758763..d70dab97ce 100644 --- a/internal/driver/glfw/window.go +++ b/internal/driver/glfw/window.go @@ -643,7 +643,7 @@ func (w *window) mouseOut() { func (w *window) mouseClicked(_ *glfw.Window, btn glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) { co, pos, layer := w.findObjectAtPositionMatching(w.canvas, w.mousePos, func(object fyne.CanvasObject) bool { switch object.(type) { - case fyne.Tappable, fyne.SecondaryTappable, fyne.Focusable, fyne.Draggable, desktop.Mouseable, desktop.Hoverable: + case fyne.Tappable, fyne.SecondaryTappable, fyne.DoubleTappable, fyne.Focusable, fyne.Draggable, desktop.Mouseable, desktop.Hoverable: return true } From 50a52a9b60676b5fee97f25e6071452e9d604f67 Mon Sep 17 00:00:00 2001 From: Stephen Houston Date: Sat, 10 Oct 2020 14:05:16 -0500 Subject: [PATCH 08/16] Fix formatting --- internal/driver/glfw/window_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/driver/glfw/window_test.go b/internal/driver/glfw/window_test.go index b5c9c6d88e..840b7ca256 100644 --- a/internal/driver/glfw/window_test.go +++ b/internal/driver/glfw/window_test.go @@ -547,10 +547,10 @@ func TestWindow_TappedAndDoubleTapped(t *testing.T) { w := createWindow("Test").(*window) tapped := 0 but := newDoubleTappableButton() - but.OnTapped = func () { + but.OnTapped = func() { tapped = 1 } - but.onDoubleTap = func () { + but.onDoubleTap = func() { tapped = 2 } w.SetContent(fyne.NewContainerWithLayout(layout.NewBorderLayout(nil, nil, nil, nil), but)) From a68f8ad96312eb357a92e9f55623ad63cb2a50dd Mon Sep 17 00:00:00 2001 From: Stephen Houston Date: Sat, 10 Oct 2020 14:55:27 -0500 Subject: [PATCH 09/16] Add mobile support for double tapping --- internal/driver/gomobile/canvas.go | 49 ++++++++++++++++++++++++++++-- internal/driver/gomobile/driver.go | 4 ++- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/internal/driver/gomobile/canvas.go b/internal/driver/gomobile/canvas.go index 039486cfc8..4842f322d4 100644 --- a/internal/driver/gomobile/canvas.go +++ b/internal/driver/gomobile/canvas.go @@ -1,6 +1,7 @@ package gomobile import ( + "context" "image" "math" "time" @@ -38,8 +39,16 @@ type mobileCanvas struct { dragging fyne.Draggable refreshQueue chan fyne.CanvasObject minSizeCache map[fyne.CanvasObject]fyne.Size + + touchTapCount int + touchCancelFunc context.CancelFunc + touchLastTapped fyne.CanvasObject } +const ( + doubleClickDelay = 500 // ms (maximum interval between clicks for double click detection) +) + func (c *mobileCanvas) Content() fyne.CanvasObject { return c.content } @@ -396,6 +405,7 @@ func (c *mobileCanvas) tapMove(pos fyne.Position, tapID int, func (c *mobileCanvas) tapUp(pos fyne.Position, tapID int, tapCallback func(fyne.Tappable, *fyne.PointEvent), tapAltCallback func(fyne.SecondaryTappable, *fyne.PointEvent), + doubleTapCallback func(fyne.DoubleTappable, *fyne.PointEvent), dragCallback func(fyne.Draggable, *fyne.DragEvent)) { if c.dragging != nil { c.dragging.DragEnd() @@ -422,6 +432,8 @@ func (c *mobileCanvas) tapUp(pos fyne.Position, tapID int, return true } else if _, ok := object.(fyne.Focusable); ok { return true + } else if _, ok := object.(fyne.DoubleTappable); ok { + return true } return false @@ -441,8 +453,19 @@ func (c *mobileCanvas) tapUp(pos fyne.Position, tapID int, // TODO move event queue to common code w.queueEvent(func() { wid.Tapped(ev) }) if duration < tapSecondaryDelay { - if wid, ok := co.(fyne.Tappable); ok { - tapCallback(wid, ev) + _, doubleTap := co.(fyne.DoubleTappable) + if doubleTap { + c.touchTapCount++ + c.touchLastTapped = co + if c.touchCancelFunc != nil { + c.touchCancelFunc() + return + } + go c.waitForDoubleTap(co, ev, tapCallback, doubleTapCallback) + } else { + if wid, ok := co.(fyne.Tappable); ok { + tapCallback(wid, ev) + } } } else { if wid, ok := co.(fyne.SecondaryTappable); ok { @@ -451,6 +474,28 @@ func (c *mobileCanvas) tapUp(pos fyne.Position, tapID int, } } +func (c *mobileCanvas) waitForDoubleTap(co fyne.CanvasObject, ev *fyne.PointEvent, tapCallback func(fyne.Tappable, *fyne.PointEvent), doubleTapCallback func(fyne.DoubleTappable, *fyne.PointEvent)) { + var ctx context.Context + ctx, c.touchCancelFunc = context.WithDeadline(context.TODO(), time.Now().Add(time.Millisecond*doubleClickDelay)) + defer c.touchCancelFunc() + select { + case <-ctx.Done(): + if c.touchTapCount == 2 && c.touchLastTapped == co { + if wid, ok := co.(fyne.DoubleTappable); ok { + doubleTapCallback(wid, ev) + } + } else { + if wid, ok := co.(fyne.Tappable); ok { + tapCallback(wid, ev) + } + } + c.touchTapCount = 0 + c.touchCancelFunc = nil + c.touchLastTapped = nil + return + } +} + func (c *mobileCanvas) setupThemeListener() { listener := make(chan fyne.Settings) fyne.CurrentApp().Settings().AddChangeListener(listener) diff --git a/internal/driver/gomobile/driver.go b/internal/driver/gomobile/driver.go index 5e9c43448a..856940b70a 100644 --- a/internal/driver/gomobile/driver.go +++ b/internal/driver/gomobile/driver.go @@ -240,8 +240,10 @@ func (d *mobileDriver) tapUpCanvas(canvas *mobileCanvas, x, y float32, tapID tou go wid.Tapped(ev) }, func(wid fyne.SecondaryTappable, ev *fyne.PointEvent) { go wid.TappedSecondary(ev) + }, func(wid fyne.DoubleTappable, ev *fyne.PointEvent) { + go wid.DoubleTapped(ev) }, func(wid fyne.Draggable, ev *fyne.DragEvent) { - go wid.DragEnd() + go wid.DragEnd() }) } From c10715a100c2558212420f37487901713baf6bdb Mon Sep 17 00:00:00 2001 From: Stephen Houston Date: Sat, 10 Oct 2020 14:55:49 -0500 Subject: [PATCH 10/16] Return from a cancelled double tap before starting go routine --- internal/driver/glfw/window.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/driver/glfw/window.go b/internal/driver/glfw/window.go index d70dab97ce..f30de0249e 100644 --- a/internal/driver/glfw/window.go +++ b/internal/driver/glfw/window.go @@ -726,6 +726,7 @@ func (w *window) mouseClicked(_ *glfw.Window, btn glfw.MouseButton, action glfw. w.mouseLastClick = co if w.mouseCancelFunc != nil { w.mouseCancelFunc() + return } go w.waitForDoubleTap(co, ev) } else { From e68037cae132ab28518b926525ef01fe8c8c9e18 Mon Sep 17 00:00:00 2001 From: Stephen Houston Date: Sat, 10 Oct 2020 15:08:36 -0500 Subject: [PATCH 11/16] Fix formatting --- internal/driver/gomobile/driver.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/driver/gomobile/driver.go b/internal/driver/gomobile/driver.go index 856940b70a..c7c7b1dc28 100644 --- a/internal/driver/gomobile/driver.go +++ b/internal/driver/gomobile/driver.go @@ -243,7 +243,7 @@ func (d *mobileDriver) tapUpCanvas(canvas *mobileCanvas, x, y float32, tapID tou }, func(wid fyne.DoubleTappable, ev *fyne.PointEvent) { go wid.DoubleTapped(ev) }, func(wid fyne.Draggable, ev *fyne.DragEvent) { - go wid.DragEnd() + go wid.DragEnd() }) } From 7677100793fa2c7fcffa4526190046feed4e42ed Mon Sep 17 00:00:00 2001 From: Stephen Houston Date: Sat, 10 Oct 2020 15:12:38 -0500 Subject: [PATCH 12/16] Thanks to Jacob for the frequent formatting failure reminders --- internal/driver/gomobile/canvas.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/driver/gomobile/canvas.go b/internal/driver/gomobile/canvas.go index 4842f322d4..15641eb98b 100644 --- a/internal/driver/gomobile/canvas.go +++ b/internal/driver/gomobile/canvas.go @@ -40,9 +40,9 @@ type mobileCanvas struct { refreshQueue chan fyne.CanvasObject minSizeCache map[fyne.CanvasObject]fyne.Size - touchTapCount int - touchCancelFunc context.CancelFunc - touchLastTapped fyne.CanvasObject + touchTapCount int + touchCancelFunc context.CancelFunc + touchLastTapped fyne.CanvasObject } const ( From fec7c1ca3541ff74954141bb0a1176c6be8ab2d4 Mon Sep 17 00:00:00 2001 From: Stephen Houston Date: Sat, 10 Oct 2020 15:36:10 -0500 Subject: [PATCH 13/16] Remove unecessary select {} for one case statements --- internal/driver/glfw/window.go | 5 ++--- internal/driver/gomobile/canvas.go | 4 +--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/internal/driver/glfw/window.go b/internal/driver/glfw/window.go index f30de0249e..482282ed63 100644 --- a/internal/driver/glfw/window.go +++ b/internal/driver/glfw/window.go @@ -742,8 +742,8 @@ func (w *window) waitForDoubleTap(co fyne.CanvasObject, ev *fyne.PointEvent) { var ctx context.Context ctx, w.mouseCancelFunc = context.WithDeadline(context.TODO(), time.Now().Add(time.Millisecond*doubleClickDelay)) defer w.mouseCancelFunc() - select { - case <-ctx.Done(): + + <-ctx.Done() if w.mouseClickCount == 2 && w.mouseLastClick == co { if wid, ok := co.(fyne.DoubleTappable); ok { w.queueEvent(func() { wid.DoubleTapped(ev) }) @@ -758,7 +758,6 @@ func (w *window) waitForDoubleTap(co fyne.CanvasObject, ev *fyne.PointEvent) { w.mouseCancelFunc = nil w.mouseLastClick = nil return - } } func (w *window) mouseScrolled(viewport *glfw.Window, xoff float64, yoff float64) { diff --git a/internal/driver/gomobile/canvas.go b/internal/driver/gomobile/canvas.go index 15641eb98b..22aad66551 100644 --- a/internal/driver/gomobile/canvas.go +++ b/internal/driver/gomobile/canvas.go @@ -478,8 +478,7 @@ func (c *mobileCanvas) waitForDoubleTap(co fyne.CanvasObject, ev *fyne.PointEven var ctx context.Context ctx, c.touchCancelFunc = context.WithDeadline(context.TODO(), time.Now().Add(time.Millisecond*doubleClickDelay)) defer c.touchCancelFunc() - select { - case <-ctx.Done(): + <-ctx.Done() if c.touchTapCount == 2 && c.touchLastTapped == co { if wid, ok := co.(fyne.DoubleTappable); ok { doubleTapCallback(wid, ev) @@ -493,7 +492,6 @@ func (c *mobileCanvas) waitForDoubleTap(co fyne.CanvasObject, ev *fyne.PointEven c.touchCancelFunc = nil c.touchLastTapped = nil return - } } func (c *mobileCanvas) setupThemeListener() { From bd15c5e8ddbbe3017cf62ba645e981b73ef6fa97 Mon Sep 17 00:00:00 2001 From: Stephen Houston Date: Sat, 10 Oct 2020 15:40:50 -0500 Subject: [PATCH 14/16] Fix formattingFix formattin --- internal/driver/glfw/window.go | 26 +++++++++++++------------- internal/driver/gomobile/canvas.go | 24 ++++++++++++------------ 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/internal/driver/glfw/window.go b/internal/driver/glfw/window.go index 482282ed63..e80941acc9 100644 --- a/internal/driver/glfw/window.go +++ b/internal/driver/glfw/window.go @@ -744,20 +744,20 @@ func (w *window) waitForDoubleTap(co fyne.CanvasObject, ev *fyne.PointEvent) { defer w.mouseCancelFunc() <-ctx.Done() - if w.mouseClickCount == 2 && w.mouseLastClick == co { - if wid, ok := co.(fyne.DoubleTappable); ok { - w.queueEvent(func() { wid.DoubleTapped(ev) }) - } - } else if co == w.mousePressed { - if wid, ok := co.(fyne.Tappable); ok { - w.queueEvent(func() { wid.Tapped(ev) }) - } + if w.mouseClickCount == 2 && w.mouseLastClick == co { + if wid, ok := co.(fyne.DoubleTappable); ok { + w.queueEvent(func() { wid.DoubleTapped(ev) }) } - w.mouseClickCount = 0 - w.mousePressed = nil - w.mouseCancelFunc = nil - w.mouseLastClick = nil - return + } else if co == w.mousePressed { + if wid, ok := co.(fyne.Tappable); ok { + w.queueEvent(func() { wid.Tapped(ev) }) + } + } + w.mouseClickCount = 0 + w.mousePressed = nil + w.mouseCancelFunc = nil + w.mouseLastClick = nil + return } func (w *window) mouseScrolled(viewport *glfw.Window, xoff float64, yoff float64) { diff --git a/internal/driver/gomobile/canvas.go b/internal/driver/gomobile/canvas.go index 22aad66551..205c097383 100644 --- a/internal/driver/gomobile/canvas.go +++ b/internal/driver/gomobile/canvas.go @@ -479,19 +479,19 @@ func (c *mobileCanvas) waitForDoubleTap(co fyne.CanvasObject, ev *fyne.PointEven ctx, c.touchCancelFunc = context.WithDeadline(context.TODO(), time.Now().Add(time.Millisecond*doubleClickDelay)) defer c.touchCancelFunc() <-ctx.Done() - if c.touchTapCount == 2 && c.touchLastTapped == co { - if wid, ok := co.(fyne.DoubleTappable); ok { - doubleTapCallback(wid, ev) - } - } else { - if wid, ok := co.(fyne.Tappable); ok { - tapCallback(wid, ev) - } + if c.touchTapCount == 2 && c.touchLastTapped == co { + if wid, ok := co.(fyne.DoubleTappable); ok { + doubleTapCallback(wid, ev) } - c.touchTapCount = 0 - c.touchCancelFunc = nil - c.touchLastTapped = nil - return + } else { + if wid, ok := co.(fyne.Tappable); ok { + tapCallback(wid, ev) + } + } + c.touchTapCount = 0 + c.touchCancelFunc = nil + c.touchLastTapped = nil + return } func (c *mobileCanvas) setupThemeListener() { From f91191c616663df3a2efd054b2b500708a680359 Mon Sep 17 00:00:00 2001 From: Stephen Houston Date: Sat, 10 Oct 2020 16:19:57 -0500 Subject: [PATCH 15/16] Fix broken mobile tests and add a test for double clicking on mobile --- internal/driver/gomobile/canvas_test.go | 69 +++++++++++++++++++++++++ internal/driver/gomobile/menu_test.go | 2 +- 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/internal/driver/gomobile/canvas_test.go b/internal/driver/gomobile/canvas_test.go index c6aee339ed..1cfdb7f056 100644 --- a/internal/driver/gomobile/canvas_test.go +++ b/internal/driver/gomobile/canvas_test.go @@ -78,6 +78,8 @@ func TestCanvas_Tapped(t *testing.T) { }, func(wid fyne.SecondaryTappable, ev *fyne.PointEvent) { altTapped = true wid.TappedSecondary(ev) + }, func (wid fyne.DoubleTappable, ev *fyne.PointEvent) { + wid.DoubleTapped(ev) }, func(wid fyne.Draggable, ev *fyne.DragEvent) { }) @@ -106,6 +108,8 @@ func TestCanvas_Tapped_Multi(t *testing.T) { c.tapUp(tapPos, 1, func(wid fyne.Tappable, ev *fyne.PointEvent) { // different tapID wid.Tapped(ev) }, func(wid fyne.SecondaryTappable, ev *fyne.PointEvent) { + }, func (wid fyne.DoubleTappable, ev *fyne.PointEvent) { + wid.DoubleTapped(ev) }, func(wid fyne.Draggable, ev *fyne.DragEvent) { }) @@ -133,6 +137,8 @@ func TestCanvas_TappedSecondary(t *testing.T) { altTappedObj = wid pointEvent = ev wid.TappedSecondary(ev) + }, func (wid fyne.DoubleTappable, ev *fyne.PointEvent) { + wid.DoubleTapped(ev) }, func(wid fyne.Draggable, ev *fyne.DragEvent) { }) @@ -184,6 +190,7 @@ func TestCanvas_Tappable(t *testing.T) { c.tapUp(fyne.NewPos(15, 15), 0, func(wid fyne.Tappable, ev *fyne.PointEvent) { }, func(wid fyne.SecondaryTappable, ev *fyne.PointEvent) { + }, func (wid fyne.DoubleTappable, ev *fyne.PointEvent) { }, func(wid fyne.Draggable, ev *fyne.DragEvent) { }) assert.True(t, content.up) @@ -195,6 +202,51 @@ func TestCanvas_Tappable(t *testing.T) { assert.True(t, content.cancel) } +func TestWindow_TappedAndDoubleTapped(t *testing.T) { + tapped := 0 + but := newDoubleTappableButton() + but.OnTapped = func() { + tapped = 1 + } + but.onDoubleTap = func() { + tapped = 2 + } + + c := NewCanvas().(*mobileCanvas) + c.SetContent(fyne.NewContainerWithLayout(layout.NewMaxLayout(), but)) + c.resize(fyne.NewSize(36, 24)) + + c.tapDown(fyne.NewPos(15, 15), 0) + c.tapUp(fyne.NewPos(15, 15), 0, func(wid fyne.Tappable, ev *fyne.PointEvent) { + wid.Tapped(ev) + }, func(wid fyne.SecondaryTappable, ev *fyne.PointEvent) { + }, func (wid fyne.DoubleTappable, ev *fyne.PointEvent) { + wid.DoubleTapped(ev) + }, func(wid fyne.Draggable, ev *fyne.DragEvent) { + }) + time.Sleep(700 * time.Millisecond) + assert.Equal(t, tapped, 1) + + c.tapDown(fyne.NewPos(15, 15), 0) + c.tapUp(fyne.NewPos(15, 15), 0, func(wid fyne.Tappable, ev *fyne.PointEvent) { + wid.Tapped(ev) + }, func(wid fyne.SecondaryTappable, ev *fyne.PointEvent) { + }, func (wid fyne.DoubleTappable, ev *fyne.PointEvent) { + wid.DoubleTapped(ev) + }, func(wid fyne.Draggable, ev *fyne.DragEvent) { + }) + c.tapDown(fyne.NewPos(15, 15), 0) + c.tapUp(fyne.NewPos(15, 15), 0, func(wid fyne.Tappable, ev *fyne.PointEvent) { + wid.Tapped(ev) + }, func(wid fyne.SecondaryTappable, ev *fyne.PointEvent) { + }, func (wid fyne.DoubleTappable, ev *fyne.PointEvent) { + wid.DoubleTapped(ev) + }, func(wid fyne.Draggable, ev *fyne.DragEvent) { + }) + time.Sleep(700 * time.Millisecond) + assert.Equal(t, tapped, 1) +} + func TestCanvas_Focusable(t *testing.T) { content := newFocusableEntry() c := NewCanvas().(*mobileCanvas) @@ -277,3 +329,20 @@ func (f *focusableEntry) FocusLost() { f.unfocusedTimes++ f.Entry.FocusLost() } + +type doubleTappableButton struct { + widget.Button + + onDoubleTap func() +} + +func (t *doubleTappableButton) DoubleTapped(_ *fyne.PointEvent) { + t.onDoubleTap() +} + +func newDoubleTappableButton() *doubleTappableButton { + but := &doubleTappableButton{} + but.ExtendBaseWidget(but) + + return but +} diff --git a/internal/driver/gomobile/menu_test.go b/internal/driver/gomobile/menu_test.go index df3007dbce..0b62f1cccf 100644 --- a/internal/driver/gomobile/menu_test.go +++ b/internal/driver/gomobile/menu_test.go @@ -25,7 +25,7 @@ func TestMobileCanvas_DismissBar(t *testing.T) { assert.NotNil(t, c.menu) // simulate tap as the test util does not know about our menu... c.tapDown(fyne.NewPos(80, 20), 1) - c.tapUp(fyne.NewPos(80, 20), 1, nil, nil, nil) + c.tapUp(fyne.NewPos(80, 20), 1, nil, nil, nil,nil) assert.Nil(t, c.menu) } From aacecf520cbdd668a2d623bf7f3d37ece56f3494 Mon Sep 17 00:00:00 2001 From: Stephen Houston Date: Sat, 10 Oct 2020 16:24:17 -0500 Subject: [PATCH 16/16] Fix formatting again --- internal/driver/gomobile/canvas_test.go | 14 +++++++------- internal/driver/gomobile/menu_test.go | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/internal/driver/gomobile/canvas_test.go b/internal/driver/gomobile/canvas_test.go index 1cfdb7f056..3ffe6a5527 100644 --- a/internal/driver/gomobile/canvas_test.go +++ b/internal/driver/gomobile/canvas_test.go @@ -78,7 +78,7 @@ func TestCanvas_Tapped(t *testing.T) { }, func(wid fyne.SecondaryTappable, ev *fyne.PointEvent) { altTapped = true wid.TappedSecondary(ev) - }, func (wid fyne.DoubleTappable, ev *fyne.PointEvent) { + }, func(wid fyne.DoubleTappable, ev *fyne.PointEvent) { wid.DoubleTapped(ev) }, func(wid fyne.Draggable, ev *fyne.DragEvent) { }) @@ -108,7 +108,7 @@ func TestCanvas_Tapped_Multi(t *testing.T) { c.tapUp(tapPos, 1, func(wid fyne.Tappable, ev *fyne.PointEvent) { // different tapID wid.Tapped(ev) }, func(wid fyne.SecondaryTappable, ev *fyne.PointEvent) { - }, func (wid fyne.DoubleTappable, ev *fyne.PointEvent) { + }, func(wid fyne.DoubleTappable, ev *fyne.PointEvent) { wid.DoubleTapped(ev) }, func(wid fyne.Draggable, ev *fyne.DragEvent) { }) @@ -137,7 +137,7 @@ func TestCanvas_TappedSecondary(t *testing.T) { altTappedObj = wid pointEvent = ev wid.TappedSecondary(ev) - }, func (wid fyne.DoubleTappable, ev *fyne.PointEvent) { + }, func(wid fyne.DoubleTappable, ev *fyne.PointEvent) { wid.DoubleTapped(ev) }, func(wid fyne.Draggable, ev *fyne.DragEvent) { }) @@ -190,7 +190,7 @@ func TestCanvas_Tappable(t *testing.T) { c.tapUp(fyne.NewPos(15, 15), 0, func(wid fyne.Tappable, ev *fyne.PointEvent) { }, func(wid fyne.SecondaryTappable, ev *fyne.PointEvent) { - }, func (wid fyne.DoubleTappable, ev *fyne.PointEvent) { + }, func(wid fyne.DoubleTappable, ev *fyne.PointEvent) { }, func(wid fyne.Draggable, ev *fyne.DragEvent) { }) assert.True(t, content.up) @@ -220,7 +220,7 @@ func TestWindow_TappedAndDoubleTapped(t *testing.T) { c.tapUp(fyne.NewPos(15, 15), 0, func(wid fyne.Tappable, ev *fyne.PointEvent) { wid.Tapped(ev) }, func(wid fyne.SecondaryTappable, ev *fyne.PointEvent) { - }, func (wid fyne.DoubleTappable, ev *fyne.PointEvent) { + }, func(wid fyne.DoubleTappable, ev *fyne.PointEvent) { wid.DoubleTapped(ev) }, func(wid fyne.Draggable, ev *fyne.DragEvent) { }) @@ -231,7 +231,7 @@ func TestWindow_TappedAndDoubleTapped(t *testing.T) { c.tapUp(fyne.NewPos(15, 15), 0, func(wid fyne.Tappable, ev *fyne.PointEvent) { wid.Tapped(ev) }, func(wid fyne.SecondaryTappable, ev *fyne.PointEvent) { - }, func (wid fyne.DoubleTappable, ev *fyne.PointEvent) { + }, func(wid fyne.DoubleTappable, ev *fyne.PointEvent) { wid.DoubleTapped(ev) }, func(wid fyne.Draggable, ev *fyne.DragEvent) { }) @@ -239,7 +239,7 @@ func TestWindow_TappedAndDoubleTapped(t *testing.T) { c.tapUp(fyne.NewPos(15, 15), 0, func(wid fyne.Tappable, ev *fyne.PointEvent) { wid.Tapped(ev) }, func(wid fyne.SecondaryTappable, ev *fyne.PointEvent) { - }, func (wid fyne.DoubleTappable, ev *fyne.PointEvent) { + }, func(wid fyne.DoubleTappable, ev *fyne.PointEvent) { wid.DoubleTapped(ev) }, func(wid fyne.Draggable, ev *fyne.DragEvent) { }) diff --git a/internal/driver/gomobile/menu_test.go b/internal/driver/gomobile/menu_test.go index 0b62f1cccf..7733250950 100644 --- a/internal/driver/gomobile/menu_test.go +++ b/internal/driver/gomobile/menu_test.go @@ -25,7 +25,7 @@ func TestMobileCanvas_DismissBar(t *testing.T) { assert.NotNil(t, c.menu) // simulate tap as the test util does not know about our menu... c.tapDown(fyne.NewPos(80, 20), 1) - c.tapUp(fyne.NewPos(80, 20), 1, nil, nil, nil,nil) + c.tapUp(fyne.NewPos(80, 20), 1, nil, nil, nil, nil) assert.Nil(t, c.menu) }