use crate::*; use unicode_width::{UnicodeWidthStr, UnicodeWidthChar}; use rand::{thread_rng, distributions::uniform::UniformSampler}; impl Tui { /// Create and launch a terminal user interface. pub fn run + Draw + 'static> ( state: &Arc> ) -> Usually>> { tui_run(state) } /// True if done pub fn exited (&self) -> bool { self.exited.fetch_and(true, Relaxed) } /// Prepare before run pub fn setup (&mut self) -> Usually<()> { tui_setup(&mut self.backend) } /// Clean up after run pub fn teardown (&mut self) -> Usually<()> { tui_teardown(&mut self.backend) } /// Apply changes to the display buffer. pub fn flip (&mut self, mut new_buffer: Buffer, size: ratatui::prelude::Rect) -> Buffer { let Self { buffer, backend, .. } = self; tui_resized(&mut backend, &mut buffer, size); tui_redrawn(&mut backend, &mut buffer, &mut new_buffer); buffer } } impl Input for TuiIn { type Event = TuiEvent; type Handled = bool; fn event (&self) -> &TuiEvent { &self.event } fn done (&self) { self.exited.store(true, Relaxed); } fn is_done (&self) -> bool { self.exited.fetch_and(true, Relaxed) } } impl Ord for TuiEvent { fn cmp (&self, other: &Self) -> std::cmp::Ordering { self.partial_cmp(other) .unwrap_or_else(||format!("{:?}", self).cmp(&format!("{other:?}"))) // FIXME perf } } impl TuiEvent { pub fn from_crossterm (event: Event) -> Self { Self(event) } #[cfg(feature = "dsl")] pub fn from_dsl (dsl: impl Language) -> Perhaps { Ok(TuiKey::from_dsl(dsl)?.to_crossterm().map(Self)) } } impl TuiKey { const SPLIT: char = '/'; #[cfg(feature = "dsl")] pub fn from_dsl (dsl: impl Language) -> Usually { if let Some(word) = dsl.word()? { let word = word.trim(); Ok(if word == ":char" { Self(None, KeyModifiers::NONE) } else if word.chars().nth(0) == Some('@') { let mut key = None; let mut modifiers = KeyModifiers::NONE; let mut tokens = word[1..].split(Self::SPLIT).peekable(); while let Some(token) = tokens.next() { if tokens.peek().is_some() { match token { "ctrl" | "Ctrl" | "c" | "C" => modifiers |= KeyModifiers::CONTROL, "alt" | "Alt" | "m" | "M" => modifiers |= KeyModifiers::ALT, "shift" | "Shift" | "s" | "S" => { modifiers |= KeyModifiers::SHIFT; // + TODO normalize character case, BackTab, etc. }, _ => panic!("unknown modifier {token}"), } } else { key = if token.len() == 1 { Some(KeyCode::Char(token.chars().next().unwrap())) } else { Some(named_key(token).unwrap_or_else(||panic!("unknown character {token}"))) } } } Self(key, modifiers) } else { return Err(format!("TuiKey: unexpected: {word}").into()) }) } else { return Err(format!("TuiKey: unspecified").into()) } } pub fn to_crossterm (&self) -> Option { self.0.map(|code|Event::Key(KeyEvent { code, modifiers: self.1, kind: KeyEventKind::Press, state: KeyEventState::NONE, })) } } impl Out for TuiOut { type Unit = u16; #[inline] fn area (&self) -> XYWH { self.area } #[inline] fn area_mut (&mut self) -> &mut XYWH { &mut self.area } #[inline] fn place_at <'t, T: Draw + ?Sized> (&mut self, area: XYWH, content: &'t T) { let last = self.area(); *self.area_mut() = area; content.draw(self); *self.area_mut() = last; } } impl TuiOut { #[inline] pub fn with_rect (&mut self, area: XYWH) -> &mut Self { self.area = area; self } pub fn update (&mut self, area: XYWH, callback: &impl Fn(&mut Cell, u16, u16)) { tui_update(&mut self.buffer, area, callback); } pub fn fill_char (&mut self, area: XYWH, c: char) { self.update(area, &|cell,_,_|{cell.set_char(c);}) } pub fn fill_bg (&mut self, area: XYWH, color: Color) { self.update(area, &|cell,_,_|{cell.set_bg(color);}) } pub fn fill_fg (&mut self, area: XYWH, color: Color) { self.update(area, &|cell,_,_|{cell.set_fg(color);}) } pub fn fill_mod (&mut self, area: XYWH, on: bool, modifier: Modifier) { if on { self.update(area, &|cell,_,_|cell.modifier.insert(modifier)) } else { self.update(area, &|cell,_,_|cell.modifier.remove(modifier)) } } pub fn fill_bold (&mut self, area: XYWH, on: bool) { self.fill_mod(area, on, Modifier::BOLD) } pub fn fill_reversed (&mut self, area: XYWH, on: bool) { self.fill_mod(area, on, Modifier::REVERSED) } pub fn fill_crossed_out (&mut self, area: XYWH, on: bool) { self.fill_mod(area, on, Modifier::CROSSED_OUT) } pub fn fill_ul (&mut self, area: XYWH, color: Option) { if let Some(color) = color { self.update(area, &|cell,_,_|{ cell.modifier.insert(ratatui::prelude::Modifier::UNDERLINED); cell.underline_color = color; }) } else { self.update(area, &|cell,_,_|{ cell.modifier.remove(ratatui::prelude::Modifier::UNDERLINED); }) } } pub fn tint_all (&mut self, fg: Color, bg: Color, modifier: Modifier) { for cell in self.buffer.content.iter_mut() { cell.fg = fg; cell.bg = bg; cell.modifier = modifier; } } pub fn blit (&mut self, text: &impl AsRef, x: u16, y: u16, style: Option