Vidya — theme layer for egui (HIG-inspired, no GTK)
README.md

Vidya #

GNOME/HIG-inspired theme layer for egui — no GTK.

Calm charcoal shells, a clear blue accent, soft borders, and a short type/spacing scale. Dark is the product default — ship apply_dark / Theme::dark() and stop there. Light (Theme::light / apply_light) exists for the showcase and for apps that explicitly need it; do not copy the demo’s dark/light toggle into every app.

Repo: tangled.org/nandi.uk/vidya

Screenshots below are Waydroid (portrait Android) captures of the demo APK.

Vidya — Overview Vidya — Forms (themed checkbox) Vidya — Actions

Vidya — Palette Vidya — Typography Vidya — Surfaces

Aesthetic #

Layer Role
Window / view / card / popover Stacked surfaces (#242424 → #383838 in dark)
Accent Adwaita-like blue (#3584e4) for primary actions, selection, and checked controls
Feedback Destructive red, success green, warning amber
Type Title 20 · title₂ 16 · body 14 · caption 12
Spacing 4 · 6 · 12 · 18 · 24 · control height 34
Radius 6 · 9 · 12

apply() installs palette + spacing on the egui context so text fields, sliders, and combos inherit the shell. Checkboxes use a dedicated themed control (checkbox) with accent fill and a drawn checkmark. It also registers a tiny symbol font fallback (DejaVu subset) so UI punctuation such as →, ●/○, disclosure ▾/▴, dashes, and curly quotes does not render as hollow boxes on Android.

Emoji → color icons: the full Twemoji 72×72 set (~3.8k glyphs) is embedded. Any reaction (flags, ZWJ sequences, skin tones) resolves via emoji_icon / paint_emoji_in / reaction_chip. Prefer these over raw Unicode on Android — egui’s default fonts only ship monochrome Noto Emoji.

On Android (edge-to-edge NativeActivity), call reserve_system_chrome(ctx, &theme) once per frame before other panels, or use top_header(ctx, &theme, |ui| { … }) for the app chrome. That reserves the system status bar and gesture/nav band so labels and chips cannot sit under the clock / indicators.

Demo #

An interactive showcase walks through overview, typography, actions, surfaces, palette swatches, and forms. The demo includes a dark/light toggle so you can compare palettes — that control is for the showcase, not a recommended app pattern.

nix run                  # apps.default → vidya-demo
nix run .#demo
# or from a remote flake:
nix run git+https://tangled.org/nandi.uk/vidya
nix develop            # rust (+ android target) · just · adb

just waydroid          # in-tree cargo apk → install → launch on Waydroid
just host              # desktop egui window
just install           # same as waydroid (rebuild APK)
just launch            # start installed activity only
just shots             # Waydroid screencaps → docs/screenshots/mobile/

just waydroid builds in android-demo/ with the nix develop toolchain (no temp/isolated copy).

Sections:

  • Overview — hero card, design tokens + grid DSL metrics table (demo can flip to light)
  • Typography — type scale samples (grid) and hierarchy
  • Actions — primary / default / destructive buttons, dialog footer, status pills
  • Surfaces — layer stack table, card & header frames
  • Palette — live semantic swatches (flip the shell to compare)
  • Forms — themed inputs, accent checkbox, slider, combo, progress

Refreshing screenshots #

With a running Waydroid session:

just install   # rebuild + install APK
just shots     # adb screencap per section → docs/screenshots/mobile/

Use #

Apps should start dark-only:

[dependencies]
vidya = { git = "https://tangled.org/nandi.uk/vidya" }
# or: vidya = { git = "ssh://git@tangled.org/nandi.uk/vidya" }
egui = "0.31"
use vidya::{apply_dark, checkbox, primary_button, Theme};

apply_dark(ctx);
let th = Theme::dark();
if primary_button(ui, &th, "Open").clicked() {
    // …
}
checkbox(ui, &th, &mut sync, "Sync preferences");

Theme::light() / apply_light are available when an app truly needs a light shell. Prefer not to expose a theme toggle unless that is a deliberate product choice (the Vidya demo does it to exercise both palettes).

Window icon #

Embed a PNG and attach it to the viewport (set Wayland app_id to match your .desktop):

use egui::ViewportBuilder;
use vidya::with_app_icon_id;

let viewport = with_app_icon_id(
    ViewportBuilder::default().with_title("I/O Usage"),
    "usage",
    include_bytes!("../assets/usage-256.png"),
);

Nix flake #

Output Role
apps.default / apps.demo Desktop showcase via cargo run (rustup + egui libs)
packages.default / packages.demo Same launcher + .desktop entry
packages.vidya Theme library sources + rlib bundle
devShells.default just · adb · egui runtime libs (rustup for cargo)
nix run                 # cargo run --manifest-path host/Cargo.toml
nix run .#demo
nix build .#vidya
nix develop

nix run keeps your cwd: from the checkout it rebuilds the live tree (same as just host). The packaged .desktop falls back to the flake source with CARGO_TARGET_DIR under ~/.cache/vidya/.

As a flake input:

inputs.vidya.url = "git+https://tangled.org/nandi.uk/vidya";

API #

Item Role
Theme::dark() / light() Palette + spacing + type scale (dark for apps; light optional)
apply / apply_dark / apply_light Install on egui::Context (+ symbol font); prefer apply_dark
install_symbol_font Fallback glyphs for arrows / disclosure triangles / bullets / quotes
emoji_icon / paint_emoji_in / has_emoji_icon Full Twemoji color set by codepoint
Icon / icon / paint_icon_in Named shortcuts + stroke Plus / Copy
icon_button Square tool button with a stroke/emoji icon
reaction_chip Themed count chip with color emoji
primary_button / button / destructive_button Styled actions
checkbox Accent-filled checkbox with drawn checkmark
status_dot Live/offline circle (drawn — no Unicode tofu on Android)
text_field_singleline / text_field_multiline Text inputs with field padding (fill parent width)
consume_command / consume_escape Consume Cmd/Ctrl+key or Esc (platform-aware)
command_shortcut_label / escape_label "Ctrl+F" / "⌘F" / "Esc" for tooltips
title / title_2 / body / dim_label Text roles
Theme::header_frame / card_frame / page_frame Layout chrome
dialog Centered resizable window with card_frame (chain size / .show)
Theme::text_edit_margin Inner field padding (12×8 default)
Layout composition Prefer these over raw set_max_width / Layout plumbing
fit_width / fill_width Pin children to residual width (no edge overflow)
vstack Non-justified vertical stack (no giant gaps in tall parents)
card Fill-width themed card + vstack content
compact_card Fixed-width card that hugs content (gauge / anomaly tiles)
pack Wrap compact tiles without stretching leftover horizontal space
hflow Wrapping horizontal row (toolbars / chips)
lead_trail Leading field + trailing actions without clipping the actions
two_col / side_by_side Responsive two-column / stack (+ pure breakpoint policy)
page_body / central_page Enforced scrollable page: top-level content is a grid (GridCtx)
page_body_cols / central_page_cols Same, with explicit top-level ColSpecs
page_scroll Escape hatch: scroll + width pin only (no grid)
GridCtx::section Full-width page section (one row / one cell) — preferred page building block
inset_row Soft inset row capped to parent width
grid / grid_cols Grid DSL — pinned to residual width; cols share viewport budget
grid_cols_with / GridOpts Grid with striping / spacing options (page shell uses non-striped)
distribute_col_max Pure policy: per-column max widths so sum + gaps ≤ available
ColSpec Flex / Fixed / MetricBps / MetricRate (floors; capped by residual)
metric_cell Right-edge mono metric; clipped to column budget
RowDsl heading / text / dim / warn / metric / metric_bps / metric_rate
metric_bps / metric_rate Fixed-width monospace rate strings (no staircase columns)
metric_cell / table_metric / table_text Low-level cells (prefer the row DSL)
data_table Index-callback table helper on top of the grid DSL
reserve_system_chrome / system_chrome Android status + nav safe areas
top_header Header panel with system chrome already reserved
App icon Embed a PNG as the native window icon
icon_data_from_png Decode PNG bytes → egui::IconData
with_app_icon ViewportBuilder + embedded PNG icon
with_app_icon_id Same + Wayland app_id (match .desktop / StartupWMClass)
try_with_app_icon / try_with_app_icon_id Fallible variants

License #

MIT