Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Icons and Resources

Overview

Teksilo supports three icon formats, SVG, PNG, and WebP, embedded at compile time via the res!() macro. Icons are tintable by default: their color follows the theme and interaction state (hover, pressed, disabled) automatically.

Supported Formats

FormatUse caseTintableNotes
SVGVector icons (preferred)YesScales to any size without loss
PNGRaster iconsYesFixed resolution, best at native size
WebPRaster icons (smaller files)YesUse lossless encoding for icons
OtherArbitrary filesN/AEmbedded as raw &'static [u8]

Usage

Embedding resources with res!()

Place resource files under resources/ in your crate root:

my-app/
  Cargo.toml
  src/
    main.rs
  resources/
    icons/
      save.svg
      star.png
      clock.webp

Embed and use them:

#![allow(unused)]
fn main() {
// SVG, returns &'static SvgIcon, compile-time validated
let save = teksilo::res!("resources/icons/save.svg");

// PNG, returns &'static RasterIcon, compile-time validated
let star = teksilo::res!("resources/icons/star.png");

// WebP, returns &'static RasterIcon (static) or &'static AnimatedIcon (animated)
let clock = teksilo::res!("resources/icons/clock.webp");

// Unknown extensions, returns &'static [u8], existence checked only
let font = teksilo::res!("resources/fonts/custom.ttf");
}

The macro validates known formats at compile time: SVG is parsed with the real SvgIcon parser and rejected if it contains no drawable geometry (an <image>/embedded-bitmap-only SVG fails, since the icon renderer skips <image> and <text>); PNG and WebP are checked by magic bytes (PNG's 8-byte signature + IHDR chunk; WebP's RIFF/WEBP signature). Unknown extensions are embedded as raw bytes without validation, only file existence is checked.

Using icons in buttons

#![allow(unused)]
fn main() {
let save = teksilo::res!("resources/icons/save.svg");

// Leading icon, most common
Button::new(lit!("Save"))
    .icon(IconWidget::from_svg_icon(save), IconLocation::Leading)
    .variant(ButtonVariant::Plain)

// Icon only, toolbars
Button::new(lit!("Save"))
    .icon(IconWidget::from_svg_icon(save), IconLocation::IconOnly)
    .variant(ButtonVariant::Ghost)

// Raster icon
let star = teksilo::res!("resources/icons/star.png");
Button::new(lit!("Favorite"))
    .icon(IconWidget::from_raster(star, 24.0), IconLocation::Leading)
}

The button controls the icon's display size via the BUTTON_ICON_SIZE constant (default 16dp) in teksilo-widgets. The icon's color is bound to the button's text color signal, it follows hover, pressed, disabled, and theme changes automatically.

Icon locations

IconLocationLayout
NoneNo icon (default)
LeadingIcon left of label
TrailingIcon right of label
IconOnlyIcon only, no label
TopIcon above label
BottomIcon below label

Standalone icons (outside buttons)

#![allow(unused)]
fn main() {
// SVG, size defaults to viewBox, override with icon_size()
IconWidget::from_svg_icon(icon).icon_size(32.0).color(Color::RED)

// Programmatic, built-in shapes
IconWidget::checkmark(24.0)
IconWidget::chevron_down(16.0)
IconWidget::chevron_right(16.0)

// From raw SVG string (no res! macro, parses at runtime)
IconWidget::from_svg(include_str!("../resources/icons/save.svg"))
}

Tintable vs full-color mode

Icons default to tintable mode: the image is treated as an alpha mask and tinted with the widget's color property. This enables theme-aware coloring.

For icons that should keep their original colors (e.g., app logos, colored emoji):

#![allow(unused)]
fn main() {
IconWidget::from_raster(logo, 32.0).mode(IconMode::FullColor)
}

In full-color mode, the icon's RGB is rendered directly; the widget color only controls opacity.

Raster images and GPU memory

A window uploads each raster it draws to a GPU texture, with a mip chain. How long the texture stays depends on how the widget got its pixels.

A shared texture, kept until the window closes. ImageWidget::new(&icon) without a mask, and IconWidget::from_raster, name the texture after the icon's identity:

  • Clones of one RasterIcon share its pixels and its texture, and a res!() raster is a single static value, so showing one image in many widgets costs one texture (one per mode for IconWidget).
  • Every RasterIcon::decode and RasterIcon::from_raw call is a new identity, even for the same bytes.
  • The texture is never freed before the window closes, even once no widget shows the icon.

A texture of its own, freed with the widget. ImageWidget::from_raw and any ImageWidget with a mask hold their pixels in a source of their own, and so does an Avatar's image. The window frees that texture at the first frame that does not draw the widget, because it was destroyed, scrolled out or parked, and uploads it again if the widget comes back. GPU memory follows what is on screen.

Pick by how long the image lives:

  • A bounded set of long-lived images (toolbar art, a logo, a handful of placeholders): res!() or a cached RasterIcon, shown with ImageWidget::new(&icon).
  • Images that come and go, such as album covers or thumbnails in a virtualized grid: ImageWidget::from_raw(pixels, width, height). Keep the decoded pixels in an application cache if decoding is expensive. A tile then costs a texture only while it is drawn.

Building tiles with ImageWidget::new(&icon) from icons decoded at runtime is the case to avoid: each new identity keeps its texture until the window closes. Debug builds print a warning when one window holds more than 256 shared image textures.

Creating Icon Assets

SVG icons

Use any SVG editor. Icons should be single-color paths on a transparent background. Fill and stroke colors in the SVG are ignored, the rendering color comes from the theme.

Standard viewBox: 0 0 24 24 (Material Design convention).

PNG icons

Export as white shape on transparent background (RGBA). The luminance of the image becomes the alpha mask for tinting.

  • Use 24x24 or 48x48 pixels for standard icons
  • Export as RGBA PNG (not indexed/palette)

WebP icons

Use lossless encoding. Lossy WebP with separate alpha planes (VP8X + ALPH chunks) may not decode correctly. Lossless WebP (VP8L) stores RGBA natively and works reliably.

With ImageMagick:

convert -size 24x24 xc:none -fill none -stroke white -strokewidth 2 \
  -draw "circle 12,12 12,3" \
  -define webp:lossless=true \
  icon.webp

With cwebp:

cwebp -lossless input.png -o icon.webp

WebP is ~40-60% smaller than PNG for the same quality, making it a good choice for apps with many icons.

Animated WebP

Animated WebP icons (loading spinners, status indicators) are supported. The res!() macro auto-detects animation and returns &'static AnimatedIcon. Use IconWidget::from_animated() to render.

Frame cycling is automatic and loops continuously. Each frame should use lossless encoding.