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");