Status: Proposed (Draft 1)
Author: Max (with contributions from ChatGPT)
Target versions: 0.5 → 0.7
Compatibility: Backward compatible; new capabilities are opt‑in.
EFx is a minimalist XML DSL on top of egui (via eframe, bevy_egui, or any other integrator) that speeds up layout of simple panels/menus/debug UIs. This RFC proposes a three‑step roadmap:
- 0.5 (attributes & core tags): turn on attribute rendering (type‑safe), add semantic tags (
Heading,Hyperlink,Image,TextField,Grid, basic windows/panels), improve diagnostics. - 0.6 (components & events): reusable components/slots and syntactic sugar for events (
onClick, etc.), classes & styles, officialbevy_eguiexamples. - 0.7 (themes & layouts): style sheets/themes, extended containers (
Table,Tabs), final polish and perf.
EFx remains an immediate‑mode DSL for egui — not a retained framework.
Goals:
- Reduce boilerplate for common
eguiwidgets/containers. - Provide an expressive, type‑checked attribute model.
- Enable reuse (components + slots) without heavy abstractions.
- Ship Tier‑1 support for
eframeandbevy_egui(examples + CI builds). - Add lightweight theming/styling without turning EFx into a CSS engine.
Non‑goals:
- Do not turn EFx into a fully retained UI framework.
- Do not chase platform‑native look & feel beyond what
eguioffers.
- Debug/tools: quickly build debug panels and simple forms.
- Windows/panels: declaratively open
Window/CentralPanel/SidePanel. - Reusability: small building blocks with parameters and slots.
- Styling: centrally adjust fonts/spacing/colors.
EFx still compiles into regular egui calls. New bits:
- Attributes: literals (
size="14"), numbers (spacing=8), booleans (presence =true), andattr={expr}(including references like&mutfor bidirectional scenarios). - Events: sugar like
onClick={|| ...}compiled intoResponsechecks. - New tags: see mapping table below. Windows/panels use
ui.ctx(). - Components & slots (0.6):
#[efx_component]over plain functions. - Styles & classes: minimal class system + Rust style structs with push/pop scope.
attr="literal"→ parsed into string/number/enum.attr={expr}→ arbitrary Rust expression; types checked by the compiler.- Boolean flags:
<Button disabled/>→disabled=true. - Defaults follow
eguidefaults.
<Button onClick={|| do_action()}>
Save
</Button>Equivalent to:
let resp = ui.button("Save");
if resp.clicked() { do_action(); }A root <Button> still returns egui::Response.
<TextField value={&mut state.name} hint="Your name"/>Generates egui::TextEdit::singleline(&mut state.name).hint_text("...") when present.
| Tag | Purpose | Generated (approx.) |
|---|---|---|
Label |
Text | ui.label(RichText::new(text).<style>) |
Heading |
Header | ui.heading(text) (+ styles) |
Hyperlink |
Link | ui.hyperlink(url) / ui.hyperlink_to(label, url) |
Button |
Button | ui.button(label) → Response |
Separator |
Separator | ui.separator() |
Row |
Horizontal | `ui.horizontal( |
Column |
Vertical | `ui.vertical( |
Grid |
Grid | `egui::Grid::new(id).show(ui, |
TextField |
Single-line input | TextEdit::singleline(&mut value) + attrs |
Image |
Image | ui.image(texture_id, size) |
Tabs* |
Tabs | via egui_extras or minimal switcher |
Table* |
Table | egui_extras::TableBuilder |
Panel |
Generic container | see below |
Window |
Window | `Window::new(title).show(ui.ctx(), |
CentralPanel |
Central area | `CentralPanel::default().show(ui.ctx(), |
SidePanel |
Side area | `SidePanel::left(id)/right(id).show(ui.ctx(), |
(*) behind the extras feature.
We rely on ui.ctx() to access egui::Context inside the codegen, keeping a single macro efx!(ui, ...).
Minimalistic — no CSS.
class="h1 muted"applies predefined presets.style={EfxStyle { font_size: 14.0, ..Default::default() }}for inline tweaks.- Push/pop style scope (restore on
Drop) to avoid global side effects.
Style sheets (0.7):
static THEME: EfxTheme = efx_theme! {
.h1 { font: Heading, size: 22.0 }
.muted { color: Gray }
};Attribute macro over a plain function:
#[efx_component]
fn Toolbar(ui: &mut egui::Ui, title: &str, #[efx_slot] actions: impl FnOnce(&mut egui::Ui)) {
efx!(ui, r#"
<Row spacing=8>
<Heading>{title}</Heading>
{ actions(ui) }
</Row>
"#);
}Use:
Toolbar(ui, "Files", |ui| efx!(ui, "<Button>New</Button><Button>Open</Button>"));- Tier‑1 (officially supported in 0.5: examples + CI build):
eframe(native + wasm).bevy_egui(native).- raw
winit+wgpu(viaegui-winit+egui-wgpu) — demonstrates EFx is framework‑agnostic.
- Tier‑2 (already compatible, examples later / community support):
egui-miniquad(formacroquad/miniquadoverlays),egui_sdl2_*(SDL2 backends),egui_glow/tao(lower‑level backends).
- AST: attributes already exist; add typed codegen (literal → int/float/bool/str/ident), support
{expr}as token stream. - Codegen:
- Map attributes to
eguicalls (e.g.,Row spacing=..→ tweakui.spacing_mut().item_spacing.xlocally). - Style scope push/pop.
- Event sugar via conditional checks on
Response.
- Map attributes to
- Diagnostics: friendly
compile_error!with spans and suggestions.
- 0.5: no breaking changes. New tags/attributes are optional.
- 0.6: components/slots — additive.
- 0.7: theme/layout utilities — additive (may introduce warnings/lints only).
Cargo features:
[features]
extras = ["egui_extras"]
bevy = []- Attribute rendering: literals +
{expr}; numbers/bools/strings. Heading,Hyperlink,TextField,Image,Grid.Window/CentralPanel/SidePanelviaui.ctx().- Diagnostics/tests (compile‑fail + snapshot codegen tests).
- Documentation (README: Supported runtimes, quickstarts for eframe/bevy/winit+wgpu).
- Examples:
examples/eframe_demo.rs,examples/bevy_overlay.rs,examples/winit_wgpu_min.rs. - CI: GitHub Actions matrix builds — eframe (native+wasm), bevy (native), winit+wgpu (native).
#[efx_component]+#[efx_slot].- Events:
onClick,onHoversugar. - Classes (
class="...") + preset pack. Table/Tabsbehindextras.- Bevy examples (menus, toolbars, tabs).
efx_theme!and style sheets.- Layout utilities (margins, alignment) as attributes on containers.
- Perf pass (allocations/clones).
- Errors/hints, docs polish.
- Avoid extra
Stringallocations. - Cache common
RichText/FontIdin presets. - Use scoped style changes to prevent global churn.
- compile‑fail: nested elements inside
Label/Button, unknown tags/attrs, wrong attr types. - snapshot codegen: expected Rust for typical snippets.
- integration: build examples on CI; no GUI runtime required on CI.
- README: “Works with any
eguiruntime:eframe,bevy_egui, rawwinit+wgpu”. - Cookbook: forms (
TextField), windows/panels, grids, links, images. - Bevy guide: imports, version pitfalls (re‑exported
egui), best practices.
- Keep
Tabs/Tablein core or behindextras? - Event sugar beyond
onClick(onChangeforTextField) — how far to go? - Separate macro for context (
efx_ctx!) — current decision: no, keepui.ctx().
efx!(ui, r#"
<Window title="Demo">
<Column spacing=8>
<Heading>EFx 0.5</Heading>
<Row spacing=6>
<Label>Welcome,</Label>
<Hyperlink url="https://efxui.com">efxui.com</Hyperlink>
</Row>
<Separator/>
<TextField value={&mut state.name} hint="Your name"/>
{ if efx!(ui, "<Button>Save</Button>").clicked() { save(); } }
</Column>
</Window>
"#);efx!(ui, r#"<Button onClick={|| save()}>Save</Button>"#);Toolbar(ui, "Files", |ui| efx!(ui, "<Button>New</Button><Button>Open</Button>"));- More syntax → more complex macro/errors — mitigate with diagnostics/tests.
- Window/panel via
ui.ctx()— careful with style scope boundaries. extrasdeps behind a feature to keep dependency tree slim.
- Attributes: literals/booleans/
{expr}compile and behave as expected. - New tags render correctly; compile‑fail tests cover invalid nesting/types.
- CI builds examples: eframe (native+wasm), bevy (native), winit+wgpu (native).
Looking for help with:
- Bevy example(s) and raw
winit+wgpuexample; - Attribute codegen & compile‑fail tests;
TextFieldimplementation details and docs.
Disclaimer: This plan may change based on feedback. Priorities can shift.
License: This RFC is part of EFx documentation and follows the project license.