Building a Comprehensive Demo Application
This guide walks through the src/demos/mod.rs file provided in the OSUI source, explaining how various features are combined to create a multi-page interactive application. This demo showcases reactive state, input handling, custom elements, and layout components.
Overview of src/demos/mod.rs
The demos::app function returns an Rsx tree, which is the declarative representation of our UI. This Rsx tree is then drawn to the Screen.
// src/demos/mod.rs
use std::sync::Arc;
use osui::prelude::*;
pub fn app(screen: Arc<Screen>) -> Rsx {
// 1. Reactive State
let count = use_state(0);
// Spawn a background thread to increment the counter
std::thread::spawn({
let count = count.clone();
move || loop {
**count.get() += 1; // Increment and mark as changed
std::thread::sleep(std::time::Duration::from_secs(1));
}
});
// 2. Main UI Definition
rsx! {
// Global event handler for Escape key to close the app
@Handler::new({
let screen = screen.clone();
move |_, e: &crossterm::event::Event| {
if let crossterm::event::Event::Key(crossterm::event::KeyEvent { code, .. }) = e {
if *code == crossterm::event::KeyCode::Esc {
screen.close();
}
}
}});
// Keep the root Paginator widget always focused
@AlwaysFocused;
Paginator { // Main page manager
// --- Page 1: Welcome and Instructions ---
FlexRow { // Horizontal layout for elements on this page
Heading, smooth: false, { "OSUI" } // Large ASCII art title
"Welcome to the OSUI demo!"
"Press tab to switch to the next page or shift+tab to the previous page"
}
// --- Page 2: Divs with different outlines ---
FlexCol, gap: 3, { // Vertical layout for elements on this page
@Transform::new().padding(2, 2);
@Style { foreground: None, background: Background::RoundedOutline(0x00ff00) };
Div {
"This is text inside a div with rounded outlines"
}
@Transform::new().padding(2, 2);
@Style { foreground: None, background: Background::Outline(0x00ff00) };
Div {
"This is text inside a div with square outlines"
}
}
// --- Page 3: Reactive Counter & Input Fields ---
FlexRow, gap: 1, {
%count // This section depends on the 'count' state
"This will increment every second: {count}"
FlexRow { // Nested FlexRow for Username input
"Username"
@Transform::new().padding(1, 1).dimensions(40, 1);
@Style { foreground: Some(0xffffff), background: Background::RoundedOutline(0xff0000) };
@Focused; // Initially focus this input field
Input { }
}
@Transform::new().margin(0, 1); // Add a margin below previous element
FlexRow { // Nested FlexRow for Password input
"Password"
@Transform::new().padding(1, 1).dimensions(40, 1);
@Style { foreground: Some(0xffffff), background: Background::RoundedOutline(0xffff00) };
Input { }
}
}
}
}
}
Dissecting the Demo
1. Central Screen and Extensions
- In
src/main.rs, theScreenis initialized, andInputExtensionandRelativeFocusExtensionare registered.InputExtensionis vital as it enables raw mode terminal input and dispatchescrossterm::event::Events throughout the OSUI system. Without it, keyboard input won't work.RelativeFocusExtensionmanages which widget is "focused," which is essential forInputelements to receive typing events. It also provides navigation between focusable elements (by default, using arrow keys when Shift is held).
- The
demos::app(screen.clone()).draw(&screen);line is the entry point for rendering the entire UI structure.
2. Global Event Handling (@Handler and Esc Key)
- The very first element in
rsx!(outside thePaginator) is aHandlercomponent attached to an implicit root widget. @Handler::new(...)creates an event handler that listens forcrossterm::event::Events.- The closure checks if the event is a
KeyCode::Esckey press. If so, it callsscreen.close(), which gracefully exits the OSUI application, restoring terminal state. @AlwaysFocusedensures this root handler always receives events, regardless of which sub-widget might have specific input focus.
3. Page Navigation with Paginator
- The top-level element is a
Paginator. This element acts as a simple tab-like interface. - Each direct child of
Paginatorbecomes a "page." ThePaginatordisplays only one child at a time. - Pressing
Tab(orShift+Tab) cycles through thePaginator's children, switching the active page. This behavior is built into thePaginatorelement'seventmethod.
4. Layout with FlexRow and FlexCol
- Each "page" within the
Paginatoruses eitherFlexRoworFlexColto organize its content. FlexRow { ... }: Arranges children horizontally. Used for the welcome message and the counter/input fields.FlexCol, gap: 3, { ... }: Arranges children vertically. Used for the Div examples, with agapof 3 cells between each child.- These flex containers automatically calculate their own size based on their children and distribute space. They are "ghost" elements, meaning they don't draw anything themselves but define layout for their children.
5. Styling and Layout Components (@Transform, @Style)
@Transform::new().padding(2, 2): Sets internal spacing of 2 cells on all sides for theDivelements.@Style { background: Background::RoundedOutline(0x00ff00) }: Applies a green rounded outline background to the firstDiv.0x00ff00is a 24-bit RGB hexadecimal color code.@Style { background: Background::Outline(0x00ff00) }: Applies a green square outline background to the secondDiv.@Transform::new().dimensions(40, 1): Sets theInputfields to a fixed width of 40 cells and a height of 1 cell.@Style { foreground: Some(0xffffff), background: Background::RoundedOutline(0xff0000) }: Styles the username input with white foreground text and a red rounded outline.
6. Reactive State and Text Interpolation (%count)
let count = use_state(0);initializes a reactive state variable.- The
std::thread::spawnblock modifiescountevery second (**count.get() += 1;). This modification automatically markscountas "changed." %count(on theFlexRowcontaining the counter text) declares a dependency. Whencountchanges, thisFlexRow(and its children, including the text) is rebuilt and re-rendered, displaying the updated value.- The text
"This will increment every second: {count}"directly interpolates thecountstate's value. This works becauseState<T>implementsDisplay.
7. Interactive Input (Input Element)
Input { }creates a text input field.@Focusedon the "Username"Inputensures it receives keyboard focus when that page is active, allowing the user to type immediately. TheRelativeFocusExtensionhelps manage this focus.- When an
Inputis focused, typing characters modifies its internalState<String>, and it redraws to show the updated text and cursor.
This demo illustrates a comprehensive use of OSUI's features, demonstrating how declarative UI, reactive state, event handling, and layout work together to build a functional TUI application.