| Crates.io | egui-cha-analyzer |
| lib.rs | egui-cha-analyzer |
| version | 0.5.0 |
| created_at | 2025-12-21 15:22:35.586279+00 |
| updated_at | 2026-01-25 17:57:20.380612+00 |
| description | Static analyzer for egui UI flow: UI -> Action -> State |
| homepage | https://github.com/ynishi/egui-cha |
| repository | https://github.com/ynishi/egui-cha |
| max_upload_size | |
| id | 1998119 |
| size | 191,745 |
A TEA (The Elm Architecture) framework for egui with a built-in Design System.
Building admin panels, debug tools, or internal utilities with egui is great, but:
// Typical egui code - logic scattered everywhere
if ui.button("Save").clicked() {
self.saving = true;
self.data.validate(); // Business logic here?
self.api.save(&self.data); // Side effects here?
self.saving = false;
self.last_error = None; // State management here?
}
// View: purely presentational
Button::primary("Save").on_click(ctx, Msg::Save);
// Update: all logic in one place
fn update(model: &mut Model, msg: Msg) -> Cmd<Msg> {
match msg {
Msg::Save => {
model.saving = true;
Cmd::try_task(
api::save(&model.data),
|_| Msg::SaveSuccess,
|e| Msg::SaveError(e),
)
}
Msg::SaveSuccess => { model.saving = false; Cmd::none() }
Msg::SaveError(e) => { model.last_error = Some(e); Cmd::none() }
}
}
Result: Logic stays in update(), views stay simple, and your "quick tool" won't fall apart as it grows.
Cmd for side effectsCmd::try_task, ErrorConsole, and on_framework_errorTestRunner for unit testing your app logiccargo add egui-cha egui-cha-ds
Or add to your Cargo.toml:
[dependencies]
egui-cha = "0.1"
egui-cha-ds = "0.1"
use egui_cha::prelude::*;
use egui_cha_ds::prelude::*;
fn main() -> eframe::Result<()> {
egui_cha::run::<MyApp>(RunConfig::new("My App"))
}
struct MyApp;
#[derive(Default)]
struct Model {
count: i32,
}
#[derive(Clone)]
enum Msg {
Increment,
Decrement,
}
impl App for MyApp {
type Model = Model;
type Msg = Msg;
fn init() -> (Model, Cmd<Msg>) {
(Model::default(), Cmd::none())
}
fn update(model: &mut Model, msg: Msg) -> Cmd<Msg> {
match msg {
Msg::Increment => model.count += 1,
Msg::Decrement => model.count -= 1,
}
Cmd::none()
}
fn view(model: &Model, ctx: &mut ViewCtx<Msg>) {
ctx.ui.heading("Counter");
ctx.ui.label(format!("Count: {}", model.count));
ctx.horizontal(|ctx| {
Button::primary("+").on_click(ctx, Msg::Increment);
Button::secondary("-").on_click(ctx, Msg::Decrement);
});
}
}
┌────────────────────────────────────────┐
│ egui-cha-ds (Design System) │
│ Button, Input, Card, Icon, Theme... │
├────────────────────────────────────────┤
│ egui-cha (TEA Core) │
│ App, Cmd, ViewCtx, Router, Component │
└────────────────────────────────────────┘
↓
egui
| Component | Description |
|---|---|
App |
Main application trait with init, update, view |
Cmd |
Side effects: task, delay, try_task, from_result, retry |
Severity |
Error levels: Debug, Info, Warn, Error, Critical |
FrameworkError |
Framework internal errors with on_framework_error hook |
ViewCtx |
UI context with emit, horizontal, vertical, group |
Router |
Page navigation with history stack |
Component |
Reusable component trait |
ScrollArea |
Scrollable container with builder pattern |
// Apply theme to egui context
let theme = Theme::dark(); // or Theme::light(), Theme::pastel(), Theme::pastel_dark()
theme.apply(ctx.ui.ctx());
Theme includes:
| Component | Description |
|---|---|
Button |
Primary, Secondary, Outline, Ghost, Danger, Warning, Success, Info |
Badge |
Default, Success, Warning, Error, Info |
Text |
Typography: h1, h2, h3, body, small, caption with modifiers |
Input |
Text input with TEA-style callbacks |
ValidatedInput |
Input with validation state |
Checkbox |
Boolean toggle with label |
Toggle |
Switch-style boolean toggle |
Slider |
Numeric range input |
Select |
Dropdown selection |
Icon |
Phosphor Icons (house, gear, info, etc.) |
Link |
Hyperlink component |
Code |
Code block display |
Tooltip |
Themed tooltips via ResponseExt trait |
ContextMenu |
Right-click menu via ContextMenuExt trait |
| Component | Description |
|---|---|
Card |
Container with optional title |
Tabs |
Tabbed navigation with TabPanel |
Modal |
Dialog overlay |
Table |
Data table component |
Navbar |
Horizontal navigation bar |
ErrorConsole |
5-level severity message display (Debug/Info/Warning/Error/Critical) |
Toast |
Temporary notifications with auto-dismiss |
Form |
Structured form with validation |
SearchBar |
Search input with submit |
// Consistent labels & icons across your app
semantics::save(ButtonStyle::Both).on_click(ctx, Msg::Save);
semantics::delete(ButtonStyle::Icon).on_click(ctx, Msg::Delete);
Available: save, edit, delete, close, add, remove, search, refresh, play, pause, stop, settings, back, forward, confirm, cancel, copy
Declarative layout syntax for composing views:
use egui_cha_ds::cha;
cha!(ctx, {
Col(spacing: 8.0) {
Card("Settings") {
Row {
@gear(20.0)
ctx.ui.label("Options")
}
Button::primary("Save").on_click(ctx, Msg::Save)
}
Scroll(max_height: 300.0) {
// scrollable content
}
}
});
| Node | Description |
|---|---|
Col |
Vertical layout (ctx.vertical) |
Row |
Horizontal layout (ctx.horizontal) |
Group |
Grouped layout (ctx.group) |
Scroll |
Vertical scroll area |
ScrollH |
Horizontal scroll area |
ScrollBoth |
Both directions scroll |
Card("title") |
Card container |
@icon |
Icon shorthand (e.g., @house, @gear(20.0)) |
spacing, paddingmax_height, max_width, min_height, min_width, idpadding// Async task
Cmd::task(async {
let data = fetch_data().await;
Msg::DataLoaded(data)
})
// Delayed message
Cmd::delay(Duration::from_secs(1), Msg::Tick)
// Fallible async with error handling
Cmd::try_task(
fetch_data(),
|data| Msg::Success(data),
|err| Msg::Error(err.to_string()),
)
// Batch multiple commands
Cmd::batch([cmd1, cmd2, cmd3])
#[derive(Clone, PartialEq, Default)]
enum Page {
#[default]
Home,
Settings,
Profile(u64),
}
// In your Model
struct Model {
router: Router<Page>,
}
// Navigate
Msg::Router(RouterMsg::Navigate(Page::Settings))
Msg::Router(RouterMsg::Back)
Msg::Router(RouterMsg::Forward)
// In view
navbar(ctx, &model.router, &[
("Home", Page::Home),
("Settings", Page::Settings),
], Msg::Router);
use egui_cha_ds::atoms::{Icon, icons};
// Using convenience constructors
Icon::house().size(20.0).show(ctx.ui);
Icon::gear().color(Color32::RED).show(ctx.ui);
// Using icon constants directly
Icon::new(icons::CHECK).show(ctx.ui);
Available icons: HOUSE, GEAR, HASH, INFO, USER, CHECK, WARNING, PLUS, MINUS, X, ARROW_LEFT, ARROW_RIGHT, FIRE, BUG, WRENCH, X_CIRCLE
use egui_cha::test_prelude::*;
#[test]
fn test_increment() {
let mut runner = TestRunner::<MyApp>::new();
runner.send(Msg::Increment);
assert_eq!(runner.model().count, 1);
assert!(runner.last_was_none()); // No side effects
}
#[test]
fn test_async_command() {
let mut runner = TestRunner::<MyApp>::new();
runner.send(Msg::FetchData);
assert!(runner.last_was_task()); // Returned an async task
}
MIT