Skip to main content

widget_usage/
widget_usage.rs

1//! Widget system demonstration showing Container-based layout and text widgets.
2//!
3//! This example showcases:
4//! - Container layouts (vertical + horizontal)
5//! - Borders + titles (Panel has been absorbed into Container)
6//! - Padding + gaps
7//! - Text widgets with word wrapping (via TextBlock)
8//!
9//! Controls:
10//! - Press 'q' to quit
11//!
12//! Notes:
13//! - This example intentionally relies on Container layout (not manual child positioning)
14//!   to avoid clipping/misalignment issues as the terminal resizes.
15
16use minui::prelude::*;
17
18fn main() -> minui::Result<()> {
19    let mut app = App::new(())?;
20
21    app.run(
22        |_state, event| {
23            // Quit on 'q' (supports both modifier-aware and legacy events).
24            match event {
25                Event::KeyWithModifiers(k) if matches!(k.key, KeyKind::Char('q')) => false,
26                Event::Character('q') => false,
27                _ => true,
28            }
29        },
30        |_state, window| {
31            let (w, h) = window.get_size();
32            create_app_layout(w, h).draw(window)?;
33            window.flush()?;
34            Ok(())
35        },
36    )?;
37
38    Ok(())
39}
40
41fn create_app_layout(term_w: u16, term_h: u16) -> Container {
42    // `ContainerPadding` is what the prelude exports (the underlying type is `Padding`).
43    use minui::widgets::ContainerPadding;
44
45    // A simple "fill the terminal" root. Children will be laid out inside it.
46    // We make it vertical with a row gap so sections are visually separated.
47    let root = Container::new()
48        .with_position_and_size(0, 0, term_w, term_h)
49        .with_layout_direction(LayoutDirection::Vertical)
50        .with_row_gap(Gap::Pixels(1))
51        .with_padding(ContainerPadding::uniform(1));
52
53    // Header: fixed height so it behaves like an app bar.
54    let header_h: u16 = 3;
55    let header = Container::new()
56        .with_position_and_size(0, 0, term_w.saturating_sub(2), header_h)
57        .with_layout_direction(LayoutDirection::Vertical)
58        .with_border()
59        .with_border_chars(BorderChars::double_line())
60        .with_border_color(ColorPair::new(Color::LightBlue, Color::Black))
61        .with_title("MinUI Widget Demo")
62        .with_title_alignment(TitleAlignment::Center)
63        .with_padding(ContainerPadding::symmetric(0, 1))
64        .add_child(Label::new("Press 'q' to quit").with_text_color(Color::Cyan));
65
66    // Footer: also fixed height.
67    let footer_h: u16 = 3;
68    let footer = Container::new()
69        .with_position_and_size(0, 0, term_w.saturating_sub(2), footer_h)
70        .with_layout_direction(LayoutDirection::Vertical)
71        .with_border()
72        .with_border_chars(BorderChars::single_line())
73        .with_border_color(ColorPair::new(Color::Yellow, Color::Black))
74        .with_padding(ContainerPadding::symmetric(0, 1))
75        .add_child(Label::new("Status: Ready • Press 'q' to quit").with_text_color(Color::Yellow));
76
77    // Body: fills the remaining space (best-effort). We size it explicitly so content has a
78    // stable viewport, but rely on Container layout for its children.
79    let body_h = term_h
80        .saturating_sub(1) // root padding top
81        .saturating_sub(1) // root padding bottom
82        .saturating_sub(header_h)
83        .saturating_sub(1) // root row gap between header/body
84        .saturating_sub(footer_h)
85        .saturating_sub(1); // root row gap between body/footer
86
87    let body = Container::new()
88        .with_position_and_size(0, 0, term_w.saturating_sub(2), body_h)
89        .with_layout_direction(LayoutDirection::Horizontal)
90        .with_column_gap(Gap::Pixels(2))
91        .with_padding(ContainerPadding::uniform(0));
92
93    // Panels are auto-sized from children, then clipped by the body container viewport.
94    // The text blocks have explicit sizes so word wrapping behaves predictably.
95    let panel_w = (term_w.saturating_sub(2))
96         .saturating_sub(2) // body column gap
97         / 2;
98    let panel_h = body_h;
99
100    // Leave space for: borders (2) + padding (2) => content width/height roughly -4.
101    let text_w = panel_w.saturating_sub(4).max(1);
102    let text_h = panel_h.saturating_sub(4).max(1);
103
104    let left_panel = Container::new()
105        .with_position_and_size(0, 0, panel_w, panel_h)
106        .with_layout_direction(LayoutDirection::Vertical)
107        .with_border()
108        .with_border_chars(BorderChars::single_line())
109        .with_border_color(ColorPair::new(Color::Red, Color::Black))
110        .with_title("Left")
111        .with_padding(ContainerPadding::uniform(1))
112        .add_child(
113            TextBlock::new(
114                text_w,
115                text_h,
116                "This demonstrates how Containers can be arranged side-by-side. \
117 They support borders, titles, padding, and child layout. \
118 Resize the terminal to see how clipping behaves.",
119            )
120            .with_word_wrap(),
121        );
122
123    let right_panel = Container::new()
124        .with_position_and_size(0, 0, panel_w, panel_h)
125        .with_layout_direction(LayoutDirection::Vertical)
126        .with_border()
127        .with_border_chars(BorderChars::single_line())
128        .with_border_color(ColorPair::new(Color::Green, Color::Black))
129        .with_title("Right")
130        .with_padding(ContainerPadding::uniform(1))
131        .add_child(
132            TextBlock::new(
133                text_w,
134                text_h,
135                "Multiple widgets can coexist inside Container layouts. \
136 Each Container manages styling, while children provide content. \
137 This text is wrapped using TextBlock.",
138            )
139            .with_word_wrap(),
140        );
141
142    let body = body.add_child(left_panel).add_child(right_panel);
143
144    root.add_child(header).add_child(body).add_child(footer)
145}