`window.size` setter doesn't work when called after `window.show()` in `startup()` method on `gtk`
Describe the bug
window.size setter doesn't work when called after window.show() in startup() method on gtk, but works when called before window.show() method.
Steps to reproduce
- Use the latest toga main branch
- Install with:
pip install -e ./core -e ./dummy -e ./gtk
- Generate a new project with briefcase
- Replace the
app.pyscript with the following:
"""
My first application
"""
import toga
from toga.style import Pack
from toga.style.pack import COLUMN, ROW
class HelloWorld(toga.App):
def startup(self):
"""
Construct and show the Toga application.
Usually, you would add your application to a main content box.
We then create a main window (with a name matching the app), and
show the main window.
"""
main_box = toga.Box()
self.main_window = toga.MainWindow(title=self.formal_name)
self.main_window.content = main_box
self.main_window.show()
self.main_window.size = (200,100)
print(self.main_window.size)
assert self.main_window.size == (200,100)
def main():
return HelloWorld()
- Errors will be encountered and returns the default window size value:
[helloworld] Starting in dev mode...
===========================================================================
(640, 480)
Traceback (most recent call last):
File "/home/proneon267/venv/lib/python3.11/site-packages/toga_gtk/app.py", line 87, in gtk_startup
self.interface._startup()
File "/home/proneon267/venv/lib/python3.11/site-packages/toga/app.py", line 710, in _startup
self.startup()
File "/home/proneon267/helloworld/src/helloworld/app.py", line 29, in startup
assert self.main_window.size == (200,100)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError
- Replace
app.pywith the following:
"""
My first application
"""
import toga
from toga.style import Pack
from toga.style.pack import COLUMN, ROW
class HelloWorld(toga.App):
def startup(self):
"""
Construct and show the Toga application.
Usually, you would add your application to a main content box.
We then create a main window (with a name matching the app), and
show the main window.
"""
main_box = toga.Box()
self.main_window = toga.MainWindow(title=self.formal_name)
self.main_window.content = main_box
self.main_window.size = (200,100)
print(self.main_window.size)
assert self.main_window.size == (200,100)
self.main_window.show()
def main():
return HelloWorld()
- No errors will be encountered and will return the correct value of
self.main_window.size:
[helloworld] Starting in dev mode...
===========================================================================
(200, 100)
Expected behavior
window.size should return the correct value in both the cases.
I guess this error is related to the call: https://github.com/beeware/toga/blob/f4dee4fdf6b1f3fb5639a12f639323afb8151f2a/gtk/src/toga_gtk/window.py#L21
Screenshots
No response
Environment
- Operating System: Arch
- Python version: 3.11.6
- Software versions:
- Briefcase: 0.3.16
- Toga: latest
- ...
Logs
No logs were generated
Additional context
No response
While this is definitely an odd inconsistency, I don't believe we should consider it a bug - or, at least, to the extent that it's a bug, the behaviour will be highly platform dependent, and almost impossible to fix, and we should document our way out of needing to fix it.
The issue is that invoking a background API doesn't necessarily mean that the effect of that API has been immediately applied. In this case, you're showing a window, then changing the window size, then inspecting the window size - but at no point are you releasing control to the GUI event loop to process the size change. Retrieving the window size is done against the "actual" size - the current manifested size of the window; but because the size change hasn't been actually drawn, you end up in a situation where you're asking for the size of the window before the drawing instruction to change the window size hasn't been applied yet.
The problem isn't an issue when the change occurs before the show(), because in that instance there's no GUI instruction required, so it's immediately applied internally by GTK. This is an internal implementation detail, though.
This problem doesn't exist on every platform - windows, macOS and iOS, for example, apply changes immediately. However, we've had multiple reports about analogous problems on Android (see #2274, 2281).
Unfortunately, I can't think of any obvious way to work around this problem, though, short of putting a "process all outstanding events" call before every attribute retrieval on GTK. I strongly suspect that this particular cure would be worse than the disease.
Ultimately, I suspect the actual "fix" for this will be documentation - highlighting that properties aren't necessarily reflected immediately.
The good news is that I don't think that this is that big of a problem. In practice, I can't think of many use cases that involve window.size = (x, y); if window.size > ... type logic - you're either setting a new size, or you're responding to a new size... not both in the same "context". I suspect that you're only observing this problem because you're messing around with testing resize behavior - and testing like this is the one area where the problem manifests a lot. That's why the tests contain a lot of await probe.redraw() calls - specifically to give the GUI the opportunity to reflect any property changes.
I agree that it should be a documentation fix. But, can you take a look at the on_resize PR's test: https://github.com/beeware/toga/actions/runs/7696871621/job/20972747241?pr=2364
=================================== FAILURES ===================================
________________________________ test_on_resize ________________________________
Traceback (most recent call last):
File "/home/runner/work/toga/toga/testbed/build/testbed/ubuntu/jammy/testbed-0.0.1/usr/lib/testbed/app/tests/test_window.py", line 496, in test_on_resize
assert main_window.size == (200, 150)
AssertionError: assert (288, 150) == (200, 150)
At index 0 diff: 288 != 200
Full diff:
- (200, 150)
? ^^
+ (288, 150)
? ^^
I am using wait_for_window() methods, but I am not sure, why main_window.size is returning the wrong size. The same error occurs locally.
My guess is that you're hitting your operating system minimum size. Taking things to the extreme - you can't make a window 10x10. At some point, there's a minimum size that allows the window manager to display buttons, window titles etc. It appears that 288 pixels is the lower limit for the horizontal size of a window in your test environment. This value is highly platform dependent - it depends on the window manager that you're using; so for test purposes, make sure you pick a number that is "big enough" to be safe, while still testing whatever property you're asserting.