返回 CodeWhale
starter.rs
1 //! A small native Codewhale app: edit, paste and echo a line with Enter.
2 //! The application owns input, data and exit; components only paint.
3 use std::{io, time::Duration};
4
5 use codewhale_ratatui::{
6 Message, MotionMode, NativeComposer, OceanColumn, Paint, PostureBar, TerminalShell,
7 TextInputOutcome, TextInputState, Theme, TuiGround, Workbar, WorkbarPanel, WorkbarRow,
8 };
9 use crossterm::{
10 event::{self, DisableBracketedPaste, EnableBracketedPaste, Event, KeyCode, KeyModifiers},
11 execute,
12 };
13
14 struct PasteMode;
15 impl Drop for PasteMode {
16 fn drop(&mut self) {
17 let _ = execute!(io::stdout(), DisableBracketedPaste);
18 }
19 }
20
21 fn main() -> io::Result<()> {
22 ratatui::run(|terminal| {
23 execute!(io::stdout(), EnableBracketedPaste)?;
24 let _paste_mode = PasteMode;
25 let theme = Theme::detect().tui();
26 let mut draft = TextInputState::new();
27 let mut message = String::from("Type a line below. Enter echoes it here; Esc closes.");
28 let workbar = Workbar::new(
29 WorkbarPanel::Tasks,
30 vec![WorkbarRow::new("echo", "Edit and submit your first message").mark("●")],
31 );
32
33 loop {
34 terminal.draw(|frame| {
35 let area = frame.area();
36 let composer = NativeComposer::new(draft.buffer().text())
37 .focused(true)
38 .cursor(draft.buffer().cursor())
39 .can_submit(!draft.buffer().is_empty())
40 .placeholder("Write something…");
41 let shell = TerminalShell::new(composer.desired_height(area.width, area.height))
42 .workbar_rows(workbar.height(area.width, &theme));
43 let regions = shell.areas(area);
44 let cursor = composer.cursor_position(regions.composer);
45 let buf = frame.buffer_mut();
46 shell.paint(area, buf, &theme);
47 Message::native(&message).paint(regions.conversation, buf, &theme);
48 composer.paint(regions.composer, buf, &theme);
49 PostureBar::new("ask").paint(regions.posture, buf, &theme);
50 workbar.paint(regions.workbar, buf, &theme);
51 // A still column keeps the native depth without an idle redraw loop.
52 let water = OceanColumn::new(Duration::ZERO, MotionMode::Still).viewport(area);
53 water.apply(area, buf, &theme);
54 if let Some(ground) = theme.tui_ground(TuiGround::Composer).bg {
55 water.apply_matching(regions.composer, buf, &theme, ground);
56 }
57 if let Some(cursor) = cursor {
58 frame.set_cursor_position(cursor);
59 }
60 })?;
61
62 match event::read()? {
63 Event::Key(key) => {
64 if key.is_press()
65 && key.code == KeyCode::Char('c')
66 && key.modifiers == KeyModifiers::CONTROL
67 {
68 break;
69 }
70 match draft.handle_key(key) {
71 TextInputOutcome::Submitted if !draft.buffer().is_empty() => {
72 message = draft.buffer().text().to_owned();
73 draft.clear();
74 }
75 TextInputOutcome::Cancelled => break,
76 _ => {}
77 }
78 }
79 Event::Paste(text) => {
80 draft.paste(&text);
81 }
82 Event::Resize(..) => {}
83 _ => {}
84 }
85 }
86 Ok(())
87 })
88 }
89
89 lines RUST