MudEngine Part 4: Single-Player Dioxus GUI
The App component
The App component ties everything together. It holds three pieces of reactive state:
world— aSignal<World>containing the game stateinput— aSignal<String>bound to the text input boxfeedback— aSignal<String>for error messages and help text
The component renders the grid, the current room description (calling world.read().look() which triggers reactivity), and an input box. When the player presses Enter, the handler parses the command, mutates the world, and updates feedback.
#[component] fn App() -> Element { let mut world = use_signal(|| World::new()); let mut input = use_signal(|| String::new()); let mut feedback = use_signal(|| String::new()); rsx! { div { id: "main", h1 { "🧙 MudEngine" } p { style: "color: #666; margin-top: -8px;", "Part 4 — Dioxus GUI" } WorldGrid { world } div { class: "description", "{world.read().look()}" } if !feedback.read().is_empty() { p { style: "color: #ff6b6b; margin: 8px 0;", "{feedback}" } } input { class: "command-input", value: "{input}", placeholder: "Type a command and press Enter...", oninput: move |e| input.set(e.value()), onkeydown: move |e| { if e.key() == Key::Enter { let cmd = input.read().trim().to_lowercase(); if cmd.is_empty() { return; } input.write().clear(); let parts: Vec<&str> = cmd.splitn(2, ' ').collect(); match parts[0] { "look" | "l" => feedback.write().clear(), "north" | "n" | "south" | "s" | "east" | "e" | "west" | "w" => { let dir = match parts[0] { "n" => "north", "s" => "south", "e" => "east", "w" => "west", d => d, }; if world.write().go(dir) { feedback.write().clear(); } else { feedback.set( format!("You cannot go {} from here.", dir), ); } } "help" | "?" => { feedback.set( "Commands: look/l | north/n south/s east/e west/w | help/? | quit/exit" .into(), ); } "quit" | "exit" => { feedback.set( "Farewell, adventurer!".into(), ); } _ => { feedback.set( "Unknown command. Type 'help' or '?' for a list." .into(), ); } } } }, } } } }
-
worldis aSignal<World>. Reading it in the render function (viaworld.read().look()) subscribes the component to changes. When theonkeydownhandler callsworld.write().go(dir), Dioxus schedules a re-render ofAppandWorldGrid(because the signal changed). -
inputis aSignal<String>that keeps the input box value in sync viavalue: "{input}"andoninput: move |e| input.set(e.value()). When Enter is pressed, we read the value, clear the signal, and process the command. -
feedbackis for transient messages (errors, help text, quit message). It is shown only when non-empty. Since it is also a signal, setting it triggers a re-render.
The WorldGrid subscribes independently via its ReadSignal<World> prop and only re-renders when the world actually changes (thanks to PartialEq).