egui_elm

Crates.ioegui_elm
lib.rsegui_elm
version0.3.3
created_at2025-11-26 04:26:45.924376+00
updated_at2025-12-05 10:45:59.478619+00
descriptionElm-style architecture on top of egui
homepage
repositoryhttps://github.com/modolet/egui_elm
max_upload_size
id1950929
size145,894
XiangX (Modolet)

documentation

https://docs.rs/egui_elm

README

egui_elm

egui_elm brings an Elm-style, purely functional architecture to egui. It focuses on a simple init / update / view / subscription model so you can write declarative desktop GUIs with predictable state transitions and explicit commands.

Features

  • Elm-inspired Program structure with standalone functions instead of traits.
  • Explicit Command type for asynchronous/side-effectful work.
  • Subscription abstraction for continuous external events such as timers.
  • Optional app runtime (run) built on top of eframe + tokio. Disable the default runtime feature if you only need the architectural pieces.

Getting started

[dependencies]
egui_elm = "0.3.3"

By default the crate enables the runtime feature, which pulls in the native runner and Tokio. To depend on only the core types, disable default features:

[dependencies]
egui_elm = { version = "0.3.3", default-features = false }

Quick example

use egui_elm::prelude::*;

#[derive(Default)]
struct Counter {
    value: i32,
}

#[derive(Clone)]
enum Message {
    Increment,
    Decrement,
}

fn init(_ctx: &egui::Context) -> (Counter, Command<Message>) {
    (Counter::default(), Command::none())
}

fn update(model: &mut Counter, message: Message) -> Command<Message> {
    match message {
        Message::Increment => model.value += 1,
        Message::Decrement => model.value -= 1,
    }
    Command::none()
}

fn view(model: &Counter, ctx: &egui::Context, ui_ctx: &ViewContext<Message>) {
    egui::CentralPanel::default().show(ctx, |ui| {
        ui.heading("Counter");
        ui.label(format!("Value: {}", model.value));
        if ui.button("Increment").clicked() {
            ui_ctx.send(Message::Increment);
        }
        if ui.button("Decrement").clicked() {
            ui_ctx.send(Message::Decrement);
        }
    });
}

fn subscription(_model: &Counter) -> Subscription<Message> {
    Subscription::none()
}

fn main() -> eframe::Result<()> {
    let program = Program::new(init, update, view, subscription);
    egui_elm::app::run(program, "Counter")
}

More runnable examples live in examples/.

Selecting a renderer

egui_elm::app::run uses the default eframe::NativeOptions. If you need to force a specific backend such as wgpu or tweak any other option, call run_with_native_options instead. Enable the crate's wgpu feature so that eframe also builds with its wgpu renderer:

[dependencies]
egui_elm = { version = "0.3.3", features = ["wgpu"] }
fn main() -> eframe::Result<()> {
    let program = Program::new(init, update, view, subscription);
    let mut native_options = eframe::NativeOptions::default();
    native_options.renderer = eframe::Renderer::Wgpu;
    egui_elm::app::run_with_native_options(program, "Counter", native_options)
}

eframe hooks

When the runtime feature is enabled you can bridge into eframe's save and on_exit lifecycle callbacks with Program::with_save and Program::with_on_exit:

fn save(model: &mut Counter, storage: &mut dyn eframe::Storage) {
    storage.set_string("count", model.value.to_string());
}

fn on_exit(model: &mut Counter, _gl: Option<&glow::Context>) {
    eprintln!("Goodbye with count = {}", model.value);
}

fn main() -> eframe::Result<()> {
    let program = Program::new(init, update, view, subscription)
        .with_save(save)
        .with_on_exit(on_exit);
    egui_elm::app::run(program, "Counter")
}

License

MIT. See LICENSE for details.

Commit count: 0

cargo fmt