Improve icon handling API
- Dominant language
- Python
- Stars
- 5.4k
- Forks
- 827
- Avg merge
- 9h 45m
- Merged PRs (30d)
- 58
Description
### What is the problem or limitation you are having?
At present, Toga has inconsistent icon handling across each platform.
On macOS and iOS, the preferred icon is `.icns` format, which *should* be a multi-resolution format; however, bugs like #3786 suggest there is some inconsistent handling of that sizing. If an explicit size is requested, the `_as_size()` method is used to generate on the Icon implementation class.
On GTK, icons are expected to be provided in multiple sizes, and are cached into a dictionary of those sizes. A `native()` method exists to retrieve images of a specific size, caching scaled versions of new sizes that weren't explicitly provided. If a single "multi-sized" icon is found, that is enough to trigger the existence of a source image; so if there's a single 16px icon, that will be used as the source for a 512px icon size request. If a single *bad* multi-sized icon is found (e.g., a corrupt 32px icon when there is a valid 16, 64, and 128px icon), that renders the entire icon set as corrupt, and a fallback icon will be used.
On Windows, the preferred icon format is `.ico`, which *can* be a multi-resolution format - but in practice, it never is, and Toga's Winforms backend doesn't treat it as one. When icons of a specific size are needed (e.g, Button icons need a 32 px icon), they are scaled on-demand using ad-hoc resize methods.
On Android, the preferred icon format is `.png`, but there's no handling for different sizes. Icons of a specific size are generated on demand using an `as_drawable()` method on the Icon class.
On Qt, there's a wide range of image formats available, but no handling for different sizes, *unless* you're looking at the app icon, where the largest available icon is used.
Toga should have a consistent behavior for icon access and use.
### Describe the solution you'd like
There should be a consistent implementation-level API across all platforms for requesting a native icon on all platforms. `native(size)` on implementation classes is as good a name as any.
This API should cache native icon instances where possible, and should generate scaled icons from the most appropriate available size.
Any resizing should only ever be done from an image larger than the requested size, from the *closest* size match, from an image that was provided in original form. That is, if you have a 512, 256 and 32px image, requesting a 64px image would result in a scaled version based on scaling the 256px image (the closest larger image). That image would go into the cache; a subsequent request for a 48px image would *also* result in a scaled version based on the 256px image, because the 64px image would not be considered a candidate for scaling input.
In the event of a cache miss, we should raise a warning.
If there is no *larger* image available, the largest available image should be used - but should also raise a warning indicating that upscaling has occurred.
When an image format is "multi-sized" (e.g. .icns), we should only raise warnings for a cache miss if we actually need to do an image resize. If the multi-format image actually provides the requested size, it should be used. The available sizes should be "unrolled" into the cached "source candidate" list.
If Toga can find a 64px PNG and a 32px ICO with the same icon prefix, they should both be used (at present, all PNG or all ICO is used) as input sizes.
We currently use `toga.platform.current_platform` as a platform specifier; we should take this opportunity to (a) switch to using `sys.platform`, and (b) take the opportunity to add backend-based handling (to differentiate GTK from Qt). If a backend-specific image is available, it should be used in preference to a `sys.platform`-based image; if there's a `sys.platform`-based image, it should be used in preference to a `toga.platform.current_platform`-based image; if there's a `current_platform`-based image, it should be used in preference to a "bare" image.
Images should only be loaded on demand, rather than loading all sizes for all formats. The best approach may be to load the list of *available* images at time of Icon construction; at the time an icon is *used* for the first time, it is loaded. This gives an opportunity to defer "invalid image format" handling (see #3565) until time of use.
If possible, we should support *any* input image size, not just the "known" sizes that we currently support.
### Describe alternatives you've considered
Current icon handling works; this would be an internal improvement that resolves some inconsistencies in icon handling.
### Additional context
This feature request comes out of diagnosing and resolving a range of icon-related issues:
* #3565 relates to handling of error cases when icons can't be loaded
* #3422 relates to how fallbacks occur
* #3786 is a failure mode in macOS toolbar icons related to ICNS size selection
* #4051 is an example of iconsistent icon handling on Windows.
Contributor guide
Research direction
Start by tracing Icon implementation methods such as _as_size(), native(), and as_drawable() across the GTK, Winforms, Android, macOS, iOS, and Qt backends. Review toga.platform.current_platform and the proposed sys.platform/backend selection, then compare the related issues #3565, #3422, #3786, and #4051. Done means a consistent lazy, cached native(size) API with defined source-size selection and warnings across platforms.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- desktop, mobile
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 30/100