diff --git a/core/src/toga/app.py b/core/src/toga/app.py index 6980ac577b..79e972ca44 100644 --- a/core/src/toga/app.py +++ b/core/src/toga/app.py @@ -27,7 +27,7 @@ from toga.window import OnCloseHandler, Window if TYPE_CHECKING: - from toga.icons import IconContent + from toga.icons import IconContentT from toga.types import PositionT, SizeT # Make sure deprecation warnings are shown by default @@ -314,7 +314,7 @@ def __init__( app_id: str | None = None, app_name: str | None = None, *, - icon: IconContent | None = None, + icon: IconContentT | None = None, author: str | None = None, version: str | None = None, home_page: str | None = None, @@ -345,7 +345,7 @@ 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. Defaults to + :param icon: The :any:`icon ` for the app. Defaults to :attr:`toga.Icon.APP_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. @@ -535,12 +535,12 @@ def home_page(self) -> str | None: def icon(self) -> Icon: """The Icon for the app. - Can be specified as any valid :any:`icon content `. + Can be specified as any valid :any:`icon content `. """ return self._icon @icon.setter - def icon(self, icon_or_name: IconContent) -> None: + def icon(self, icon_or_name: IconContentT) -> None: if isinstance(icon_or_name, Icon): self._icon = icon_or_name else: @@ -855,7 +855,7 @@ def __init__( app_id: str | None = None, app_name: str | None = None, *, - icon: IconContent | None = None, + icon: IconContentT | None = None, author: str | None = None, version: str | None = None, home_page: str | None = None, diff --git a/core/src/toga/command.py b/core/src/toga/command.py index 167bde400d..ba6bb5f956 100644 --- a/core/src/toga/command.py +++ b/core/src/toga/command.py @@ -10,7 +10,7 @@ if TYPE_CHECKING: from toga.app import App - from toga.icons import IconContent + from toga.icons import IconContentT class Group: @@ -167,7 +167,7 @@ def __init__( *, shortcut: str | Key | None = None, tooltip: str | None = None, - icon: IconContent | None = None, + icon: IconContentT | None = None, group: Group = Group.COMMANDS, section: int = 0, order: int = 0, @@ -184,7 +184,7 @@ 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 :any:`icon content ` that can be used to decorate + :param icon: The :any:`icon content ` 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. @@ -234,12 +234,12 @@ def enabled(self, value: bool) -> None: def icon(self) -> Icon | None: """The Icon for the command. - Can be specified as any valid :any:`icon content `. + Can be specified as any valid :any:`icon content `. """ return self._icon @icon.setter - def icon(self, icon_or_name: IconContent | None) -> None: + def icon(self, icon_or_name: IconContentT | None) -> None: if isinstance(icon_or_name, Icon) or icon_or_name is None: self._icon = icon_or_name else: diff --git a/core/src/toga/icons.py b/core/src/toga/icons.py index 810322bc54..8d45474eb7 100644 --- a/core/src/toga/icons.py +++ b/core/src/toga/icons.py @@ -15,7 +15,7 @@ else: from typing import TypeAlias - IconContent: TypeAlias = str | Path | toga.Icon + IconContentT: TypeAlias = str | Path | toga.Icon class cachedicon: diff --git a/core/src/toga/images.py b/core/src/toga/images.py index c2c3fb4f97..eb4d9fc668 100644 --- a/core/src/toga/images.py +++ b/core/src/toga/images.py @@ -1,6 +1,7 @@ from __future__ import annotations import importlib +import os import sys import warnings from functools import lru_cache @@ -31,10 +32,10 @@ ImageT = TypeVar("ImageT") # Define the types that can be used as Image content - PathLike: TypeAlias = str | Path - BytesLike: TypeAlias = bytes | bytearray | memoryview - ImageLike: TypeAlias = Any - ImageContent: TypeAlias = PathLike | BytesLike | ImageLike + PathLikeT: TypeAlias = str | os.PathLike + BytesLikeT: TypeAlias = bytes | bytearray | memoryview + ImageLikeT: TypeAlias = Any + ImageContentT: TypeAlias = PathLikeT | BytesLikeT | ImageLikeT # Define a type variable representing an image of an externally defined type. ExternalImageT = TypeVar("ExternalImageT") @@ -49,7 +50,7 @@ class ImageConverter(Protocol): image_class: type[ExternalImageT] @staticmethod - def convert_from_format(image_in_format: ExternalImageT) -> BytesLike: + def convert_from_format(image_in_format: ExternalImageT) -> BytesLikeT: """Convert from :any:`image_class` to data in a :ref:`known image format `. @@ -61,7 +62,7 @@ def convert_from_format(image_in_format: ExternalImageT) -> BytesLike: @staticmethod def convert_to_format( - data: BytesLike, + data: BytesLikeT, image_class: type[ExternalImageT], ) -> ExternalImageT: """Convert from data to :any:`image_class` or specified subclass. @@ -83,7 +84,7 @@ def convert_to_format( class Image: def __init__( self, - src: ImageContent = NOT_PROVIDED, + src: ImageContentT = NOT_PROVIDED, *, path: object = NOT_PROVIDED, # DEPRECATED data: object = NOT_PROVIDED, # DEPRECATED @@ -91,7 +92,7 @@ def __init__( """Create a new image. :param src: The source from which to load the image. Can be any valid - :any:`image content ` type. + :any:`image content ` type. :param path: **DEPRECATED** - Use ``src``. :param data: **DEPRECATED** - Use ``src``. :raises FileNotFoundError: If a path is provided, but that path does not exist. diff --git a/core/src/toga/plugins/image_formats.py b/core/src/toga/plugins/image_formats.py index 4bd5778397..4787a3267b 100644 --- a/core/src/toga/plugins/image_formats.py +++ b/core/src/toga/plugins/image_formats.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from toga.images import BytesLike + from toga.images import BytesLikeT # Presumably, other converter plugins will be included with, or only installed # alongside, the packages they're for. But since this is provided in Toga, we need to @@ -29,7 +29,7 @@ def convert_from_format(image_in_format: PIL.Image.Image) -> bytes: @staticmethod def convert_to_format( - data: BytesLike, + data: BytesLikeT, image_class: type[PIL.Image.Image], ) -> PIL.Image.Image: # PIL Images aren't designed to be subclassed, so no implementation is necessary diff --git a/core/src/toga/widgets/button.py b/core/src/toga/widgets/button.py index 6f34389d8a..c6ab49b7b1 100644 --- a/core/src/toga/widgets/button.py +++ b/core/src/toga/widgets/button.py @@ -8,7 +8,7 @@ from .base import StyleT, Widget if TYPE_CHECKING: - from toga.icons import IconContent + from toga.icons import IconContentT class OnPressHandler(Protocol): @@ -24,7 +24,7 @@ class Button(Widget): def __init__( self, text: str | None = None, - icon: IconContent | None = None, + icon: IconContentT | None = None, id: str | None = None, style: StyleT | None = None, on_press: toga.widgets.button.OnPressHandler | None = None, @@ -34,7 +34,7 @@ def __init__( :param text: The text to display on the button. :param icon: The icon to display on the button. Can be specified as any valid - :any:`icon content `. + :any:`icon content `. :param id: The ID for the widget. :param style: A style object. If no style is provided, a default style will be applied to the widget. @@ -100,7 +100,7 @@ def text(self, value: str | None) -> None: def icon(self) -> toga.Icon | None: """The icon displayed on the button. - Can be specified as any valid :any:`icon content `. + Can be specified as any valid :any:`icon content `. If the button is currently displaying text, and an icon is assigned, the text will be replaced by the new icon. @@ -113,7 +113,7 @@ def icon(self) -> toga.Icon | None: return self._impl.get_icon() @icon.setter - def icon(self, value: IconContent | None) -> None: + def icon(self, value: IconContentT | None) -> None: if isinstance(value, toga.Icon): icon = value text = "" diff --git a/core/src/toga/widgets/imageview.py b/core/src/toga/widgets/imageview.py index 6826a3c32d..663b3d1071 100644 --- a/core/src/toga/widgets/imageview.py +++ b/core/src/toga/widgets/imageview.py @@ -9,7 +9,7 @@ from toga.widgets.base import StyleT, Widget if TYPE_CHECKING: - from toga.images import ImageContent, ImageT + from toga.images import ImageContentT, ImageT def rehint_imageview( @@ -70,7 +70,7 @@ def rehint_imageview( class ImageView(Widget): def __init__( self, - image: ImageContent | None = None, + image: ImageContentT | None = None, id: str | None = None, style: StyleT | None = None, ): @@ -78,7 +78,7 @@ def __init__( Create a new image view. :param image: The image to display. Can be any valid :any:`image content - ` type; or :any:`None` to display no image. + ` type; or :any:`None` to display no image. :param id: The ID for the widget. :param style: A style object. If no style is provided, a default style will be applied to the widget. @@ -111,12 +111,12 @@ def image(self) -> toga.Image | None: """The image to display. When setting an image, you can provide any valid :any:`image content - ` type; or :any:`None` to clear the image view. + ` type; or :any:`None` to clear the image view. """ return self._image @image.setter - def image(self, image: ImageContent) -> None: + def image(self, image: ImageContentT) -> None: if isinstance(image, toga.Image): self._image = image elif image is None: diff --git a/core/src/toga/widgets/numberinput.py b/core/src/toga/widgets/numberinput.py index b50f556219..8a9907fe67 100644 --- a/core/src/toga/widgets/numberinput.py +++ b/core/src/toga/widgets/numberinput.py @@ -17,8 +17,7 @@ else: from typing import TypeAlias - NumberInputT: TypeAlias = Union[Decimal, float, str] - StepInputT: TypeAlias = Union[Decimal, int] + NumberInputT: TypeAlias = Union[Decimal, int, float, str] # Implementation notes # ==================== @@ -35,7 +34,7 @@ NUMERIC_RE = re.compile(r"[^0-9\.-]") -def _clean_decimal(value: NumberInputT, step: StepInputT | None = None) -> Decimal: +def _clean_decimal(value: NumberInputT, step: NumberInputT | None = None) -> Decimal: # Decimal(3.7) yields "3.700000000...177". # However, Decimal(str(3.7)) yields "3.7". If the user provides a float, # convert to a string first. @@ -87,7 +86,7 @@ def __init__( self, id: str | None = None, style: StyleT | None = None, - step: StepInputT = 1, + step: NumberInputT = 1, min: NumberInputT | None = None, max: NumberInputT | None = None, value: NumberInputT | None = None, @@ -181,7 +180,7 @@ def step(self) -> Decimal: return self._step @step.setter - def step(self, step: StepInputT) -> None: + def step(self, step: NumberInputT) -> None: try: self._step = _clean_decimal(step) except (ValueError, TypeError, InvalidOperation): diff --git a/core/src/toga/widgets/optioncontainer.py b/core/src/toga/widgets/optioncontainer.py index 5de0337fd1..8716ebb7c6 100644 --- a/core/src/toga/widgets/optioncontainer.py +++ b/core/src/toga/widgets/optioncontainer.py @@ -15,12 +15,12 @@ from typing_extensions import TypeAlias else: from typing import TypeAlias - from toga.icons import IconContent + from toga.icons import IconContentT - OptionContainerContent: TypeAlias = ( + OptionContainerContentT: TypeAlias = ( tuple[str, Widget] - | tuple[str, Widget, IconContent | None] - | tuple[str, Widget, IconContent | None, bool] + | tuple[str, Widget, IconContentT | None] + | tuple[str, Widget, IconContentT | None, bool] | toga.OptionItem ) @@ -40,14 +40,14 @@ def __init__( text: str, content: Widget, *, - icon: IconContent | None = None, + icon: IconContentT | None = None, enabled: bool = True, ): """A tab of content in an OptionContainer. :param text: The text label for the new tab. :param content: The content widget to use for the new tab. - :param icon: The :any:`icon content ` to use to represent the tab. + :param icon: The :any:`icon content ` to use to represent the tab. :param enabled: Should the new tab be enabled? """ if content is None: @@ -128,7 +128,7 @@ def text(self, value: object) -> None: def icon(self) -> toga.Icon: """The Icon for the tab of content. - Can be specified as any valid :any:`icon content `. + Can be specified as any valid :any:`icon content `. If the platform does not support the display of icons, this property will return ``None`` regardless of any value provided. @@ -139,7 +139,7 @@ def icon(self) -> toga.Icon: return self._interface._impl.get_option_icon(self.index) @icon.setter - def icon(self, icon_or_name: IconContent | None) -> None: + def icon(self, icon_or_name: IconContentT | None) -> None: if get_platform_factory().OptionContainer.uses_icons: if icon_or_name is None: icon = None @@ -274,7 +274,7 @@ def append( text_or_item: str, content: Widget, *, - icon: IconContent | None = None, + icon: IconContentT | None = None, enabled: bool | None = True, ) -> None: ... @@ -283,7 +283,7 @@ def append( text_or_item: str | OptionItem, content: Widget | None = None, *, - icon: IconContent | None = None, + icon: IconContentT | None = None, enabled: bool | None = None, ) -> None: """Add a new tab of content to the OptionContainer. @@ -295,7 +295,7 @@ def append( :param text_or_item: An :any:`OptionItem`; or, the text label for the new tab. :param content: The content widget to use for the new tab. - :param icon: The :any:`icon content ` to use to represent the tab. + :param icon: The :any:`icon content ` to use to represent the tab. :param enabled: Should the new tab be enabled? (Default: ``True``) """ self.insert(len(self), text_or_item, content, icon=icon, enabled=enabled) @@ -314,7 +314,7 @@ def insert( text_or_item: str, content: Widget, *, - icon: IconContent | None = None, + icon: IconContentT | None = None, enabled: bool | None = True, ) -> None: ... @@ -324,7 +324,7 @@ def insert( text_or_item: str | OptionItem, content: Widget | None = None, *, - icon: IconContent | None = None, + icon: IconContentT | None = None, enabled: bool | None = None, ) -> None: """Insert a new tab of content to the OptionContainer at the specified index. @@ -337,7 +337,7 @@ def insert( :param index: The index where the new tab should be inserted. :param text_or_item: An :any:`OptionItem`; or, the text label for the new tab. :param content: The content widget to use for the new tab. - :param icon: The :any:`icon content ` to use to represent the tab. + :param icon: The :any:`icon content ` to use to represent the tab. :param enabled: Should the new tab be enabled? (Default: ``True``) """ if isinstance(text_or_item, OptionItem): @@ -380,7 +380,7 @@ def __init__( self, id: str | None = None, style: StyleT | None = None, - content: Iterable[OptionContainerContent] | None = None, + content: Iterable[OptionContainerContentT] | None = None, on_select: toga.widgets.optioncontainer.OnSelectHandler | None = None, ): """Create a new OptionContainer. @@ -389,7 +389,7 @@ def __init__( :param style: A style object. If no style is provided, a default style will be applied to the widget. :param content: The initial :any:`OptionContainer content - ` to display in the OptionContainer. + ` to display in the OptionContainer. :param on_select: Initial :any:`on_select` handler. """ super().__init__(id=id, style=style) diff --git a/core/src/toga/widgets/splitcontainer.py b/core/src/toga/widgets/splitcontainer.py index 7dc120d667..04589bc737 100644 --- a/core/src/toga/widgets/splitcontainer.py +++ b/core/src/toga/widgets/splitcontainer.py @@ -1,7 +1,7 @@ from __future__ import annotations import sys -from typing import TYPE_CHECKING, Tuple, Union +from typing import TYPE_CHECKING from toga.app import App from toga.constants import Direction @@ -15,7 +15,7 @@ else: from typing import TypeAlias - ContentT: TypeAlias = Union[Widget, Tuple[Widget, float], None] + SplitContainerContentT: TypeAlias = Widget | tuple[Widget, float] | None class SplitContainer(Widget): @@ -27,7 +27,7 @@ def __init__( id: str | None = None, style: StyleT | None = None, direction: Direction = Direction.VERTICAL, - content: tuple[ContentT, ContentT] = (None, None), + content: tuple[SplitContainerContentT, SplitContainerContentT] = (None, None), ): """Create a new SplitContainer. @@ -38,11 +38,14 @@ def __init__( :attr:`~toga.constants.Direction.HORIZONTAL` or :attr:`~toga.constants.Direction.VERTICAL`; defaults to :attr:`~toga.constants.Direction.VERTICAL` - :param content: Initial :any:`content` of the container. Defaults to both panels - being empty. + :param content: Initial :any:`SplitContainer content ` + of the container. Defaults to both panels being empty. """ super().__init__(id=id, style=style) - self._content: tuple[ContentT, ContentT] = (None, None) + self._content: tuple[SplitContainerContentT, SplitContainerContentT] = ( + None, + None, + ) # Create a platform specific implementation of a SplitContainer self._impl = self.factory.SplitContainer(interface=self) @@ -68,7 +71,7 @@ def focus(self) -> None: pass @property - def content(self) -> tuple[ContentT, ContentT]: + def content(self) -> tuple[SplitContainerContentT, SplitContainerContentT]: """The widgets displayed in the SplitContainer. This property accepts a sequence of exactly 2 elements, each of which can be @@ -86,7 +89,9 @@ def content(self) -> tuple[ContentT, ContentT]: return self._content @content.setter - def content(self, content: tuple[ContentT, ContentT]) -> None: + def content( + self, content: tuple[SplitContainerContentT, SplitContainerContentT] + ) -> None: try: if len(content) != 2: raise TypeError() diff --git a/docs/reference/api/containers/optioncontainer.rst b/docs/reference/api/containers/optioncontainer.rst index 728a53d51e..b6e3d79a0e 100644 --- a/docs/reference/api/containers/optioncontainer.rst +++ b/docs/reference/api/containers/optioncontainer.rst @@ -174,14 +174,14 @@ Notes Reference --------- -.. c:type:: OptionContainerContent +.. c:type:: OptionContainerContentT An item of :any:`OptionContainer` content can be: * a 2-tuple, containing the title for the tab, and the content widget; - * a 3-tuple, containing the title, content widget, and :any:`icon ` + * a 3-tuple, containing the title, content widget, and :any:`icon ` for the tab; - * a 4-tuple, containing the title, content widget, :any:`icon ` for + * a 4-tuple, containing the title, content widget, :any:`icon ` for the tab, and enabled status; or * an :any:`toga.OptionItem` instance. diff --git a/docs/reference/api/containers/splitcontainer.rst b/docs/reference/api/containers/splitcontainer.rst index 48c0ea8e13..6d04683daf 100644 --- a/docs/reference/api/containers/splitcontainer.rst +++ b/docs/reference/api/containers/splitcontainer.rst @@ -49,7 +49,7 @@ Usage left_container = toga.Box() right_container = toga.ScrollContainer() - split = toga.SplitContainer(content=[left_container, right_container]) + split = toga.SplitContainer(content=(left_container, right_container)) Content can be specified when creating the widget, or after creation by assigning the ``content`` attribute. The direction of the split can also be configured, either @@ -65,7 +65,7 @@ at time of creation, or by setting the ``direction`` attribute: left_container = toga.Box() right_container = toga.ScrollContainer() - split.content = [left_container, right_container] + split.content = (left_container, right_container) By default, the space of the SplitContainer will be evenly divided between the two panels. To specify an uneven split, you can provide a flex value when specifying @@ -80,7 +80,7 @@ and right panels. left_container = toga.Box() right_container = toga.ScrollContainer() - split.content = [(left_container, 3), (right_container, 2)] + split.content = ((left_container, 3), (right_container, 2)) This only specifies the initial split; the split can be modified by the user once it is displayed. @@ -88,5 +88,12 @@ once it is displayed. Reference --------- +.. c:type:: SplitContainerContentT + + An item of :any:`SplitContainer` content can be: + + * a :class:`~toga.Widget`; or + * a 2-tuple, containing a :class:`~toga.Widget`, and an :any:`int` flex value + .. autoclass:: toga.SplitContainer :exclude-members: HORIZONTAL, VERTICAL, window, app diff --git a/docs/reference/api/resources/icons.rst b/docs/reference/api/resources/icons.rst index 90cdfad228..3f517d4ef7 100644 --- a/docs/reference/api/resources/icons.rst +++ b/docs/reference/api/resources/icons.rst @@ -80,7 +80,7 @@ permission error). In this case, an error will be raised. Reference --------- -.. c:type:: IconContent +.. c:type:: IconContentT When specifying an :any:`Icon`, you can provide: diff --git a/docs/reference/api/resources/images.rst b/docs/reference/api/resources/images.rst index 59aa204451..498d2ccee8 100644 --- a/docs/reference/api/resources/images.rst +++ b/docs/reference/api/resources/images.rst @@ -30,7 +30,7 @@ Usage widget configured to use an Icon (probably :class:`~toga.Button`), *not* an ``on_press`` handler on an :class:`~toga.Image` or :class:`~toga.ImageView`. -An image can be constructed from a :any:`wide range of sources `: +An image can be constructed from a :any:`wide range of sources `: .. code-block:: python @@ -86,13 +86,13 @@ Notes Reference --------- -.. c:type:: ImageContent +.. c:type:: ImageContentT When specifying content for an :any:`Image`, you can provide: * a string specifying an absolute or relative path to a file in a :ref:`known image format `; - * an absolute or relative :any:`pathlib.Path` object describing a file in a + * an absolute or relative :class:`~pathlib.Path` object describing a file in a :ref:`known image format `; * a "blob of bytes" data type (:any:`bytes`, :any:`bytearray`, or :any:`memoryview`) containing raw image data in a :ref:`known image format `; diff --git a/docs/reference/api/widgets/numberinput.rst b/docs/reference/api/widgets/numberinput.rst index 950aad45b0..77b532a693 100644 --- a/docs/reference/api/widgets/numberinput.rst +++ b/docs/reference/api/widgets/numberinput.rst @@ -53,9 +53,9 @@ Usage widget = toga.NumberInput(min_value=1, max_value=10, step=0.001) widget.value = 2.718 -NumberInput's properties can accept integers, floats, and strings containing -numbers, but they always return :any:`decimal.Decimal` objects to ensure -precision is retained. +NumberInput's properties can accept :class:`~decimal.Decimal`, :any:`int`, :any:`float`, +or :any:`str` containing numbers, but they always return :class:`~decimal.Decimal` +objects to ensure precision is retained. Reference --------- diff --git a/dummy/src/toga_dummy/plugins/image_formats.py b/dummy/src/toga_dummy/plugins/image_formats.py index d58686b739..923ceb418c 100644 --- a/dummy/src/toga_dummy/plugins/image_formats.py +++ b/dummy/src/toga_dummy/plugins/image_formats.py @@ -6,7 +6,7 @@ import toga if TYPE_CHECKING: - from toga.images import BytesLike + from toga.images import BytesLikeT class CustomImage: @@ -26,7 +26,7 @@ def convert_from_format(image_in_format: CustomImage): @staticmethod def convert_to_format( - data: BytesLike, + data: BytesLikeT, image_class: type[CustomImage], ) -> CustomImage: image = image_class() @@ -45,7 +45,7 @@ def convert_from_format(image_in_format: Any): @staticmethod def convert_to_format( - data: BytesLike, + data: BytesLikeT, image_class: type[Any], ) -> Any: raise Exception("Converter should be disabled")