| 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 |