Reactive GUI
Use builder-style widgets that rebuild when state changes.
use factorio_rs::{ factorio_api::{IndexOrName, lua_fn, lua_fn0}, prelude::*,};use factorio_rs_gui::shared::button::Button;use factorio_rs_gui::shared::frame::Frame;use factorio_rs_gui::shared::text::Text;use factorio_rs_gui::shared::widget::Widget;
const ROOT: &str = "my_mod_counter";
fn app() -> impl Into<Widget> { let count = factorio_rs_gui::state!(0); let label = format!("Count: {}", count.get()); let increment = lua_fn(move |event: OnGuiClickEvent| { let _ = event; count.set(count.get() + 1); });
Frame::new() .caption("Counter") .centered() .align_horizontal(LuaStyleHorizontalAlign::Center) .align_vertical(LuaStyleVerticalAlign::Center) .direction(GuiDirection::Vertical) .child(Text::new(&label)) .child(Button::new("Increment").on_click(increment))}
#[factorio_rs::event(OnPlayerCreated)]pub fn on_player_created(event: OnPlayerCreatedEvent) { if let Some(player) = game.get_player(IndexOrName::Index(event.player_index)) { factorio_rs_gui::shared::runtime::mount( player.gui().screen(), ROOT, lua_fn0(app), ); }}
#[factorio_rs::event(OnTick)]pub fn on_tick(_event: OnTickEvent) { // Re-bind after `game.reload_mods()` / hot-reload (module locals wipe). factorio_rs_gui::shared::runtime::install(ROOT, lua_fn0(app));}How it works
Section titled “How it works”state!(init)allocates a hook slot that survives rebuilds (namespaced per root).mount(parent, root_name, app)stores the app, appliesroot_nameto the rootFrame, builds the tree, and wires GUIscript.on_eventhandlers. Use a mod-uniqueroot_nameso other GUIs onplayer.gui.screendo not collide.- Button
on_clickregisters handlers in your mod;mount/installregisterdispatch_clickso you do not need anOnGuiClickstub. State::setmarks dirty and rebuilds that root (destroy + re-runapp).- Call
install(root_name, app)after script reload (e.g. fromon_tick).
Multiple windows in one mod: mount each with a different root_name. Hooks,
handlers, and locations are isolated per root. unmount(root_name) tears one
down.
Adaptations from a fully reactive DSL: use format! (not "Count: {count}"
literals), lua_fn / lua_fn0 for callbacks, and a concrete Widget enum.
Frame::child takes impl Into<Widget>, so pass Text / Button / Frame
directly (backed by impl From<...> for Widget). Prefer
factorio_rs_gui::shared::... paths so Factorio requires resolve.
Try it
Section titled “Try it”From this repository:
cd ecosystem/factorio-rs-gui && factorio-rs build && factorio-rs installcd examples/gui_counterfactorio-rs build && factorio-rs install --openApps outside the monorepo: depend on
factorio-rs-gui on crates.io and
enable the
library mod on the Factorio mod portal.
Working tree:
examples/gui_counter.
See also
Section titled “See also”- Getting started / State / Lifecycle
- Widgets
- GUI basics - imperative
LuaGuiElementAddParams - Sharing code between mods - library deps
- Persist with storage - hook values use
storage