diff --git a/internal/driver/glfw/window.go b/internal/driver/glfw/window.go index 066c2a5b67..05139f4f5b 100644 --- a/internal/driver/glfw/window.go +++ b/internal/driver/glfw/window.go @@ -74,6 +74,7 @@ type window struct { mouseLastClick fyne.CanvasObject mousePressed fyne.CanvasObject onClosed func() + onCloseIntercepted func() xpos, ypos int width, height int @@ -292,6 +293,10 @@ func (w *window) SetOnClosed(closed func()) { w.onClosed = closed } +func (w *window) SetCloseIntercept(callback func()) { + w.onCloseIntercepted = callback +} + func (w *window) getMonitorForWindow() *glfw.Monitor { xOff := w.xpos + (w.width / 2) yOff := w.ypos + (w.height / 2) @@ -404,7 +409,20 @@ func (w *window) Close() { if w.viewport == nil { return } - w.closed(w.viewport) + + w.viewport.SetShouldClose(true) + + w.canvas.walkTrees(nil, func(node *renderCacheNode) { + switch co := node.obj.(type) { + case fyne.Widget: + cache.DestroyRenderer(co) + } + }) + + // trigger callbacks + if w.onClosed != nil { + w.queueEvent(w.onClosed) + } } func (w *window) ShowAndRun() { @@ -446,19 +464,14 @@ func (w *window) Canvas() fyne.Canvas { } func (w *window) closed(viewport *glfw.Window) { - viewport.SetShouldClose(true) + viewport.SetShouldClose(false) - w.canvas.walkTrees(nil, func(node *renderCacheNode) { - switch co := node.obj.(type) { - case fyne.Widget: - cache.DestroyRenderer(co) - } - }) - - // trigger callbacks - if w.onClosed != nil { - w.queueEvent(w.onClosed) + if w.onCloseIntercepted != nil { + w.queueEvent(w.onCloseIntercepted) + return } + + w.Close() } // destroy this window and, if it's the last window quit the app diff --git a/internal/driver/glfw/window_test.go b/internal/driver/glfw/window_test.go index a8e16e8ee5..594e96cf05 100644 --- a/internal/driver/glfw/window_test.go +++ b/internal/driver/glfw/window_test.go @@ -821,6 +821,38 @@ func TestWindow_Clipboard(t *testing.T) { cb.SetContent(cliboardContent) } +func TestWindow_CloseInterception(t *testing.T) { + d := NewGLDriver() + w := d.CreateWindow("test").(*window) + w.create() + + onIntercepted := false + onClosed := false + w.SetCloseIntercept(func() { + onIntercepted = true + }) + w.SetOnClosed(func() { + onClosed = true + }) + w.Close() + w.waitForEvents() + assert.False(t, onIntercepted) // The interceptor is not called by the Close. + assert.True(t, onClosed) + + onIntercepted = false + onClosed = false + w.closed(w.viewport) + w.waitForEvents() + assert.True(t, onIntercepted) // The interceptor is called by the closed. + assert.False(t, onClosed) // If the interceptor is set Close is not called. + + onClosed = false + w.SetCloseIntercept(nil) + w.closed(w.viewport) + w.waitForEvents() + assert.True(t, onClosed) // Close is called if the interceptor is not set. +} + // This test makes our developer screens flash, let's not run it regularly... //func TestWindow_Shortcut(t *testing.T) { // w := createWindow("Test") diff --git a/internal/driver/gomobile/window.go b/internal/driver/gomobile/window.go index 94675d24c5..996031d496 100644 --- a/internal/driver/gomobile/window.go +++ b/internal/driver/gomobile/window.go @@ -9,10 +9,11 @@ import ( ) type window struct { - title string - visible bool - onClosed func() - isChild bool + title string + visible bool + onClosed func() + onCloseIntercepted func() + isChild bool clipboard fyne.Clipboard canvas *mobileCanvas @@ -92,6 +93,10 @@ func (w *window) SetOnClosed(callback func()) { w.onClosed = callback } +func (w *window) SetCloseIntercept(callback func()) { + w.onCloseIntercepted = callback +} + func (w *window) Show() { menu := fyne.CurrentApp().Driver().(*mobileDriver).findMenu(w) menuButton := widget.NewButtonWithIcon("", theme.MenuIcon(), func() { @@ -103,7 +108,7 @@ func (w *window) Show() { if w.isChild { exit := widget.NewButtonWithIcon("", theme.CancelIcon(), func() { - w.Close() + w.tryClose() }) title := widget.NewLabel(w.title) title.Alignment = fyne.TextAlignCenter @@ -129,6 +134,15 @@ func (w *window) Hide() { } } +func (w *window) tryClose() { + if w.onCloseIntercepted != nil { + w.onCloseIntercepted() + return + } + + w.Close() +} + func (w *window) Close() { d := fyne.CurrentApp().Driver().(*mobileDriver) pos := -1 diff --git a/test/testwindow.go b/test/testwindow.go index 3c18cdaa10..2178d7c852 100644 --- a/test/testwindow.go +++ b/test/testwindow.go @@ -5,11 +5,12 @@ import ( ) type testWindow struct { - title string - fullScreen bool - fixedSize bool - focused bool - onClosed func() + title string + fullScreen bool + fixedSize bool + focused bool + onClosed func() + onCloseIntercepted func() canvas *testCanvas clipboard fyne.Clipboard @@ -112,6 +113,10 @@ func (w *testWindow) SetOnClosed(closed func()) { w.onClosed = closed } +func (w *testWindow) SetCloseIntercept(callback func()) { + w.onCloseIntercepted = callback +} + func (w *testWindow) SetPadded(padded bool) { w.canvas.SetPadded(padded) } diff --git a/window.go b/window.go index d9c22a41de..bea673b44d 100644 --- a/window.go +++ b/window.go @@ -64,6 +64,10 @@ type Window interface { // SetOnClosed sets a function that runs when the window is closed. SetOnClosed(func()) + // SetCloseIntercept sets a function that runs instead of closing if defined. + // Close() should be called explicitly in the interceptor to close the window. + SetCloseIntercept(func()) + // Show the window on screen. Show() // Hide the window from the user.