From 6e2eda073e9e1543414d2a844a62a755d244f07b Mon Sep 17 00:00:00 2001 From: proneon267 Date: Wed, 23 Aug 2023 10:14:57 -0700 Subject: [PATCH 01/37] Added support for `WinForms` and `gtk`. --- core/src/toga/app.py | 25 +++++++++++++++++++++++++ core/src/toga/window.py | 21 +++++++++++++++++++++ gtk/src/toga_gtk/window.py | 10 ++++++++++ winforms/src/toga_winforms/window.py | 11 +++++++++++ 4 files changed, 67 insertions(+) diff --git a/core/src/toga/app.py b/core/src/toga/app.py index 90ea3a24e8..a618795a00 100644 --- a/core/src/toga/app.py +++ b/core/src/toga/app.py @@ -132,6 +132,8 @@ def __init__( minimizable: bool = True, factory: None = None, # DEPRECATED ! on_close: None = None, + on_gain_focus: callable | None = None, + on_lose_focus: callable | None = None, ) -> None: ###################################################################### # 2022-09: Backwards compatibility @@ -152,6 +154,8 @@ def __init__( closeable=True, minimizable=minimizable, on_close=on_close, + on_gain_focus=on_gain_focus, + on_lose_focus=on_lose_focus, ) @Window.on_close.setter @@ -185,6 +189,8 @@ def __init__( startup: AppStartupMethod | None = None, windows: Iterable[Window] = (), on_exit: OnExitHandler | None = None, + on_gain_focus: callable | None = None, + on_lose_focus: callable | None = None, factory: None = None, # DEPRECATED ! ): """An App is the top level of any GUI program. @@ -384,6 +390,9 @@ class is defined, and look for a ``.dist-info`` file matching that name. self._impl = self._create_impl() self.on_exit = on_exit + self.on_gain_focus = on_gain_focus + self.on_lose_focus = on_lose_focus + def _create_impl(self): return self.factory.App(interface=self) @@ -628,6 +637,22 @@ def add_background_task(self, handler: BackgroundTask) -> None: """ self._impl.loop.call_soon_threadsafe(wrapped_handler(self, handler), None) + @property + def on_gain_focus(self) -> callable: + return self._on_gain_focus + + @on_gain_focus.setter + def on_gain_focus(self, handler): + self._on_gain_focus = wrapped_handler(self, handler) + + @property + def on_lose_focus(self) -> callable: + return self._on_lose_focus + + @on_lose_focus.setter + def on_lose_focus(self, handler): + self._on_lose_focus = wrapped_handler(self, handler) + class DocumentApp(App): def __init__( diff --git a/core/src/toga/window.py b/core/src/toga/window.py index 5d9996a0a4..868f41511d 100644 --- a/core/src/toga/window.py +++ b/core/src/toga/window.py @@ -88,6 +88,8 @@ def __init__( minimizable: bool = True, factory: None = None, # DEPRECATED ! on_close: OnCloseHandler | None = None, + on_gain_focus: callable | None = None, + on_lose_focus: callable | None = None, ) -> None: ###################################################################### # 2022-09: Backwards compatibility @@ -123,6 +125,9 @@ def __init__( self.on_close = on_close + self.on_gain_focus = on_gain_focus + self.on_lose_focus = on_lose_focus + @property def id(self) -> str: """The DOM identifier for the window. @@ -271,6 +276,22 @@ def close(self) -> None: self.app.windows -= self self._impl.close() + @property + def on_gain_focus(self) -> callable: + return self._on_gain_focus + + @on_gain_focus.setter + def on_gain_focus(self, handler): + self._on_gain_focus = wrapped_handler(self, handler) + + @property + def on_lose_focus(self) -> callable: + return self._on_lose_focus + + @on_lose_focus.setter + def on_lose_focus(self, handler): + self._on_lose_focus = wrapped_handler(self, handler) + ############################################################ # Dialogs ############################################################ diff --git a/gtk/src/toga_gtk/window.py b/gtk/src/toga_gtk/window.py index 5771ebdd73..1255745d8d 100644 --- a/gtk/src/toga_gtk/window.py +++ b/gtk/src/toga_gtk/window.py @@ -20,6 +20,8 @@ def __init__(self, interface, title, position, size): self.native._impl = self self.native.connect("delete-event", self.gtk_delete_event) + self.native.connect("focus-in-event", self.window_on_gain_focus) + self.native.connect("focus-out-event", self.window_on_lose_focus) self.native.set_default_size(size[0], size[1]) @@ -138,3 +140,11 @@ def set_full_screen(self, is_full_screen): self.native.fullscreen() else: self.native.unfullscreen() + + def window_on_gain_focus(self, sender, event): + self.interface.app.on_gain_focus(self.interface) + self.interface.on_gain_focus(self.interface) + + def window_on_lose_focus(self, sender, event): + self.interface.app.on_lose_focus(self.interface) + self.interface.on_lose_focus(self.interface) diff --git a/winforms/src/toga_winforms/window.py b/winforms/src/toga_winforms/window.py index 0e4a33bf0e..673de5a2ea 100644 --- a/winforms/src/toga_winforms/window.py +++ b/winforms/src/toga_winforms/window.py @@ -43,6 +43,9 @@ def __init__(self, interface, title, position, size): self.native.FormBorderStyle = self.native.FormBorderStyle.FixedSingle self.native.MaximizeBox = False + self.native.Activated += self.window_on_gain_focus + self.native.Deactivate += self.window_on_lose_focus + def create_toolbar(self): if self.interface.toolbar: if self.toolbar_native: @@ -173,3 +176,11 @@ def resize_content(self): self.native.ClientSize.Width, self.native.ClientSize.Height - vertical_shift, ) + + def window_on_gain_focus(self, sender, event): + self.interface.app.on_gain_focus(self.interface) + self.interface.on_gain_focus(self.interface) + + def window_on_lose_focus(self, sender, event): + self.interface.app.on_lose_focus(self.interface) + self.interface.on_lose_focus(self.interface) From 4bb2a422b3fdd985642a666026409522e8b544a7 Mon Sep 17 00:00:00 2001 From: proneon267 Date: Wed, 23 Aug 2023 10:20:48 -0700 Subject: [PATCH 02/37] Added changelog. --- changes/2009.feature.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/2009.feature.rst diff --git a/changes/2009.feature.rst b/changes/2009.feature.rst new file mode 100644 index 0000000000..d07b9ca03b --- /dev/null +++ b/changes/2009.feature.rst @@ -0,0 +1 @@ +Added `on_gain_focus` and `on_lose_focus` handlers both on the `toga.App` and `toga.Window`. From 3a202e47ac39f06491c6dce88ddd79dc23a2a27e Mon Sep 17 00:00:00 2001 From: proneon267 Date: Wed, 23 Aug 2023 21:07:20 -0700 Subject: [PATCH 03/37] Added support for `cocoa`. --- cocoa/src/toga_cocoa/app.py | 8 ++++++++ cocoa/src/toga_cocoa/window.py | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/cocoa/src/toga_cocoa/app.py b/cocoa/src/toga_cocoa/app.py index c983720d4d..b2b85ab19b 100644 --- a/cocoa/src/toga_cocoa/app.py +++ b/cocoa/src/toga_cocoa/app.py @@ -61,6 +61,14 @@ def applicationOpenUntitledFile_(self, sender) -> bool: self.impl.select_file() return True + @objc_method + def applicationDidBecomeActive_(self, application): + self.interface.on_gain_focus(self.interface) + + @objc_method + def applicationWillResignActive_(self, application): + self.interface.on_lose_focus(self.interface) + @objc_method def addDocument_(self, document) -> None: # print("Add Document", document) diff --git a/cocoa/src/toga_cocoa/window.py b/cocoa/src/toga_cocoa/window.py index 52de33dd7f..cf81a7861b 100644 --- a/cocoa/src/toga_cocoa/window.py +++ b/cocoa/src/toga_cocoa/window.py @@ -48,6 +48,14 @@ def windowDidResize_(self, notification) -> None: # Set the window to the new size self.interface.content.refresh() + @objc_method + def windowDidBecomeMain_(self, notification): + self.interface.on_gain_focus(self.interface) + + @objc_method + def windowDidResignMain_(self, notification): + self.interface.on_lose_focus(self.interface) + ###################################################################### # Toolbar delegate methods ###################################################################### From 9cecb4a47462cc6477766cac18cc2f0dfe253866 Mon Sep 17 00:00:00 2001 From: proneon267 Date: Wed, 23 Aug 2023 21:30:14 -0700 Subject: [PATCH 04/37] Added support for `web` --- web/src/toga_web/window.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/web/src/toga_web/window.py b/web/src/toga_web/window.py index 6e437cda4e..74f8ec784e 100644 --- a/web/src/toga_web/window.py +++ b/web/src/toga_web/window.py @@ -16,6 +16,8 @@ def __init__(self, interface, title, position, size): app_placeholder = js.document.getElementById("app-placeholder") app_placeholder.appendChild(self.native) + js.document.body.onfocus = self.window_on_gain_focus + js.document.body.onblur = self.window_on_lose_focusS self.set_title(title) def get_title(self): @@ -77,3 +79,11 @@ def set_size(self, size): def set_full_screen(self, is_full_screen): self.interface.factory.not_implemented("Window.set_full_screen()") + + def window_on_gain_focus(self, sender, event): + self.interface.app.on_gain_focus(self.interface) + self.interface.on_gain_focus(self.interface) + + def window_on_lose_focus(self, sender, event): + self.interface.app.on_lose_focus(self.interface) + self.interface.on_lose_focus(self.interface) From 6f38ae2acc81500e506218517327139ccb2e256a Mon Sep 17 00:00:00 2001 From: proneon267 Date: Sun, 27 Aug 2023 19:20:05 -0700 Subject: [PATCH 05/37] Added support for `android`. --- android/src/toga_android/app.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/android/src/toga_android/app.py b/android/src/toga_android/app.py index 269bf49171..8b0c466fa7 100644 --- a/android/src/toga_android/app.py +++ b/android/src/toga_android/app.py @@ -48,6 +48,17 @@ def onDestroy(self): def onRestart(self): print("Toga app: onRestart") + def onWindowFocusChanged(self, hasFocus): + print("Toga app: onWindowFocusChanged") + if hasFocus: + self._impl.interface.on_gain_focus(self._impl.interface) + for window in self._impl.interface.windows: + window.on_gain_focus(self._impl.interface) + else: + self._impl.interface.on_lose_focus(self._impl.interface) + for window in self._impl.interface.windows: + window.on_lose_focus(self._impl.interface) + def onActivityResult(self, requestCode, resultCode, resultData): """Callback method, called from MainActivity when an Intent ends. From 3b03ec45e7544f43a3516f935df5a3a42c7a883f Mon Sep 17 00:00:00 2001 From: proneon267 Date: Mon, 28 Aug 2023 02:49:07 -0700 Subject: [PATCH 06/37] Coreected `cocoa` implementation. --- cocoa/src/toga_cocoa/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cocoa/src/toga_cocoa/app.py b/cocoa/src/toga_cocoa/app.py index b2b85ab19b..1803ff6154 100644 --- a/cocoa/src/toga_cocoa/app.py +++ b/cocoa/src/toga_cocoa/app.py @@ -63,11 +63,11 @@ def applicationOpenUntitledFile_(self, sender) -> bool: @objc_method def applicationDidBecomeActive_(self, application): - self.interface.on_gain_focus(self.interface) + self.impl.interface.on_gain_focus(self.interface) @objc_method def applicationWillResignActive_(self, application): - self.interface.on_lose_focus(self.interface) + self.impl.interface.on_lose_focus(self.interface) @objc_method def addDocument_(self, document) -> None: From 096e6bf19ac17e7541e7ca2b3e7367a6480d03b3 Mon Sep 17 00:00:00 2001 From: proneon267 Date: Mon, 28 Aug 2023 02:55:03 -0700 Subject: [PATCH 07/37] Re: `cocoa` implementation correction. --- cocoa/src/toga_cocoa/window.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cocoa/src/toga_cocoa/window.py b/cocoa/src/toga_cocoa/window.py index cf81a7861b..fbf2526dc4 100644 --- a/cocoa/src/toga_cocoa/window.py +++ b/cocoa/src/toga_cocoa/window.py @@ -50,11 +50,11 @@ def windowDidResize_(self, notification) -> None: @objc_method def windowDidBecomeMain_(self, notification): - self.interface.on_gain_focus(self.interface) + self.impl.interface.on_gain_focus(self.interface) @objc_method def windowDidResignMain_(self, notification): - self.interface.on_lose_focus(self.interface) + self.impl.interface.on_lose_focus(self.interface) ###################################################################### # Toolbar delegate methods From 943612dd4f938870eb1e33cd634c39ccec9a7be3 Mon Sep 17 00:00:00 2001 From: proneon267 Date: Mon, 28 Aug 2023 03:19:26 -0700 Subject: [PATCH 08/37] Added support for `iOS`. --- iOS/src/toga_iOS/app.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/iOS/src/toga_iOS/app.py b/iOS/src/toga_iOS/app.py index c137e2e07c..2d92718b95 100644 --- a/iOS/src/toga_iOS/app.py +++ b/iOS/src/toga_iOS/app.py @@ -15,10 +15,16 @@ class PythonAppDelegate(UIResponder): @objc_method def applicationDidBecomeActive_(self, application) -> None: print("App became active.") + App.app.interface.on_gain_focus(App.app.interface) + for window in App.app.interface.windows: + window.on_gain_focus(App.app.interface) @objc_method def applicationWillResignActive_(self, application) -> None: print("App about to leave foreground.", flush=True) + App.app.interface.on_lose_focus(App.app.interface) + for window in App.app.interface.windows: + window.on_lose_focus(App.app.interface) @objc_method def applicationDidEnterBackground_(self, application) -> None: From 7b864fe6914f0cecdfe6e21991b2a54046c96ca6 Mon Sep 17 00:00:00 2001 From: proneon267 Date: Mon, 28 Aug 2023 04:22:42 -0700 Subject: [PATCH 09/37] Fixed `winforms` implementation. --- winforms/src/toga_winforms/window.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/winforms/src/toga_winforms/window.py b/winforms/src/toga_winforms/window.py index 75d71a1a5c..ec473b0ba2 100644 --- a/winforms/src/toga_winforms/window.py +++ b/winforms/src/toga_winforms/window.py @@ -178,9 +178,11 @@ def resize_content(self): ) def window_on_gain_focus(self, sender, event): - self.interface.app.on_gain_focus(self.interface) + if self.interface.app is not None: + self.interface.app.on_gain_focus(self.interface) self.interface.on_gain_focus(self.interface) def window_on_lose_focus(self, sender, event): - self.interface.app.on_lose_focus(self.interface) + if self.interface.app is not None: + self.interface.app.on_lose_focus(self.interface) self.interface.on_lose_focus(self.interface) From 88c56b928f4428a8fbb7e9ae36fee58d9ef98d71 Mon Sep 17 00:00:00 2001 From: proneon267 Date: Mon, 28 Aug 2023 07:52:54 -0700 Subject: [PATCH 10/37] Added a test in window example app. --- examples/window/window/app.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/examples/window/window/app.py b/examples/window/window/app.py index 184ad0cc76..1d10f020d0 100644 --- a/examples/window/window/app.py +++ b/examples/window/window/app.py @@ -122,17 +122,38 @@ def close_handler(self, window, **kwargs): return False return True + def on_app_gain_focus(self, widget, **kwargs): + self.app_focus_label.text = "App is in focus" + + def on_app_lose_focus(self, widget, **kwargs): + self.app_focus_label.text = "App is not in focus" + + def on_window_gain_focus(self, widget, **kwargs): + self.window_focus_label.text = "MainWindow is in focus" + + def on_window_lose_focus(self, widget, **kwargs): + self.window_focus_label.text = "MainWindow is not in focus" + def startup(self): # Track in-app closes self.close_count = 0 + self.on_gain_focus = self.on_app_gain_focus + self.on_lose_focus = self.on_app_lose_focus + # Set up main window - self.main_window = toga.MainWindow(title=self.name) + self.main_window = toga.MainWindow( + title=self.name, + on_gain_focus=self.on_window_gain_focus, + on_lose_focus=self.on_window_lose_focus, + ) self.on_exit = self.exit_handler # Label to show responses. self.label = toga.Label("Ready.") + self.app_focus_label = toga.Label("App focus status") + self.window_focus_label = toga.Label("Window focus status") # Buttons btn_style = Pack(flex=1, padding=5) btn_do_origin = toga.Button( @@ -175,6 +196,8 @@ def startup(self): self.inner_box = toga.Box( children=[ self.label, + self.app_focus_label, + self.window_focus_label, btn_do_origin, btn_do_left, btn_do_right, From 156bc9e23b872e53594196ae5f7a64a0cc35fe36 Mon Sep 17 00:00:00 2001 From: proneon267 Date: Tue, 29 Aug 2023 23:32:26 -0700 Subject: [PATCH 11/37] Corrected `Android` implementation. --- android/src/toga_android/app.py | 15 ++++++++++++--- examples/window/window/app.py | 4 ++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/android/src/toga_android/app.py b/android/src/toga_android/app.py index 553d2c5b47..388dab21da 100644 --- a/android/src/toga_android/app.py +++ b/android/src/toga_android/app.py @@ -4,6 +4,7 @@ import toga from android.media import RingtoneManager +from android.os import Build from toga.command import Group from .libs.activity import IPythonApp, MainActivity @@ -36,9 +37,17 @@ def onStart(self): def onResume(self): print("Toga app: onResume") + if Build.VERSION.SDK_INT < Build.VERSION_CODES.Q: + self._impl.interface.on_gain_focus(self._impl.interface) + for window in self._impl.interface.windows: + window.on_gain_focus(self._impl.interface) def onPause(self): print("Toga app: onPause") + if Build.VERSION.SDK_INT < Build.VERSION_CODES.Q: + self._impl.interface.on_lose_focus(self._impl.interface) + for window in self._impl.interface.windows: + window.on_lose_focus(self._impl.interface) def onStop(self): print("Toga app: onStop") @@ -49,9 +58,9 @@ def onDestroy(self): def onRestart(self): print("Toga app: onRestart") - def onWindowFocusChanged(self, hasFocus): - print("Toga app: onWindowFocusChanged") - if hasFocus: + def onTopResumedActivityChanged(self, isTopResumedActivity): + print("Toga app: onTopResumedActivityChanged") + if isTopResumedActivity: self._impl.interface.on_gain_focus(self._impl.interface) for window in self._impl.interface.windows: window.on_gain_focus(self._impl.interface) diff --git a/examples/window/window/app.py b/examples/window/window/app.py index 1d10f020d0..de6eb1913f 100644 --- a/examples/window/window/app.py +++ b/examples/window/window/app.py @@ -124,15 +124,19 @@ def close_handler(self, window, **kwargs): def on_app_gain_focus(self, widget, **kwargs): self.app_focus_label.text = "App is in focus" + print("App is in focus") def on_app_lose_focus(self, widget, **kwargs): self.app_focus_label.text = "App is not in focus" + print("App is not in focus") def on_window_gain_focus(self, widget, **kwargs): self.window_focus_label.text = "MainWindow is in focus" + print("MainWindow is in focus") def on_window_lose_focus(self, widget, **kwargs): self.window_focus_label.text = "MainWindow is not in focus" + print("MainWindow is not in focus") def startup(self): # Track in-app closes From 7b232093656c7f6861d0c15c5bfc052db30620f7 Mon Sep 17 00:00:00 2001 From: proneon267 Date: Tue, 12 Sep 2023 08:05:48 -0700 Subject: [PATCH 12/37] Added `on_show` & `on_hide` handlers on winforms. --- core/src/toga/app.py | 24 ++++++++++++++++++++++++ core/src/toga/window.py | 20 ++++++++++++++++++++ examples/window/window/app.py | 25 +++++++++++++++++++++++++ winforms/src/toga_winforms/window.py | 27 +++++++++++++++++++++++++++ 4 files changed, 96 insertions(+) diff --git a/core/src/toga/app.py b/core/src/toga/app.py index a618795a00..92cd0489ba 100644 --- a/core/src/toga/app.py +++ b/core/src/toga/app.py @@ -134,6 +134,8 @@ def __init__( on_close: None = None, on_gain_focus: callable | None = None, on_lose_focus: callable | None = None, + on_show: callable | None = None, + on_hide: callable | None = None, ) -> None: ###################################################################### # 2022-09: Backwards compatibility @@ -156,6 +158,8 @@ def __init__( on_close=on_close, on_gain_focus=on_gain_focus, on_lose_focus=on_lose_focus, + on_show=on_show, + on_hide=on_hide, ) @Window.on_close.setter @@ -191,6 +195,8 @@ def __init__( on_exit: OnExitHandler | None = None, on_gain_focus: callable | None = None, on_lose_focus: callable | None = None, + on_show: callable | None = None, + on_hide: callable | None = None, factory: None = None, # DEPRECATED ! ): """An App is the top level of any GUI program. @@ -392,6 +398,8 @@ class is defined, and look for a ``.dist-info`` file matching that name. self.on_gain_focus = on_gain_focus self.on_lose_focus = on_lose_focus + self.on_show = on_show + self.on_hide = on_hide def _create_impl(self): return self.factory.App(interface=self) @@ -653,6 +661,22 @@ def on_lose_focus(self) -> callable: def on_lose_focus(self, handler): self._on_lose_focus = wrapped_handler(self, handler) + @property + def on_show(self) -> callable: + return self._on_show + + @on_show.setter + def on_show(self, handler): + self._on_show = wrapped_handler(self, handler) + + @property + def on_hide(self) -> callable: + return self._on_hide + + @on_hide.setter + def on_hide(self, handler): + self._on_hide = wrapped_handler(self, handler) + class DocumentApp(App): def __init__( diff --git a/core/src/toga/window.py b/core/src/toga/window.py index 868f41511d..1a7754bf0b 100644 --- a/core/src/toga/window.py +++ b/core/src/toga/window.py @@ -90,6 +90,8 @@ def __init__( on_close: OnCloseHandler | None = None, on_gain_focus: callable | None = None, on_lose_focus: callable | None = None, + on_show: callable | None = None, + on_hide: callable | None = None, ) -> None: ###################################################################### # 2022-09: Backwards compatibility @@ -127,6 +129,8 @@ def __init__( self.on_gain_focus = on_gain_focus self.on_lose_focus = on_lose_focus + self.on_show = on_show + self.on_hide = on_hide @property def id(self) -> str: @@ -292,6 +296,22 @@ def on_lose_focus(self) -> callable: def on_lose_focus(self, handler): self._on_lose_focus = wrapped_handler(self, handler) + @property + def on_show(self) -> callable: + return self._on_show + + @on_show.setter + def on_show(self, handler): + self._on_show = wrapped_handler(self, handler) + + @property + def on_hide(self) -> callable: + return self._on_hide + + @on_hide.setter + def on_hide(self, handler): + self._on_hide = wrapped_handler(self, handler) + ############################################################ # Dialogs ############################################################ diff --git a/examples/window/window/app.py b/examples/window/window/app.py index de6eb1913f..01d433cadc 100644 --- a/examples/window/window/app.py +++ b/examples/window/window/app.py @@ -130,6 +130,14 @@ def on_app_lose_focus(self, widget, **kwargs): self.app_focus_label.text = "App is not in focus" print("App is not in focus") + def on_app_show(self, widget, **kwargs): + self.app_visible_label.text = "App is visible" + print("App is visible") + + def on_app_hide(self, widget, **kwargs): + self.app_visible_label.text = "App is not visible" + print("App is not visible") + def on_window_gain_focus(self, widget, **kwargs): self.window_focus_label.text = "MainWindow is in focus" print("MainWindow is in focus") @@ -138,18 +146,30 @@ def on_window_lose_focus(self, widget, **kwargs): self.window_focus_label.text = "MainWindow is not in focus" print("MainWindow is not in focus") + def on_window_show(self, widget, **kwargs): + self.window_visible_label.text = "MainWindow is visible" + print("MainWindow is visible") + + def on_window_hide(self, widget, **kwargs): + self.window_visible_label.text = "MainWindow is not visible" + print("MainWindow is not visible") + def startup(self): # Track in-app closes self.close_count = 0 self.on_gain_focus = self.on_app_gain_focus self.on_lose_focus = self.on_app_lose_focus + self.on_show = self.on_app_show + self.on_hide = self.on_app_hide # Set up main window self.main_window = toga.MainWindow( title=self.name, on_gain_focus=self.on_window_gain_focus, on_lose_focus=self.on_window_lose_focus, + on_show=self.on_window_show, + on_hide=self.on_window_hide, ) self.on_exit = self.exit_handler @@ -157,7 +177,10 @@ def startup(self): self.label = toga.Label("Ready.") self.app_focus_label = toga.Label("App focus status") + self.app_visible_label = toga.Label("App visible status") self.window_focus_label = toga.Label("Window focus status") + self.window_visible_label = toga.Label("Window visible status") + # Buttons btn_style = Pack(flex=1, padding=5) btn_do_origin = toga.Button( @@ -201,7 +224,9 @@ def startup(self): children=[ self.label, self.app_focus_label, + self.app_visible_label, self.window_focus_label, + self.window_visible_label, btn_do_origin, btn_do_left, btn_do_right, diff --git a/winforms/src/toga_winforms/window.py b/winforms/src/toga_winforms/window.py index ec473b0ba2..44c4dbacf5 100644 --- a/winforms/src/toga_winforms/window.py +++ b/winforms/src/toga_winforms/window.py @@ -46,6 +46,9 @@ def __init__(self, interface, title, position, size): self.native.Activated += self.window_on_gain_focus self.native.Deactivate += self.window_on_lose_focus + self.native.VisibleChanged += self.window_visible_changed + self.native.SizeChanged += self.window_size_changed + def create_toolbar(self): if self.interface.toolbar: if self.toolbar_native: @@ -186,3 +189,27 @@ def window_on_lose_focus(self, sender, event): if self.interface.app is not None: self.interface.app.on_lose_focus(self.interface) self.interface.on_lose_focus(self.interface) + + def window_visible_changed(self, sender, event): + if self.native.Visible: + if self.interface.app is not None: + self.interface.app.on_show(self.interface) + self.interface.on_show(self.interface) + else: + if self.interface.app is not None: + self.interface.app.on_hide(self.interface) + self.interface.on_hide(self.interface) + + def window_size_changed(self, sender, event): + if self.native.WindowState == WinForms.FormWindowState.Minimized: + if self.interface.app is not None: + self.interface.app.on_hide(self.interface) + self.interface.on_hide(self.interface) + elif self.native.WindowState == WinForms.FormWindowState.Maximized: + if self.interface.app is not None: + self.interface.app.on_show(self.interface) + self.interface.on_show(self.interface) + elif self.native.WindowState == WinForms.FormWindowState.Normal: + if self.interface.app is not None: + self.interface.app.on_show(self.interface) + self.interface.on_show(self.interface) From 0427a8f2cdaaafa8be39b3147b888ffbdc9ce514 Mon Sep 17 00:00:00 2001 From: proneon267 Date: Tue, 12 Sep 2023 08:50:49 -0700 Subject: [PATCH 13/37] Fixed overlapping event triggers on winforms. --- gtk/src/toga_gtk/window.py | 6 ++++-- winforms/src/toga_winforms/window.py | 25 +++++++++++++++++++++---- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/gtk/src/toga_gtk/window.py b/gtk/src/toga_gtk/window.py index 1255745d8d..a45e6152fe 100644 --- a/gtk/src/toga_gtk/window.py +++ b/gtk/src/toga_gtk/window.py @@ -142,9 +142,11 @@ def set_full_screen(self, is_full_screen): self.native.unfullscreen() def window_on_gain_focus(self, sender, event): - self.interface.app.on_gain_focus(self.interface) + if self.interface.app is not None: + self.interface.app.on_gain_focus(self.interface) self.interface.on_gain_focus(self.interface) def window_on_lose_focus(self, sender, event): - self.interface.app.on_lose_focus(self.interface) + if self.interface.app is not None: + self.interface.app.on_lose_focus(self.interface) self.interface.on_lose_focus(self.interface) diff --git a/winforms/src/toga_winforms/window.py b/winforms/src/toga_winforms/window.py index 44c4dbacf5..52159b1f2d 100644 --- a/winforms/src/toga_winforms/window.py +++ b/winforms/src/toga_winforms/window.py @@ -19,6 +19,7 @@ def __init__(self, interface, title, position, size): # to ignore. The `_is_closing` flag lets us easily identify if the # window is in the process of closing. self._is_closing = False + self._is_previously_visible = False self.native = WinForms.Form() self.native.interface = self.interface @@ -191,25 +192,41 @@ def window_on_lose_focus(self, sender, event): self.interface.on_lose_focus(self.interface) def window_visible_changed(self, sender, event): - if self.native.Visible: + if self.native.Visible and not self._is_previously_visible: + self._is_previously_visible = True if self.interface.app is not None: self.interface.app.on_show(self.interface) self.interface.on_show(self.interface) else: + self._is_previously_visible = False if self.interface.app is not None: self.interface.app.on_hide(self.interface) self.interface.on_hide(self.interface) def window_size_changed(self, sender, event): - if self.native.WindowState == WinForms.FormWindowState.Minimized: + if ( + self.native.WindowState == WinForms.FormWindowState.Minimized + and self._is_previously_visible + ): + self._is_previously_visible = False if self.interface.app is not None: self.interface.app.on_hide(self.interface) self.interface.on_hide(self.interface) - elif self.native.WindowState == WinForms.FormWindowState.Maximized: + + elif ( + self.native.WindowState == WinForms.FormWindowState.Maximized + and not self._is_previously_visible + ): + self._is_previously_visible = True if self.interface.app is not None: self.interface.app.on_show(self.interface) self.interface.on_show(self.interface) - elif self.native.WindowState == WinForms.FormWindowState.Normal: + + elif ( + self.native.WindowState == WinForms.FormWindowState.Normal + and not self._is_previously_visible + ): + self._is_previously_visible = True if self.interface.app is not None: self.interface.app.on_show(self.interface) self.interface.on_show(self.interface) From b9e04121d1b8655ca840bb9b3d2ff4926e188b5d Mon Sep 17 00:00:00 2001 From: proneon267 Date: Wed, 13 Sep 2023 07:49:07 -0700 Subject: [PATCH 14/37] Added `on_show` & `on_hide` handlers on gtk. --- gtk/src/toga_gtk/window.py | 50 +++++++++++++++++++++++++++- winforms/src/toga_winforms/window.py | 8 ++--- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/gtk/src/toga_gtk/window.py b/gtk/src/toga_gtk/window.py index a45e6152fe..2ee07bddde 100644 --- a/gtk/src/toga_gtk/window.py +++ b/gtk/src/toga_gtk/window.py @@ -2,7 +2,7 @@ from toga.handlers import wrapped_handler from .container import TogaContainer -from .libs import Gtk +from .libs import Gdk, Gtk class Window: @@ -13,6 +13,7 @@ def __init__(self, interface, title, position, size): self.interface._impl = self self._is_closing = False + self._is_previously_visible = False self.layout = None @@ -22,6 +23,7 @@ def __init__(self, interface, title, position, size): self.native.connect("delete-event", self.gtk_delete_event) self.native.connect("focus-in-event", self.window_on_gain_focus) self.native.connect("focus-out-event", self.window_on_lose_focus) + self.native.connect("window-state-event", self.window_on_state_changed) self.native.set_default_size(size[0], size[1]) @@ -150,3 +152,49 @@ def window_on_lose_focus(self, sender, event): if self.interface.app is not None: self.interface.app.on_lose_focus(self.interface) self.interface.on_lose_focus(self.interface) + + def window_on_state_changed(self, sender, event): + if ( + event.new_window_state & Gdk.WindowState.WITHDRAWN + and self._is_previously_visible + ): + self._is_previously_visible = False + if self.interface.app is not None: + self.interface.app.on_hide(self.interface) + self.interface.on_hide(self.interface) + + elif ( + event.new_window_state & Gdk.WindowState.ICONIFIED + and self._is_previously_visible + ): + self._is_previously_visible = False + if self.interface.app is not None: + self.interface.app.on_hide(self.interface) + self.interface.on_hide(self.interface) + + elif ( + event.new_window_state & Gdk.WindowState.MAXIMIZED + and not self._is_previously_visible + ): + self._is_previously_visible = True + if self.interface.app is not None: + self.interface.app.on_show(self.interface) + self.interface.on_show(self.interface) + + elif ( + event.new_window_state & Gdk.WindowState.FULLSCREEN + and not self._is_previously_visible + ): + self._is_previously_visible = True + if self.interface.app is not None: + self.interface.app.on_show(self.interface) + self.interface.on_show(self.interface) + + elif ( + event.new_window_state & Gdk.WindowState.FOCUSED + and not self._is_previously_visible + ): + self._is_previously_visible = True + if self.interface.app is not None: + self.interface.app.on_show(self.interface) + self.interface.on_show(self.interface) diff --git a/winforms/src/toga_winforms/window.py b/winforms/src/toga_winforms/window.py index 52159b1f2d..4c0d2168b8 100644 --- a/winforms/src/toga_winforms/window.py +++ b/winforms/src/toga_winforms/window.py @@ -47,8 +47,8 @@ def __init__(self, interface, title, position, size): self.native.Activated += self.window_on_gain_focus self.native.Deactivate += self.window_on_lose_focus - self.native.VisibleChanged += self.window_visible_changed - self.native.SizeChanged += self.window_size_changed + self.native.VisibleChanged += self.window_on_visible_changed + self.native.SizeChanged += self.window_on_size_changed def create_toolbar(self): if self.interface.toolbar: @@ -191,7 +191,7 @@ def window_on_lose_focus(self, sender, event): self.interface.app.on_lose_focus(self.interface) self.interface.on_lose_focus(self.interface) - def window_visible_changed(self, sender, event): + def window_on_visible_changed(self, sender, event): if self.native.Visible and not self._is_previously_visible: self._is_previously_visible = True if self.interface.app is not None: @@ -203,7 +203,7 @@ def window_visible_changed(self, sender, event): self.interface.app.on_hide(self.interface) self.interface.on_hide(self.interface) - def window_size_changed(self, sender, event): + def window_on_size_changed(self, sender, event): if ( self.native.WindowState == WinForms.FormWindowState.Minimized and self._is_previously_visible From 7f7468d766cdc7bce753040448c693accffcfdc6 Mon Sep 17 00:00:00 2001 From: proneon267 Date: Wed, 13 Sep 2023 19:24:03 -0700 Subject: [PATCH 15/37] Added `on_show()` & `on_hide()` handlers on cocoa. --- cocoa/src/toga_cocoa/window.py | 46 ++++++++++++++++++++++++++++ core/src/toga/app.py | 41 ------------------------- examples/window/window/app.py | 25 --------------- gtk/src/toga_gtk/window.py | 36 +++++++--------------- winforms/src/toga_winforms/window.py | 34 ++++++-------------- 5 files changed, 67 insertions(+), 115 deletions(-) diff --git a/cocoa/src/toga_cocoa/window.py b/cocoa/src/toga_cocoa/window.py index fbf2526dc4..0f24b573fa 100644 --- a/cocoa/src/toga_cocoa/window.py +++ b/cocoa/src/toga_cocoa/window.py @@ -56,6 +56,50 @@ def windowDidBecomeMain_(self, notification): def windowDidResignMain_(self, notification): self.impl.interface.on_lose_focus(self.interface) + @objc_method + def windowDidBecomeKey_(self, notification): + if bool(self.impl.native.isVisible) and not self.impl._is_previously_shown: + self.impl._is_previously_shown = True + self.impl.interface.on_show(self.interface) + + @objc_method + def windowDidMiniaturize_(self, notification): + if not bool(self.impl.native.isVisible) and self.impl._is_previously_shown: + self.impl._is_previously_shown = False + self.impl.interface.on_hide(self.interface) + + @objc_method + def windowDidDeminiaturize_(self, notification): + if bool(self.impl.native.isVisible) and not self.impl._is_previously_shown: + self.impl._is_previously_shown = True + self.impl.interface.on_show(self.interface) + + @objc_method + def windowDidEnterFullScreen_(self, notification): + if bool(self.impl.native.isVisible) and not self.impl._is_previously_shown: + self.impl._is_previously_shown = True + self.impl.interface.on_show(self.interface) + + @objc_method + def windowDidExitFullScreen_(self, notification): + if bool(self.impl.native.isVisible) and not self.impl._is_previously_shown: + self.impl._is_previously_shown = True + self.impl.interface.on_show(self.interface) + + # when the user clicks the zoom button to unzoom a window + @objc_method + def windowWillUseStandardFrame_defaultFrame_(self, window, defaultFrame): + if bool(self.impl.native.isVisible) and not self.impl._is_previously_shown: + self.impl._is_previously_shown = True + self.impl.interface.on_show(self.interface) + + # when the user clicks the zoom button to zoom a window + @objc_method + def windowShouldZoom_toFrame_(self, window, toFrame): + if bool(self.impl.native.isVisible) and not self.impl._is_previously_shown: + self.impl._is_previously_shown = True + self.impl.interface.on_show(self.interface) + ###################################################################### # Toolbar delegate methods ###################################################################### @@ -133,6 +177,8 @@ def __init__(self, interface, title, position, size): self.interface = interface self.interface._impl = self + self._is_previously_shown = False + mask = NSTitledWindowMask if self.interface.closeable: mask |= NSClosableWindowMask diff --git a/core/src/toga/app.py b/core/src/toga/app.py index 92cd0489ba..e5affaab0a 100644 --- a/core/src/toga/app.py +++ b/core/src/toga/app.py @@ -193,10 +193,6 @@ def __init__( startup: AppStartupMethod | None = None, windows: Iterable[Window] = (), on_exit: OnExitHandler | None = None, - on_gain_focus: callable | None = None, - on_lose_focus: callable | None = None, - on_show: callable | None = None, - on_hide: callable | None = None, factory: None = None, # DEPRECATED ! ): """An App is the top level of any GUI program. @@ -396,11 +392,6 @@ class is defined, and look for a ``.dist-info`` file matching that name. self._impl = self._create_impl() self.on_exit = on_exit - self.on_gain_focus = on_gain_focus - self.on_lose_focus = on_lose_focus - self.on_show = on_show - self.on_hide = on_hide - def _create_impl(self): return self.factory.App(interface=self) @@ -645,38 +636,6 @@ def add_background_task(self, handler: BackgroundTask) -> None: """ self._impl.loop.call_soon_threadsafe(wrapped_handler(self, handler), None) - @property - def on_gain_focus(self) -> callable: - return self._on_gain_focus - - @on_gain_focus.setter - def on_gain_focus(self, handler): - self._on_gain_focus = wrapped_handler(self, handler) - - @property - def on_lose_focus(self) -> callable: - return self._on_lose_focus - - @on_lose_focus.setter - def on_lose_focus(self, handler): - self._on_lose_focus = wrapped_handler(self, handler) - - @property - def on_show(self) -> callable: - return self._on_show - - @on_show.setter - def on_show(self, handler): - self._on_show = wrapped_handler(self, handler) - - @property - def on_hide(self) -> callable: - return self._on_hide - - @on_hide.setter - def on_hide(self, handler): - self._on_hide = wrapped_handler(self, handler) - class DocumentApp(App): def __init__( diff --git a/examples/window/window/app.py b/examples/window/window/app.py index 01d433cadc..eff27728d2 100644 --- a/examples/window/window/app.py +++ b/examples/window/window/app.py @@ -122,22 +122,6 @@ def close_handler(self, window, **kwargs): return False return True - def on_app_gain_focus(self, widget, **kwargs): - self.app_focus_label.text = "App is in focus" - print("App is in focus") - - def on_app_lose_focus(self, widget, **kwargs): - self.app_focus_label.text = "App is not in focus" - print("App is not in focus") - - def on_app_show(self, widget, **kwargs): - self.app_visible_label.text = "App is visible" - print("App is visible") - - def on_app_hide(self, widget, **kwargs): - self.app_visible_label.text = "App is not visible" - print("App is not visible") - def on_window_gain_focus(self, widget, **kwargs): self.window_focus_label.text = "MainWindow is in focus" print("MainWindow is in focus") @@ -158,11 +142,6 @@ def startup(self): # Track in-app closes self.close_count = 0 - self.on_gain_focus = self.on_app_gain_focus - self.on_lose_focus = self.on_app_lose_focus - self.on_show = self.on_app_show - self.on_hide = self.on_app_hide - # Set up main window self.main_window = toga.MainWindow( title=self.name, @@ -176,8 +155,6 @@ def startup(self): # Label to show responses. self.label = toga.Label("Ready.") - self.app_focus_label = toga.Label("App focus status") - self.app_visible_label = toga.Label("App visible status") self.window_focus_label = toga.Label("Window focus status") self.window_visible_label = toga.Label("Window visible status") @@ -223,8 +200,6 @@ def startup(self): self.inner_box = toga.Box( children=[ self.label, - self.app_focus_label, - self.app_visible_label, self.window_focus_label, self.window_visible_label, btn_do_origin, diff --git a/gtk/src/toga_gtk/window.py b/gtk/src/toga_gtk/window.py index 2ee07bddde..78e2569404 100644 --- a/gtk/src/toga_gtk/window.py +++ b/gtk/src/toga_gtk/window.py @@ -13,7 +13,7 @@ def __init__(self, interface, title, position, size): self.interface._impl = self self._is_closing = False - self._is_previously_visible = False + self._is_previously_shown = False self.layout = None @@ -144,57 +144,43 @@ def set_full_screen(self, is_full_screen): self.native.unfullscreen() def window_on_gain_focus(self, sender, event): - if self.interface.app is not None: - self.interface.app.on_gain_focus(self.interface) self.interface.on_gain_focus(self.interface) def window_on_lose_focus(self, sender, event): - if self.interface.app is not None: - self.interface.app.on_lose_focus(self.interface) self.interface.on_lose_focus(self.interface) def window_on_state_changed(self, sender, event): if ( event.new_window_state & Gdk.WindowState.WITHDRAWN - and self._is_previously_visible + and self._is_previously_shown ): - self._is_previously_visible = False - if self.interface.app is not None: - self.interface.app.on_hide(self.interface) + self._is_previously_shown = False self.interface.on_hide(self.interface) elif ( event.new_window_state & Gdk.WindowState.ICONIFIED - and self._is_previously_visible + and self._is_previously_shown ): - self._is_previously_visible = False - if self.interface.app is not None: - self.interface.app.on_hide(self.interface) + self._is_previously_shown = False self.interface.on_hide(self.interface) elif ( event.new_window_state & Gdk.WindowState.MAXIMIZED - and not self._is_previously_visible + and not self._is_previously_shown ): - self._is_previously_visible = True - if self.interface.app is not None: - self.interface.app.on_show(self.interface) + self._is_previously_shown = True self.interface.on_show(self.interface) elif ( event.new_window_state & Gdk.WindowState.FULLSCREEN - and not self._is_previously_visible + and not self._is_previously_shown ): - self._is_previously_visible = True - if self.interface.app is not None: - self.interface.app.on_show(self.interface) + self._is_previously_shown = True self.interface.on_show(self.interface) elif ( event.new_window_state & Gdk.WindowState.FOCUSED - and not self._is_previously_visible + and not self._is_previously_shown ): - self._is_previously_visible = True - if self.interface.app is not None: - self.interface.app.on_show(self.interface) + self._is_previously_shown = True self.interface.on_show(self.interface) diff --git a/winforms/src/toga_winforms/window.py b/winforms/src/toga_winforms/window.py index 4c0d2168b8..9bb869d18d 100644 --- a/winforms/src/toga_winforms/window.py +++ b/winforms/src/toga_winforms/window.py @@ -19,7 +19,7 @@ def __init__(self, interface, title, position, size): # to ignore. The `_is_closing` flag lets us easily identify if the # window is in the process of closing. self._is_closing = False - self._is_previously_visible = False + self._is_previously_shown = False self.native = WinForms.Form() self.native.interface = self.interface @@ -182,51 +182,37 @@ def resize_content(self): ) def window_on_gain_focus(self, sender, event): - if self.interface.app is not None: - self.interface.app.on_gain_focus(self.interface) self.interface.on_gain_focus(self.interface) def window_on_lose_focus(self, sender, event): - if self.interface.app is not None: - self.interface.app.on_lose_focus(self.interface) self.interface.on_lose_focus(self.interface) def window_on_visible_changed(self, sender, event): - if self.native.Visible and not self._is_previously_visible: - self._is_previously_visible = True - if self.interface.app is not None: - self.interface.app.on_show(self.interface) + if self.native.Visible and not self._is_previously_shown: + self._is_previously_shown = True self.interface.on_show(self.interface) else: - self._is_previously_visible = False - if self.interface.app is not None: - self.interface.app.on_hide(self.interface) + self._is_previously_shown = False self.interface.on_hide(self.interface) def window_on_size_changed(self, sender, event): if ( self.native.WindowState == WinForms.FormWindowState.Minimized - and self._is_previously_visible + and self._is_previously_shown ): - self._is_previously_visible = False - if self.interface.app is not None: - self.interface.app.on_hide(self.interface) + self._is_previously_shown = False self.interface.on_hide(self.interface) elif ( self.native.WindowState == WinForms.FormWindowState.Maximized - and not self._is_previously_visible + and not self._is_previously_shown ): - self._is_previously_visible = True - if self.interface.app is not None: - self.interface.app.on_show(self.interface) + self._is_previously_shown = True self.interface.on_show(self.interface) elif ( self.native.WindowState == WinForms.FormWindowState.Normal - and not self._is_previously_visible + and not self._is_previously_shown ): - self._is_previously_visible = True - if self.interface.app is not None: - self.interface.app.on_show(self.interface) + self._is_previously_shown = True self.interface.on_show(self.interface) From 429568bba1fba4a6371dcf3b3bfeda1b01f72bda Mon Sep 17 00:00:00 2001 From: proneon267 Date: Wed, 13 Sep 2023 19:30:07 -0700 Subject: [PATCH 16/37] Added `on_show()` & `on_hide()` on iOS. --- iOS/src/toga_iOS/app.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/iOS/src/toga_iOS/app.py b/iOS/src/toga_iOS/app.py index 2d92718b95..dc218eab01 100644 --- a/iOS/src/toga_iOS/app.py +++ b/iOS/src/toga_iOS/app.py @@ -29,10 +29,14 @@ def applicationWillResignActive_(self, application) -> None: @objc_method def applicationDidEnterBackground_(self, application) -> None: print("App entered background.") + for window in App.app.interface.windows: + window.on_hide(App.app.interface) @objc_method def applicationWillEnterForeground_(self, application) -> None: print("App about to enter foreground.") + for window in App.app.interface.windows: + window.on_show(App.app.interface) @objc_method def application_didFinishLaunchingWithOptions_( From 3440221f5d9d80f706c6f44b45c77c22f1607931 Mon Sep 17 00:00:00 2001 From: proneon267 Date: Wed, 13 Sep 2023 19:31:54 -0700 Subject: [PATCH 17/37] Corrected iOS implementation. --- iOS/src/toga_iOS/app.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/iOS/src/toga_iOS/app.py b/iOS/src/toga_iOS/app.py index dc218eab01..5ca00a3ba0 100644 --- a/iOS/src/toga_iOS/app.py +++ b/iOS/src/toga_iOS/app.py @@ -15,14 +15,12 @@ class PythonAppDelegate(UIResponder): @objc_method def applicationDidBecomeActive_(self, application) -> None: print("App became active.") - App.app.interface.on_gain_focus(App.app.interface) for window in App.app.interface.windows: window.on_gain_focus(App.app.interface) @objc_method def applicationWillResignActive_(self, application) -> None: print("App about to leave foreground.", flush=True) - App.app.interface.on_lose_focus(App.app.interface) for window in App.app.interface.windows: window.on_lose_focus(App.app.interface) From fc8a29c5e9a7a5343d6a7d105c47919d0a12a0fd Mon Sep 17 00:00:00 2001 From: proneon267 Date: Thu, 14 Sep 2023 05:32:11 -0700 Subject: [PATCH 18/37] Added `on_show` & `on_hide` handlers on Android. --- android/src/toga_android/app.py | 8 ++++---- iOS/src/toga_iOS/app.py | 11 ++++++++--- iOS/src/toga_iOS/libs/uikit.py | 6 ++++++ 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/android/src/toga_android/app.py b/android/src/toga_android/app.py index 7ee478ee24..d32fb0512c 100644 --- a/android/src/toga_android/app.py +++ b/android/src/toga_android/app.py @@ -35,23 +35,25 @@ def onCreate(self): def onStart(self): print("Toga app: onStart") + for window in self._impl.interface.windows: + window.on_show(self._impl.interface) def onResume(self): print("Toga app: onResume") if Build.VERSION.SDK_INT < Build.VERSION_CODES.Q: - self._impl.interface.on_gain_focus(self._impl.interface) for window in self._impl.interface.windows: window.on_gain_focus(self._impl.interface) def onPause(self): print("Toga app: onPause") if Build.VERSION.SDK_INT < Build.VERSION_CODES.Q: - self._impl.interface.on_lose_focus(self._impl.interface) for window in self._impl.interface.windows: window.on_lose_focus(self._impl.interface) def onStop(self): print("Toga app: onStop") + for window in self._impl.interface.windows: + window.on_hide(self._impl.interface) def onDestroy(self): print("Toga app: onDestroy") @@ -62,11 +64,9 @@ def onRestart(self): def onTopResumedActivityChanged(self, isTopResumedActivity): print("Toga app: onTopResumedActivityChanged") if isTopResumedActivity: - self._impl.interface.on_gain_focus(self._impl.interface) for window in self._impl.interface.windows: window.on_gain_focus(self._impl.interface) else: - self._impl.interface.on_lose_focus(self._impl.interface) for window in self._impl.interface.windows: window.on_lose_focus(self._impl.interface) diff --git a/iOS/src/toga_iOS/app.py b/iOS/src/toga_iOS/app.py index 5ca00a3ba0..9efe138a50 100644 --- a/iOS/src/toga_iOS/app.py +++ b/iOS/src/toga_iOS/app.py @@ -3,7 +3,7 @@ from rubicon.objc import objc_method from rubicon.objc.eventloop import EventLoopPolicy, iOSLifecycle -from toga_iOS.libs import UIResponder +from toga_iOS.libs import UIApplication, UIApplicationState, UIResponder from toga_iOS.window import Window @@ -15,8 +15,13 @@ class PythonAppDelegate(UIResponder): @objc_method def applicationDidBecomeActive_(self, application) -> None: print("App became active.") - for window in App.app.interface.windows: - window.on_gain_focus(App.app.interface) + app_state = UIApplication.sharedApplication().applicationState + if app_state == UIApplicationState.UIApplicationStateActive: + for window in App.app.interface.windows: + window.on_gain_focus(App.app.interface) + else: + for window in App.app.interface.windows: + window.on_lose_focus(App.app.interface) @objc_method def applicationWillResignActive_(self, application) -> None: diff --git a/iOS/src/toga_iOS/libs/uikit.py b/iOS/src/toga_iOS/libs/uikit.py index 1c001b84d0..0d8f9f121a 100644 --- a/iOS/src/toga_iOS/libs/uikit.py +++ b/iOS/src/toga_iOS/libs/uikit.py @@ -147,6 +147,12 @@ class UIInterfaceOrientation(Enum): LandscapeRight = 4 +class UIApplicationState(Enum): + UIApplicationStateActive = 0 + UIApplicationStateInactive = 1 + UIApplicationStateBackground = 2 + + ###################################################################### # UIBarButtonItem.h UIBarButtonItem = ObjCClass("UIBarButtonItem") From 6fd5e3c6630f0db1464114f5c0ac07cdf843d606 Mon Sep 17 00:00:00 2001 From: proneon267 Date: Thu, 14 Sep 2023 06:28:01 -0700 Subject: [PATCH 19/37] Added `on_show` & `on_hide` on web. --- web/src/toga_web/window.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/web/src/toga_web/window.py b/web/src/toga_web/window.py index 74f8ec784e..4f0f4e93f5 100644 --- a/web/src/toga_web/window.py +++ b/web/src/toga_web/window.py @@ -17,7 +17,10 @@ def __init__(self, interface, title, position, size): app_placeholder.appendChild(self.native) js.document.body.onfocus = self.window_on_gain_focus - js.document.body.onblur = self.window_on_lose_focusS + js.document.body.onblur = self.window_on_lose_focus + js.document.addEventListener( + "visibilitychange", self.window_on_visibility_change + ) self.set_title(title) def get_title(self): @@ -81,9 +84,14 @@ def set_full_screen(self, is_full_screen): self.interface.factory.not_implemented("Window.set_full_screen()") def window_on_gain_focus(self, sender, event): - self.interface.app.on_gain_focus(self.interface) self.interface.on_gain_focus(self.interface) def window_on_lose_focus(self, sender, event): - self.interface.app.on_lose_focus(self.interface) self.interface.on_lose_focus(self.interface) + + def window_on_visibility_change(self, sender, event): + if hasattr(js.document, "hidden"): + if js.document.visibilityState == "visible": + self.interface.on_show(self.interface) + else: + self.interface.on_hide(self.interface) From 8604faaeead96d31d69ce743bf0d34f1953958b1 Mon Sep 17 00:00:00 2001 From: proneon267 Date: Thu, 14 Sep 2023 20:44:21 -0700 Subject: [PATCH 20/37] Modified as per suggestions. --- cocoa/src/toga_cocoa/app.py | 8 -------- iOS/src/toga_iOS/app.py | 4 ++-- winforms/src/toga_winforms/window.py | 16 ++++++++-------- 3 files changed, 10 insertions(+), 18 deletions(-) diff --git a/cocoa/src/toga_cocoa/app.py b/cocoa/src/toga_cocoa/app.py index 1803ff6154..c983720d4d 100644 --- a/cocoa/src/toga_cocoa/app.py +++ b/cocoa/src/toga_cocoa/app.py @@ -61,14 +61,6 @@ def applicationOpenUntitledFile_(self, sender) -> bool: self.impl.select_file() return True - @objc_method - def applicationDidBecomeActive_(self, application): - self.impl.interface.on_gain_focus(self.interface) - - @objc_method - def applicationWillResignActive_(self, application): - self.impl.interface.on_lose_focus(self.interface) - @objc_method def addDocument_(self, document) -> None: # print("Add Document", document) diff --git a/iOS/src/toga_iOS/app.py b/iOS/src/toga_iOS/app.py index 9efe138a50..d44201ee76 100644 --- a/iOS/src/toga_iOS/app.py +++ b/iOS/src/toga_iOS/app.py @@ -3,7 +3,7 @@ from rubicon.objc import objc_method from rubicon.objc.eventloop import EventLoopPolicy, iOSLifecycle -from toga_iOS.libs import UIApplication, UIApplicationState, UIResponder +from toga_iOS.libs import UIApplicationState, UIResponder from toga_iOS.window import Window @@ -15,7 +15,7 @@ class PythonAppDelegate(UIResponder): @objc_method def applicationDidBecomeActive_(self, application) -> None: print("App became active.") - app_state = UIApplication.sharedApplication().applicationState + app_state = application.applicationState if app_state == UIApplicationState.UIApplicationStateActive: for window in App.app.interface.windows: window.on_gain_focus(App.app.interface) diff --git a/winforms/src/toga_winforms/window.py b/winforms/src/toga_winforms/window.py index 9bb869d18d..14366e9cc8 100644 --- a/winforms/src/toga_winforms/window.py +++ b/winforms/src/toga_winforms/window.py @@ -44,11 +44,11 @@ def __init__(self, interface, title, position, size): self.native.FormBorderStyle = self.native.FormBorderStyle.FixedSingle self.native.MaximizeBox = False - self.native.Activated += self.window_on_gain_focus - self.native.Deactivate += self.window_on_lose_focus + self.native.Activated += self.winforms_on_gain_focus + self.native.Deactivate += self.winforms_on_lose_focus - self.native.VisibleChanged += self.window_on_visible_changed - self.native.SizeChanged += self.window_on_size_changed + self.native.VisibleChanged += self.winforms_on_visible_changed + self.native.SizeChanged += self.winforms_on_size_changed def create_toolbar(self): if self.interface.toolbar: @@ -181,13 +181,13 @@ def resize_content(self): self.native.ClientSize.Height - vertical_shift, ) - def window_on_gain_focus(self, sender, event): + def winforms_on_gain_focus(self, sender, event): self.interface.on_gain_focus(self.interface) - def window_on_lose_focus(self, sender, event): + def winforms_on_lose_focus(self, sender, event): self.interface.on_lose_focus(self.interface) - def window_on_visible_changed(self, sender, event): + def winforms_on_visible_changed(self, sender, event): if self.native.Visible and not self._is_previously_shown: self._is_previously_shown = True self.interface.on_show(self.interface) @@ -195,7 +195,7 @@ def window_on_visible_changed(self, sender, event): self._is_previously_shown = False self.interface.on_hide(self.interface) - def window_on_size_changed(self, sender, event): + def winforms_on_size_changed(self, sender, event): if ( self.native.WindowState == WinForms.FormWindowState.Minimized and self._is_previously_shown From eaeaf5dd6aaab67336a810576dc350b2e3ed90da Mon Sep 17 00:00:00 2001 From: proneon267 Date: Thu, 14 Sep 2023 20:58:33 -0700 Subject: [PATCH 21/37] Added comment for android version exclusion. --- android/src/toga_android/app.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/android/src/toga_android/app.py b/android/src/toga_android/app.py index d32fb0512c..d7b093db2b 100644 --- a/android/src/toga_android/app.py +++ b/android/src/toga_android/app.py @@ -40,12 +40,17 @@ def onStart(self): def onResume(self): print("Toga app: onResume") + # onTopResumedActivityChanged is not available on android versions less + # than Q. onResume is the best indicator for the gain input focus event. + # https://developer.android.com/reference/android/app/Activity#onWindowFocusChanged(boolean):~:text=If%20the%20intent,the%20best%20indicator. if Build.VERSION.SDK_INT < Build.VERSION_CODES.Q: for window in self._impl.interface.windows: window.on_gain_focus(self._impl.interface) def onPause(self): print("Toga app: onPause") + # onTopResumedActivityChanged is not available on android versions less + # than Q. onPause is the best indicator for the lost input focus event. if Build.VERSION.SDK_INT < Build.VERSION_CODES.Q: for window in self._impl.interface.windows: window.on_lose_focus(self._impl.interface) From 894a89cfdc6d2d33a062174d2c552af25e402929 Mon Sep 17 00:00:00 2001 From: proneon267 Date: Sat, 18 Nov 2023 02:46:37 -0800 Subject: [PATCH 22/37] Rebasing on the latest main branch --- gtk/src/toga_gtk/window.py | 2 +- winforms/src/toga_winforms/window.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gtk/src/toga_gtk/window.py b/gtk/src/toga_gtk/window.py index 1b27419a1b..2989d21813 100644 --- a/gtk/src/toga_gtk/window.py +++ b/gtk/src/toga_gtk/window.py @@ -223,4 +223,4 @@ def get_image_data(self): return buffer else: # pragma: nocover # This shouldn't ever happen, and it's difficult to manufacture in test conditions - raise ValueError(f"Unable to generate screenshot of {self}") \ No newline at end of file + raise ValueError(f"Unable to generate screenshot of {self}") diff --git a/winforms/src/toga_winforms/window.py b/winforms/src/toga_winforms/window.py index 10a7b2757a..57f0042692 100644 --- a/winforms/src/toga_winforms/window.py +++ b/winforms/src/toga_winforms/window.py @@ -238,4 +238,4 @@ def get_image_data(self): stream = MemoryStream() bitmap.Save(stream, ImageFormat.Png) - return stream.ToArray() \ No newline at end of file + return stream.ToArray() From cb2e54179a7de7734a47de04ef201967663060a0 Mon Sep 17 00:00:00 2001 From: proneon267 Date: Sat, 18 Nov 2023 03:51:59 -0800 Subject: [PATCH 23/37] Miscellaneous Fixes --- winforms/src/toga_winforms/window.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/winforms/src/toga_winforms/window.py b/winforms/src/toga_winforms/window.py index 57f0042692..e3e727579c 100644 --- a/winforms/src/toga_winforms/window.py +++ b/winforms/src/toga_winforms/window.py @@ -41,11 +41,11 @@ def __init__(self, interface, title, position, size): self.set_full_screen(self.interface.full_screen) - self.native.Activated += self.winforms_on_gain_focus - self.native.Deactivate += self.winforms_on_lose_focus + self.native.Activated += WeakrefCallable(self.winforms_on_gain_focus) + self.native.Deactivate += WeakrefCallable(self.winforms_on_lose_focus) - self.native.VisibleChanged += self.winforms_on_visible_changed - self.native.SizeChanged += self.winforms_on_size_changed + self.native.VisibleChanged += WeakrefCallable(self.winforms_on_visible_changed) + self.native.SizeChanged += WeakrefCallable(self.winforms_on_size_changed) def create_toolbar(self): if self.interface.toolbar: From f7a014bd5c76c5b78b08a2a744093907424aeaad Mon Sep 17 00:00:00 2001 From: proneon267 Date: Sat, 18 Nov 2023 12:54:27 -0800 Subject: [PATCH 24/37] Added Core Tests --- core/src/toga/window.py | 1 + core/tests/test_window.py | 52 ++++++++++++++++++++++++++++++++++ dummy/src/toga_dummy/window.py | 12 ++++++++ 3 files changed, 65 insertions(+) diff --git a/core/src/toga/window.py b/core/src/toga/window.py index 73f134fb10..082ce2c02c 100644 --- a/core/src/toga/window.py +++ b/core/src/toga/window.py @@ -346,6 +346,7 @@ def as_image(self) -> Image: @property def on_gain_focus(self) -> callable: + print("--dewwejhjkhjkhjhkj") return self._on_gain_focus @on_gain_focus.setter diff --git a/core/tests/test_window.py b/core/tests/test_window.py index 61a5e1209e..c0313915c2 100644 --- a/core/tests/test_window.py +++ b/core/tests/test_window.py @@ -353,6 +353,58 @@ def test_as_image(window): assert image.data == b"pretend this is PNG image data" +def test_on_gain_focus(window): + assert window._on_gain_focus._raw is None + + on_gain_focus_handler = Mock() + window.on_gain_focus = on_gain_focus_handler + + assert window.on_gain_focus._raw == on_gain_focus_handler + + window._impl.simulate_on_gain_focus() + + on_gain_focus_handler.assert_called_once_with(window) + + +def test_on_lose_focus(window): + assert window.on_lose_focus._raw is None + + on_lose_focus_handler = Mock() + window.on_lose_focus = on_lose_focus_handler + + assert window.on_lose_focus._raw == on_lose_focus_handler + + window._impl.simulate_on_lose_focus() + + on_lose_focus_handler.assert_called_once_with(window) + + +def test_on_show(window): + assert window.on_show._raw is None + + on_show_handler = Mock() + window.on_show = on_show_handler + + assert window.on_show._raw == on_show_handler + + window._impl.simulate_on_show() + + on_show_handler.assert_called_once_with(window) + + +def test_on_hide(window): + assert window.on_hide._raw is None + + on_hide_handler = Mock() + window.on_hide = on_hide_handler + + assert window.on_hide._raw == on_hide_handler + + window._impl.simulate_on_hide() + + on_hide_handler.assert_called_once_with(window) + + def test_info_dialog(window, app): """An info dialog can be shown""" on_result_handler = Mock() diff --git a/dummy/src/toga_dummy/window.py b/dummy/src/toga_dummy/window.py index 7179239f79..5147709e04 100644 --- a/dummy/src/toga_dummy/window.py +++ b/dummy/src/toga_dummy/window.py @@ -99,3 +99,15 @@ def set_full_screen(self, is_full_screen): def simulate_close(self): self.interface.on_close() + + def simulate_on_gain_focus(self): + self.interface.on_gain_focus() + + def simulate_on_lose_focus(self): + self.interface.on_lose_focus() + + def simulate_on_show(self): + self.interface.on_show() + + def simulate_on_hide(self): + self.interface.on_hide() From 0a1d09313bcf1a605cadd01564b91534410778f7 Mon Sep 17 00:00:00 2001 From: proneon267 Date: Sat, 18 Nov 2023 13:02:04 -0800 Subject: [PATCH 25/37] Miscellaneous Fixes --- core/src/toga/window.py | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/toga/window.py b/core/src/toga/window.py index 082ce2c02c..73f134fb10 100644 --- a/core/src/toga/window.py +++ b/core/src/toga/window.py @@ -346,7 +346,6 @@ def as_image(self) -> Image: @property def on_gain_focus(self) -> callable: - print("--dewwejhjkhjkhjhkj") return self._on_gain_focus @on_gain_focus.setter From c441e77603307fcc1d5095fa31594564a5dbcf6b Mon Sep 17 00:00:00 2001 From: proneon267 Date: Sat, 18 Nov 2023 21:47:19 -0800 Subject: [PATCH 26/37] Added tests for windows testbed --- testbed/tests/test_window.py | 54 ++++++++++++++++++++++++++++ winforms/src/toga_winforms/window.py | 16 ++++----- 2 files changed, 62 insertions(+), 8 deletions(-) diff --git a/testbed/tests/test_window.py b/testbed/tests/test_window.py index 15f5db29ea..2858e37182 100644 --- a/testbed/tests/test_window.py +++ b/testbed/tests/test_window.py @@ -487,6 +487,60 @@ async def test_as_image(main_window, main_window_probe): main_window_probe.assert_image_size(screenshot.size, main_window_probe.content_size) +@pytest.mark.parametrize( + "second_window_kwargs", + [dict(title="Secondary Window", position=(200, 150))], +) +async def test_on_gain_focus( + main_window, main_window_probe, second_window, second_window_probe +): + on_gain_focus_handler = Mock() + main_window.on_gain_focus = on_gain_focus_handler + main_window.app.current_window = second_window + await second_window_probe.wait_for_window("Setting second window as current window") + main_window.app.current_window = main_window + await main_window_probe.wait_for_window("Setting main window as current window") + on_gain_focus_handler.assert_called_once_with(main_window) + + +@pytest.mark.parametrize( + "second_window_kwargs", + [dict(title="Secondary Window", position=(200, 150))], +) +async def test_on_lose_focus( + main_window, main_window_probe, second_window, second_window_probe +): + on_lose_focus_handler = Mock() + main_window.on_lose_focus = on_lose_focus_handler + main_window.app.current_window = main_window + await main_window_probe.wait_for_window("Setting main window as current window") + second_window.app.current_window = second_window + await second_window_probe.wait_for_window("Setting second window as current window") + on_lose_focus_handler.assert_called_once_with(main_window) + main_window.app.current_window = main_window + await main_window_probe.wait_for_window("Setting main window as current window") + + +async def test_on_show(main_window, main_window_probe): + on_show_handler = Mock() + main_window.on_show = on_show_handler + main_window.hide() + await main_window_probe.wait_for_window("Hiding the MainWindow") + main_window.show() + await main_window_probe.wait_for_window("Showing the MainWindow") + on_show_handler.assert_called_once_with(main_window) + + +async def test_on_hide(main_window, main_window_probe): + on_hide_handler = Mock() + main_window.on_hide = on_hide_handler + main_window.hide() + await main_window_probe.wait_for_window("Hiding the MainWindow") + on_hide_handler.assert_called_once_with(main_window) + main_window.show() + await main_window_probe.wait_for_window("Showing the MainWindow") + + ######################################################################################## # Dialog tests ######################################################################################## diff --git a/winforms/src/toga_winforms/window.py b/winforms/src/toga_winforms/window.py index e3e727579c..4fd6ac723f 100644 --- a/winforms/src/toga_winforms/window.py +++ b/winforms/src/toga_winforms/window.py @@ -190,40 +190,40 @@ def resize_content(self): ) def winforms_on_gain_focus(self, sender, event): - self.interface.on_gain_focus(self.interface) + self.interface.on_gain_focus() def winforms_on_lose_focus(self, sender, event): - self.interface.on_lose_focus(self.interface) + self.interface.on_lose_focus() def winforms_on_visible_changed(self, sender, event): if self.native.Visible and not self._is_previously_shown: self._is_previously_shown = True - self.interface.on_show(self.interface) + self.interface.on_show() else: self._is_previously_shown = False - self.interface.on_hide(self.interface) + self.interface.on_hide() - def winforms_on_size_changed(self, sender, event): + def winforms_on_size_changed(self, sender, event): # pragma: no cover if ( self.native.WindowState == WinForms.FormWindowState.Minimized and self._is_previously_shown ): self._is_previously_shown = False - self.interface.on_hide(self.interface) + self.interface.on_hide() elif ( self.native.WindowState == WinForms.FormWindowState.Maximized and not self._is_previously_shown ): self._is_previously_shown = True - self.interface.on_show(self.interface) + self.interface.on_show() elif ( self.native.WindowState == WinForms.FormWindowState.Normal and not self._is_previously_shown ): self._is_previously_shown = True - self.interface.on_show(self.interface) + self.interface.on_show() def get_image_data(self): size = Size(self.native_content.Size.Width, self.native_content.Size.Height) From 059ea1438a827c72b3427a4ec41b7f7b8a4087e2 Mon Sep 17 00:00:00 2001 From: proneon267 Date: Sat, 18 Nov 2023 22:11:23 -0800 Subject: [PATCH 27/37] Fixed event triggers on gtk, cocoa --- cocoa/src/toga_cocoa/window.py | 18 +++++----- gtk/src/toga_gtk/window.py | 49 +++++++++++----------------- winforms/src/toga_winforms/window.py | 11 ++----- 3 files changed, 30 insertions(+), 48 deletions(-) diff --git a/cocoa/src/toga_cocoa/window.py b/cocoa/src/toga_cocoa/window.py index 4bdb5b8e93..52ea061b06 100644 --- a/cocoa/src/toga_cocoa/window.py +++ b/cocoa/src/toga_cocoa/window.py @@ -42,55 +42,55 @@ def windowDidResize_(self, notification) -> None: @objc_method def windowDidBecomeMain_(self, notification): - self.impl.interface.on_gain_focus(self.interface) + self.impl.interface.on_gain_focus() @objc_method def windowDidResignMain_(self, notification): - self.impl.interface.on_lose_focus(self.interface) + self.impl.interface.on_lose_focus() @objc_method def windowDidBecomeKey_(self, notification): if bool(self.impl.native.isVisible) and not self.impl._is_previously_shown: self.impl._is_previously_shown = True - self.impl.interface.on_show(self.interface) + self.impl.interface.on_show() @objc_method def windowDidMiniaturize_(self, notification): if not bool(self.impl.native.isVisible) and self.impl._is_previously_shown: self.impl._is_previously_shown = False - self.impl.interface.on_hide(self.interface) + self.impl.interface.on_hide() @objc_method def windowDidDeminiaturize_(self, notification): if bool(self.impl.native.isVisible) and not self.impl._is_previously_shown: self.impl._is_previously_shown = True - self.impl.interface.on_show(self.interface) + self.impl.interface.on_show() @objc_method def windowDidEnterFullScreen_(self, notification): if bool(self.impl.native.isVisible) and not self.impl._is_previously_shown: self.impl._is_previously_shown = True - self.impl.interface.on_show(self.interface) + self.impl.interface.on_show() @objc_method def windowDidExitFullScreen_(self, notification): if bool(self.impl.native.isVisible) and not self.impl._is_previously_shown: self.impl._is_previously_shown = True - self.impl.interface.on_show(self.interface) + self.impl.interface.on_show() # when the user clicks the zoom button to unzoom a window @objc_method def windowWillUseStandardFrame_defaultFrame_(self, window, defaultFrame): if bool(self.impl.native.isVisible) and not self.impl._is_previously_shown: self.impl._is_previously_shown = True - self.impl.interface.on_show(self.interface) + self.impl.interface.on_show() # when the user clicks the zoom button to zoom a window @objc_method def windowShouldZoom_toFrame_(self, window, toFrame): if bool(self.impl.native.isVisible) and not self.impl._is_previously_shown: self.impl._is_previously_shown = True - self.impl.interface.on_show(self.interface) + self.impl.interface.on_show() ###################################################################### # Toolbar delegate methods diff --git a/gtk/src/toga_gtk/window.py b/gtk/src/toga_gtk/window.py index 2989d21813..cbfb793843 100644 --- a/gtk/src/toga_gtk/window.py +++ b/gtk/src/toga_gtk/window.py @@ -155,46 +155,35 @@ def set_full_screen(self, is_full_screen): self.native.unfullscreen() def window_on_gain_focus(self, sender, event): - self.interface.on_gain_focus(self.interface) + self.interface.on_gain_focus() def window_on_lose_focus(self, sender, event): - self.interface.on_lose_focus(self.interface) + self.interface.on_lose_focus() def window_on_state_changed(self, sender, event): - if ( - event.new_window_state & Gdk.WindowState.WITHDRAWN - and self._is_previously_shown - ): - self._is_previously_shown = False - self.interface.on_hide(self.interface) + hide_conditions = ( + Gdk.WindowState.WITHDRAWN, + Gdk.WindowState.ICONIFIED, + ) + show_conditions = ( + Gdk.WindowState.MAXIMIZED, + Gdk.WindowState.FULLSCREEN, + Gdk.WindowState.FOCUSED, + ) - elif ( - event.new_window_state & Gdk.WindowState.ICONIFIED - and self._is_previously_shown + if any( + event.new_window_state & state and self._is_previously_shown + for state in hide_conditions ): self._is_previously_shown = False - self.interface.on_hide(self.interface) - - elif ( - event.new_window_state & Gdk.WindowState.MAXIMIZED - and not self._is_previously_shown - ): - self._is_previously_shown = True - self.interface.on_show(self.interface) - - elif ( - event.new_window_state & Gdk.WindowState.FULLSCREEN - and not self._is_previously_shown - ): - self._is_previously_shown = True - self.interface.on_show(self.interface) + self.interface.on_hide() - elif ( - event.new_window_state & Gdk.WindowState.FOCUSED - and not self._is_previously_shown + elif any( + event.new_window_state & state and not self._is_previously_shown + for state in show_conditions ): self._is_previously_shown = True - self.interface.on_show(self.interface) + self.interface.on_show() def get_image_data(self): display = self.native.get_display() diff --git a/winforms/src/toga_winforms/window.py b/winforms/src/toga_winforms/window.py index 4fd6ac723f..7ddc7a61f8 100644 --- a/winforms/src/toga_winforms/window.py +++ b/winforms/src/toga_winforms/window.py @@ -210,16 +210,9 @@ def winforms_on_size_changed(self, sender, event): # pragma: no cover ): self._is_previously_shown = False self.interface.on_hide() - - elif ( - self.native.WindowState == WinForms.FormWindowState.Maximized - and not self._is_previously_shown - ): - self._is_previously_shown = True - self.interface.on_show() - elif ( - self.native.WindowState == WinForms.FormWindowState.Normal + self.native.WindowState + in (WinForms.FormWindowState.Maximized, WinForms.FormWindowState.Normal) and not self._is_previously_shown ): self._is_previously_shown = True From 81dd316682b1468b7e5a6fc42fd3f109f0082a48 Mon Sep 17 00:00:00 2001 From: proneon267 Date: Sat, 18 Nov 2023 22:34:03 -0800 Subject: [PATCH 28/37] Fixed event triggers on Android --- android/src/toga_android/app.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/android/src/toga_android/app.py b/android/src/toga_android/app.py index 9db5a682d6..1c629286a4 100644 --- a/android/src/toga_android/app.py +++ b/android/src/toga_android/app.py @@ -38,7 +38,7 @@ def onCreate(self): def onStart(self): print("Toga app: onStart") for window in self._impl.interface.windows: - window.on_show(self._impl.interface) + window.on_show() def onResume(self): print("Toga app: onResume") @@ -47,7 +47,7 @@ def onResume(self): # https://developer.android.com/reference/android/app/Activity#onWindowFocusChanged(boolean):~:text=If%20the%20intent,the%20best%20indicator. if Build.VERSION.SDK_INT < Build.VERSION_CODES.Q: for window in self._impl.interface.windows: - window.on_gain_focus(self._impl.interface) + window.on_gain_focus() def onPause(self): print("Toga app: onPause") # pragma: no cover @@ -55,12 +55,12 @@ def onPause(self): # than Q. onPause is the best indicator for the lost input focus event. if Build.VERSION.SDK_INT < Build.VERSION_CODES.Q: for window in self._impl.interface.windows: - window.on_lose_focus(self._impl.interface) + window.on_lose_focus() def onStop(self): print("Toga app: onStop") # pragma: no cover for window in self._impl.interface.windows: - window.on_hide(self._impl.interface) + window.on_hide() def onDestroy(self): print("Toga app: onDestroy") # pragma: no cover @@ -72,10 +72,10 @@ def onTopResumedActivityChanged(self, isTopResumedActivity): print("Toga app: onTopResumedActivityChanged") # pragma: no cover if isTopResumedActivity: for window in self._impl.interface.windows: - window.on_gain_focus(self._impl.interface) + window.on_gain_focus() else: for window in self._impl.interface.windows: - window.on_lose_focus(self._impl.interface) + window.on_lose_focus() # TODO #1798: document and test this somehow def onActivityResult(self, requestCode, resultCode, resultData): # pragma: no cover From d94a69aad3cb63d7ff1095d3b41a37d61c67a7b4 Mon Sep 17 00:00:00 2001 From: proneon267 Date: Sun, 19 Nov 2023 01:52:55 -0800 Subject: [PATCH 29/37] Fixed cocoa implementation --- cocoa/src/toga_cocoa/window.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/cocoa/src/toga_cocoa/window.py b/cocoa/src/toga_cocoa/window.py index 52ea061b06..a2912fb67f 100644 --- a/cocoa/src/toga_cocoa/window.py +++ b/cocoa/src/toga_cocoa/window.py @@ -55,39 +55,41 @@ def windowDidBecomeKey_(self, notification): self.impl.interface.on_show() @objc_method - def windowDidMiniaturize_(self, notification): + def windowDidMiniaturize_(self, notification): # pragma: no cover if not bool(self.impl.native.isVisible) and self.impl._is_previously_shown: self.impl._is_previously_shown = False self.impl.interface.on_hide() @objc_method - def windowDidDeminiaturize_(self, notification): + def windowDidDeminiaturize_(self, notification): # pragma: no cover if bool(self.impl.native.isVisible) and not self.impl._is_previously_shown: self.impl._is_previously_shown = True self.impl.interface.on_show() @objc_method - def windowDidEnterFullScreen_(self, notification): + def windowDidEnterFullScreen_(self, notification): # pragma: no cover if bool(self.impl.native.isVisible) and not self.impl._is_previously_shown: self.impl._is_previously_shown = True self.impl.interface.on_show() @objc_method - def windowDidExitFullScreen_(self, notification): + def windowDidExitFullScreen_(self, notification): # pragma: no cover if bool(self.impl.native.isVisible) and not self.impl._is_previously_shown: self.impl._is_previously_shown = True self.impl.interface.on_show() # when the user clicks the zoom button to unzoom a window @objc_method - def windowWillUseStandardFrame_defaultFrame_(self, window, defaultFrame): + def windowWillUseStandardFrame_defaultFrame_( + self, window, defaultFrame + ): # pragma: no cover if bool(self.impl.native.isVisible) and not self.impl._is_previously_shown: self.impl._is_previously_shown = True self.impl.interface.on_show() # when the user clicks the zoom button to zoom a window @objc_method - def windowShouldZoom_toFrame_(self, window, toFrame): + def windowShouldZoom_toFrame_(self, window, toFrame): # pragma: no cover if bool(self.impl.native.isVisible) and not self.impl._is_previously_shown: self.impl._is_previously_shown = True self.impl.interface.on_show() @@ -332,9 +334,11 @@ def set_app(self, app): def show(self): self.native.makeKeyAndOrderFront(None) + self.interface.on_show() def hide(self): self.native.orderOut(self.native) + self.interface.on_hide() def get_visible(self): return bool(self.native.isVisible) From c0405c33ac2fff6fe4c072213193e771d1caf0fc Mon Sep 17 00:00:00 2001 From: proneon267 Date: Sun, 19 Nov 2023 12:38:39 -0800 Subject: [PATCH 30/37] Miscellaneous Fixes --- android/src/toga_android/app.py | 14 ++--- iOS/src/toga_iOS/app.py | 10 +-- testbed/tests/test_window.py | 108 ++++++++++++++++---------------- 3 files changed, 66 insertions(+), 66 deletions(-) diff --git a/android/src/toga_android/app.py b/android/src/toga_android/app.py index 1c629286a4..6406fb03fe 100644 --- a/android/src/toga_android/app.py +++ b/android/src/toga_android/app.py @@ -40,7 +40,7 @@ def onStart(self): for window in self._impl.interface.windows: window.on_show() - def onResume(self): + def onResume(self): # pragma: no cover print("Toga app: onResume") # onTopResumedActivityChanged is not available on android versions less # than Q. onResume is the best indicator for the gain input focus event. @@ -49,16 +49,16 @@ def onResume(self): for window in self._impl.interface.windows: window.on_gain_focus() - def onPause(self): - print("Toga app: onPause") # pragma: no cover + def onPause(self): # pragma: no cover + print("Toga app: onPause") # onTopResumedActivityChanged is not available on android versions less # than Q. onPause is the best indicator for the lost input focus event. if Build.VERSION.SDK_INT < Build.VERSION_CODES.Q: for window in self._impl.interface.windows: window.on_lose_focus() - def onStop(self): - print("Toga app: onStop") # pragma: no cover + def onStop(self): # pragma: no cover + print("Toga app: onStop") for window in self._impl.interface.windows: window.on_hide() @@ -68,8 +68,8 @@ def onDestroy(self): def onRestart(self): print("Toga app: onRestart") # pragma: no cover - def onTopResumedActivityChanged(self, isTopResumedActivity): - print("Toga app: onTopResumedActivityChanged") # pragma: no cover + def onTopResumedActivityChanged(self, isTopResumedActivity): # pragma: no cover + print("Toga app: onTopResumedActivityChanged") if isTopResumedActivity: for window in self._impl.interface.windows: window.on_gain_focus() diff --git a/iOS/src/toga_iOS/app.py b/iOS/src/toga_iOS/app.py index a7c45e27d6..24c55c9a9e 100644 --- a/iOS/src/toga_iOS/app.py +++ b/iOS/src/toga_iOS/app.py @@ -18,28 +18,28 @@ def applicationDidBecomeActive_(self, application) -> None: app_state = application.applicationState if app_state == UIApplicationState.UIApplicationStateActive: for window in App.app.interface.windows: - window.on_gain_focus(App.app.interface) + window.on_gain_focus() else: for window in App.app.interface.windows: - window.on_lose_focus(App.app.interface) + window.on_lose_focus() @objc_method def applicationWillResignActive_(self, application) -> None: print("App about to leave foreground.", flush=True) for window in App.app.interface.windows: - window.on_lose_focus(App.app.interface) + window.on_lose_focus() @objc_method def applicationDidEnterBackground_(self, application) -> None: print("App entered background.") for window in App.app.interface.windows: - window.on_hide(App.app.interface) + window.on_hide() @objc_method def applicationWillEnterForeground_(self, application) -> None: print("App about to enter foreground.") for window in App.app.interface.windows: - window.on_show(App.app.interface) + window.on_show() @objc_method def application_didFinishLaunchingWithOptions_( diff --git a/testbed/tests/test_window.py b/testbed/tests/test_window.py index 2858e37182..5880637812 100644 --- a/testbed/tests/test_window.py +++ b/testbed/tests/test_window.py @@ -479,6 +479,60 @@ async def test_full_screen(second_window, second_window_probe): assert not second_window_probe.is_full_screen assert second_window_probe.content_size == initial_content_size + @pytest.mark.parametrize( + "second_window_kwargs", + [dict(title="Secondary Window", position=(200, 150))], + ) + async def test_on_gain_focus( + main_window, main_window_probe, second_window, second_window_probe + ): + on_gain_focus_handler = Mock() + main_window.on_gain_focus = on_gain_focus_handler + main_window.app.current_window = second_window + await second_window_probe.wait_for_window( + "Setting second window as current window" + ) + main_window.app.current_window = main_window + await main_window_probe.wait_for_window("Setting main window as current window") + on_gain_focus_handler.assert_called_once_with(main_window) + + @pytest.mark.parametrize( + "second_window_kwargs", + [dict(title="Secondary Window", position=(200, 150))], + ) + async def test_on_lose_focus( + main_window, main_window_probe, second_window, second_window_probe + ): + on_lose_focus_handler = Mock() + main_window.on_lose_focus = on_lose_focus_handler + main_window.app.current_window = main_window + await main_window_probe.wait_for_window("Setting main window as current window") + second_window.app.current_window = second_window + await second_window_probe.wait_for_window( + "Setting second window as current window" + ) + on_lose_focus_handler.assert_called_once_with(main_window) + main_window.app.current_window = main_window + await main_window_probe.wait_for_window("Setting main window as current window") + + async def test_on_show(main_window, main_window_probe): + on_show_handler = Mock() + main_window.on_show = on_show_handler + main_window.hide() + await main_window_probe.wait_for_window("Hiding the MainWindow") + main_window.show() + await main_window_probe.wait_for_window("Showing the MainWindow") + on_show_handler.assert_called_once_with(main_window) + + async def test_on_hide(main_window, main_window_probe): + on_hide_handler = Mock() + main_window.on_hide = on_hide_handler + main_window.hide() + await main_window_probe.wait_for_window("Hiding the MainWindow") + on_hide_handler.assert_called_once_with(main_window) + main_window.show() + await main_window_probe.wait_for_window("Showing the MainWindow") + async def test_as_image(main_window, main_window_probe): """The window can be captured as a screenshot""" @@ -487,60 +541,6 @@ async def test_as_image(main_window, main_window_probe): main_window_probe.assert_image_size(screenshot.size, main_window_probe.content_size) -@pytest.mark.parametrize( - "second_window_kwargs", - [dict(title="Secondary Window", position=(200, 150))], -) -async def test_on_gain_focus( - main_window, main_window_probe, second_window, second_window_probe -): - on_gain_focus_handler = Mock() - main_window.on_gain_focus = on_gain_focus_handler - main_window.app.current_window = second_window - await second_window_probe.wait_for_window("Setting second window as current window") - main_window.app.current_window = main_window - await main_window_probe.wait_for_window("Setting main window as current window") - on_gain_focus_handler.assert_called_once_with(main_window) - - -@pytest.mark.parametrize( - "second_window_kwargs", - [dict(title="Secondary Window", position=(200, 150))], -) -async def test_on_lose_focus( - main_window, main_window_probe, second_window, second_window_probe -): - on_lose_focus_handler = Mock() - main_window.on_lose_focus = on_lose_focus_handler - main_window.app.current_window = main_window - await main_window_probe.wait_for_window("Setting main window as current window") - second_window.app.current_window = second_window - await second_window_probe.wait_for_window("Setting second window as current window") - on_lose_focus_handler.assert_called_once_with(main_window) - main_window.app.current_window = main_window - await main_window_probe.wait_for_window("Setting main window as current window") - - -async def test_on_show(main_window, main_window_probe): - on_show_handler = Mock() - main_window.on_show = on_show_handler - main_window.hide() - await main_window_probe.wait_for_window("Hiding the MainWindow") - main_window.show() - await main_window_probe.wait_for_window("Showing the MainWindow") - on_show_handler.assert_called_once_with(main_window) - - -async def test_on_hide(main_window, main_window_probe): - on_hide_handler = Mock() - main_window.on_hide = on_hide_handler - main_window.hide() - await main_window_probe.wait_for_window("Hiding the MainWindow") - on_hide_handler.assert_called_once_with(main_window) - main_window.show() - await main_window_probe.wait_for_window("Showing the MainWindow") - - ######################################################################################## # Dialog tests ######################################################################################## From 927b3d4693338990178c095fb0372c8cd1ed4df3 Mon Sep 17 00:00:00 2001 From: proneon267 Date: Sun, 19 Nov 2023 05:41:50 -0800 Subject: [PATCH 31/37] Fixed iOS implementation --- iOS/src/toga_iOS/app.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/iOS/src/toga_iOS/app.py b/iOS/src/toga_iOS/app.py index 24c55c9a9e..ba6cdcdc48 100644 --- a/iOS/src/toga_iOS/app.py +++ b/iOS/src/toga_iOS/app.py @@ -3,7 +3,7 @@ from rubicon.objc import objc_method from rubicon.objc.eventloop import EventLoopPolicy, iOSLifecycle -from toga_iOS.libs import UIApplicationState, UIResponder, av_foundation +from toga_iOS.libs import UIResponder, av_foundation from toga_iOS.window import Window @@ -15,13 +15,8 @@ class PythonAppDelegate(UIResponder): @objc_method def applicationDidBecomeActive_(self, application) -> None: print("App became active.") - app_state = application.applicationState - if app_state == UIApplicationState.UIApplicationStateActive: - for window in App.app.interface.windows: - window.on_gain_focus() - else: - for window in App.app.interface.windows: - window.on_lose_focus() + for window in App.app.interface.windows: + window.on_gain_focus() @objc_method def applicationWillResignActive_(self, application) -> None: @@ -48,6 +43,8 @@ def application_didFinishLaunchingWithOptions_( print("App finished launching.") App.app.native = application App.app.create() + for window in App.app.interface.windows: + window.on_show() return True @objc_method From 8c364a1b772c79cbc35ed060265de32d90a19cfc Mon Sep 17 00:00:00 2001 From: proneon267 Date: Sun, 19 Nov 2023 07:21:37 -0800 Subject: [PATCH 32/37] Empty commit for CI From 869da6a41b0b11c867f4ef2bef58381e60ae5347 Mon Sep 17 00:00:00 2001 From: proneon267 Date: Mon, 20 Nov 2023 04:33:20 -0800 Subject: [PATCH 33/37] Miscellaneous Fixes --- iOS/src/toga_iOS/libs/uikit.py | 6 ---- testbed/tests/test_window.py | 54 ++++++++++++++-------------------- 2 files changed, 22 insertions(+), 38 deletions(-) diff --git a/iOS/src/toga_iOS/libs/uikit.py b/iOS/src/toga_iOS/libs/uikit.py index fabfedf307..134ad5dec3 100644 --- a/iOS/src/toga_iOS/libs/uikit.py +++ b/iOS/src/toga_iOS/libs/uikit.py @@ -147,12 +147,6 @@ class UIInterfaceOrientation(Enum): LandscapeRight = 4 -class UIApplicationState(Enum): - UIApplicationStateActive = 0 - UIApplicationStateInactive = 1 - UIApplicationStateBackground = 2 - - ###################################################################### # UIBarButtonItem.h UIBarButtonItem = ObjCClass("UIBarButtonItem") diff --git a/testbed/tests/test_window.py b/testbed/tests/test_window.py index 5880637812..ce48f42c72 100644 --- a/testbed/tests/test_window.py +++ b/testbed/tests/test_window.py @@ -483,55 +483,45 @@ async def test_full_screen(second_window, second_window_probe): "second_window_kwargs", [dict(title="Secondary Window", position=(200, 150))], ) - async def test_on_gain_focus( + async def test_focus_events( main_window, main_window_probe, second_window, second_window_probe ): - on_gain_focus_handler = Mock() - main_window.on_gain_focus = on_gain_focus_handler - main_window.app.current_window = second_window - await second_window_probe.wait_for_window( - "Setting second window as current window" - ) - main_window.app.current_window = main_window - await main_window_probe.wait_for_window("Setting main window as current window") - on_gain_focus_handler.assert_called_once_with(main_window) + main_window_on_gain_focus_handler = Mock() + main_window_on_lose_focus_handler = Mock() + main_window.on_gain_focus = main_window_on_gain_focus_handler + main_window.on_lose_focus = main_window_on_lose_focus_handler + + second_window_on_gain_focus_handler = Mock() + second_window_on_lose_focus_handler = Mock() + second_window.on_gain_focus = second_window_on_gain_focus_handler + second_window.on_lose_focus = second_window_on_lose_focus_handler - @pytest.mark.parametrize( - "second_window_kwargs", - [dict(title="Secondary Window", position=(200, 150))], - ) - async def test_on_lose_focus( - main_window, main_window_probe, second_window, second_window_probe - ): - on_lose_focus_handler = Mock() - main_window.on_lose_focus = on_lose_focus_handler main_window.app.current_window = main_window await main_window_probe.wait_for_window("Setting main window as current window") - second_window.app.current_window = second_window + main_window_on_gain_focus_handler.assert_called_once_with(main_window) + second_window_on_lose_focus_handler.assert_called_once_with(second_window) + + main_window.app.current_window = second_window await second_window_probe.wait_for_window( "Setting second window as current window" ) - on_lose_focus_handler.assert_called_once_with(main_window) - main_window.app.current_window = main_window - await main_window_probe.wait_for_window("Setting main window as current window") + assert main_window.app.current_window == second_window + main_window_on_lose_focus_handler.assert_called_once_with(main_window) + second_window_on_gain_focus_handler.assert_called_once_with(second_window) - async def test_on_show(main_window, main_window_probe): + async def test_visibility_events(main_window, main_window_probe): on_show_handler = Mock() - main_window.on_show = on_show_handler - main_window.hide() - await main_window_probe.wait_for_window("Hiding the MainWindow") - main_window.show() - await main_window_probe.wait_for_window("Showing the MainWindow") - on_show_handler.assert_called_once_with(main_window) - - async def test_on_hide(main_window, main_window_probe): on_hide_handler = Mock() + main_window.on_show = on_show_handler main_window.on_hide = on_hide_handler + main_window.hide() await main_window_probe.wait_for_window("Hiding the MainWindow") on_hide_handler.assert_called_once_with(main_window) + main_window.show() await main_window_probe.wait_for_window("Showing the MainWindow") + on_show_handler.assert_called_once_with(main_window) async def test_as_image(main_window, main_window_probe): From 24235482e8fdac7f7d00b9f312d940538e50d964 Mon Sep 17 00:00:00 2001 From: proneon267 Date: Mon, 20 Nov 2023 05:09:15 -0800 Subject: [PATCH 34/37] Miscellaneous Fixes --- core/src/toga/window.py | 56 ++++++++++++++++++++++++++++++++--- examples/window/window/app.py | 8 ++--- 2 files changed, 56 insertions(+), 8 deletions(-) diff --git a/core/src/toga/window.py b/core/src/toga/window.py index 73f134fb10..64474fbffe 100644 --- a/core/src/toga/window.py +++ b/core/src/toga/window.py @@ -33,6 +33,54 @@ def __call__(self, window: Window, **kwargs: Any) -> bool: ... +class OnGainFocusHandler(Protocol): + def __call__(self, window: Window, **kwargs: Any) -> None: + """A handler to invoke when a window gains input focus. + + :param window: The window instance that gains input focus. + :param kwargs: Ensures compatibility with additional arguments introduced in + future versions. + """ + ... + + +class OnLoseFocusHandler(Protocol): + def __call__(self, window: Window, **kwargs: Any) -> None: + """A handler to invoke when a window loses input focus. + + :param window: The window instance that loses input focus. + :param kwargs: Ensures compatibility with additional arguments introduced in + future ver + """ + ... + + +class OnShowHandler(Protocol): + def __call__(self, window: Window, **kwargs: Any) -> None: + """A handler to invoke when a window becomes visible to the user from a not visible state. + + Not visible to the user refers to window states like minimized, hidden, etc. + + :param window: The window instance that becomes visible. + :param kwargs: Ensures compatibility with additional arguments introduced in + future ver + """ + ... + + +class OnHideHandler(Protocol): + def __call__(self, window: Window, **kwargs: Any) -> None: + """A handler to invoke when a window becomes not visible to the user. + + Not visible to the user refers to window states like minimized, hidden, etc. + + :param window: The window instance that becomes not visible to the user. + :param kwargs: Ensures compatibility with additional arguments introduced in + future ver + """ + ... + + T = TypeVar("T") @@ -70,10 +118,10 @@ def __init__( closable: bool = True, minimizable: bool = True, on_close: OnCloseHandler | None = None, - on_gain_focus: callable | None = None, - on_lose_focus: callable | None = None, - on_show: callable | None = None, - on_hide: callable | None = None, + on_gain_focus: OnGainFocusHandler | None = None, + on_lose_focus: OnLoseFocusHandler | None = None, + on_show: OnShowHandler | None = None, + on_hide: OnHideHandler | None = None, resizeable=None, # DEPRECATED closeable=None, # DEPRECATED ) -> None: diff --git a/examples/window/window/app.py b/examples/window/window/app.py index a7dc2a4d11..19258c79c4 100644 --- a/examples/window/window/app.py +++ b/examples/window/window/app.py @@ -122,19 +122,19 @@ def close_handler(self, window, **kwargs): return False return True - def on_window_gain_focus(self, widget, **kwargs): + def on_window_gain_focus(self, window, **kwargs): self.window_focus_label.text = "MainWindow is in focus" print("MainWindow is in focus") - def on_window_lose_focus(self, widget, **kwargs): + def on_window_lose_focus(self, window, **kwargs): self.window_focus_label.text = "MainWindow is not in focus" print("MainWindow is not in focus") - def on_window_show(self, widget, **kwargs): + def on_window_show(self, window, **kwargs): self.window_visible_label.text = "MainWindow is visible" print("MainWindow is visible") - def on_window_hide(self, widget, **kwargs): + def on_window_hide(self, window, **kwargs): self.window_visible_label.text = "MainWindow is not visible" print("MainWindow is not visible") From 9fd0e0ed926b0d5225abb29aab9e6aa881e04010 Mon Sep 17 00:00:00 2001 From: proneon267 <45512885+proneon267@users.noreply.github.com> Date: Mon, 20 Nov 2023 05:10:25 -0800 Subject: [PATCH 35/37] Update changes/2009.feature.rst Co-authored-by: Russell Keith-Magee --- changes/2009.feature.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changes/2009.feature.rst b/changes/2009.feature.rst index d07b9ca03b..70ce61bca7 100644 --- a/changes/2009.feature.rst +++ b/changes/2009.feature.rst @@ -1 +1 @@ -Added `on_gain_focus` and `on_lose_focus` handlers both on the `toga.App` and `toga.Window`. +App and Window can now respond to changes in focus. From 77b8822d52df043c45a2fd88f390246cd4df9e27 Mon Sep 17 00:00:00 2001 From: proneon267 Date: Mon, 20 Nov 2023 05:29:44 -0800 Subject: [PATCH 36/37] Miscellaneous Fixes --- core/src/toga/app.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/core/src/toga/app.py b/core/src/toga/app.py index cc8af89544..18d90c0cfa 100644 --- a/core/src/toga/app.py +++ b/core/src/toga/app.py @@ -139,10 +139,6 @@ def __init__( size: tuple[int, int] = (640, 480), resizable: bool = True, minimizable: bool = True, - on_gain_focus: callable | None = None, - on_lose_focus: callable | None = None, - on_show: callable | None = None, - on_hide: callable | None = None, resizeable=None, # DEPRECATED closeable=None, # DEPRECATED ): @@ -168,10 +164,6 @@ def __init__( resizable=resizable, closable=True, minimizable=minimizable, - on_gain_focus=on_gain_focus, - on_lose_focus=on_lose_focus, - on_show=on_show, - on_hide=on_hide, # Deprecated arguments resizeable=resizeable, closeable=closeable, From 69f90d5fba2fb60a8b24fa69eb6586aa82c2e3ae Mon Sep 17 00:00:00 2001 From: proneon267 Date: Mon, 20 Nov 2023 05:31:10 -0800 Subject: [PATCH 37/37] Miscellaneous Fixes --- changes/2009.feature.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changes/2009.feature.rst b/changes/2009.feature.rst index 70ce61bca7..a02380091b 100644 --- a/changes/2009.feature.rst +++ b/changes/2009.feature.rst @@ -1 +1 @@ -App and Window can now respond to changes in focus. +Window can now respond to changes in focus and visibility.