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

Add OptionContainer for iOS #2259

Merged
merged 18 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from 17 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
1 change: 1 addition & 0 deletions changes/2259.feature.1.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
An OptionContainer widget was added for iOS.
1 change: 1 addition & 0 deletions changes/2259.feature.2.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
OptionContainer content can now be constructed using ``toga.OptionItem`` objects.
1 change: 1 addition & 0 deletions changes/2259.removal.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
When inserting or appending a tab to an OptionContainer, the ``enabled`` argument must now be provided as a keyword argument.
12 changes: 10 additions & 2 deletions cocoa/src/toga_cocoa/widgets/optioncontainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@ def set_bounds(self, x, y, width, height):

def content_refreshed(self, container):
container.min_width = container.content.interface.layout.min_width
container.min_height = container.content.interface.layout.min_width
container.min_height = container.content.interface.layout.min_height

def add_content(self, index, text, widget):
def add_content(self, index, text, widget, icon):
# Create the container for the widget
container = Container(on_refresh=self.content_refreshed)
container.content = widget
Expand Down Expand Up @@ -117,6 +117,14 @@ def set_option_text(self, index, value):
tabview = self.native.tabViewItemAtIndex(index)
tabview.label = value

def set_option_icon(self, index, value):
# Icons aren't supported
pass

def get_option_icon(self, index):
# Icons aren't supported
return None

def get_option_text(self, index):
tabview = self.native.tabViewItemAtIndex(index)
return tabview.label
Expand Down
2 changes: 1 addition & 1 deletion cocoa/src/toga_cocoa/widgets/splitcontainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def set_bounds(self, x, y, width, height):

def content_refreshed(self, container):
container.min_width = container.content.interface.layout.min_width
container.min_height = container.content.interface.layout.min_width
container.min_height = container.content.interface.layout.min_height

def set_content(self, content, flex):
# Clear any existing content
Expand Down
4 changes: 4 additions & 0 deletions cocoa/tests_backend/widgets/optioncontainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,7 @@ def tab_enabled(self, index):
# property lookup mechanism. Invoke it by passing the message directly.
item = self.native.tabViewItemAtIndex(index)
return send_message(item, SEL("_isTabEnabled"), restype=bool, argtypes=[])

