Skip to content

Icon

Usage

Icons and Images are not the same!

Toga draws a distinction between an Icon and an Image. An Icon is small, square, and might vary between platforms. It is a visual element that is often used as part of an interactive element such as a button, toolbar item, or tab selector - but the Icon itself isn't an interactive element.

An Image, on the other hand, can have an arbitrary size or aspect ratio, and is not platform dependent - the same image will be used on every platform. An Image is not an interactive element, because there is no visual cue to the user that the image can be interacted with.

If you are looking for a widget that the user can click on, you're looking for a widget configured to use an Icon (probably Button), not an on_press handler on an Image or ImageView.

The filename specified for an icon should be specified without an extension; the platform will determine an appropriate extension, and may also modify the name of the icon to include a platform and/or size qualifier.

The following formats are supported (in order of preference):

  • Android - PNG
  • iOS - ICNS, PNG, BMP, ICO
  • macOS - ICNS, PNG, PDF
  • GTK - PNG, ICO, ICNS; 512, 256, 128, 72, 64, 32, and 16px variants of each icon can be provided;
  • Windows - ICO, PNG, BMP

The first matching icon of the most specific platform, with the most specific size will be used. For example, on Windows, specifying an icon of myicon will cause Toga to look for (in order):

  • myicon-windows.ico
  • myicon.ico
  • myicon-windows.png
  • myicon.png
  • myicon-windows.bmp
  • myicon.bmp

On GTK, Toga will perform this lookup for each variant size, falling back to a name without a size specifier if a size-specific variant has not been provided. For example, when resolving the 32px variant, Toga will look for (in order):

  • myicon-linux-32.png
  • myicon-32.png
  • myicon-linux-32.ico
  • myicon-32.ico
  • myicon-linux-32.icns
  • myicon-32.icns
  • myicon.png
  • myicon.ico

Any icon that is found will be resized to the required size. Toga will generate any GTK icon variants that are not available from the highest resolution provided (e.g., if no 128px variant can be found, one will be created by scaling the highest resolution variant that is available).

An icon is guaranteed to have an implementation, regardless of the path specified. If you specify a path and no matching icon can be found, Toga will output a warning to the console, and return DEFAULT_ICON. The only exception to this is if an icon file is found, but it cannot be loaded (e.g., due to a file format or permission error). In this case, an error will be raised.

When you provide a relative path, Toga resolves it relative to the module that defines your toga.App subclass. If you instantiate toga.App directly (without subclassing), there is no separate “app module”, so the relative path is resolved relative to the Toga installation instead of your project.

Reference

Source code in core/src/toga/icons.py
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
class Icon:
    APP_ICON = CachedIcon(_APP_ICON)
    """The application icon.

    The application icon will be loaded from `resources/<app name>` (where `<app
    name>` is the value of [`toga.App.app_name`][]).

    If this resource cannot be found, and the app has been packaged as a binary, the
    icon from the application binary will be used as a fallback.

    Otherwise, [`Icon.DEFAULT_ICON`][toga.Icon.DEFAULT_ICON] will be used.
    """

    DEFAULT_ICON = CachedIcon("toga", system=True)
    """The default icon used as a fallback - Toga's "Tiberius the yak" icon."""

    OPTION_CONTAINER_DEFAULT_TAB_ICON = CachedIcon("optioncontainer-tab", system=True)
    """The default icon used to decorate option container tabs."""

    def __init__(
        self,
        path: str | Path,
        *,
        system: bool = False,  # Deliberately undocumented; for internal use only
    ):
        """Create a new icon.

        :param path: Base filename for the icon. Should not contain an extension
            (If an extension is specified, it will be ignored). Paths can be absolute
            or relative. Relative paths start from the folder where your [`toga.App`][]
            subclass is defined.

            If the icon cannot be found, the default icon will be
            [Icon.DEFAULT_ICON][toga.Icon.DEFAULT_ICON].

            If icon is found, but cannot be loaded (due to a file format
            or permission error), a warning will be emitted and
            [Icon.DEFAULT_ICON][toga.Icon.DEFAULT_ICON] will be used.
        :param system: **For internal use only**
        """
        self.factory = get_factory()
        try:
            # Try to load the icon with the given path snippet. If the request is for
            # the app icon, use `resources/<app name>` as the path.
            if path is _APP_ICON:
                self.path = Path(f"resources/{toga.App.app.app_name}")
            else:
                self.path = Path(path)

            self.system = system
            if self.system:
                if isinstance(self.factory, Factory):
                    resource_path = Path(self.factory.resources.__file__).parent
                else:
                    # doesn't get covered in core tests
                    resource_path = (
                        Path(self.factory.__file__).parent / "resources"
                    )  # pragma: no cover
            else:
                resource_path = toga.App.app.paths.app

            full_path: dict[str, Path] | Path
            if self.factory.Icon.SIZES:
                full_path = {}
                for size in self.factory.Icon.SIZES:
                    try:
                        full_path[size] = self._full_path(
                            size=size,
                            extensions=self.factory.Icon.EXTENSIONS,
                            resource_path=resource_path,
                        )
                    except FileNotFoundError:
                        # This size variant wasn't found; we can skip it
                        pass
            else:
                full_path = self._full_path(
                    size=None,
                    extensions=self.factory.Icon.EXTENSIONS,
                    resource_path=resource_path,
                )

            self._impl = self.factory.Icon(interface=self, path=full_path)
        except (FileNotFoundError, ValueError) as exc:
            # Icon path couldn't be resolved or loaded. If the path is the sentinel
            # for the app icon, and this isn't running as a script, fall back to the
            # application binary.
            if path is _APP_ICON:
                if isinstance(exc, ValueError):
                    fallback = (
                        "application binary icon"
                        if toga.App.app.is_bundled
                        else "default icon"
                    )
                    print(
                        f"WARNING: Unable to load app icon {self.path}; "
                        f"falling back to {fallback}"
                    )
                if toga.App.app.is_bundled:
                    try:
                        # Use the application binary's icon
                        self._impl = self.factory.Icon(interface=self, path=None)
                    except (FileNotFoundError, ValueError):
                        # Can't find the application binary's icon.
                        print(
                            "WARNING: Can't find app icon; falling back to default icon"
                        )
                        self._impl = self.DEFAULT_ICON._impl
                else:
                    self._impl = self.DEFAULT_ICON._impl
            else:
                if isinstance(exc, FileNotFoundError):
                    print(
                        f"WARNING: Can't find icon {self.path}; "
                        f"falling back to default icon"
                    )
                else:
                    print(
                        f"WARNING: Unable to load icon {self.path}; "
                        f"falling back to default icon"
                    )
                self._impl = self.DEFAULT_ICON._impl

    def _full_path(
        self,
        size: str | None,
        extensions: Iterable[str],
        resource_path: Path,
    ) -> Path:
        platform = toga.platform.current_platform
        if size:
            for extension in extensions:
                for filename in [
                    f"{self.path.stem}-{platform}-{size}{extension}",
                    f"{self.path.stem}-{size}{extension}",
                ]:
                    icon_path = resource_path / self.path.parent / filename
                    if icon_path.exists():
                        return icon_path

        # Look for size-less alternatives
        for extension in extensions:
            for filename in [
                f"{self.path.stem}-{platform}{extension}",
                f"{self.path.stem}{extension}",
            ]:
                icon_path = resource_path / self.path.parent / filename
                if icon_path.exists():
                    return icon_path

        raise FileNotFoundError(f"Can't find icon {self.path}")

    def __eq__(self, other: object) -> bool:
        return isinstance(other, Icon) and other._impl.path == self._impl.path

APP_ICON = CachedIcon(_APP_ICON) class-attribute instance-attribute

The application icon.

The application icon will be loaded from resources/<app name> (where <app name> is the value of toga.App.app_name).

If this resource cannot be found, and the app has been packaged as a binary, the icon from the application binary will be used as a fallback.

Otherwise, Icon.DEFAULT_ICON will be used.

DEFAULT_ICON = CachedIcon('toga', system=True) class-attribute instance-attribute

The default icon used as a fallback - Toga's "Tiberius the yak" icon.

OPTION_CONTAINER_DEFAULT_TAB_ICON = CachedIcon('optioncontainer-tab', system=True) class-attribute instance-attribute

The default icon used to decorate option container tabs.

__init__(path, *, system=False)

Create a new icon.

:param path: Base filename for the icon. Should not contain an extension (If an extension is specified, it will be ignored). Paths can be absolute or relative. Relative paths start from the folder where your toga.App subclass is defined.

If the icon cannot be found, the default icon will be
[Icon.DEFAULT_ICON][toga.Icon.DEFAULT_ICON].

If icon is found, but cannot be loaded (due to a file format
or permission error), a warning will be emitted and
[Icon.DEFAULT_ICON][toga.Icon.DEFAULT_ICON] will be used.

:param system: For internal use only

Source code in core/src/toga/icons.py
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
def __init__(
    self,
    path: str | Path,
    *,
    system: bool = False,  # Deliberately undocumented; for internal use only
):
    """Create a new icon.

    :param path: Base filename for the icon. Should not contain an extension
        (If an extension is specified, it will be ignored). Paths can be absolute
        or relative. Relative paths start from the folder where your [`toga.App`][]
        subclass is defined.

        If the icon cannot be found, the default icon will be
        [Icon.DEFAULT_ICON][toga.Icon.DEFAULT_ICON].

        If icon is found, but cannot be loaded (due to a file format
        or permission error), a warning will be emitted and
        [Icon.DEFAULT_ICON][toga.Icon.DEFAULT_ICON] will be used.
    :param system: **For internal use only**
    """
    self.factory = get_factory()
    try:
        # Try to load the icon with the given path snippet. If the request is for
        # the app icon, use `resources/<app name>` as the path.
        if path is _APP_ICON:
            self.path = Path(f"resources/{toga.App.app.app_name}")
        else:
            self.path = Path(path)

        self.system = system
        if self.system:
            if isinstance(self.factory, Factory):
                resource_path = Path(self.factory.resources.__file__).parent
            else:
                # doesn't get covered in core tests
                resource_path = (
                    Path(self.factory.__file__).parent / "resources"
                )  # pragma: no cover
        else:
            resource_path = toga.App.app.paths.app

        full_path: dict[str, Path] | Path
        if self.factory.Icon.SIZES:
            full_path = {}
            for size in self.factory.Icon.SIZES:
                try:
                    full_path[size] = self._full_path(
                        size=size,
                        extensions=self.factory.Icon.EXTENSIONS,
                        resource_path=resource_path,
                    )
                except FileNotFoundError:
                    # This size variant wasn't found; we can skip it
                    pass
        else:
            full_path = self._full_path(
                size=None,
                extensions=self.factory.Icon.EXTENSIONS,
                resource_path=resource_path,
            )

        self._impl = self.factory.Icon(interface=self, path=full_path)
    except (FileNotFoundError, ValueError) as exc:
        # Icon path couldn't be resolved or loaded. If the path is the sentinel
        # for the app icon, and this isn't running as a script, fall back to the
        # application binary.
        if path is _APP_ICON:
            if isinstance(exc, ValueError):
                fallback = (
                    "application binary icon"
                    if toga.App.app.is_bundled
                    else "default icon"
                )
                print(
                    f"WARNING: Unable to load app icon {self.path}; "
                    f"falling back to {fallback}"
                )
            if toga.App.app.is_bundled:
                try:
                    # Use the application binary's icon
                    self._impl = self.factory.Icon(interface=self, path=None)
                except (FileNotFoundError, ValueError):
                    # Can't find the application binary's icon.
                    print(
                        "WARNING: Can't find app icon; falling back to default icon"
                    )
                    self._impl = self.DEFAULT_ICON._impl
            else:
                self._impl = self.DEFAULT_ICON._impl
        else:
            if isinstance(exc, FileNotFoundError):
                print(
                    f"WARNING: Can't find icon {self.path}; "
                    f"falling back to default icon"
                )
            else:
                print(
                    f"WARNING: Unable to load icon {self.path}; "
                    f"falling back to default icon"
                )
            self._impl = self.DEFAULT_ICON._impl

When specifying an Icon, you can provide:

  • a string specifying an absolute or relative path;
  • an absolute or relative [pathlib.Path][] object; or
  • an instance of toga.Icon.

If a relative path is provided, it will be anchored relative to the module that defines your Toga application class.