egui_expressive

Authoring-layer helpers for advanced custom UI in egui

A helper library on top of egui that reduces boilerplate for building polished custom widgets. Not a new renderer — all drawing uses egui's Painter, shapes, and interaction systems.

Target egui version: 0.31 No external renderer Zero-cost when unused

package_2 Getting Started

Add the following to your Cargo.toml:

[dependencies]
egui_expressive = "0.1"

# Disable debug overlay in release
[profile.release]
# default-features = false  # uncomment to strip debug module

architecture Design Principles

1. egui-is-the-floor

All drawing, layout, and input handling ultimately use egui APIs.

2. Zero-cost-when-unused

Feature flags gate optional subsystems.

3. State-machines-over-boolean-soup

Typed state machines, not bags of bool fields.

4. Builders-terminate-into-egui-types

Builders produce egui::Shape, egui::Response, etc.

5. Culling-is-first-class

Large surface helpers make viewport culling the default.

Modules

brush draw

Layered Painter Helpers

Key Types

LayeredPainter ShapeBuilder RectBuilder CircleBuilder PathBuilder

Usage Example

use egui_expressive::draw::{LayeredPainter, ShapeBuilder};

fn my_widget(ui: &mut egui::Ui, rect: egui::Rect) {
    let lp = LayeredPainter::from_ui(ui);
    // Shadow on background layer
    lp.background().add(
        ShapeBuilder::rect(rect.expand(4.0))
            .fill(egui::Color32::from_black_alpha(40))
            .rounding(6.0)
            .build()
    );
    // Main body
    lp.main().add(
        ShapeBuilder::rect(rect)
            .fill(egui::Color32::from_rgb(50, 50, 70))
            .stroke(egui::Stroke::new(1.0, egui::Color32::WHITE))
            .rounding(4.0)
            .build()
    );
}

palette style

Visual State System

Key Types

VisualVariant VisualState<T> WidgetTheme ResolvedTheme Lerp

Usage Example

use egui_expressive::style::{VisualState, WidgetTheme};

let theme = WidgetTheme {
    bg: VisualState {
        inactive: egui::Color32::from_rgb(40, 40, 55),
        hovered:  egui::Color32::from_rgb(55, 55, 75),
        pressed:  egui::Color32::from_rgb(80, 80, 110),
        selected: egui::Color32::from_rgb(60, 100, 180),
        focused:  egui::Color32::from_rgb(50, 80, 140),
        disabled: egui::Color32::from_rgb(30, 30, 35),
    },
    ..Default::default()
};
let resolved = theme.resolve(&response, is_selected);

settings state

State Machines

Key Types

StateSlot<T> StateMachine<S> InteractionState

Usage Example

use egui_expressive::state::StateMachine;

#[derive(Clone, PartialEq)]
enum KnobMode { Normal, Fine, Locked }

let mode: StateMachine<KnobMode> = StateMachine::new(id.with("mode"));
if response.secondary_clicked() {
    mode.set(ctx, KnobMode::Fine);
}

mouse interaction

Drag & Gesture Helpers

Key Types

DragDelta DragAxis PanZoom drag_to_value_delta key_pressed

Usage Example

use egui_expressive::interaction::{DragDelta, DragAxis, drag_to_value_delta};

let drag = DragDelta::from_response(ctx, id, &response, DragAxis::Y);
if drag.delta.y != 0.0 {
    let delta = drag_to_value_delta(-drag.delta.y, 0.0..=1.0, 200.0, &drag.modifiers);
    *value = (*value + delta).clamp(0.0, 1.0);
}

animation animation

Easing & Spring Physics

Key Types

Easing Tween Spring AnimSequence

Usage Example

use egui_expressive::animation::{Tween, Easing};

let tween = Tween::new(id.with("highlight"), 0.15, Easing::EaseOut);
let alpha = tween.animate_f32(ctx, if hovered { 1.0 } else { 0.0 }, 0.0);

map surface

Large Canvas & Viewport Culling

Key Types

LargeCanvas ViewportCuller PanZoom

Usage Example

use egui_expressive::surface::LargeCanvas;

LargeCanvas::new(id, egui::vec2(50_000.0, 800.0))
    .zoom_range(0.1, 4.0)
    .show(ui, |ui, origin, pan_zoom, culler| {
        let rows = culler.visible_rows(40.0, 1000);
        for row in rows {
            let logical_rect = egui::Rect::from_min_size(
                egui::pos2(0.0, row as f32 * 40.0),
                egui::vec2(50_000.0, 38.0),
            );
            if culler.is_visible(logical_rect) {
                let screen_rect = culler.rect_to_screen(logical_rect);
                ui.painter().rect_filled(screen_rect, 0.0, egui::Color32::DARK_GRAY);
            }
        }
    });

tune widgets

Reusable Controls

Key Types

Knob Fader Meter StepGrid Orientation

Usage Example

use egui_expressive::widgets::{Knob, Fader, Meter, StepGrid, Orientation};

// DAW channel strip
ui.horizontal(|ui| {
    ui.add(Knob::new(&mut gain, -60.0..=6.0).size(48.0).label("GAIN"));
    ui.add(Meter::new(vu_level).peak(peak_level).size(egui::vec2(16.0, 120.0)));
    ui.add(Fader::new(&mut volume, 0.0..=1.0)
        .size(egui::vec2(40.0, 120.0))
        .orientation(Orientation::Vertical));
});
// Step sequencer
ui.add(StepGrid::new(&mut steps, 8, 16)
    .cell_size(egui::vec2(28.0, 28.0))
    .active_col(playhead));

bug_report debug

Visual Debugging

Key Types

DebugOverlay

Usage Example

use egui_expressive::debug::DebugOverlay;

let dbg = DebugOverlay::default();
dbg.rect(ctx, body_rect, "knob_body");
dbg.response(ctx, &response, "knob_interact");