Skip to content

Commit

Permalink
Fixed cocoa delegate
Browse files Browse the repository at this point in the history
  • Loading branch information
proneon267 committed Sep 9, 2024
1 parent 3661110 commit c928ac1
Show file tree
Hide file tree
Showing 17 changed files with 563 additions and 578 deletions.
5 changes: 2 additions & 3 deletions android/src/toga_android/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,9 +325,8 @@ def enter_presentation_mode(self, screen_window_dict):
window._impl.set_window_state(WindowState.PRESENTATION)

def exit_presentation_mode(self):
for window in self.interface.windows:
if window.state == WindowState.PRESENTATION:
window._impl.set_window_state(WindowState.NORMAL)
# There is only a single window on android.
self.interface.main_window._impl.set_window_state(WindowState.NORMAL)

######################################################################
# Platform-specific APIs
Expand Down
22 changes: 11 additions & 11 deletions android/src/toga_android/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,34 +176,31 @@ def set_window_state(self, state):
if current_state == WindowState.FULLSCREEN:
decor_view.setSystemUiVisibility(0)

# elif current_state == WindowState.PRESENTATION:
else:
else: # current_state == WindowState.PRESENTATION:
decor_view.setSystemUiVisibility(0)
self.show_actionbar(True)
self._in_presentation_mode = False

self.set_window_state(state)

# elif current_state == WindowState.NORMAL:
else:
else: # current_state == WindowState.NORMAL:
if state == WindowState.MAXIMIZED:
# On Android Maximized state is same as the Normal state.
# no-op on Android.
pass

elif state == WindowState.MINIMIZED:
self.interface.factory.not_implemented(
"Window.set_window_state(WindowState.MINIMIZED)"
)
# no-op on Android.
pass

elif state == WindowState.FULLSCREEN:
decor_view.setSystemUiVisibility(
# These constants are all marked as deprecated as of API 30.
decor_view.SYSTEM_UI_FLAG_FULLSCREEN
| decor_view.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| decor_view.SYSTEM_UI_FLAG_IMMERSIVE
)

# elif state == WindowState.PRESENTATION:
else:
else: # state == WindowState.PRESENTATION:
decor_view.setSystemUiVisibility(
decor_view.SYSTEM_UI_FLAG_FULLSCREEN
| decor_view.SYSTEM_UI_FLAG_HIDE_NAVIGATION
Expand Down Expand Up @@ -243,4 +240,7 @@ def create_toolbar(self):

def show_actionbar(self, show):
actionbar = self.app.native.getSupportActionBar()
actionbar.show() if show else actionbar.hide()
if show:
actionbar.show()
else:
actionbar.hide()
16 changes: 2 additions & 14 deletions cocoa/src/toga_cocoa/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,12 +378,12 @@ def enter_presentation_mode(self, screen_window_dict):
opts.setObject(
NSNumber.numberWithBool(True), forKey="NSFullScreenModeAllScreens"
)

for screen, window in screen_window_dict.items():
# The widgets are actually added to window._impl.container.native, instead of
# window.content._impl.native. And window._impl.native.contentView is
# window._impl.container.native. Hence, we need to go fullscreen on
# window._impl.container.native instead.
# window._impl.container.native instead.
window._impl.container.native.enterFullScreenMode(
screen._impl.native, withOptions=opts
)
Expand All @@ -394,15 +394,6 @@ def enter_presentation_mode(self, screen_window_dict):
window._impl.container.native.window.interface = window
window.content.refresh()

# Process any pending window state.
if (
window._impl._pending_state_transition
and window._impl._pending_state_transition != WindowState.PRESENTATION
):
window._impl._apply_state(WindowState.NORMAL)
else:
window._impl._pending_state_transition = None

def exit_presentation_mode(self):
opts = NSMutableDictionary.alloc().init()
opts.setObject(
Expand All @@ -413,6 +404,3 @@ def exit_presentation_mode(self):
if window.state == WindowState.PRESENTATION:
window._impl.container.native.exitFullScreenModeWithOptions(opts)
window.content.refresh()

# Process any pending window state.
window._impl._apply_state(window._impl._pending_state_transition)
82 changes: 55 additions & 27 deletions cocoa/src/toga_cocoa/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,24 @@ def windowShouldClose_(self, notification) -> bool:
self.interface.on_close()
return False

@objc_method
def windowWillClose_(self, notification) -> None:
# Setting the toolbar and window delegate to None doesn't disconnect
# the delegates and still triggers the delegate events.

# Hence, simply remove the toolbar instead.
self.toolbar = None

# However, window delegate methods, need check guards.

@objc_method
def windowDidResize_(self, notification) -> None:
if self.interface and self.interface.content:
if self.interface and self.interface.content: # pragma: no branch
# Set the window to the new size
self.interface.content.refresh()

@objc_method
def windowDidMiniaturize_(self, notification) -> None:
# Needs a delay on macOS-arm64 testbed, delaying longer on the testbed doesn't work.
self.performSelector(
SEL("enteredMiniaturize:"), withObject=None, afterDelay=0.1
)
Expand Down Expand Up @@ -196,6 +205,11 @@ def __init__(self, interface, title, position, size):
if self.interface.minimizable:
mask |= NSWindowStyleMask.Miniaturizable

# The objc callback methods can be called during the initialization
# of the NSWindow. So, any objc method referencing impl or interface
# will get an Attribute error. Hence, first allocate and assign the
# impl & interface to it. Then finally initialize the NSWindow.

# Create the window with a default frame;
# we'll update size and position later.
self.native = TogaWindow.alloc().initWithContentRect(
Expand All @@ -220,7 +234,7 @@ def __init__(self, interface, title, position, size):
self.set_size(size)
self.set_position(position if position is not None else _initial_position())

self.native.delegate = self.native
self.native.setDelegate(self.native)

self.container = Container(on_refresh=self.content_refreshed)
self.native.contentView = self.container.native
Expand Down Expand Up @@ -339,9 +353,7 @@ def get_visible(self):
######################################################################

def get_window_state(self):
if self.interface.content and bool(
self.interface.content._impl.native.isInFullScreenMode()
):
if bool(self.container.native.isInFullScreenMode()):
return WindowState.PRESENTATION
elif bool(self.native.styleMask & NSWindowStyleMask.FullScreen):
return WindowState.FULLSCREEN
Expand Down Expand Up @@ -371,50 +383,66 @@ def set_window_state(self, state):
if self._pending_state_transition:
self._pending_state_transition = state
else:
# If the app is in presentation mode, but this window isn't, then
# exit app presentation mode before setting the requested state.
if any(
window.state == WindowState.PRESENTATION and window != self.interface
for window in self.interface.app.windows
):
self.interface.app.exit_presentation_mode()

current_state = self.get_window_state()
if current_state == state:
return
self._pending_state_transition = state
if self.get_window_state() != WindowState.NORMAL:
if current_state != WindowState.NORMAL:
self._apply_state(WindowState.NORMAL)
else:
self._apply_state(state)

def _apply_state(self, target_state):
current_state = self.get_window_state()
if target_state is None:
return

elif target_state == self.get_window_state():
elif target_state == current_state:
self._pending_state_transition = None
return

elif target_state == WindowState.NORMAL:
current_state = self.get_window_state()
if current_state == WindowState.MAXIMIZED:
self.native.setIsZoomed(False)
self._apply_state(self._pending_state_transition)
elif current_state == WindowState.MINIMIZED:
self.native.setIsMiniaturized(False)
elif current_state == WindowState.FULLSCREEN:
self.native.toggleFullScreen(self.native)
# elif current_state == WindowState.PRESENTATION:
else: # pragma: no cover
# Presentation mode is natively supported by cocoa and is app-based.
# `exit_presentation_mode()` is called early in `window.state` setter.
# Hence, this branch will never be reached.
pass
elif target_state == WindowState.MAXIMIZED:
self.native.setIsZoomed(True)
# No need to check for other pending states, since this is completely
# applied here only.
# No need to check for other pending states,
# since this is fully applied at this point.
self._pending_state_transition = None

elif target_state == WindowState.MINIMIZED:
self.native.setIsMiniaturized(True)

elif target_state == WindowState.FULLSCREEN:
self.native.toggleFullScreen(self.native)
# elif target_state == WindowState.PRESENTATION:
else:

elif target_state == WindowState.PRESENTATION:
self.interface.app.enter_presentation_mode(
{self.interface.screen: self.interface}
)
# No need to check for other pending states,
# since this is fully applied at this point.
self._pending_state_transition = None

else: # target_state == WindowState.NORMAL:
if current_state == WindowState.MAXIMIZED:
self.native.setIsZoomed(False)
self._apply_state(self._pending_state_transition)

elif current_state == WindowState.MINIMIZED:
self.native.setIsMiniaturized(False)

elif current_state == WindowState.FULLSCREEN:
self.native.toggleFullScreen(self.native)

else: # current_state == WindowState.PRESENTATION:
self.interface.app.exit_presentation_mode()
self._apply_state(self._pending_state_transition)

######################################################################
# Window capabilities
Expand Down
18 changes: 1 addition & 17 deletions cocoa/tests_backend/window.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from rubicon.objc import objc_id, send_message

from toga.constants import WindowState
from toga_cocoa.libs import NSWindow, NSWindowStyleMask

from .dialogs import DialogsMixin
Expand Down Expand Up @@ -48,21 +47,6 @@ def presentation_content_size(self):
self.window.content._impl.native.frame.size.height,
)

def get_window_state(self):
if self.window.content and bool(
self.window.content._impl.native.isInFullScreenMode()
):
current_state = WindowState.PRESENTATION
elif bool(self.native.styleMask & NSWindowStyleMask.FullScreen):
current_state = WindowState.FULLSCREEN
elif bool(self.native.isZoomed):
current_state = WindowState.MAXIMIZED
elif bool(self.native.isMiniaturized):
current_state = WindowState.MINIMIZED
else:
current_state = WindowState.NORMAL
return current_state

@property
def is_resizable(self):
return bool(self.native.styleMask & NSWindowStyleMask.Resizable)
Expand All @@ -77,7 +61,7 @@ def is_minimizable(self):

@property
def is_minimized(self):
return bool(self.get_window_state() == WindowState.MINIMIZED)
return bool(self.native.isMiniaturized)

def minimize(self):
self.native.performMiniaturize(None)
Expand Down
14 changes: 1 addition & 13 deletions core/src/toga/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,8 +346,6 @@ def __init__(
self._main_window = App._UNDEFINED
self._windows = WindowSet(self)

self._full_screen_windows: tuple[Window, ...] | None = None

# Create the implementation. This will trigger any startup logic.
self.factory.App(interface=self)

Expand Down Expand Up @@ -833,18 +831,8 @@ def enter_presentation_mode(
screen_window_dict = dict()
if isinstance(windows, list):
for window, screen in zip(windows, self.screens):
if window.content is None:
raise ValueError(
f"Cannot enter presentation mode on {window.title} window without a content."
)
else:
screen_window_dict[screen] = window
screen_window_dict[screen] = window
elif isinstance(windows, dict):
for _, window in windows.items():
if window.content is None:
raise ValueError(
f"Cannot enter presentation mode on {window.title} window without a content."
)
screen_window_dict = windows
else:
raise ValueError(
Expand Down
6 changes: 1 addition & 5 deletions core/src/toga/constants/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,7 @@ def __str__(self) -> str:


class WindowState(Enum):
"""The possible window states of an app.
Note: Changing window state while the app is in presentation mode will cause
the app to exit presentation mode, and the new window state will be set.
"""
"""The possible window states of an app."""

NORMAL = 0
"""The ``NORMAL`` state represents the default state of the window or app when it is
Expand Down
18 changes: 5 additions & 13 deletions core/src/toga/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -482,22 +482,14 @@ def state(self, state: WindowState) -> None:
WindowState.FULLSCREEN,
WindowState.PRESENTATION,
}:
warnings.warn(
raise RuntimeError(
f"Cannot set window state to {state} of a non-resizable window."
)
elif self.content is None and state == WindowState.PRESENTATION:
warnings.warn(
"Cannot enter presentation mode on a window without a content."
)
else:
# Changing the window state while the app is in presentation mode
# can cause rendering glitches. Hence, first exit presentation and
# then set the new window state.
self.app.exit_presentation_mode()

# State checks are done at the backend because backends like Cocoa use
# non-blocking OS calls. Checking state at the core could occur during
# transitions, leading to incorrect assertions and potential glitches.
# State checks are handled by the backend (e.g., Cocoa) to
# accommodate non-blocking OS calls. Performing these
# checks at the core level could interfere with transitions,
# resulting in incorrect assertions and potential glitches.
self._impl.set_window_state(state)

######################################################################
Expand Down
Loading

0 comments on commit c928ac1

Please sign in to comment.