-
-
Notifications
You must be signed in to change notification settings - Fork 666
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
Add window states API #2473
base: main
Are you sure you want to change the base?
Add window states API #2473
Conversation
…to window_states
I understand that this might be helpful for diagnosis, but in terms of the final PR, a single "object as delegate" is vastly preferable. Object allocation is expensive, so allocating as few objects as possible is the preferred approach unless there's a specific reason why it isn't possible.
I agree a connection with garbage collection is likely; but I don't think it's accurate to say there's a problem with either probe. The probes are doing entirely legal things; they're just forcing a garbage collection run at a specific (and predictable) time. If an object is able to be garbage collected, then the issue is that the object being referenced has (or could be) disposed. That indicates there's a lifecycle issue with Window objects. This is also consistent with the "remove weak=True" observation - if they were strong references, the objects wouldn't be garbage collected, because there would still be a strong reference. So - the question is what sequence of events leads to the impl and/or interface being deleted before the native object (and, more importantly, why this set of changes is exposing the problem). Understanding exactly which methods are being invoked in a impl/interace=None state, and under what conditions, would help to clarify the sequence of events leading to that state.
They are instance properties. The slot is defined on the class, but the actual values are instance specific. |
It seems that my initial intuition about the delegate events getting triggered during the closing of the window during each run of a test, seems to be correct. The delegate events are triggered when Hence, we need to disconnect the delegate from the toga/cocoa/src/toga_cocoa/window.py Lines 47 to 53 in 03cf66d
But the delegate events are still getting triggered as evident by the macOS testbed failures. However, the current reason for why the events are still getting triggered is that there is a timing mismatch between disconnecting of the delegate from the I need to research more and check for any other available options in order to avoid triggering the delegate events. To consistently trigger the errors, I have added |
70ddc13
to
767d82c
Compare
749a35e
to
c928ac1
Compare
7bbf330
to
0f7fe4f
Compare
…to window_states
I have gone through the review of the code related issues. I have added some new tests and fixed test cases in order to have full test coverage. I know that this PR is relatively large to review at one time. But if time permits, could you please review the tests of core and testbed and let me know about any missing test cases or any required changes. Also, I am not sure where to put the platform guide for window state in the docs. Should I put it under the "How to guides" section in the docs? In the platform guide, I was thinking of providing a brief usage examples, and to have a table matrix to indicate which states are available on different platforms and mention any platform specific quirks below the table. Does this sound correct? |
I'll put it on my TODO list for early this week.
It doesn't need to anything near that elaborate. The platform guide is the Notes section of the relevant widget (Window, in this case). It only needs to list the details that might be of interest, on a per-platform basis. Window already contains some of these platform notes. Add a similar bullet point for window state changes; or, if you think it's clearer, split it into a bullet point for each platform detailing that platform's specific quirks. It definitely doesn't need to be a table, or example code, or anything like that. For a feature as straightforward as this, if the usage isn't obvious from the API, we've designed a bad API. There might be a need for a section in the docs to detail the difference between "full sreen" and "presentation" mode - but even that is largely covered by the enumeration of window states. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I got part way through a review, and then discovered a bunch of comments from past reviews that haven't had any response, then realised you'd only asked for a review of the core and testbed tests - so you got an extra review of most of Android and Cocoa for free :-)
As for the state of the tests... they're definitely getting better, but there are still some problems. Details inline; but the biggest problem of all is the use of random in selecting test conditions - that's an absolute non-starter.
The biggest thing of all to keep in mind is "why do I need to add another test?". There should be no unique feature that we are testing in multiple ways. We should only be adding a new test case when there is a unique set of pre or post conditions that we need to evaluate. To that end, there may be different parameterization strategies required. In some cases, a literal X * Y matrix will be the right approach; but in others, Y tests, parameterized by X will be the best approach.
pass | ||
def enter_presentation_mode(self, screen_window_dict): | ||
for screen, window in screen_window_dict.items(): | ||
window._impl._before_presentation_mode_screen = window.screen |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this variable in aid of? It doesn't appear to be used anywhere.
pass | ||
def exit_presentation_mode(self): | ||
# There is only a single window on android. | ||
self.interface.main_window._impl.set_window_state(WindowState.NORMAL) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't seem like a full reversal. It's somewhat a moot point because you can only have one window in an Android app; but given the "enter" method supports multiple screens, it seems like this should reverse any screen change (or the enter method shouldn't worry about screen changes).
decor_view = self.app.native.getWindow().getDecorView() | ||
|
||
if current_state == state: | ||
return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like this should be an optimisation at the core level - it's something that will be common to every implementation.
| decor_view.SYSTEM_UI_FLAG_IMMERSIVE | ||
) | ||
self.show_actionbar(False) | ||
self._in_presentation_mode = True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any reason that _in_presentation_mode
shouldn't be set by show_actionbar()
? It seems like that's really what is being tracked here.
async def wait_for_window(self, message, minimize=False, full_screen=False): | ||
await self.redraw(message) | ||
async def wait_for_window( | ||
self, message, minimize=False, full_screen=False, rapid_state_switching=False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the argument list gets too long, we try to break the arguments into separate lines, rather than the "all on one line indented" format. Black will auto-format this if you add a comma to the end of the argument list.
assert main_window.state == final_state | ||
finally: | ||
# Set to NORMAL state | ||
main_window.state = WindowState.NORMAL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like this main_window.state == NORMAL
and app.current_window = main_window
cleanup tasks should be part of the window_cleanup fixture - so that every test can guarantee that it leaves the app in a clean state.
(WindowState.PRESENTATION, WindowState.FULLSCREEN), | ||
], | ||
) | ||
async def test_window_state_direct_change( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why differentiate "direct" from "same" state changes? We're looking to validate that moving from X to Y works - for any value of X and Y, including X == Y. In what way are the "same state" tests different?
main_window.content.clear() | ||
|
||
async def test_window_state_presentation(main_window, main_window_probe): | ||
"""The window can enter into presentation state.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is this any different to the "NORMAL->PRESENTATION" state change test?
WindowState.MAXIMIZED, | ||
WindowState.FULLSCREEN, | ||
WindowState.PRESENTATION, | ||
], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As with the mobile variant - it's not clear to me how an X->Y test is different from an X->X test.
"""Window can be made full screen""" | ||
assert not second_window_probe.is_full_screen | ||
async def test_window_state_minimized(second_window, second_window_probe): | ||
"""Window can have minimized window state.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need explicit tests of minimized state when we've already validated every possible transition?
Or, if the issue is that there are properties of the final minimized state that need to be validated (which seems possible) - then we should have a standalone test case for each final state, parameterised by original state, rather than a single full x->y matrix test.
Fixes #1857
Design discussion: #1884
The following are the API changes:
On the interface:
toga.constants.WindowState
WindowState.NORMAL
WindowState.MAXIMIZED
WindowState.MINIMIZED
WindowState.FULLSCREEN
WindowState.PRESENTATION
toga.Window
toga.Window.state
(getter)toga.Window.state
(setter)toga.Window.full_screen
(getter)toga.Window.full_screen
(setter)toga.App
toga.App.is_in_presentation_mode
(getter)toga.App.enter_presentation_mode()
toga.App.exit_presentation_mode()
toga.App.is_full_screen
(getter)toga.App.set_full_screen()
toga.App.exit_full_screen()
On the backend:
toga.Window
get_window_state()
set_window_state()
toga.App
enter_presentation_mode()
exit_presentation_mode()
However, I did encounter some issues, which I have put as inline comments. I do have some other questions about rubicon for implementing the new API, but I will post them later on.
TODO: Write and modify documentation, fix issues with tests, implement for the other remaining platforms, etc.
PR Checklist: