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
, theScreen
is initialized, andInputExtension
andRelativeFocusExtension
are registered.InputExtension
is vital as it enables raw mode terminal input and dispatchescrossterm::event::Event
s throughout the OSUI system. Without it, keyboard input won't work.RelativeFocusExtension
manages which widget is "focused," which is essential forInput
elements 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 aHandler
component attached to an implicit root widget. @Handler::new(...)
creates an event handler that listens forcrossterm::event::Event
s.- The closure checks if the event is a
KeyCode::Esc
key press. If so, it callsscreen.close()
, which gracefully exits the OSUI application, restoring terminal state. @AlwaysFocused
ensures 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
Paginator
becomes a "page." ThePaginator
displays only one child at a time. - Pressing
Tab
(orShift+Tab
) cycles through thePaginator
's children, switching the active page. This behavior is built into thePaginator
element'sevent
method.
4. Layout with FlexRow
and FlexCol
- Each "page" within the
Paginator
uses eitherFlexRow
orFlexCol
to 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 agap
of 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 theDiv
elements.@Style { background: Background::RoundedOutline(0x00ff00) }
: Applies a green rounded outline background to the firstDiv
.0x00ff00
is 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 theInput
fields 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::spawn
block modifiescount
every second (**count.get() += 1;
). This modification automatically markscount
as "changed." %count
(on theFlexRow
containing the counter text) declares a dependency. Whencount
changes, 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 thecount
state's value. This works becauseState<T>
implementsDisplay
.
7. Interactive Input (Input
Element)
Input { }
creates a text input field.@Focused
on the "Username"Input
ensures it receives keyboard focus when that page is active, allowing the user to type immediately. TheRelativeFocusExtension
helps manage this focus.- When an
Input
is 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.