tui-lipan 0.2.0

Opinionated, component-based TUI framework for Rust - declarative components, reconciliation, layout engine, focus, overlays, and rich widgets on top of ratatui.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
//! A comprehensive showcase of Lipan's built-in widgets.
//!
//! This example demonstrates Checkboxes, ProgressBars, Spinners, and Sliders.
//!
//! Controls:
//! - Tab/Shift+Tab: Navigate between focusable widgets
//! - Space/Enter: Toggle checkboxes or interact with sliders
//! - Click: Direct interaction with widgets
//! - Drag: Adjust progress bars and sliders
//! - Spinners: Animate automatically
//! - 'q'/Esc: Quit

use tui_lipan::prelude::*;
use tui_lipan::style::palette;

struct App {
    checkboxes: [bool; 4],
    progress: f64,
    slider_val: f64,
}

#[derive(Clone, Debug)]
enum Msg {
    Toggle(usize, bool),
    SetProgress(f64),
    SetSlider(f64),
}

impl Component for App {
    type Message = Msg;
    type Properties = ();
    type State = ();

    fn create_state(&self, _props: &Self::Properties) -> Self::State {}

    fn update(&mut self, msg: Self::Message, _ctx: &mut Context<Self>) -> Update {
        match msg {
            Msg::Toggle(idx, checked) => {
                self.checkboxes[idx] = checked;
            }
            Msg::SetProgress(progress) => {
                self.progress = progress;
            }
            Msg::SetSlider(val) => {
                self.slider_val = val;
            }
        }
        Update::layout()
    }

    fn on_key(&mut self, key: KeyEvent, ctx: &mut Context<Self>) -> KeyUpdate {
        match key.code {
            KeyCode::Char('q') => {
                ctx.quit();
                KeyUpdate::handled(Update::full())
            }
            _ => KeyUpdate::unhandled(Update::none()),
        }
    }

    fn view(&self, ctx: &Context<Self>) -> Element {
        let link = ctx.link();

        rsx! {
            VStack {
                gap: 0,
                padding: 0,
                Frame {
                    header_left: "Checkboxes (Tab to navigate, Space/Enter/Click to toggle)",
                    border: true,
                    border_style: BorderStyle::Rounded,
                    height: Length::Auto,
                    VStack {
                        gap: 1,
                        padding: 1,
                        Checkbox {
                            checked: self.checkboxes[0],
                            label: "Bracket style - classic terminal look",
                            variant: CheckboxVariant::Bracket,
                            on_toggle: link.callback(move |e: CheckboxEvent| { Msg::Toggle(0, e.state.is_checked()) }),
                            focusable: false,
                        },
                        Checkbox {
                            checked: self.checkboxes[1],
                            label: "Circle style - radio-like appearance",
                            variant: CheckboxVariant::Circle,
                            on_toggle: link.callback(move |e: CheckboxEvent| { Msg::Toggle(1, e.state.is_checked()) }),
                        },
                        Checkbox {
                            checked: self.checkboxes[2],
                            label: "Box style - modern with checkmark",
                            variant: CheckboxVariant::Box,
                            on_toggle: link.callback(move |e: CheckboxEvent| { Msg::Toggle(2, e.state.is_checked()) }),
                        },
                        Checkbox {
                            checked: self.checkboxes[3],
                            label: "Custom style - (Y)/(N)",
                            variant: CheckboxVariant::Custom {
                                checked: "(Y)",
                                unchecked: "(N)",
                                indeterminate: "(?)",
                            },
                            on_toggle: link.callback(move |e: CheckboxEvent| { Msg::Toggle(3, e.state.is_checked()) }),
                        },
                    },
                },
                Frame {
                    header_left: "Sliders (drag to change value)",
                    border: true,
                    border_style: BorderStyle::Rounded,
                    height: Length::Auto,
                    VStack {
                        gap: 1,
                        padding: 1,
                        Slider {
                            value: self.slider_val,
                            min: 0.0,
                            max: 100.0,
                            step: 1.0,
                            label: "Standard Slider",
                            focusable: true,
                            focus_style: Style::new().fg(palette::BLUE),
                            on_change: link.callback(Msg::SetSlider),
                        },
                        Slider {
                            value: self.slider_val,
                            min: 0.0,
                            max: 100.0,
                            step: 5.0,
                            label: "Interactive (Hover/Focus)",
                            style: Style::new().fg(palette::SLATE),
                            thumb_style: Style::new().fg(palette::BLUE),
                            focusable: true,
                            focus_style: Style::new().fg(palette::CYAN),
                            focus_thumb_style: Style::new().fg(palette::CYAN).bold(),
                            hover_thumb_style: Style::new().fg(palette::RED),
                            thumb_symbol: "",
                            hover_thumb_symbol: "",
                            on_change: link.callback(Msg::SetSlider),
                        },
                    },
                },
                Frame {
                    header_left: "Progress Bars (drag to change value)",
                    border: true,
                    border_style: BorderStyle::Rounded,
                    height: Length::Flex(1),
                    VStack {
                        gap: 0,
                        padding: (0, 1),
                        HStack {
                            gap: 1,
                            Text { content: "Block:  " },
                            ProgressBar {
                                progress: self.progress,
                                progress_style: ProgressStyle::Block,
                                show_percentage: true,
                                filled_style: Style::new().fg(palette::EMERALD),
                                draggable: true,
                                on_change: link.callback(|e: ProgressEvent| Msg::SetProgress(e.progress)),
                            },
                        },
                        HStack {
                            gap: 1,
                            Text { content: "Rect:   " },
                            ProgressBar {
                                progress: self.progress,
                                progress_style: ProgressStyle::Rect,
                                show_percentage: true,
                                filled_style: Style::new().fg(palette::ROSE),
                                draggable: true,
                                on_change: link.callback(|e: ProgressEvent| Msg::SetProgress(e.progress)),
                            },
                        },
                        HStack {
                            gap: 1,
                            Text { content: "Custom: " },
                            ProgressBar {
                                progress: self.progress,
                                progress_style: ProgressStyle::Custom {
                                    filled: '#',
                                    empty: '-',
                                },
                                show_percentage: true,
                                filled_style: Style::new().fg(palette::LIME),
                                draggable: true,
                                on_change: link.callback(|e: ProgressEvent| Msg::SetProgress(e.progress)),
                            },
                        },
                        HStack {
                            gap: 1,
                            Text { content: "Line:   " },
                            ProgressBar {
                                progress: self.progress,
                                progress_style: ProgressStyle::Line,
                                show_percentage: true,
                                filled_style: Style::new().fg(palette::SKY),
                                draggable: true,
                                on_change: link.callback(|e: ProgressEvent| Msg::SetProgress(e.progress)),
                            },
                        },
                        HStack {
                            gap: 1,
                            Text { content: "Dots:   " },
                            ProgressBar {
                                progress: self.progress,
                                progress_style: ProgressStyle::Dots,
                                show_percentage: true,
                                filled_style: Style::new().fg(palette::FUCHSIA),
                                draggable: true,
                                on_change: link.callback(|e: ProgressEvent| Msg::SetProgress(e.progress)),
                            },
                        },
                        HStack {
                            gap: 1,
                            Text { content: "Dotted: " },
                            ProgressBar {
                                progress: self.progress,
                                progress_style: ProgressStyle::LineDotted,
                                show_percentage: true,
                                filled_style: Style::new().fg(palette::AMBER),
                                draggable: true,
                                on_change: link.callback(|e: ProgressEvent| Msg::SetProgress(e.progress)),
                            },
                        },
                        HStack {
                            gap: 1,
                            Text { content: "Braille:" },
                            ProgressBar {
                                progress: self.progress,
                                progress_style: ProgressStyle::Braille,
                                show_percentage: true,
                                filled_style: Style::new().fg(palette::TEAL),
                                draggable: true,
                                on_change: link.callback(|e: ProgressEvent| Msg::SetProgress(e.progress)),
                            },
                        },
                        HStack {
                            gap: 1,
                            Text { content: "Stepped: " },
                            ProgressBar {
                                progress: self.progress,
                                progress_style: ProgressStyle::Block,
                                show_percentage: true,
                                filled_style: Style::new().fg(palette::VIOLET),
                                draggable: true,
                                step: 0.1,
                                focusable: true,
                                focus_style: Style::new().fg(palette::YELLOW),
                                on_change: link.callback(|e: ProgressEvent| Msg::SetProgress(e.progress)),
                            },
                        },
                    },
                },
                Frame {
                    header_left: "Spinners (auto-animated)",
                    border: true,
                    border_style: BorderStyle::Rounded,
                    height: Length::Flex(1),
                    VStack {
                        gap: 0,
                        padding: 0,
                        align: Align::Center,
                        HStack {
                            gap: 0,
                            padding: 0,
                            align: Align::Center,
                            justify: Justify::SpaceBetween,
                            Spinner {
                                spinner_style: SpinnerStyle::Dots,
                                label: "Dots",
                                style: Style::new().fg(Color::Cyan),
                            },
                            Spinner {
                                spinner_style: SpinnerStyle::Line,
                                label: "Line",
                                style: Style::new().fg(Color::Yellow),
                            },
                            Spinner {
                                spinner_style: SpinnerStyle::Circle,
                                label: "Circle",
                                style: Style::new().fg(Color::Green),
                            },
                            Spinner {
                                spinner_style: SpinnerStyle::Braille,
                                label: "Braille",
                                style: Style::new().fg(Color::Magenta),
                            },
                            Spinner {
                                spinner_style: SpinnerStyle::Arc,
                                label: "Arc",
                                style: Style::new().fg(Color::Red),
                            },
                            Spinner {
                                spinner_style: SpinnerStyle::Bar,
                                label: "Bar",
                                style: Style::new().fg(Color::Green),
                            },
                            Spinner {
                                spinner_style: SpinnerStyle::Arrow,
                                label: "Arrow",
                                style: Style::new().fg(Color::Yellow),
                            },
                            Spinner {
                                spinner_style: SpinnerStyle::Fade,
                                label: "Fade",
                                style: Style::new().fg(Color::Magenta),
                            },
                        },
                        HStack {
                            gap: 0,
                            padding: 0,
                            align: Align::Center,
                            justify: Justify::SpaceBetween,
                            Spinner {
                                spinner_style: SpinnerStyle::Trail,
                                label: "Trail",
                                style: Style::new().fg(Color::Cyan),
                            },
                            Spinner {
                                spinner_style: SpinnerStyle::Earth,
                                label: "Earth",
                            },
                            Spinner {
                                spinner_style: SpinnerStyle::Moon,
                                label: "Moon",
                            },
                            Spinner {
                                spinner_style: SpinnerStyle::Box,
                                label: "Box",
                                style: Style::new().fg(Color::LightBlue),
                            },
                            Spinner {
                                spinner_style: SpinnerStyle::ThreeDot,
                                label: "ThreeDot",
                                style: Style::new().fg(Color::Cyan),
                            },
                            Spinner {
                                spinner_style: SpinnerStyle::ThreeDotFade,
                                label: "ThreeDotFade",
                                style: Style::new().fg(Color::Blue),
                            },
                            Spinner {
                                spinner_style: SpinnerStyle::SquareFade,
                                label: "SquareFade",
                                style: Style::new().fg(Color::Magenta),
                            },
                        },
                        HStack {
                            gap: 0,
                            padding: 0,
                            align: Align::Center,
                            justify: Justify::SpaceBetween,
                            Spinner {
                                spinner_style: SpinnerStyle::OpenCode,
                                label: "OpenCode",
                                speed: SpinnerSpeed::Fast,
                            },
                            Spinner {
                                spinner_style: SpinnerStyle::Claude,
                                label: "Claude",
                                style: Style::new().fg(Color::Rgb(217, 119, 87)),
                                speed: SpinnerSpeed::Custom {
                                    frame_ms: 150,
                                },
                            },
                        },
                        HStack {
                            gap: 0,
                            padding: 0,
                            align: Align::Center,
                            justify: Justify::SpaceBetween,
                            VStack {
                                width: Length::Flex(1),
                                height: Length::Auto,
                                align: Align::Center,
                                Spinner {
                                    spinner_style: SpinnerStyle::Lightsaber,
                                    label: "Lightsaber (Cyan)",
                                    style: Style::new().fg(Color::Rgb(0, 200, 255)),
                                    speed: SpinnerSpeed::Fast,
                                    width: Length::Auto,
                                },
                            },
                            VStack {
                                width: Length::Flex(1),
                                height: Length::Auto,
                                align: Align::Center,
                                Spinner {
                                    spinner_style: SpinnerStyle::Lightsaber,
                                    label: "Lightsaber (Red)",
                                    style: Style::new().fg(Color::Rgb(220, 0, 0)),
                                    speed: SpinnerSpeed::Fast,
                                    width: Length::Auto,
                                },
                            },
                        },
                    },
                },
                Text {
                    content: "Press 'q' or Esc to quit",
                    style: Style::new().dim(),
                },
            }
        }
    }
}

fn main() -> tui_lipan::Result<()> {
    let app = App {
        checkboxes: [false, true, false, true],
        progress: 0.35,
        slider_val: 50.0,
    };

    tui_lipan::App::new().mount(app).run()
}