-
-
Notifications
You must be signed in to change notification settings - Fork 778
Added on_resize
handler on toga.Window
#2364
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 41 commits
bbceb8c
9429020
cd8abe0
b80483b
55a8f4b
9353148
5429987
8d131cd
96ddd64
3291ae1
0b24cc8
702c1db
b494da2
8edded7
bf8b9ed
857ff3e
3d8d952
38999e3
c2d5ab5
e10f729
a1c9b53
522798a
a5d7e12
877b736
b8672e2
0091aed
c17b1ec
81be13a
38bf570
7e98675
fc59fe4
33a21e8
a779463
071d039
fd58fcc
7733a31
d6cec42
aef5b8d
6fe1167
aa54e5b
eb5c3cb
9adea65
6c01b60
2759ffb
24be047
8fa656d
7509eca
6479d74
f16f18f
c39854a
ebe2e67
1ac2e6e
2d35ceb
cbda777
393f73f
07daff0
98048f6
a86cd65
1d3bc3b
5a99825
1516450
6ad56d6
2e4bfba
b796fe3
e2baade
3a15780
c932b65
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Toga Windows now supports calling user functions on resize events. | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -158,6 +158,25 @@ def __call__(self, window: Window, **kwargs: Any) -> None: | |
... | ||
|
||
|
||
class OnResizeHandler(Protocol): | ||
def __call__(self, window: Window, **kwargs: Any) -> None: | ||
"""A handler to invoke when a window resizes. | ||
|
||
This event is also triggered when any change in available layout size occurs. | ||
However, a change in visibility (e.g. when a window is hidden or minimized) | ||
does not cause a change in layout size and therefore, the event will not be | ||
triggered. | ||
|
||
On mobile platforms, it is also triggered when the orientation of the | ||
device is changed. | ||
|
||
:param window: The window instance that resizes. | ||
:param kwargs: Ensures compatibility with additional arguments introduced in | ||
future ver | ||
proneon267 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
""" | ||
... | ||
|
||
|
||
_DialogResultT = TypeVar("_DialogResultT") | ||
|
||
|
||
|
@@ -197,6 +216,7 @@ def __init__( | |
on_lose_focus: OnLoseFocusHandler | None = None, | ||
on_show: OnShowHandler | None = None, | ||
on_hide: OnHideHandler | None = None, | ||
on_resize: OnResizeHandler | None = None, | ||
content: Widget | None = None, | ||
) -> None: | ||
"""Create a new Window. | ||
|
@@ -226,6 +246,17 @@ def __init__( | |
self._closable = closable | ||
self._minimizable = minimizable | ||
|
||
# Prime up the event handlers on the interface, as they might be | ||
# called during the initialization of the native window class. | ||
Comment on lines
+250
to
+251
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On re-review, this raised a red flag for me. Handlers should not be firing as a result of initial conditions passed to the constructor. If the issue is that the code might result in the handler being invoked, and the handler need to exist, then the priming should be with a |
||
self.on_close = on_close | ||
|
||
self.on_gain_focus = on_gain_focus | ||
self.on_lose_focus = on_lose_focus | ||
self.on_show = on_show | ||
self.on_hide = on_hide | ||
|
||
self.on_resize = on_resize | ||
|
||
# The app needs to exist before windows are created. _app will only be None | ||
# until the window is added to the app below. | ||
self._app: App = None | ||
|
@@ -247,13 +278,6 @@ def __init__( | |
if content: | ||
self.content = content | ||
|
||
self.on_close = on_close | ||
|
||
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 __lt__(self, other: Window) -> bool: | ||
return self.id < other.id | ||
|
||
|
@@ -652,6 +676,15 @@ def on_hide(self) -> callable: | |
def on_hide(self, handler): | ||
self._on_hide = wrapped_handler(self, handler) | ||
|
||
@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) | ||
|
||
###################################################################### | ||
# 2024-06: Backwards compatibility for <= 0.4.5 | ||
###################################################################### | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,3 +11,14 @@ def gtk_text_align(alignment): | |
CENTER: (0.5, Gtk.Justification.CENTER), | ||
JUSTIFY: (0.0, Gtk.Justification.FILL), | ||
}[alignment] | ||
|
||
|
||
def create_toga_native(native_gtk_class): # pragma: no-cover-if-gtk3 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As noted elsewhere - if this is code that isn't needed on GTK4, it should be in an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, I have moved it into an if block. |
||
"""Create a new native class from a native gtk class, whose virtual functions | ||
could be safely overridden.""" | ||
toga_native_class = type( | ||
native_gtk_class.__gtype__.name, | ||
(native_gtk_class,), | ||
{"base_class": native_gtk_class}, # Store the base class type | ||
) | ||
return toga_native_class |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why does this need another module? Isn't it just another utility? It's in wrapper for winforms because there is not "utils" class. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, I have moved the wrapper class to utils. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import weakref | ||
|
||
|
||
class WeakrefCallable: # pragma: no-cover-if-gtk3 | ||
""" | ||
A wrapper for callable that holds a weak reference to it. | ||
|
||
This can be useful in particular when setting gtk virtual function handlers, | ||
to avoid cyclical reference cycles between python and gi that are detected | ||
neither by the python garbage collector nor the gi. | ||
""" | ||
|
||
def __init__(self, function): | ||
try: | ||
self.ref = weakref.WeakMethod(function) | ||
except TypeError: # pragma: no cover | ||
self.ref = weakref.ref(function) | ||
|
||
def __call__(self, *args, **kwargs): | ||
function = self.ref() | ||
if function: # pragma: no branch | ||
return function(*args, **kwargs) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,12 +9,15 @@ | |
from toga.window import _initial_position | ||
|
||
from .container import TogaContainer | ||
from .libs import GTK_VERSION, IS_WAYLAND, Gdk, GLib, Gtk | ||
from .libs import GTK_VERSION, IS_WAYLAND, Gdk, GLib, Gtk, hook_up_vfunc_implementation | ||
from .libs.utils import create_toga_native | ||
from .screens import Screen as ScreenImpl | ||
|
||
if TYPE_CHECKING: # pragma: no cover | ||
from toga.types import PositionT, SizeT | ||
|
||
from .libs.wrapper import WeakrefCallable | ||
|
||
|
||
class Window: | ||
def __init__(self, interface, title, position, size): | ||
|
@@ -23,6 +26,9 @@ def __init__(self, interface, title, position, size): | |
|
||
self.layout = None | ||
|
||
if GTK_VERSION >= (4, 0, 0): # pragma: no-cover-if-gtk3 | ||
self._window_size = Size(0, 0) | ||
|
||
self.create() | ||
self.native._impl = self | ||
|
||
|
@@ -42,10 +48,17 @@ def __init__(self, interface, title, position, size): | |
self.native.connect("window-state-event", self.gtk_window_state_event) | ||
self.native.connect("focus-in-event", self.gtk_focus_in_event) | ||
self.native.connect("focus-out-event", self.gtk_focus_out_event) | ||
self.native.connect("configure-event", self.gtk_configure_event) | ||
else: # pragma: no-cover-if-gtk3 | ||
self.native.connect("notify::fullscreened", self.gtk_window_state_event) | ||
self.native.connect("notify::maximized", self.gtk_window_state_event) | ||
self.native.connect("notify::minimized", self.gtk_window_state_event) | ||
# do_size_allocate is a virtual function, used to track window resize. | ||
hook_up_vfunc_implementation( | ||
self.native.do_size_allocate, | ||
self.native.__gtype__, | ||
WeakrefCallable(self.gtk_do_size_allocate), | ||
) | ||
|
||
self._window_state_flags = None | ||
self._in_presentation = False | ||
|
@@ -82,11 +95,31 @@ def __init__(self, interface, title, position, size): | |
self.native.set_child(self.layout) | ||
|
||
def create(self): | ||
self.native = Gtk.Window() | ||
if GTK_VERSION < (4, 0, 0): # pragma: no-cover-if-gtk4 | ||
self.native = Gtk.Window() | ||
else: # pragma: no-cover-if-gtk3 | ||
self.native = create_toga_native(Gtk.Window)() | ||
|
||
###################################################################### | ||
# Native event handlers | ||
###################################################################### | ||
def gtk_do_size_allocate( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When we're adding code that has different code for GTK3/GTK4, we're putting it into There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for clarifying it. I have moved GTK4 code into if blocks. |
||
self, native, width, height, baseline | ||
): # pragma: no-cover-if-gtk3 | ||
|
||
# Note: Virtual methods can't use super() to access the original | ||
# implementation, so they use native.base_class instead. | ||
|
||
# Call the parent class's size_allocate via native.base_class. | ||
native.base_class.do_size_allocate(native, width, height, baseline) | ||
|
||
if self._window_size != (width, height): | ||
self._window_size = Size(width, height) | ||
self.interface.on_resize() | ||
|
||
def gtk_configure_event(self, widget, data): # pragma: no-cover-if-gtk4 | ||
freakboy3742 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
self.interface.on_resize() | ||
|
||
def gtk_show(self, widget): | ||
self.interface.on_show() | ||
|
||
|
@@ -228,12 +261,10 @@ def set_content(self, widget): | |
|
||
def get_size(self) -> Size: | ||
if GTK_VERSION < (4, 0, 0): # pragma: no-cover-if-gtk4 | ||
width, height = self.native.get_default_size() | ||
size = self.native.get_size() | ||
return Size(size.width, size.height) | ||
else: # pragma: no-cover-if-gtk3 | ||
width, height = self.native.get_default_size() | ||
return Size(width, height) | ||
return self._window_size | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Apologies if I'm forgetting this from a past review - but why is this change required? It seems less than ideal to have window size be a stateful property, rather than something determined at runtime. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
def set_size(self, size: SizeT): | ||
if GTK_VERSION < (4, 0, 0): # pragma: no-cover-if-gtk4 | ||
|
@@ -444,7 +475,10 @@ def get_image_data(self): | |
|
||
class MainWindow(Window): | ||
def create(self): | ||
self.native = Gtk.ApplicationWindow() | ||
if GTK_VERSION < (4, 0, 0): # pragma: no-cover-if-gtk4 | ||
self.native = Gtk.ApplicationWindow() | ||
else: # pragma: no-cover-if-gtk3 | ||
self.native = create_toga_native(Gtk.ApplicationWindow)() | ||
if GTK_VERSION < (4, 0, 0): # pragma: no-cover-if-gtk4 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You've got 2 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, I have merged them. |
||
self.native.set_role("MainWindow") | ||
|
||
|
Uh oh!
There was an error while loading. Please reload this page.