From bbceb8c2426dd7d6a7d37592270baf5dd5aa40c6 Mon Sep 17 00:00:00 2001 From: proneon267 Date: Fri, 26 Jan 2024 04:22:46 -0800 Subject: [PATCH 1/9] Added on_resize handler on oga.Window --- android/src/toga_android/window.py | 1 + cocoa/src/toga_cocoa/window.py | 1 + core/src/toga/window.py | 22 ++++++++++++++++++++++ core/tests/test_window.py | 13 +++++++++++++ dummy/src/toga_dummy/window.py | 3 +++ gtk/src/toga_gtk/window.py | 4 ++++ iOS/src/toga_iOS/app.py | 2 ++ testbed/tests/test_window.py | 13 +++++++++++++ winforms/src/toga_winforms/window.py | 1 + 9 files changed, 60 insertions(+) diff --git a/android/src/toga_android/window.py b/android/src/toga_android/window.py index ffd523a7d1..6a3d76e4b6 100644 --- a/android/src/toga_android/window.py +++ b/android/src/toga_android/window.py @@ -24,6 +24,7 @@ def onGlobalLayout(self): """ native_parent = self.window.native_content.getParent() self.window.resize_content(native_parent.getWidth(), native_parent.getHeight()) + self.window.interface.on_resize() class Window(Container): diff --git a/cocoa/src/toga_cocoa/window.py b/cocoa/src/toga_cocoa/window.py index 17b70dd16b..513f3e1dbb 100644 --- a/cocoa/src/toga_cocoa/window.py +++ b/cocoa/src/toga_cocoa/window.py @@ -36,6 +36,7 @@ def windowDidResize_(self, notification) -> None: if self.interface.content: # Set the window to the new size self.interface.content.refresh() + self.impl.interface.on_resize() ###################################################################### # Toolbar delegate methods diff --git a/core/src/toga/window.py b/core/src/toga/window.py index bfd6057f91..32cdb318fd 100644 --- a/core/src/toga/window.py +++ b/core/src/toga/window.py @@ -89,6 +89,17 @@ def __call__(self, window: Window, **kwargs: Any) -> bool: ... +class OnResizeHandler(Protocol): + def __call__(self, window: Window, **kwargs: Any) -> None: + """A handler to invoke when a window resizes. + + :param window: The window instance that resizes. + :param kwargs: Ensures compatibility with additional arguments introduced in + future ver + """ + ... + + T = TypeVar("T") @@ -125,6 +136,7 @@ def __init__( resizable: bool = True, closable: bool = True, minimizable: bool = True, + on_resize: OnResizeHandler | None = None, on_close: OnCloseHandler | None = None, resizeable=None, # DEPRECATED closeable=None, # DEPRECATED @@ -196,6 +208,7 @@ def __init__( self._toolbar = CommandSet(on_change=self._impl.create_toolbar, app=self._app) self.on_close = on_close + self.on_resize = on_resize @property def id(self) -> str: @@ -358,6 +371,15 @@ def visible(self, visible: bool) -> None: else: self.hide() + @property + def on_resize(self) -> OnResizeHandler: + """The handler to invoke when the window resizes.""" + return self._on_resize + + @on_resize.setter + def on_resize(self, handler): + self._on_resize = wrapped_handler(self, handler) + @property def on_close(self) -> OnCloseHandler: """The handler to invoke if the user attempts to close the window.""" diff --git a/core/tests/test_window.py b/core/tests/test_window.py index f925dafa16..ee160af190 100644 --- a/core/tests/test_window.py +++ b/core/tests/test_window.py @@ -354,6 +354,19 @@ def test_as_image(window): assert image.size == (318, 346) +def test_on_resize(window): + assert window.on_resize._raw is None + + on_resize_handler = Mock() + window.on_resize = on_resize_handler + + assert window.on_resize._raw == on_resize_handler + + window._impl.simulate_on_resize() + + on_resize_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 710ec40fc3..cc28a2e731 100644 --- a/dummy/src/toga_dummy/window.py +++ b/dummy/src/toga_dummy/window.py @@ -105,3 +105,6 @@ def set_full_screen(self, is_full_screen): def simulate_close(self): self.interface.on_close() + + def simulate_on_resize(self): + self.interface.on_resize() diff --git a/gtk/src/toga_gtk/window.py b/gtk/src/toga_gtk/window.py index 9e2c1389fc..7c53f7a45b 100644 --- a/gtk/src/toga_gtk/window.py +++ b/gtk/src/toga_gtk/window.py @@ -16,6 +16,7 @@ def __init__(self, interface, title, position, size): self.create() self.native._impl = self + self.native.connect("size-allocate", self.gtk_size_allocate) self.native.connect("delete-event", self.gtk_delete_event) self.native.set_default_size(size[0], size[1]) @@ -126,6 +127,9 @@ def hide(self): def get_visible(self): return self.native.get_property("visible") + def gtk_size_allocate(self, widget, data): + self.interface.on_resize() + def gtk_delete_event(self, widget, data): if self._is_closing: should_close = True diff --git a/iOS/src/toga_iOS/app.py b/iOS/src/toga_iOS/app.py index 343790c2a1..a43e4d109c 100644 --- a/iOS/src/toga_iOS/app.py +++ b/iOS/src/toga_iOS/app.py @@ -48,6 +48,8 @@ def application_didChangeStatusBarOrientation_( """This callback is invoked when rotating the device from landscape to portrait and vice versa.""" App.app.interface.main_window.content.refresh() + for window in App.app.interface.windows: + window.on_resize() class App: diff --git a/testbed/tests/test_window.py b/testbed/tests/test_window.py index 70067ce9c5..dfc1b2f0ec 100644 --- a/testbed/tests/test_window.py +++ b/testbed/tests/test_window.py @@ -480,6 +480,19 @@ async def test_full_screen(second_window, second_window_probe): assert second_window_probe.content_size == initial_content_size +async def test_on_resize(main_window, main_window_probe): + if toga.platform.current_platform in {"android", "iOS", "textual", "web"}: + pytest.xfail("Window.on_resize is non functional on current platform.") + main_window_on_resize_handler = Mock() + main_window.on_resize = main_window_on_resize_handler + + main_window.size = (200, 150) + await main_window_probe.wait_for_window("Main window has been resized") + assert main_window.size == (200, 150) + + main_window_on_resize_handler.assert_called_once_with(main_window) + + async def test_as_image(main_window, main_window_probe): """The window can be captured as a screenshot""" diff --git a/winforms/src/toga_winforms/window.py b/winforms/src/toga_winforms/window.py index 86ea5e76b0..3c3e56f3fb 100644 --- a/winforms/src/toga_winforms/window.py +++ b/winforms/src/toga_winforms/window.py @@ -136,6 +136,7 @@ def get_visible(self): def winforms_Resize(self, sender, event): self.resize_content() + self.interface.on_resize() def winforms_FormClosing(self, sender, event): # If the app is exiting, or a manual close has been requested, don't get From 9429020e55b818e7d0d820d444445bcf6ffb68be Mon Sep 17 00:00:00 2001 From: proneon267 Date: Fri, 26 Jan 2024 04:27:40 -0800 Subject: [PATCH 2/9] Added changelog --- changes/2304.feature.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/2304.feature.rst diff --git a/changes/2304.feature.rst b/changes/2304.feature.rst new file mode 100644 index 0000000000..5d22a1b9bc --- /dev/null +++ b/changes/2304.feature.rst @@ -0,0 +1 @@ +Toga Windows now supports calling user functions on resize events. From cd8abe045e57f7c4fe1d59a0dc7d4658850f9044 Mon Sep 17 00:00:00 2001 From: proneon267 Date: Fri, 26 Jan 2024 08:04:48 -0800 Subject: [PATCH 3/9] Fixed test --- testbed/tests/test_window.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/testbed/tests/test_window.py b/testbed/tests/test_window.py index dfc1b2f0ec..4308b9a6a4 100644 --- a/testbed/tests/test_window.py +++ b/testbed/tests/test_window.py @@ -486,12 +486,17 @@ async def test_on_resize(main_window, main_window_probe): main_window_on_resize_handler = Mock() main_window.on_resize = main_window_on_resize_handler + initial_size = main_window.size + main_window.size = (200, 150) await main_window_probe.wait_for_window("Main window has been resized") assert main_window.size == (200, 150) main_window_on_resize_handler.assert_called_once_with(main_window) + main_window.size = initial_size + await main_window_probe.wait_for_window("Main window has been resized") + async def test_as_image(main_window, main_window_probe): """The window can be captured as a screenshot""" From b80483be20a3e0615dae6827e6e25c3fe7acb4a4 Mon Sep 17 00:00:00 2001 From: proneon267 <45512885+proneon267@users.noreply.github.com> Date: Fri, 26 Jan 2024 12:59:14 -0500 Subject: [PATCH 4/9] Added on textual backend --- textual/src/toga_textual/window.py | 1 + 1 file changed, 1 insertion(+) diff --git a/textual/src/toga_textual/window.py b/textual/src/toga_textual/window.py index 239a8568c1..6ec3cda023 100644 --- a/textual/src/toga_textual/window.py +++ b/textual/src/toga_textual/window.py @@ -111,6 +111,7 @@ def on_mount(self) -> None: def on_resize(self, event) -> None: self.interface.content.refresh() + self.interface.on_resize() class Window: From 55a8f4b7eb895cb2bc82a678de4192448b5c868c Mon Sep 17 00:00:00 2001 From: proneon267 <45512885+proneon267@users.noreply.github.com> Date: Fri, 26 Jan 2024 13:00:34 -0500 Subject: [PATCH 5/9] Updated test --- testbed/tests/test_window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testbed/tests/test_window.py b/testbed/tests/test_window.py index 4308b9a6a4..357bb729c7 100644 --- a/testbed/tests/test_window.py +++ b/testbed/tests/test_window.py @@ -481,7 +481,7 @@ async def test_full_screen(second_window, second_window_probe): async def test_on_resize(main_window, main_window_probe): - if toga.platform.current_platform in {"android", "iOS", "textual", "web"}: + if toga.platform.current_platform in {"android", "iOS", "web"}: pytest.xfail("Window.on_resize is non functional on current platform.") main_window_on_resize_handler = Mock() main_window.on_resize = main_window_on_resize_handler From 935314851969c664d411f1834059f03ee4322a62 Mon Sep 17 00:00:00 2001 From: proneon267 Date: Sat, 27 Jan 2024 23:54:14 -0800 Subject: [PATCH 6/9] Fixed tests --- gtk/src/toga_gtk/window.py | 2 +- testbed/tests/test_window.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/gtk/src/toga_gtk/window.py b/gtk/src/toga_gtk/window.py index 7c53f7a45b..dcec906663 100644 --- a/gtk/src/toga_gtk/window.py +++ b/gtk/src/toga_gtk/window.py @@ -16,7 +16,7 @@ def __init__(self, interface, title, position, size): self.create() self.native._impl = self - self.native.connect("size-allocate", self.gtk_size_allocate) + self.native.connect("configure-event", self.gtk_size_allocate) self.native.connect("delete-event", self.gtk_delete_event) self.native.set_default_size(size[0], size[1]) diff --git a/testbed/tests/test_window.py b/testbed/tests/test_window.py index 357bb729c7..39ebe06348 100644 --- a/testbed/tests/test_window.py +++ b/testbed/tests/test_window.py @@ -492,10 +492,11 @@ async def test_on_resize(main_window, main_window_probe): await main_window_probe.wait_for_window("Main window has been resized") assert main_window.size == (200, 150) - main_window_on_resize_handler.assert_called_once_with(main_window) + main_window_on_resize_handler.assert_called_with(main_window) main_window.size = initial_size await main_window_probe.wait_for_window("Main window has been resized") + assert main_window.size == initial_size async def test_as_image(main_window, main_window_probe): From 5429987cf8085cba4533d592605c04692b2967ed Mon Sep 17 00:00:00 2001 From: proneon267 <45512885+proneon267@users.noreply.github.com> Date: Sun, 28 Jan 2024 05:12:15 -0500 Subject: [PATCH 7/9] Miscellaneous Fixes --- gtk/src/toga_gtk/window.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gtk/src/toga_gtk/window.py b/gtk/src/toga_gtk/window.py index dcec906663..d0e73c71b9 100644 --- a/gtk/src/toga_gtk/window.py +++ b/gtk/src/toga_gtk/window.py @@ -16,7 +16,7 @@ def __init__(self, interface, title, position, size): self.create() self.native._impl = self - self.native.connect("configure-event", self.gtk_size_allocate) + self.native.connect("configure-event", self.gtk_configure_event) self.native.connect("delete-event", self.gtk_delete_event) self.native.set_default_size(size[0], size[1]) @@ -127,7 +127,7 @@ def hide(self): def get_visible(self): return self.native.get_property("visible") - def gtk_size_allocate(self, widget, data): + def gtk_configure_event(self, widget, data): self.interface.on_resize() def gtk_delete_event(self, widget, data): From 8d131cd9916a174b589959cf5df4fdbce76748d9 Mon Sep 17 00:00:00 2001 From: proneon267 <45512885+proneon267@users.noreply.github.com> Date: Mon, 29 Jan 2024 05:31:12 -0800 Subject: [PATCH 8/9] Misc. Fix --- testbed/tests/test_window.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/testbed/tests/test_window.py b/testbed/tests/test_window.py index 39ebe06348..f68d2abdee 100644 --- a/testbed/tests/test_window.py +++ b/testbed/tests/test_window.py @@ -486,6 +486,9 @@ async def test_on_resize(main_window, main_window_probe): main_window_on_resize_handler = Mock() main_window.on_resize = main_window_on_resize_handler + main_window.show() + await main_window_probe.wait_for_window("Main window has been shown") + initial_size = main_window.size main_window.size = (200, 150) From 3291ae1d30644ef8489bd3da5c2b2d559f7d067b Mon Sep 17 00:00:00 2001 From: proneon267 Date: Wed, 20 Mar 2024 22:58:01 -0700 Subject: [PATCH 9/9] updated to latest main branch --- core/tests/test_window.py | 15 +-------------- dummy/src/toga_dummy/window.py | 4 ++-- gtk/src/toga_gtk/window.py | 4 ++-- winforms/src/toga_winforms/window.py | 4 ++-- 4 files changed, 7 insertions(+), 20 deletions(-) diff --git a/core/tests/test_window.py b/core/tests/test_window.py index 428b853cb0..1a5c6586c8 100644 --- a/core/tests/test_window.py +++ b/core/tests/test_window.py @@ -366,7 +366,7 @@ def test_on_resize(window): on_resize_handler.assert_called_once_with(window) - + def test_screen(window, app): """A window can be moved to a different screen.""" # Cannot actually change window.screen, so just check @@ -393,19 +393,6 @@ def test_screen_position(window, app): assert window.position == (-1266, -668) assert window.screen_position == (100, 100) - -def test_on_resize(window): - assert window.on_resize._raw is None - - on_resize_handler = Mock() - window.on_resize = on_resize_handler - - assert window.on_resize._raw == on_resize_handler - - window._impl.simulate_on_resize() - - on_resize_handler.assert_called_once_with(window) - def test_info_dialog(window, app): """An info dialog can be shown.""" diff --git a/dummy/src/toga_dummy/window.py b/dummy/src/toga_dummy/window.py index 0abd7fc5f0..97afcbfc8a 100644 --- a/dummy/src/toga_dummy/window.py +++ b/dummy/src/toga_dummy/window.py @@ -106,10 +106,10 @@ def set_full_screen(self, is_full_screen): def simulate_close(self): self.interface.on_close() - + def get_current_screen(self): # `window.screen` will return `Secondary Screen` return ScreenImpl(native=("Secondary Screen", (-1366, -768), (1366, 768))) - + def simulate_on_resize(self): self.interface.on_resize() diff --git a/gtk/src/toga_gtk/window.py b/gtk/src/toga_gtk/window.py index 5120fffa23..fb9a5c8f95 100644 --- a/gtk/src/toga_gtk/window.py +++ b/gtk/src/toga_gtk/window.py @@ -57,7 +57,7 @@ def create(self): ###################################################################### # Native event handlers ###################################################################### - + def gtk_configure_event(self, widget, data): self.interface.on_resize() @@ -157,7 +157,7 @@ def show(self): def set_content(self, widget): # Set the new widget to be the container's content self.container.content = widget - + ###################################################################### # Window size ###################################################################### diff --git a/winforms/src/toga_winforms/window.py b/winforms/src/toga_winforms/window.py index d9db2b2b51..36d38bf828 100644 --- a/winforms/src/toga_winforms/window.py +++ b/winforms/src/toga_winforms/window.py @@ -207,10 +207,10 @@ def set_position(self, position): def get_visible(self): return self.native.Visible - + def hide(self): self.native.Hide() - + ###################################################################### # Window state ######################################################################