Skip to content
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

Use a single base class for all app types #2244

Closed
wants to merge 12 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 28 additions & 19 deletions android/src/toga_android/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,11 @@
from java import dynamic_proxy
from org.beeware.android import IPythonApp, MainActivity

import toga
from toga.command import Command, Group, Separator

from .libs import events
from .screens import Screen as ScreenImpl
from .window import Window


class MainWindow(Window):
_is_main_window = True


class TogaApp(dynamic_proxy(IPythonApp)):
Expand Down Expand Up @@ -95,6 +91,11 @@ def onOptionsItemSelected(self, menuitem):
return True

def onPrepareOptionsMenu(self, menu):
# If the main window doesn't have a toolbar, there's no preparation required;
# this is a simple main window, which can't have commands.
if not hasattr(self._impl.interface.main_window, "toolbar"):
return False

menu.clear()
itemid = 1 # 0 is the same as Menu.NONE.
groupid = 1
Expand Down Expand Up @@ -179,6 +180,9 @@ def onPrepareOptionsMenu(self, menu):


class App:
# Android apps exit when the last window is closed
CLOSE_ON_LAST_WINDOW = True

def __init__(self, interface):
self.interface = interface
self.interface._impl = self
Expand All @@ -190,14 +194,6 @@ def __init__(self, interface):
def native(self):
return self._listener.native if self._listener else None

def create(self):
# The `_listener` listens for activity event callbacks. For simplicity,
# the app's `.native` is the listener's native Java class.
self._listener = TogaApp(self)
# Call user code to populate the main window
self.interface._startup()
self.create_app_commands()

######################################################################
# Commands and menus
######################################################################
Expand All @@ -206,14 +202,15 @@ def create_app_commands(self):
self.interface.commands.add(
# About should be the last item in the menu, in a section on its own.
Command(
lambda _: self.interface.about(),
self.interface._menu_about,
f"About {self.interface.formal_name}",
section=sys.maxsize,
),
)

def create_menus(self):
self.native.invalidateOptionsMenu() # Triggers onPrepareOptionsMenu
# On Android, menus and toolbars are populated using the same mechanism.
self.interface.main_window._impl.create_toolbar()

######################################################################
# App lifecycle
Expand All @@ -222,16 +219,28 @@ def create_menus(self):
def exit(self):
pass # pragma: no cover

def finalize(self):
self.create_app_commands()
self.create_menus()

def main_loop(self):
# In order to support user asyncio code, start the Python/Android cooperative event loop.
self.loop.run_forever_cooperatively()

# On Android, Toga UI integrates automatically into the main Android event loop by virtue
# of the Android Activity system.
self.create()
# On Android, Toga UI integrates automatically into the main Android event loop
# by virtue of the Android Activity system. The `_listener` listens for activity
# event callbacks. For simplicity, the app's `.native` is the listener's native
# Java class.
self._listener = TogaApp(self)

# Call user code to populate the main window
self.interface._startup()

def set_main_window(self, window):
pass
if window is None:
raise RuntimeError("Session-based apps are not supported on Android")
elif window == toga.App.BACKGROUND:
raise RuntimeError("Background apps are not supported on Android")

######################################################################
# App resources
Expand Down
7 changes: 4 additions & 3 deletions android/src/toga_android/factory.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from toga import NotImplementedWarning

from . import dialogs
from .app import App, MainWindow
from .app import App
from .command import Command
from .fonts import Font
from .hardware.camera import Camera
Expand Down Expand Up @@ -30,7 +30,7 @@
from .widgets.textinput import TextInput
from .widgets.timeinput import TimeInput
from .widgets.webview import WebView
from .window import Window
from .window import MainWindow, Window


def not_implemented(feature):
Expand All @@ -40,7 +40,6 @@ def not_implemented(feature):
__all__ = [
"App",
"Command",
"MainWindow",
"not_implemented",
# Resources
"dialogs",
Expand Down Expand Up @@ -76,7 +75,9 @@ def not_implemented(feature):
"TimeInput",
# "Tree",
"WebView",
# Windows
"Window",
"MainWindow",
]


Expand Down
30 changes: 21 additions & 9 deletions android/src/toga_android/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,12 @@ def onGlobalLayout(self):


class Window(Container):
_is_main_window = False

def __init__(self, interface, title, position, size):
super().__init__()
self.interface = interface
self.interface._impl = self
self._initial_title = title

if not self._is_main_window:
raise RuntimeError(
"Secondary windows cannot be created on mobile platforms"
)

######################################################################
# Window properties
######################################################################
Expand All @@ -60,17 +53,27 @@ def close(self):
pass

def create_toolbar(self):
self.app.native.invalidateOptionsMenu()
# Simple windows don't have a titlebar
pass

def _configure_titlebar(self):
# Simple windows hide the titlebar.
self.app.native.getSupportActionBar().hide()

def set_app(self, app):
if len(app.interface.windows) > 1:
raise RuntimeError("Secondary windows cannot be created on Android")

self.app = app
native_parent = app.native.findViewById(R.id.content)
native_parent = self.app.native.findViewById(R.id.content)
self.init_container(native_parent)
native_parent.getViewTreeObserver().addOnGlobalLayoutListener(
LayoutListener(self)
)
self.set_title(self._initial_title)

self._configure_titlebar()

def show(self):
pass

Expand Down Expand Up @@ -155,3 +158,12 @@ def get_image_data(self):
stream = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 0, stream)
return bytes(stream.toByteArray())


class MainWindow(Window):
def _configure_titlebar(self):
# The titlebar will be visible by default.
pass

def create_toolbar(self):
self.app.native.invalidateOptionsMenu()
1 change: 1 addition & 0 deletions changes/1870.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Apps can now create a main window that does not have a menu bar.
1 change: 1 addition & 0 deletions changes/2209.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The API for document-based apps has been finalized.
1 change: 1 addition & 0 deletions changes/2209.removal.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The separate ``toga.DocumentApp`` base class is no longer required. All uses of ``toga.DocumentApp`` can be replaced with ``toga.App`.
Loading
Loading