def assert_tab_icon(self, index, expected):
# No tab icons, so if anything is returned, that's an error
assert self.widget.content[index].icon is None
2 changes: 2 additions & 0 deletions core/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ dev = [
"pytest-freezer == 0.4.8",
"setuptools-scm == 8.0.4",
"tox == 4.11.4",
# typing-extensions needed for TypeAlias added in Py 3.10
"typing-extensions == 4.9.0 ; python_version < '3.10'"
]
docs = [
"furo == 2023.9.10",
Expand Down
3 changes: 2 additions & 1 deletion core/src/toga/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from .widgets.label import Label
from .widgets.multilinetextinput import MultilineTextInput
from .widgets.numberinput import NumberInput
from .widgets.optioncontainer import OptionContainer
from .widgets.optioncontainer import OptionContainer, OptionItem
from .widgets.passwordinput import PasswordInput
from .widgets.progressbar import ProgressBar
from .widgets.scrollcontainer import ScrollContainer
Expand Down Expand Up @@ -71,6 +71,7 @@
"MultilineTextInput",
"NumberInput",
"OptionContainer",
"OptionItem",
"PasswordInput",
"ProgressBar",
"ScrollContainer",
Expand Down
25 changes: 15 additions & 10 deletions core/src/toga/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import webbrowser
from collections.abc import Collection, Iterator, Mapping, MutableSet
from email.message import Message
from typing import Any, Protocol
from typing import TYPE_CHECKING, Any, Protocol
from warnings import warn

from toga.command import Command, CommandSet
Expand All @@ -20,6 +20,9 @@
from toga.widgets.base import Widget, WidgetRegistry
from toga.window import Window

if TYPE_CHECKING:
from toga.icons import IconContent

# Make sure deprecation warnings are shown by default
warnings.filterwarnings("default", category=DeprecationWarning)

Expand Down Expand Up @@ -246,7 +249,7 @@ def __init__(
app_id: str | None = None,
app_name: str | None = None,
*,
icon: Icon | str | None = None,
icon: IconContent = None,
author: str | None = None,
version: str | None = None,
home_page: str | None = None,
Expand All @@ -262,8 +265,8 @@ def __init__(
:meth:`~toga.App.main_loop()` method, which will start the event loop of your
App.

:param formal_name: The human-readable name of the app. If not provided,
the metadata key ``Formal-Name`` must be present.
:param formal_name: The human-readable name of the app. If not provided, the
metadata key ``Formal-Name`` must be present.
:param app_id: The unique application identifier. This will usually be a
reversed domain name, e.g. ``org.beeware.myapp``. If not provided, the
metadata key ``App-ID`` must be present.
Expand All @@ -277,10 +280,10 @@ def __init__(
For example, an ``app_id`` of ``com.example.my-app`` would yield a
distribution name of ``my-app``.
#. As a last resort, the name ``toga``.
:param icon: The :any:`Icon` for the app. If not provided, Toga will attempt to
load an icon from ``resources/app_name``, where ``app_name`` is defined
above. If no resource matching this name can be found, a warning will be
printed, and the app will fall back to a default icon.
:param icon: The :any:`icon <IconContent>` for the app. If not provided, Toga
will attempt to load an icon from ``resources/app_name``, where ``app_name``
is defined above. If no resource matching this name can be found, a warning
will be printed, and the app will fall back to a default icon.
:param author: The person or organization to be credited as the author of the
app. If not provided, the metadata key ``Author`` will be used.
:param version: The version number of the app. If not provided, the metadata
Expand Down Expand Up @@ -500,13 +503,15 @@ def id(self) -> str:
def icon(self) -> Icon:
"""The Icon for the app.

Can be specified as any valid :any:`icon content <IconContent>`.

When setting the icon, you can provide either an :any:`Icon` instance, or a
path that will be passed to the ``Icon`` constructor.
"""
return self._icon

@icon.setter
def icon(self, icon_or_name: Icon | str) -> None:
def icon(self, icon_or_name: IconContent) -> None:
if isinstance(icon_or_name, Icon):
self._icon = icon_or_name
else:
Expand Down Expand Up @@ -711,7 +716,7 @@ def __init__(
app_id: str | None = None,
app_name: str | None = None,
*,
icon: str | None = None,
icon: IconContent = None,
author: str | None = None,
version: str | None = None,
home_page: str | None = None,
Expand Down
10 changes: 5 additions & 5 deletions core/src/toga/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

if TYPE_CHECKING:
from toga.app import App
from toga.icons import IconContent


class Group:
Expand Down Expand Up @@ -166,7 +167,7 @@ def __init__(
*,
shortcut: str | Key | None = None,
tooltip: str | None = None,
icon: str | Icon | None = None,
icon: IconContent = None,
group: Group = Group.COMMANDS,
section: int = 0,
order: int = 0,
Expand All @@ -183,8 +184,8 @@ def __init__(
:param text: A label for the command.
:param shortcut: A key combination that can be used to invoke the command.
:param tooltip: A short description of what the command will do.
:param icon: The icon, or icon resource, that can be used to decorate the
command if the platform requires.
:param icon: The :any:`icon content <IconContent>` that can be used to decorate
the command if the platform requires.
:param group: The group to which this command belongs.
:param section: The section where the command should appear within its group.
:param order: The position where the command should appear within its section.
Expand Down Expand Up @@ -232,8 +233,7 @@ def enabled(self, value: bool):
def icon(self) -> Icon | None:
"""The Icon for the command.

When setting the icon, you can provide either an :any:`Icon` instance, or a
path that will be passed to the ``Icon`` constructor.
Can be specified as any valid :any:`icon content <IconContent>`.
"""
return self._icon

Expand Down
14 changes: 14 additions & 0 deletions core/src/toga/icons.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
from __future__ import annotations

import sys
from pathlib import Path
from typing import TYPE_CHECKING

import toga
from toga.platform import get_platform_factory

if TYPE_CHECKING:
if sys.version_info < (3, 10):
from typing_extensions import TypeAlias
else:
from typing import TypeAlias
Comment on lines +11 to +14
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can leave as-is if you want (since I'll have to handle all this in #2252) but you don't have to worry about a conditional import. pyupgrade will automatically switch to using only typing to import TypeAlias once 3.9 is no longer supported.


IconContent: TypeAlias = str | Path | toga.Icon | None


class cachedicon:
def __init__(self, f):
Expand Down Expand Up @@ -36,6 +46,10 @@ def TOGA_ICON(cls) -> Icon:
def DEFAULT_ICON(cls) -> Icon:
return Icon("resources/toga", system=True)

@cachedicon
def OPTION_CONTAINER_DEFAULT_TAB_ICON(cls) -> Icon:
return Icon("resources/optioncontainer-tab", system=True)

def __init__(
self,
path: str | Path,
Expand Down
Binary file added core/src/toga/resources/optioncontainer-tab.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading