Repository: microsandbox/rxtui Branch: main Commit: ddd15bea46e4 Files: 99 Total size: 1.1 MB Directory structure: gitextract_sx7sm9zf/ ├── .gitignore ├── .pre-commit-config.yaml ├── API_REFERENCE.md ├── CONTRIBUTING.md ├── Cargo.toml ├── DEVELOPMENT.md ├── DOCS.md ├── IMPLEMENTATION.md ├── LICENSE ├── QUICK_REFERENCE.md ├── README.md ├── TUTORIAL.md ├── examples/ │ ├── README.md │ ├── align.rs │ ├── components.rs │ ├── counter.rs │ ├── demo.rs │ ├── demo_pages/ │ │ ├── mod.rs │ │ ├── page10_unicode.rs │ │ ├── page11_content_sizing.rs │ │ ├── page12_focus.rs │ │ ├── page13_rich_text.rs │ │ ├── page14_text_input.rs │ │ ├── page15_scrollable.rs │ │ ├── page16_text_alignment.rs │ │ ├── page1_overflow.rs │ │ ├── page2_direction.rs │ │ ├── page3_percentages.rs │ │ ├── page4_borders.rs │ │ ├── page5_absolute.rs │ │ ├── page6_text_styles.rs │ │ ├── page7_auto_sizing.rs │ │ ├── page8_text_wrap.rs │ │ └── page9_element_wrap.rs │ ├── form.rs │ ├── gap.rs │ ├── hover.rs │ ├── inline.rs │ ├── progressbar.rs │ ├── rxtui.rs │ ├── scroll.rs │ ├── scroll_nested.rs │ ├── shimmer_text.rs │ ├── spinner.rs │ ├── spinner_custom.rs │ ├── stopwatch.rs │ └── textinput.rs ├── plan.md ├── rxtui/ │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ ├── lib/ │ │ ├── app/ │ │ │ ├── config.rs │ │ │ ├── context.rs │ │ │ ├── core.rs │ │ │ ├── events.rs │ │ │ ├── inline.rs │ │ │ ├── mod.rs │ │ │ └── renderer.rs │ │ ├── bounds.rs │ │ ├── buffer.rs │ │ ├── component.rs │ │ ├── components/ │ │ │ ├── mod.rs │ │ │ ├── shimmer_text.rs │ │ │ ├── spinner.rs │ │ │ └── text_input.rs │ │ ├── diff.rs │ │ ├── effect/ │ │ │ ├── mod.rs │ │ │ ├── runtime.rs │ │ │ └── types.rs │ │ ├── key.rs │ │ ├── lib.rs │ │ ├── macros/ │ │ │ ├── internal.rs │ │ │ ├── mod.rs │ │ │ └── node.rs │ │ ├── node/ │ │ │ ├── div.rs │ │ │ ├── mod.rs │ │ │ ├── rich_text.rs │ │ │ └── text.rs │ │ ├── prelude.rs │ │ ├── providers.rs │ │ ├── render_tree/ │ │ │ ├── mod.rs │ │ │ ├── node.rs │ │ │ ├── tests/ │ │ │ │ ├── layout_tests.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── rich_text_tests.rs │ │ │ │ ├── sizing_tests.rs │ │ │ │ └── wrapping_tests.rs │ │ │ └── tree.rs │ │ ├── style.rs │ │ ├── terminal.rs │ │ ├── tests/ │ │ │ └── rich_text_tests.rs │ │ ├── utils.rs │ │ ├── vdom.rs │ │ └── vnode.rs │ └── tests/ │ └── macro_tests.rs └── rxtui-macros/ ├── Cargo.toml ├── LICENSE ├── README.md └── src/ └── lib.rs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Rust target/** **/*.rs.bk Cargo.lock # IDE .idea/ .vscode/ *.swp *.swo *~ # OS .DS_Store Thumbs.db # Environment .env .env.local .env.test # Build artifacts dist/ build/ # Logs *.log # Testing coverage/ *.lcov # Temporary files tmp/ temp/ ignore/ ================================================ FILE: .pre-commit-config.yaml ================================================ repos: # Standard pre-commit hooks - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - id: check-added-large-files - id: check-merge-conflict - id: mixed-line-ending args: [--fix=lf] - id: no-commit-to-branch args: [--branch, main, --branch, master] # Rust-specific hooks - repo: local hooks: - id: cargo-fmt name: cargo fmt entry: cargo fmt --all -- language: system types: [rust] pass_filenames: false - id: cargo-clippy name: cargo clippy entry: cargo clippy --all-targets --all-features -- -D warnings language: system types: [rust] pass_filenames: false ================================================ FILE: API_REFERENCE.md ================================================ # RxTUI API Reference Complete API documentation for the RxTUI framework. ## Module Structure ``` rxtui ├── prelude // Common imports ├── component // Component trait and types ├── node // UI node types ├── style // Styling types ├── app // Application core ├── components // Built-in components ├── macros // Macro exports └── effect // Async effects (feature-gated) ``` ## Prelude ```rust use rxtui::prelude::*; ``` Imports all commonly used types: - Core: `App`, `Context`, `Component`, `Node`, `Action` - State: `State`, `StateExt`, `Message`, `MessageExt` - Style: `Color`, `Style`, `Direction`, `Spacing`, `Border`, `BorderStyle`, `BorderEdges` - Key: `Key`, `KeyWithModifiers` - Macros: `node!`, `#[component]`, `#[update]`, `#[view]`, `#[effect]` ## Core Types ### Component ```rust pub trait Component: 'static { fn update(&self, ctx: &Context, msg: Box, topic: Option<&str>) -> Action; fn view(&self, ctx: &Context) -> Node; fn effects(&self, ctx: &Context) -> Vec; fn as_any(&self) -> &dyn Any; fn as_any_mut(&mut self) -> &mut dyn Any; } ``` Derive with: ```rust #[derive(Component)] struct MyComponent; ``` ### Action ```rust pub enum Action { Update(Box), // Update component state UpdateTopic(String, Box), // Update topic state None, // No action Exit, // Exit application } ``` Helper methods: ```rust Action::update(state) // Shorthand for Update Action::update_topic(topic, state) // Shorthand for UpdateTopic Action::none() // Shorthand for None Action::exit() // Shorthand for Exit ``` ### Context ```rust impl Context { // Message handling pub fn handler(&self, msg: M) -> Box; pub fn handler_with_value(&self, f: F) -> Box where F: Fn(T) -> M, M: Message; // State management pub fn get_state(&self) -> S; pub fn get_state_or(&self, default: S) -> S; // Topic messaging pub fn send_to_topic(&self, topic: &str, msg: M); pub fn read_topic(&self, topic: &str) -> Option; // Direct messaging pub fn send(&self, msg: M); } ``` ### Message ```rust pub trait Message: Any + Send + Sync + 'static { fn as_any(&self) -> &dyn Any; fn clone_box(&self) -> Box; } ``` Auto-implemented for types that are `Clone + Send + Sync + 'static`. ### State ```rust pub trait State: Any + Send + Sync + 'static { fn as_any(&self) -> &dyn Any; fn as_any_mut(&mut self) -> &mut dyn Any; fn clone_box(&self) -> Box; } ``` Auto-implemented for types that are `Clone + Send + Sync + 'static`. ## Node Types ### Node ```rust pub enum Node { Component(Arc), Div(Div), Text(Text), RichText(RichText), } ``` ### Div ```rust impl Div { pub fn new() -> Self; // Layout pub fn direction(self, dir: Direction) -> Self; pub fn gap(self, gap: u16) -> Self; pub fn wrap(self, mode: WrapMode) -> Self; // Alignment pub fn justify_content(self, justify: JustifyContent) -> Self; pub fn align_items(self, align: AlignItems) -> Self; pub fn align_self(self, align: AlignSelf) -> Self; // Sizing pub fn width(self, w: u16) -> Self; pub fn width_fraction(self, frac: f32) -> Self; pub fn width_auto(self) -> Self; pub fn width_content(self) -> Self; pub fn height(self, h: u16) -> Self; pub fn height_fraction(self, frac: f32) -> Self; pub fn height_auto(self) -> Self; pub fn height_content(self) -> Self; // Styling pub fn background(self, color: Color) -> Self; pub fn padding(self, spacing: Spacing) -> Self; pub fn style(self, style: Style) -> Self; // Borders pub fn border_color(self, color: Color) -> Self; pub fn border_style_with_color(self, style: BorderStyle, color: Color) -> Self; pub fn border_edges(self, edges: BorderEdges) -> Self; pub fn border_full(self, style: BorderStyle, color: Color, edges: BorderEdges) -> Self; // Positioning pub fn position(self, pos: Position) -> Self; pub fn top(self, offset: i16) -> Self; pub fn right(self, offset: i16) -> Self; pub fn bottom(self, offset: i16) -> Self; pub fn left(self, offset: i16) -> Self; pub fn z_index(self, z: i32) -> Self; // Scrolling pub fn overflow(self, overflow: Overflow) -> Self; pub fn show_scrollbar(self, show: bool) -> Self; // Focus pub fn focusable(self, focusable: bool) -> Self; pub fn focus_style(self, style: Style) -> Self; // Events pub fn on_click(self, handler: impl Fn()) -> Self; pub fn on_key(self, key: Key, handler: impl Fn()) -> Self; pub fn on_key_global(self, key: Key, handler: impl Fn()) -> Self; pub fn on_char(self, ch: char, handler: impl Fn()) -> Self; pub fn on_char_global(self, ch: char, handler: impl Fn()) -> Self; pub fn on_any_char(self, handler: impl Fn(char)) -> Self; pub fn on_focus(self, handler: impl Fn()) -> Self; pub fn on_blur(self, handler: impl Fn()) -> Self; // Children pub fn children(self, children: Vec) -> Self; pub fn child(self, child: Node) -> Self; } ``` ### Text ```rust impl Text { pub fn new(content: impl Into) -> Self; // Styling pub fn color(self, color: Color) -> Self; pub fn background(self, color: Color) -> Self; pub fn bold(self) -> Self; pub fn italic(self) -> Self; pub fn underline(self) -> Self; pub fn strikethrough(self) -> Self; pub fn style(self, style: TextStyle) -> Self; // Wrapping pub fn wrap(self, mode: TextWrap) -> Self; // Alignment pub fn align(self, align: TextAlign) -> Self; } ``` ### RichText ```rust impl RichText { pub fn new() -> Self; // Add spans pub fn text(self, content: impl Into) -> Self; pub fn styled(self, content: impl Into, style: TextStyle) -> Self; pub fn colored(self, content: impl Into, color: Color) -> Self; pub fn bold(self, content: impl Into) -> Self; pub fn italic(self, content: impl Into) -> Self; // Apply to all spans pub fn color(self, color: Color) -> Self; pub fn background(self, color: Color) -> Self; pub fn bold_all(self) -> Self; pub fn italic_all(self) -> Self; // Wrapping pub fn wrap(self, mode: TextWrap) -> Self; // Alignment pub fn align(self, align: TextAlign) -> Self; // Cursor support pub fn with_cursor(content: &str, position: usize, style: TextStyle) -> Self; } ``` ## Style Types ### Color ```rust pub enum Color { // Basic colors Black, Red, Green, Yellow, Blue, Magenta, Cyan, White, // Bright colors BrightBlack, BrightRed, BrightGreen, BrightYellow, BrightBlue, BrightMagenta, BrightCyan, BrightWhite, // RGB Rgb(u8, u8, u8), } impl Color { pub fn from_hex(hex: &str) -> Result; } ``` ### Style ```rust pub struct Style { pub background: Option, pub direction: Option, pub padding: Option, pub width: Option, pub height: Option, pub gap: Option, pub wrap: Option, pub overflow: Option, pub border: Option, pub position: Option, pub top: Option, pub right: Option, pub bottom: Option, pub left: Option, pub z_index: Option, pub justify_content: Option, pub align_items: Option, pub align_self: Option, } impl Style { pub fn new() -> Self; pub fn background(self, color: Color) -> Self; pub fn padding(self, spacing: Spacing) -> Self; pub fn border(self, color: Color) -> Self; // ... builder methods for all fields } ``` ### Key ```rust pub enum Key { // Regular character Char(char), // Special keys Esc, Enter, Tab, BackTab, Backspace, Delete, // Arrow keys Up, Down, Left, Right, // Navigation PageUp, PageDown, Home, End, // Function keys F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, } pub struct KeyWithModifiers { pub key: Key, pub ctrl: bool, pub alt: bool, pub shift: bool, pub meta: bool, // Cmd on macOS, Win on Windows } impl KeyWithModifiers { pub fn new(key: Key) -> Self; pub fn with_ctrl(key: Key) -> Self; pub fn with_alt(key: Key) -> Self; pub fn with_shift(key: Key) -> Self; pub fn is_primary_modifier(&self) -> bool; // Platform-aware (Cmd on macOS, Ctrl elsewhere) } ``` ### TextAlign ```rust pub enum TextAlign { Left, // Align text to the left edge (default) Center, // Center text horizontally Right, // Align text to the right edge } ``` ### JustifyContent ```rust pub enum JustifyContent { Start, // Pack items at the start of the main axis (default) Center, // Center items along the main axis End, // Pack items at the end of the main axis SpaceBetween, // Distribute items evenly, first at start, last at end SpaceAround, // Distribute items evenly with equal space around each item SpaceEvenly, // Distribute items evenly with equal space between and around items } ``` ### AlignItems ```rust pub enum AlignItems { Start, // Align items at the start of the cross axis (default) Center, // Center items along the cross axis End, // Align items at the end of the cross axis } ``` ### AlignSelf ```rust pub enum AlignSelf { Auto, // Use the parent's AlignItems value (default) Start, // Align at the start of the cross axis Center, // Center along the cross axis End, // Align at the end of the cross axis } ``` ### TextStyle ```rust pub struct TextStyle { pub color: Option, pub background: Option, pub bold: Option, pub italic: Option, pub underline: Option, pub strikethrough: Option, pub wrap: Option, pub align: Option, } impl TextStyle { pub fn new() -> Self; pub fn color(self, color: Color) -> Self; pub fn background(self, color: Color) -> Self; pub fn bold(self) -> Self; pub fn italic(self) -> Self; pub fn underline(self) -> Self; pub fn strikethrough(self) -> Self; pub fn merge(base: Option, overlay: Option) -> Option; } ``` ### Dimension ```rust pub enum Dimension { Fixed(u16), // Exact size Percentage(f32), // Normalized (0.0 to 1.0) Auto, // Share remaining Content, // Fit content } ``` ### Direction ```rust pub enum Direction { Horizontal, Vertical, } ``` ### Spacing ```rust pub struct Spacing { pub top: u16, pub right: u16, pub bottom: u16, pub left: u16, } impl Spacing { pub fn all(value: u16) -> Self; pub fn horizontal(value: u16) -> Self; pub fn vertical(value: u16) -> Self; pub fn new(top: u16, right: u16, bottom: u16, left: u16) -> Self; } ``` ### BorderStyle ```rust pub enum BorderStyle { Single, Double, Rounded, Thick, } ``` ### BorderEdges ```rust bitflags! { pub struct BorderEdges: u8 { const TOP = 0b0001; const RIGHT = 0b0010; const BOTTOM = 0b0100; const LEFT = 0b1000; const ALL = 0b1111; } } ``` ### Border ```rust pub struct Border { pub enabled: bool, pub style: BorderStyle, pub color: Color, pub edges: BorderEdges, } impl Border { pub fn new(color: Color) -> Self; pub fn style(self, style: BorderStyle) -> Self; pub fn edges(self, edges: BorderEdges) -> Self; } ``` ### Position ```rust pub enum Position { Relative, Absolute, } ``` ### Overflow ```rust pub enum Overflow { None, // No clipping Hidden, // Clip content Scroll, // Scrollable Auto, // Auto scrollbars } ``` ### WrapMode ```rust pub enum WrapMode { NoWrap, Wrap, } ``` ### TextWrap ```rust pub enum TextWrap { None, Character, Word, WordBreak, } ``` ## App ```rust pub struct App { // Private fields } impl App { /// Creates app with alternate screen mode (default). pub fn new() -> Result; /// Creates app with inline rendering mode. /// Content renders directly in terminal and persists after exit. pub fn inline() -> Result; /// Creates app with custom inline configuration. pub fn inline_with_config(config: InlineConfig) -> Result; /// Creates app with specified terminal mode. pub fn with_mode(mode: TerminalMode) -> Result; /// Runs the application with the given root component. pub fn run(&mut self, root: C) -> Result<()>; } ``` ### TerminalMode ```rust /// Terminal rendering mode. pub enum TerminalMode { /// Full-screen alternate buffer (default behavior). /// Content disappears when app exits. AlternateScreen, /// Inline rendering in main terminal buffer. /// Content persists in terminal history after app exits. Inline(InlineConfig), } ``` ### InlineConfig ```rust /// Configuration for inline rendering mode. pub struct InlineConfig { /// How to determine rendering height. pub height: InlineHeight, /// Whether to show cursor during rendering. pub cursor_visible: bool, /// Whether to preserve output after app exits. pub preserve_on_exit: bool, /// Whether to capture mouse events. /// Default is false to allow natural terminal scrolling. pub mouse_capture: bool, } impl Default for InlineConfig { fn default() -> Self { Self { height: InlineHeight::Content { max: None }, cursor_visible: false, preserve_on_exit: true, mouse_capture: false, } } } ``` ### InlineHeight ```rust /// Height determination strategy for inline mode. pub enum InlineHeight { /// Fixed number of lines. Fixed(u16), /// Grow to fit content, with optional maximum. Content { max: Option }, /// Fill remaining terminal space below cursor. Fill { min: u16 }, } ``` ### RenderConfig ```rust pub struct RenderConfig { pub poll_duration_ms: u64, // Event poll timeout (default: 16) pub use_double_buffer: bool, // Enable double buffering (default: true) pub use_diffing: bool, // Enable cell diffing (default: true) pub use_alternate_screen: bool, // Use alternate screen (default: true) } ``` ## Key ```rust pub enum Key { // Special keys Backspace, Enter, Left, Right, Up, Down, Home, End, PageUp, PageDown, Tab, Delete, Insert, Esc, // Function keys F(u8), // F1-F12 // Character Char(char), // Null Null, } ``` ### KeyWithModifiers ```rust pub struct KeyWithModifiers { pub key: Key, pub ctrl: bool, pub alt: bool, pub shift: bool, pub meta: bool, } impl KeyWithModifiers { pub fn with_ctrl(key: Key) -> Self; pub fn with_alt(key: Key) -> Self; pub fn with_shift(key: Key) -> Self; pub fn with_meta(key: Key) -> Self; } ``` ## Built-in Components ### TextInput ```rust use rxtui::components::TextInput; impl TextInput { pub fn new() -> Self; // Content pub fn placeholder(self, text: impl Into) -> Self; pub fn password(self, enabled: bool) -> Self; // Container styling pub fn background(self, color: Color) -> Self; pub fn border(self, color: Color) -> Self; pub fn border_style(self, style: BorderStyle, color: Color) -> Self; pub fn border_edges(self, edges: BorderEdges) -> Self; pub fn border_full(self, style: BorderStyle, color: Color, edges: BorderEdges) -> Self; pub fn padding(self, spacing: Spacing) -> Self; pub fn z_index(self, z: i32) -> Self; pub fn position(self, pos: Position) -> Self; pub fn absolute(self) -> Self; pub fn top(self, offset: i16) -> Self; pub fn right(self, offset: i16) -> Self; pub fn bottom(self, offset: i16) -> Self; pub fn left(self, offset: i16) -> Self; // Sizing pub fn width(self, w: u16) -> Self; pub fn width_fraction(self, frac: f32) -> Self; pub fn width_auto(self) -> Self; pub fn width_content(self) -> Self; pub fn height(self, h: u16) -> Self; pub fn height_fraction(self, frac: f32) -> Self; pub fn height_auto(self) -> Self; pub fn height_content(self) -> Self; // Text styling pub fn content_color(self, color: Color) -> Self; pub fn content_bold(self, bold: bool) -> Self; pub fn content_background(self, color: Color) -> Self; pub fn placeholder_color(self, color: Color) -> Self; pub fn placeholder_background(self, color: Color) -> Self; pub fn placeholder_bold(self, bold: bool) -> Self; pub fn placeholder_italic(self, italic: bool) -> Self; pub fn placeholder_underline(self, underline: bool) -> Self; pub fn placeholder_style(self, style: TextStyle) -> Self; pub fn content_style(self, style: TextStyle) -> Self; pub fn cursor_color(self, color: Color) -> Self; // Focus pub fn focusable(self, enabled: bool) -> Self; pub fn focus_border(self, color: Color) -> Self; pub fn focus_border_style(self, style: BorderStyle, color: Color) -> Self; pub fn focus_background(self, color: Color) -> Self; pub fn focus_style(self, style: Style) -> Self; pub fn focus_padding(self, spacing: Spacing) -> Self; // Wrapping pub fn wrap(self, mode: TextWrap) -> Self; // Events pub fn on_change(self, callback: impl Fn(String) + 'static) -> Self; pub fn on_submit(self, callback: impl Fn() + 'static) -> Self; pub fn on_blur(self, callback: impl Fn() + 'static) -> Self; pub fn on_key(self, key: Key, handler: impl Fn() + 'static) -> Self; pub fn on_key_global(self, key: Key, handler: impl Fn() + 'static) -> Self; pub fn on_key_with_modifiers(self, key: KeyWithModifiers, handler: impl Fn() + 'static) -> Self; pub fn on_key_with_modifiers_global( self, key: KeyWithModifiers, handler: impl Fn() + 'static, ) -> Self; } ``` Messages: ```rust pub enum TextInputMsg { Focused, Blurred, CharInput(char), Backspace, Delete, CursorLeft, CursorRight, CursorHome, CursorEnd, // ... more } ``` ## Attribute Macros ### #[derive(Component)] Automatically implements the Component trait: ```rust #[derive(Component)] struct MyComponent { // Fields } ``` ### #[component] Enables collection of `#[effect]` methods: ```rust #[derive(Component)] struct Timer; #[component] // Required for #[effect] impl Timer { // Methods } ``` ### #[update] Simplifies update method with automatic state handling: ```rust // Basic #[update] fn update(&self, ctx: &Context, msg: MyMsg) -> Action { // No state parameter = stateless } // With state #[update] fn update(&self, ctx: &Context, msg: MyMsg, mut state: MyState) -> Action { // State automatically fetched and passed } // With topics #[update(msg = MyMsg, topics = ["topic" => TopicMsg])] fn update(&self, ctx: &Context, messages: Messages, mut state: MyState) -> Action { match messages { Messages::MyMsg(msg) => { /* ... */ } Messages::TopicMsg(msg) => { /* ... */ } } } // Dynamic topics #[update(msg = MyMsg, topics = [self.topic_field => TopicMsg])] fn update(&self, ctx: &Context, messages: Messages, state: MyState) -> Action { // Topic name from component field } ``` ### #[view] Simplifies view method with automatic state handling: ```rust // Without state #[view] fn view(&self, ctx: &Context) -> Node { // No state needed } // With state #[view] fn view(&self, ctx: &Context, state: MyState) -> Node { // State automatically fetched and passed } ``` ### #[effect] Marks async methods as effects (requires `effects` feature): ```rust #[component] impl MyComponent { #[effect] async fn background_task(&self, ctx: &Context) { // Async code } #[effect] async fn with_state(&self, ctx: &Context, state: MyState) { // Can access state } } ``` ## Effects (Feature-gated) Enable with: ```toml rxtui = { path = "rxtui", features = ["effects"] } ``` ### Effect Type ```rust pub type Effect = Pin + Send>>; ``` ### Manual Implementation ```rust impl Component for MyComponent { fn effects(&self, ctx: &Context) -> Vec { vec![ Box::pin(async move { // Async task }) ] } } ``` ## node! Macro ### Syntax Reference ```rust node! { // Element types div(...) [...], text(...), richtext(...) [...], vstack(...) [...], hstack(...) [...], input(...), spacer(n), node(component), // Properties (in parentheses) prop: value, flag, // Boolean flags // Children (in brackets) [ child1, child2, ], // Event handlers (start with @) @event: handler, } ``` ### Property Shortcuts | Short | Full | Type | |-------|------|------| | `bg` | `background` | Color | | `dir` | `direction` | Direction | | `pad` | `padding` | u16 (all sides) | | `pad_h` | - | u16 (horizontal) | | `pad_v` | - | u16 (vertical) | | `w` | `width` | u16 | | `h` | `height` | u16 | | `w_frac` | - | f32 (0.0-1.0) | | `h_frac` | - | f32 (0.0-1.0) | | `w_auto` | - | flag | | `h_auto` | - | flag | | `w_content` | - | flag | | `h_content` | - | flag | | `justify` | `justify_content` | JustifyContent | | `align` | `align_items` | AlignItems | | `align_self` | - | AlignSelf | ### Color Values ```rust // Named colors (no prefix needed) color: red color: bright_blue // Hex strings color: "#FF5733" color: "#F50" // Expressions (need parentheses) color: (Color::Rgb(255, 0, 0)) color: (my_color_variable) // Conditional color: (if condition { red } else { blue }) // Optional (with ! suffix) color: (optional_color)! ``` ### Event Handlers | Syntax | Description | |--------|-------------| | `@click: handler` | Mouse click | | `@char('x'): handler` | Character key | | `@key(enter): handler` | Special key | | `@key(Char('-')): handler` | Character via Key enum | | `@key(ctrl + 'c'): handler` | Key with modifiers | | `@char_global('q'): handler` | Global character | | `@key_global(esc): handler` | Global special key | | `@key_global(ctrl + enter): handler` | Global key with modifiers | | `@focus: handler` | Gained focus | | `@blur: handler` | Lost focus | | `@any_char: \|ch\| handler` | Any character | ## Helper Macros ### color_value! Internal macro for parsing color values in node!: ```rust color_value!(red) // Named color color_value!("#FF0000") // Hex color color_value!((expr)) // Expression ``` ### direction_value! Internal macro for parsing directions: ```rust direction_value!(horizontal) direction_value!(vertical) direction_value!(h) // Short for horizontal direction_value!(v) // Short for vertical ``` ### justify_value! Internal macro for parsing justify content values: ```rust justify_value!(start) justify_value!(center) justify_value!(end) justify_value!(space_between) justify_value!(space_around) justify_value!(space_evenly) ``` ### align_items_value! Internal macro for parsing align items values: ```rust align_items_value!(start) align_items_value!(center) align_items_value!(end) ``` ### align_self_value! Internal macro for parsing align self values: ```rust align_self_value!(auto) align_self_value!(start) align_self_value!(center) align_self_value!(end) ``` ## Type Aliases ```rust pub type ComponentId = String; pub type TopicName = String; ``` ## Traits ### MessageExt Extension trait for message downcasting: ```rust pub trait MessageExt { fn downcast(&self) -> Option<&T>; } ``` ### StateExt Extension trait for state downcasting: ```rust pub trait StateExt { fn downcast(&self) -> Option<&T>; } ``` ## Error Types RxTUI uses `std::io::Result` for most operations that can fail (terminal I/O). ## Platform Support - **Unix/Linux**: Full support - **macOS**: Full support - **Windows**: Supported via crossterm backend ## Feature Flags | Flag | Description | |------|-------------| | `effects` | Enable async effects system (requires tokio) | ## Thread Safety - Components must be `Send + Sync + 'static` - State and Messages must be `Send + Sync + 'static` - Effects run on a separate Tokio runtime ## Performance Considerations - Virtual DOM diffing minimizes updates - Double buffering eliminates flicker - Cell-level diffing reduces terminal I/O - Lazy state cloning only when modified - Topic messages use zero-copy routing when possible ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to RxTUI Thank you for your interest in contributing to RxTUI! We welcome contributions from everyone. ## How to Contribute ### Reporting Issues If you find a bug or have a feature request, please open an issue on our [GitHub repository](https://github.com/yourusername/rxtui/issues). When reporting issues, please include: - A clear description of the problem - Steps to reproduce the issue - Expected behavior vs actual behavior - Your environment (OS, Rust version, etc.) - Any relevant code snippets or error messages ### Submitting Pull Requests 1. **Fork the repository** and create your branch from `main` 2. **Make your changes** following our code style guidelines 3. **Add tests** for any new functionality 4. **Update documentation** if you've changed APIs 5. **Ensure all tests pass** with `cargo test` 6. **Run formatting** with `cargo fmt` 7. **Check linting** with `cargo clippy` 8. **Submit a pull request** with a clear description of your changes ### Code Style - Follow Rust's standard naming conventions - Use `rustfmt` for code formatting - Keep functions focused and small - Add comments for complex logic - Write clear commit messages ### Testing - Write unit tests for new functionality - Ensure all existing tests pass - Add integration tests for new features - Test on multiple platforms if possible ### Documentation - Update relevant documentation for API changes - Add examples for new features - Keep code comments up to date - Update the CHANGELOG for notable changes ## Development Setup See [DEVELOPMENT.md](DEVELOPMENT.md) for detailed setup instructions. ## Code of Conduct Please be respectful and inclusive in all interactions. We aim to create a welcoming environment for all contributors. ## Questions? Feel free to open an issue for any questions about contributing! ## License By contributing to RxTUI, you agree that your contributions will be licensed under the Apache License 2.0. ================================================ FILE: Cargo.toml ================================================ [package] name = "rxtui-workspace" version = "0.1.8" edition = "2021" publish = false [features] default = ["effects", "components"] effects = ["rxtui/effects"] components = ["rxtui/components"] [workspace] resolver = "2" members = [ "rxtui", "rxtui-macros", ] [workspace.package] version = "0.1.8" edition = "2024" authors = ["Microsandbox Team"] license = "Apache-2.0" repository = "https://github.com/microsandbox/rxtui" homepage = "https://github.com/microsandbox/rxtui" documentation = "https://docs.rs/rxtui" [workspace.dependencies] serde = { version = "1.0", features = ["derive"] } thiserror = "2.0" [dependencies] rxtui = { path = "rxtui", features = ["effects", "components"] } tokio = { version = "1", features = ["rt", "rt-multi-thread", "time", "sync", "macros"] } # Profile configurations [profile.release] opt-level = 3 lto = true codegen-units = 1 ================================================ FILE: DEVELOPMENT.md ================================================ # Development Guide This guide will help you set up your development environment for working on RxTUI. ## Prerequisites - Rust 1.70+ (install via [rustup](https://rustup.rs/)) - Git - A terminal emulator with Unicode support ## Setting Up Your Development Environment ### 1. Clone the Repository ```bash git clone https://github.com/yourusername/rxtui.git cd rxtui ``` ### 2. Install Dependencies ```bash cargo build ``` This will download and compile all dependencies. ### 3. Run Tests ```bash # Run all tests cargo test # Run tests with output cargo test -- --nocapture # Run specific test cargo test test_name ``` ### 4. Build Examples ```bash # Build all examples cargo build --examples # Run specific example cargo run --example demo ``` ## Project Structure ``` rxtui/ ├── rxtui/ # Main library crate │ ├── lib/ # Library source code │ │ ├── app/ # Application core │ │ ├── components/ # Built-in components │ │ ├── macros/ # Macro implementations │ │ └── ... │ ├── examples/ # Example applications │ └── tests/ # Integration tests ├── rxtui-macros/ # Proc macro crate ├── docs/ # Documentation └── examples/ # Standalone examples ``` ## Development Workflow ### Running in Development Mode For faster iteration during development: ```bash # Watch for changes and rebuild cargo watch -x build # Run tests on file change cargo watch -x test # Run specific example on change cargo watch -x "run --example demo" ``` ### Debugging Enable debug output: ```rust // In your app configuration let app = App::new()? .render_config(RenderConfig { use_double_buffer: false, // Disable for debugging use_diffing: false, // See all renders poll_duration_ms: 100, // Slower polling }); ``` ### Performance Profiling ```bash # Build with release optimizations but keep debug symbols cargo build --release --features debug # Profile with your favorite tool # Example with perf on Linux: perf record --call-graph=dwarf cargo run --release --example demo perf report ``` ## Common Development Tasks ### Adding a New Component 1. Create component file in `rxtui/lib/components/` 2. Implement the Component trait 3. Add to `mod.rs` exports 4. Write tests in component file 5. Add example usage ### Modifying the node! Macro 1. Edit `rxtui/lib/macros/node.rs` 2. Test with `cargo test macro_tests` 3. Update documentation if syntax changes 4. Add examples of new syntax ### Adding Event Handlers 1. Define event in `rxtui/lib/app/events.rs` 2. Add handler parsing in macro 3. Implement event dispatch 4. Write tests for new events ## Testing Guidelines ### Unit Tests Place unit tests in the same file as the code: ```rust #[cfg(test)] mod tests { use super::*; #[test] fn test_feature() { // Test implementation } } ``` ### Integration Tests Place in `rxtui/tests/` directory: ```rust use rxtui::prelude::*; #[test] fn test_complete_flow() { // Test complete user flow } ``` ### Visual Tests For testing rendered output: ```rust #[test] fn test_rendering() { let buffer = TestBuffer::new(80, 24); // Render and assert buffer contents } ``` ## Code Quality ### Before Committing Run these checks: ```bash # Format code cargo fmt # Run clippy cargo clippy -- -D warnings # Run tests cargo test # Check documentation cargo doc --no-deps --open ``` ### Continuous Integration Our CI runs: - `cargo fmt -- --check` - `cargo clippy -- -D warnings` - `cargo test` - `cargo doc` ## Troubleshooting ### Common Issues **Terminal doesn't display correctly** - Ensure your terminal supports Unicode - Check TERM environment variable - Try different terminal emulator **Tests fail with display issues** - Tests should use headless mode - Mock terminal for testing **Performance issues** - Enable optimizations: `cargo build --release` - Profile to find bottlenecks - Check render configuration ## Getting Help - Open an issue on GitHub - Join our community discussions - Check existing issues for solutions ## Release Process 1. Update version in `Cargo.toml` 2. Update CHANGELOG.md 3. Run full test suite 4. Create git tag 5. Push to trigger CI release ## Resources - [Rust Book](https://doc.rust-lang.org/book/) - [Cargo Documentation](https://doc.rust-lang.org/cargo/) - [Terminal Escape Sequences](https://en.wikipedia.org/wiki/ANSI_escape_code) - [crossterm Documentation](https://docs.rs/crossterm/) ================================================ FILE: DOCS.md ================================================ # RxTUI Documentation RxTUI is a reactive terminal user interface framework for Rust that brings modern component-based architecture to the terminal. It combines React-like patterns with efficient terminal rendering through virtual DOM diffing. ## Table of Contents - [Getting Started](#getting-started) - [Terminal Modes](#terminal-modes) - [Components](#components) - [The node! Macro](#the-node-macro) - [State Management](#state-management) - [Message Handling](#message-handling) - [Topic-Based Communication](#topic-based-communication) - [Layout System](#layout-system) - [Styling](#styling) - [Event Handling](#event-handling) - [Built-in Components](#built-in-components) - [Effects (Async)](#effects-async) - [Examples](#examples) ## Getting Started Add RxTUI to your `Cargo.toml`: ```toml [dependencies] rxtui = "0.1" tokio = { version = "1.0", features = ["full"] } # Required for async effects ``` Note: The `effects` feature is enabled by default. To disable it: ```toml [dependencies] rxtui = { version = "0.1", default-features = false } ``` Create your first app: ```rust use rxtui::prelude::*; #[derive(Component)] struct HelloWorld; impl HelloWorld { #[view] fn view(&self, ctx: &Context) -> Node { node! { div(bg: blue, pad: 2, @key_global(esc): ctx.handler(())) [ text("Hello, Terminal!", color: white, bold), text("Press Esc to exit", color: white) ] } } } fn main() -> std::io::Result<()> { App::new()?.run(HelloWorld) } ```
• • •
## Terminal Modes RxTUI supports two terminal rendering modes: **Alternate Screen** (default) and **Inline**. #### Alternate Screen Mode (Default) The default mode uses the terminal's alternate screen buffer. This is ideal for full-screen applications: ```rust fn main() -> std::io::Result<()> { App::new()?.run(MyComponent) // Uses alternate screen } ``` Characteristics: - Takes over the full terminal screen - Content disappears when the app exits - Best for interactive applications, editors, dashboards #### Inline Mode Inline mode renders directly in the terminal buffer without switching screens. Content persists after the app exits, making it ideal for CLI tools: ```rust fn main() -> std::io::Result<()> { // Simple inline mode with defaults App::inline()?.run(MyComponent)?; // This prints after the UI since content is preserved println!("Done! The UI above is preserved."); Ok(()) } ``` Characteristics: - Renders in the main terminal buffer - Content persists in terminal history after exit - Height is content-based by default (grows to fit) - Mouse capture disabled by default (allows terminal scrolling) #### Custom Inline Configuration For fine-grained control over inline rendering: ```rust use rxtui::{App, InlineConfig, InlineHeight}; fn main() -> std::io::Result<()> { let config = InlineConfig { // Fixed height of 10 lines height: InlineHeight::Fixed(10), // Show cursor during rendering cursor_visible: true, // Preserve output after exit preserve_on_exit: true, // Don't capture mouse (allow terminal scrolling) mouse_capture: false, }; App::inline_with_config(config)?.run(MyComponent) } ``` #### Height Modes Control how inline mode determines rendering height: ```rust // Fixed number of lines InlineHeight::Fixed(10) // Grow to fit content, with optional maximum InlineHeight::Content { max: Some(24) } // Max 24 lines InlineHeight::Content { max: None } // No limit (default) // Fill remaining terminal space below cursor InlineHeight::Fill { min: 5 } // At least 5 lines ```
• • •
## Components Everything in RxTUI is a component. Think of them as self-contained UI pieces that know how to manage their own state and behavior. Components have three main capabilities: handling events (through `update`), rendering UI (through `view`), and running async operations (through `effect`): #### Basic Component ```rust #[derive(Component)] struct TodoList; impl TodoList { #[update] fn update(&self, ctx: &Context, msg: TodoMsg, mut state: TodoState) -> Action { // Messages come here from events in your view // You update state, then return Action::update(state) to re-render } #[view] fn view(&self, ctx: &Context, state: TodoState) -> Node { // This renders your UI using the current state // Uses the node! macro to build the UI tree } #[effect] async fn fetch_todos(&self, ctx: &Context, state: TodoState) { // Async effects for background tasks // Useful for timers, API calls, or any async operation } } ``` #### Component Trait The `#[derive(Component)]` macro automatically implements the Component trait. You can also implement it manually: ```rust impl Component for MyComponent { fn update(&self, ctx: &Context, msg: Box, topic: Option<&str>) -> Action { // Handle messages } fn view(&self, ctx: &Context) -> Node { // Return UI tree } fn effects(&self, ctx: &Context) -> Vec { // Return async effects } } ``` #### Complete Working Example Here's a complete working example of a stopwatch component with async effects: ```rust use rxtui::prelude::*; #[derive(Component)] struct Stopwatch; impl Stopwatch { #[update] fn update(&self, _ctx: &Context, tick: bool, state: u64) -> Action { if !tick { return Action::exit(); } Action::update(state + 10) } #[view] fn view(&self, ctx: &Context, state: u64) -> Node { let seconds = state / 1000; let centiseconds = (state % 1000) / 10; node! { div( pad: 2, align: center, w_frac: 1.0, gap: 1, @key(esc): ctx.handler(false), @char_global('q'): ctx.handler(false) ) [ richtext[ text("Elapsed: ", color: white), text( format!(" {}.{:02}s ", seconds, centiseconds), color: "#ffffff", bg: "#9d29c3", bold ), ], text("press esc or q to exit", color: bright_black) ] } } #[effect] async fn tick(&self, ctx: &Context) { loop { tokio::time::sleep(std::time::Duration::from_millis(10)).await; ctx.send(true); } } } fn main() -> std::io::Result<()> { App::new()?.fast_polling().run(Stopwatch) } ``` This example demonstrates: - State management with the `#[update]` method handling timer ticks - Async effects with the `#[effect]` method for continuous updates - Rich text formatting with inline styles and hex colors - Global keyboard event handling with `@key` and `@char_global` - Layout control with centering and responsive width (`w_frac: 1.0`)
• • •
## The node! Macro The `node!` macro is how you actually build your UI. It gives you a clean, declarative syntax that lives inside your component's `view` method. Instead of imperatively creating and configuring widgets, you describe what the UI should look like: #### Basic Syntax ```rust node! { // Root node div(..., ...) [ // Children nodes here text("content", ...), div(...) [ // Nested nodes ... ] ] } ``` Example: ```rust node! { div( bg: blue, pad: 2, border: white, @key(enter): ctx.handler("submit"), @click: ctx.handler("clicked") ) [ richtext(align: center, wrap: word) [ text("Welcome to ", color: bright_white), text("RxTUI", color: yellow, bold), text("!", color: bright_white) ], div [ text("Nested content") ] ] } ``` #### Elements ##### Expressions You can use any Rust expression that returns a `Node` by wrapping it in parentheses: ```rust node! { div [ // Variable (my_node_variable), // Match expression (match state.status { Loading => node! { text("Loading...") }, Ready => node! { text("Ready!") }, }), // If expression (if condition { node! { text("True branch") } } else { node! { text("False branch") } }), // Method call (self.create_node()), ] } ``` ##### Spread Operator Use the `...` spread operator to expand a `Vec` as children: ```rust node! { div [ // Spread a vector of nodes ...(vec![ node! { text("Item 1") }, node! { text("Item 2") }, node! { text("Item 3") }, ]), // Spread from iterator ...(state.items.iter().map(|item| { node! { div(pad: 1) [ text(&item.name) ] } }).collect::>()), // Combine with regular children text("Header", bold), ...(item_nodes), text("Footer"), ] } ``` This is particularly useful for rendering lists or collections dynamically. ##### Div Container ```rust node! { div( // Layout dir: vertical, // or horizontal, v, h gap: 2, // space between children wrap: wrap, // wrap mode // Sizing w: 50, // fixed width h: 20, // fixed height w_frac: 0.5, // 50% of parent width h_frac: 0.8, // 80% of parent height w_auto, // automatic width h_content, // size to content // Styling bg: blue, // background color pad: 2, // padding all sides pad_h: 1, // horizontal padding pad_v: 1, // vertical padding // Borders border: white, // border color border_style: rounded, border_color: yellow, border_edges: BorderEdges::TOP | BorderEdges::BOTTOM, // Interaction focusable, // can receive focus overflow: scroll, // scroll, hidden, auto show_scrollbar: true, // Positioning absolute, // absolute positioning top: 5, left: 10, z: 100 // z-index ) [ // Children here ] } ``` ##### Text ```rust node! { div [ // Simple text text("Hello"), // Styled text text("Styled", color: red, bold, italic, underline), // Dynamic text text(format!("Count: {}", count)), // Text with wrapping text("Long text...", wrap: word), // Text with alignment text("Centered", align: center), text("Right aligned", align: right) ] } ``` ##### Rich Text ```rust node! { div [ richtext [ text("Normal "), text("Bold", bold), text(" and "), text("Colored", color: red) ], // With top-level styling richtext(wrap: word) [ text("Line 1 "), text("Important", color: yellow, bold), text(" continues...") ], // With alignment richtext(align: center) [ text("Centered "), text("rich text", bold) ] ] } ``` ##### Stacks ```rust node! { div [ // Vertical stack (default) vstack [ text("Top"), text("Bottom") ], // Horizontal stack hstack(gap: 2) [ text("Left"), text("Right") ] ] } ``` ##### Components ```rust node! { div [ // Embed other components node(MyComponent::new("config")), node(Counter) ] } ``` ##### Spacers ```rust node! { div [ text("Top"), spacer(2), // 2 lines of space text("Bottom") ] } ``` #### Event Handlers ```rust node! { div( focusable, // Mouse events @click: ctx.handler(Msg::Clicked), // Keyboard events (requires focus) @char('a'): ctx.handler(Msg::KeyA), @key(enter): ctx.handler(Msg::Enter), @key(Char('-')): ctx.handler(Msg::Minus), // Focus events @focus: ctx.handler(Msg::Focused), @blur: ctx.handler(Msg::Blurred), // Global events (work without focus) @char_global('q'): ctx.handler(Msg::Quit), @key_global(esc): ctx.handler(Msg::Exit), // Any character handler @any_char: |ch| ctx.handler(Msg::Typed(ch)) ) [ text("Interactive") ] } ``` #### Optional Properties Use `!` suffix for optional properties: ```rust node! { div( // Only applied if Some bg: (optional_color)!, w: (optional_width)!, border: (if selected { Some(Color::Yellow) } else { None })! ) [ text("Conditional styling") ] } ```
• • •
## State Management These are the heart of your component's logic. State is just your data - what your component needs to remember. #### Component State ```rust #[derive(Debug, Clone, Default)] struct MyState { counter: i32, text: String, } impl MyComponent { #[update] fn update(&self, ctx: &Context, msg: MyMsg, mut state: MyState) -> Action { // The #[update] macro automatically fetches state // and passes it as the last parameter state.counter += 1; Action::update(state) // Save the new state } #[view] fn view(&self, ctx: &Context, state: MyState) -> Node { // The #[view] macro automatically fetches state node! { div [ text(format!("Counter: {}", state.counter)) ] } } } ``` #### Manual State Access ```rust fn update(&self, ctx: &Context, msg: Box, _topic: Option<&str>) -> Action { // Manually get state (or initialize with Default) let mut state = ctx.get_state::(); // Modify state state.counter += 1; // Return updated state Action::update(state) } ```
• • •
## Message Handling Messages are how components respond to events - user clicks, key presses, timers firing. When a message arrives, you update your state, and the UI automatically re-renders. #### Basic Messages ```rust #[derive(Debug, Clone)] enum MyMsg { Click, KeyPress(char), Update(String), } impl MyComponent { #[update] fn update(&self, ctx: &Context, msg: MyMsg, mut state: MyState) -> Action { match msg { MyMsg::Click => { state.clicked = true; Action::update(state) } MyMsg::KeyPress(ch) => { state.text.push(ch); Action::update(state) } MyMsg::Update(text) => { state.text = text; Action::update(state) } } } } ``` #### Actions Update methods return an Action: ```rust pub enum Action { Update(Box), // Update component state UpdateTopic(String, Box), // Update topic state None, // No action Exit, // Exit application } ``` #### Message with Value ```rust // In view node! { div [ @any_char: ctx.handler_with_value(|ch| Box::new(MyMsg::Typed(ch))) ] } ```
• • •
## Topic-Based Communication Topics enable cross-component communication without direct references. #### Sending to Topics ```rust impl Dashboard { #[update] fn update(&self, ctx: &Context, msg: DashboardMsg, state: DashboardState) -> Action { match msg { DashboardMsg::NotifyAll => { // Send message to topic ctx.send_to_topic("notifications", NotificationMsg::Alert); Action::none() } } } } ``` #### Receiving Topic Messages ```rust impl NotificationBar { // Static topic #[update(msg = LocalMsg, topics = ["notifications" => NotificationMsg])] fn update(&self, ctx: &Context, messages: Messages, mut state: State) -> Action { match messages { Messages::LocalMsg(msg) => { // Handle local messages } Messages::NotificationMsg(msg) => { // Handle topic messages // Returning Action::update claims topic ownership state.notifications.push(msg); Action::update(state) } } } } ``` #### Dynamic Topics ```rust struct Counter { topic_name: String, // Topic determined at runtime } impl Counter { // Dynamic topic from field #[update(msg = CounterMsg, topics = [self.topic_name => ResetSignal])] fn update(&self, ctx: &Context, messages: Messages, mut state: CounterState) -> Action { match messages { Messages::CounterMsg(msg) => { /* ... */ } Messages::ResetSignal(_) => { // Reset when signal received Action::update(CounterState::default()) } } } } ``` #### Topic State ```rust // Write topic state (first writer becomes owner) Action::UpdateTopic("app.settings".to_string(), Box::new(settings)) // Read topic state from any component let settings: Option = ctx.read_topic("app.settings"); ```
• • •
## Layout System RxTUI provides a flexible layout system with multiple sizing modes. #### Dimension Types ```rust pub enum Dimension { Fixed(u16), // Exact size in cells Percentage(f32), // Percentage of parent (stored 0.0 to 1.0) Auto, // Share remaining space equally Content, // Size based on children } ``` #### Layout Examples ```rust node! { // Fixed layout div(w: 80, h: 24) [ text("Fixed size") ], // Percentage-based div(w_frac: 0.5, h_frac: 0.8) [ text("50% width, 80% height") ], // Auto sizing - share remaining space hstack [ div(w: 20) [ text("Fixed") ], div(w_auto) [ text("Auto 1") ], // Gets 50% of remaining div(w_auto) [ text("Auto 2") ] // Gets 50% of remaining ], // Content-based sizing div(w_content, h_content) [ text("Size fits content") ] } ``` #### Direction and Wrapping ```rust node! { // Vertical layout (default) div(dir: vertical, gap: 2) [ text("Line 1"), text("Line 2") ], // Horizontal layout div(dir: horizontal, gap: 1) [ text("Col 1"), text("Col 2") ], // With wrapping div(dir: horizontal, wrap: wrap, w: 40) [ // Children wrap to next line when width exceeded div(w: 15) [ text("Item 1") ], div(w: 15) [ text("Item 2") ], div(w: 15) [ text("Item 3") ] // Wraps to next line ] } ``` #### Scrolling ```rust node! { div( h: 10, // Fixed container height overflow: scroll, // Enable scrolling show_scrollbar: true, focusable // Must be focusable for keyboard scrolling ) [ // Content taller than container text("Line 1"), text("Line 2"), // ... many more lines text("Line 50") ] } ``` Scrolling controls: - **Arrow keys**: Scroll up/down by 1 line - **Page Up/Down**: Scroll by container height - **Home/End**: Jump to top/bottom - **Mouse wheel**: Scroll up/down Note: Only vertical scrolling is currently implemented.
• • •
## Styling #### Colors RxTUI supports multiple color formats: ```rust node! { div [ // Named colors text("Red", color: red), text("Bright Blue", color: bright_blue), // Hex colors text("Hex", color: "#FF5733"), // RGB text("RGB", color: (Color::Rgb(255, 128, 0))), // Conditional text("Status", color: (if ok { Color::Green } else { Color::Red })) ] } ``` Available named colors: - Basic: `black`, `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `white` - Bright: `bright_black`, `bright_red`, `bright_green`, `bright_yellow`, `bright_blue`, `bright_magenta`, `bright_cyan`, `bright_white` #### Text Alignment Text and RichText nodes support horizontal alignment within their containers: ```rust node! { div(w: 50) [ // Basic text alignment text("Left aligned", align: left), text("Centered text", align: center), text("Right aligned", align: right), // RichText alignment richtext(align: center) [ text("This "), text("rich text", bold), text(" is centered") ], // Alignment with wrapping text( "Long text that wraps to multiple lines. Each line will be aligned.", wrap: word, align: right ) ] } ``` Note: Text nodes with alignment automatically expand to fill their parent's width to enable proper alignment calculation. #### Div Alignment (Flexbox-style) Divs support CSS Flexbox-style alignment for their children along both the main and cross axes: ```rust node! { // Justify content (main axis) div(dir: h, justify: center, w: 50) [ div(w: 10, h: 3, bg: red) [], div(w: 10, h: 3, bg: green) [], div(w: 10, h: 3, bg: blue) [] ], // Align items (cross axis) div(dir: h, align: end, w: 50, h: 10) [ div(w: 10, h: 3, bg: red) [], div(w: 10, h: 5, bg: green) [], div(w: 10, h: 7, bg: blue) [] ], // Combined justify and align div(dir: v, justify: space_between, align: center, w: 40, h: 20) [ text("Item 1"), text("Item 2"), text("Item 3") ], // With align_self override div(dir: h, align: start, w: 50, h: 10) [ div(w: 10, h: 3, bg: red) [], div(w: 10, h: 3, bg: green, align_self: center) [], div(w: 10, h: 3, bg: blue, align_self: end) [] ] } ``` **JustifyContent** (distributes items along main axis): - `start` - Pack items at the start (default) - `center` - Center items - `end` - Pack items at the end - `space_between` - Distribute evenly, first at start, last at end - `space_around` - Equal space around each item - `space_evenly` - Equal space between and around items **AlignItems** (aligns items on cross axis): - `start` - Align at the start (default) - `center` - Center items - `end` - Align at the end **AlignSelf** (per-child cross axis override): - `auto` - Use parent's align_items (default) - `start` - Align at the start - `center` - Center - `end` - Align at the end The main axis is determined by the direction: - `dir: h` (horizontal) - main axis is horizontal, cross axis is vertical - `dir: v` (vertical) - main axis is vertical, cross axis is horizontal #### Borders ```rust node! { div [ // Simple border div(border: white) [ text("Single border") ], // Border styles div( border_style: rounded, border_color: cyan ) [ text("Rounded border") ], // Partial borders div( border: white, border_edges: top | bottom ) [ text("Top and bottom only") ] ] } ``` Border styles: - `Single` - Normal lines - `Double` - Double lines - `Rounded` - Rounded corners - `Thick` - Thick lines #### Spacing ```rust node! { div [ // Padding div(pad: 2) [ text("All sides") ], div(pad_h: 2) [ text("Horizontal") ], div(pad_v: 1) [ text("Vertical") ], div(padding: (Spacing::new(1, 2, 3, 4))) [ text("Custom") ], // Gap between children div(gap: 2) [ text("Item 1"), text("Item 2") // 2 cells gap ] ] } ``` #### Focus Styles ```rust node! { div( focusable, border: white, focus_style: ({ Style::default() .background(Color::Blue) .border(Color::Yellow) }) ) [ text("Changes style when focused") ] } ```
• • •
## Event Handling #### Focus-Based Events Most events require the element to be focused: ```rust node! { div( focusable, // Mouse @click: ctx.handler(Msg::Clicked), // Keyboard @char('a'): ctx.handler(Msg::PressedA), @key(enter): ctx.handler(Msg::Confirmed), @key(backspace): ctx.handler(Msg::Delete), // Focus @focus: ctx.handler(Msg::GainedFocus), @blur: ctx.handler(Msg::LostFocus) ) [ text("Click or press keys") ] } ``` #### Global Events Global events work regardless of focus: ```rust node! { div( // Application-wide shortcuts @char_global('q'): ctx.handler(Msg::Quit), @key_global(esc): ctx.handler(Msg::Cancel), @char_global('/'): ctx.handler(Msg::Search) ) [ // Children here ] } ``` #### Focus Navigation - **Tab**: Move to next focusable element - **Shift+Tab**: Move to previous focusable element #### Programmatic Focus Use the `Context` focus helpers to move focus immediately after a render: ```rust #[view] fn view(&self, ctx: &Context, state: MyState) -> Node { if ctx.is_first_render() { ctx.focus_self(); // focus the first focusable node in this component } node! { div [ input(focusable), button(focusable) ] } } ``` - `ctx.focus_self()` focuses the first focusable element inside the component's subtree. - `ctx.focus_first()` focuses the first focusable element in the entire app. - `ctx.is_first_render()` is handy for gating autofocus so you do not wrestle with user-driven focus changes later.
• • •
## Built-in Components #### TextInput A full-featured text input component: ```rust use rxtui::components::TextInput; node! { div [ // Basic input input(placeholder: "Enter name...", focusable), // Custom styling input( placeholder: "Password...", password, // Mask input border: yellow, w: 40, content_color: green, cursor_color: white ), // Or use the builder API node( TextInput::new() .placeholder("Email...") .width(50) .border(Color::Cyan) .focus_border(Color::Yellow) ) ] } ``` TextInput features: - Full text editing (insert, delete, backspace) - Cursor movement (arrows, Home/End) - Word navigation (Alt+B/F or Ctrl+arrows) - Word deletion (Ctrl+W, Alt+D) - Line deletion (Ctrl+U/K) - Password mode - Placeholder text - Customizable styling
• • •
## Effects (Async) Effects enable async operations like timers, network requests, and file monitoring. #### Basic Effect ```rust use rxtui::prelude::*; use std::time::Duration; #[derive(Component)] struct Timer; #[component] // Required to collect #[effect] methods impl Timer { #[update] fn update(&self, ctx: &Context, msg: TimerMsg, mut state: TimerState) -> Action { match msg { TimerMsg::Tick => { state.seconds += 1; Action::update(state) } } } #[view] fn view(&self, ctx: &Context, state: TimerState) -> Node { node! { div [ text(format!("Time: {}s", state.seconds)) ] } } #[effect] async fn tick(&self, ctx: &Context) { loop { tokio::time::sleep(Duration::from_secs(1)).await; ctx.send(TimerMsg::Tick); } } } ``` #### Multiple Effects ```rust #[component] impl MyComponent { #[effect] async fn monitor_file(&self, ctx: &Context) { // Watch for file changes } #[effect] async fn fetch_data(&self, ctx: &Context, state: MyState) { // Effects can access state if state.should_fetch { // Fetch from API } } } ``` #### Manual Effects ```rust impl Component for MyComponent { fn effects(&self, ctx: &Context) -> Vec { vec![ Box::pin(async move { // Async code }) ] } } ```
• • •
## Advanced Topics #### Performance Tips 1. **Use keys for lists**: Helps with efficient diffing (not yet implemented) 2. **Minimize state updates**: Only update when necessary 3. **Use topics wisely**: Don't overuse for simple parent-child communication 4. **Profile rendering**: Use `RenderConfig` for debugging #### Debugging ```rust let mut app = App::new()? .render_config(RenderConfig { use_double_buffer: false, // Disable for debugging use_diffing: false, // Show all updates poll_duration_ms: 100, // Slow down for observation }); app.run(MyComponent)?; ``` ================================================ FILE: IMPLEMENTATION.md ================================================ # RxTUI - Implementation Details ## Overview RxTUI is a reactive terminal user interface framework inspired by Elm's message-passing architecture and React's component model. It provides a declarative, component-based API for building interactive terminal applications with efficient rendering through virtual DOM diffing and advanced cross-component communication via topic-based messaging. ## Architecture ```text ┌─────────────────────────────────────────────────────────┐ │ Component System │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Components │ │ Messages │ │ Topics │ │ │ │ - update() │ │ - Direct │ │ - Ownership │ │ │ │ - view() │ │ - Topic │ │ - Broadcast │ │ │ │ - effects() │ │ - Async │ │ - State │ │ │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ │ │ │ │ ┌──────▼─────────────────▼─────────────────▼────────┐ │ │ │ Context │ │ │ │ - StateMap: Component state storage │ │ │ │ - Dispatcher: Message routing │ │ │ │ - TopicStore: Topic ownership & state │ │ │ └──────────────────────┬────────────────────────────┘ │ └─────────────────────────┼───────────────────────────────┘ │ ┌─────────────────────────▼──────────────────────────────┐ │ Rendering Pipeline │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Node │──│ VNode │──│ RenderNode │ │ │ │ (Component) │ │ (Virtual) │ │ (Positioned) │ │ │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ │ │ │ │ ┌──────▼─────────────────▼─────────────────▼───────┐ │ │ │ Virtual DOM (VDom) │ │ │ │ - Diff: Compare old and new trees │ │ │ │ - Patch: Generate minimal updates │ │ │ │ - Layout: Calculate positions and sizes │ │ │ └──────────────────────┬───────────────────────────┘ │ └─────────────────────────┼──────────────────────────────┘ │ ┌─────────────────────────▼──────────────────────────────┐ │ Terminal Output │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │Double Buffer │ │Cell Diffing │ │ Optimized │ │ │ │ Front/Back │ │ Updates │ │ Renderer │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ └────────────────────────────────────────────────────────┘ ``` ## Core Components ### 1. Component System (`lib/component.rs`) The component system is the heart of the framework, providing a React-like component model with state management and message passing. #### Component Trait ```rust pub trait Component: 'static { fn update(&self, ctx: &Context, msg: Box, topic: Option<&str>) -> Action { Action::default() } fn view(&self, ctx: &Context) -> Node; #[cfg(feature = "effects")] fn effects(&self, ctx: &Context) -> Vec { vec![] } fn as_any(&self) -> &dyn Any; fn as_any_mut(&mut self) -> &mut dyn Any; } ``` **Key Design Decisions:** - Components are stateless - all state is managed by Context - Update method receives optional topic for cross-component messaging - Components can be derived using `#[derive(Component)]` macro - Effects support async background tasks (with feature flag) - Default implementations provided for update and effects #### Message and State Traits Both Message and State traits are auto-implemented for any type that is `Clone + Send + Sync + 'static`: ```rust pub trait Message: Any + Send + Sync + 'static { fn as_any(&self) -> &dyn Any; fn clone_box(&self) -> Box; } pub trait State: Any + Send + Sync + 'static { fn as_any(&self) -> &dyn Any; fn as_any_mut(&mut self) -> &mut dyn Any; fn clone_box(&self) -> Box; } ``` **Extension Traits for Downcasting:** - `MessageExt` provides `downcast()` for message type checking - `StateExt` provides `downcast()` for state type checking #### Actions Components return actions from their update method: ```rust #[derive(Default)] pub enum Action { Update(Box), // Update component's local state UpdateTopic(String, Box), // Update topic state (first writer owns) None, // No action needed #[default] Exit, // Exit the application } ``` Helper methods for ergonomic construction: ```rust Action::update(state) // Create Update action Action::update_topic(topic, state) // Create UpdateTopic action Action::none() // Create None action Action::exit() // Create Exit action ``` ### 2. Context System (`lib/app/context.rs`) The Context provides components with everything they need to function: #### Core Components **Context Structure:** - `current_component_id`: Component being processed - `dispatch`: Message dispatcher - `states`: Component state storage (StateMap) - `topics`: Topic-based messaging (Arc) - `message_queues`: Regular message queues (Arc>) - `topic_message_queues`: Topic message queues (Arc>) **StateMap:** - Stores component states with interior mutability using `Arc>` - `get_or_init()`: Get state or initialize with Default, handles type mismatches - Type-safe state retrieval with automatic downcasting - Thread-safe with RwLock protection **Dispatcher:** - Routes messages to components or topics - `send_to_id(component_id, message)`: Direct component messaging - `send_to_topic(topic, message)`: Topic-based messaging - Shared message queue storage with Context **TopicStore:** - Manages topic ownership (first writer becomes owner) - Stores topic states separately from component states - Tracks which component owns which topic via `owners: RwLock>` - Thread-safe with RwLock protection - `update_topic()`: Returns bool indicating if update was successful #### Context Public API ```rust impl Context { // Message handling pub fn handler(&self, msg: M) -> Box; pub fn handler_with_value(&self, f: F) -> Box; // State management pub fn get_state(&self) -> S; pub fn get_state_or(&self, default: S) -> S; // Direct messaging pub fn send(&self, msg: M); // Topic messaging pub fn send_to_topic(&self, topic: &str, msg: M); pub fn read_topic(&self, topic: &str) -> Option; } ``` #### Message Flow 1. **Direct Messages**: Sent to specific component via `ctx.handler(msg)` which creates closures 2. **Topic Messages**: Sent via `ctx.send_to_topic(topic, msg)` - If topic has owner → delivered only to owner - If no owner → broadcast to all components until one claims it ### 3. Topic-Based Messaging System A unique feature for cross-component communication without direct references: #### Concepts - **Topics**: Named channels for messages (e.g., "counter_a", "global_state") - **Ownership**: First component to write to a topic becomes its owner - **Unassigned Messages**: Messages to unclaimed topics are broadcast to all components #### How It Works 1. **Sending Messages:** ```rust ctx.send_to_topic("my-topic", MyMessage); ``` 2. **Claiming Ownership:** ```rust // First component to return this action owns the topic Action::UpdateTopic("my-topic".to_string(), Box::new(MyState)) ``` 3. **Handling Topic Messages (Using Macros):** ```rust // With the #[update] macro: #[update(msg = MyMsg, topics = ["my-topic" => TopicMsg])] fn update(&self, ctx: &Context, messages: Messages, mut state: MyState) -> Action { match messages { Messages::MyMsg(msg) => { /* handle regular message */ } Messages::TopicMsg(msg) => { /* handle topic message */ } } } // Dynamic topics from component fields: #[update(msg = MyMsg, topics = [self.topic_name => TopicMsg])] fn update(&self, ctx: &Context, messages: Messages, state: MyState) -> Action { // Topic name from self.topic_name field } ``` 4. **Reading Topic State:** ```rust let state: Option = ctx.read_topic("my-topic"); ``` **Design Rationale:** - Enables decoupled component communication - Supports both single-writer/multiple-reader and broadcast patterns - Automatic ownership management prevents conflicts - Idempotent updates - multiple attempts to claim ownership are safe ### 4. Application Core (`lib/app/core.rs`) The App struct manages the entire application lifecycle: #### Initialization ```rust App::new() // Standard initialization App::with_config(RenderConfig { ... }) // With custom config ``` - Enables terminal raw mode and alternate screen - Hides cursor and enables mouse capture - Initializes double buffer for flicker-free rendering - Sets up event handling with crossterm - Creates effect runtime (if feature enabled) using Tokio #### Event Loop The main loop (`run_loop`) follows this sequence: 1. **Component Tree Expansion**: - Start with root component - Recursively expand components to VNodes - Assign component IDs based on tree position using `ComponentId::child(index)` method (e.g., "0", "0.0", "0.1") 2. **Message Processing**: - Components drain all pending messages (regular + topic) - Messages trigger state updates via component's `update` method - Handle actions (Update, UpdateTopic, Exit, None) 3. **Virtual DOM Update**: - VDom diffs new tree against current - Generates patches for changes - Updates render tree 4. **Layout & Rendering**: - Calculate positions and sizes based on Dimension types - Render to back buffer - Diff buffers and apply changes to terminal 5. **Event Handling**: - Process keyboard/mouse events (poll with 16ms timeout by default) - Events trigger new messages via event handlers - Handle terminal resize events 6. **Effect Management** (if feature enabled): - Spawn effects for newly mounted components - Cleanup effects for unmounted components - Effects run in Tokio runtime with JoinHandle tracking #### Component Tree Expansion The `expand_component_tree` method is crucial: 1. Drains all messages for the component (both regular and topic messages) 2. Processes each message: - Regular messages → component's update - Topic messages → check if component handles topic 3. Handles actions: - `Update` → update component state via StateMap - `UpdateTopic` → update topic state, claim ownership if first - `Exit` → propagate exit signal - `None` → no operation 4. Calls component's `view` to get UI tree 5. Recursively expands child components ### 5. Node Types Three levels of node representation: #### Node (`lib/node/mod.rs`) High-level component tree: ```rust pub enum Node { Component(Arc), // Component instance (Arc for sharing) Div(Div), // Container with children Text(Text), // Text content RichText(RichText), // Styled text with multiple spans } ``` #### VNode (`lib/vnode.rs`) Virtual DOM nodes after component expansion: ```rust pub enum VNode { Div(Div), // Expanded div (generic over child type) Text(Text), // Text node RichText(RichText), // Rich text node } ``` #### RenderNode (`lib/render_tree/node.rs`) Positioned nodes ready for drawing: ```rust pub struct RenderNode { pub node_type: RenderNodeType, pub x: u16, pub y: u16, // Position pub width: u16, pub height: u16, // Size pub content_width: u16, // Actual content size pub content_height: u16, pub scroll_y: u16, // Vertical scroll offset pub scrollable: bool, // Has overflow:scroll/auto pub style: Option