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.
Create src/components/app.rs. It composes the WorldGrid component and drives the World game logic through the command input.
use crate::components::world_grid::WorldGrid; use crate::game::world::{Direction, World}; use dioxus::prelude::*; #[component] pub 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: "game", 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 |event| input.set(event.value()), onkeydown: move |event: KeyboardEvent| { if event.key() == Key::Enter { let cmd = input.read().trim().to_lowercase(); if cmd.is_empty() { return; } input.write().clear(); match cmd.as_str() { "look" | "l" => feedback.write().clear(), "north" | "n" | "south" | "s" | "east" | "e" | "west" | "w" => { let dir = match cmd.as_str() { "north" | "n" => Direction::North, "south" | "s" => Direction::South, "east" | "e" => Direction::East, "west" | "w" => Direction::West, _ => unreachable!(), }; if world.write().go(dir) { feedback.write().clear(); } else { feedback.set( format!("You cannot go {dir} from here."), ); } } "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).