From ddb3c28c0112e2c78aa942f2d7d54db82390a812 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 9 Dec 2024 11:00:48 +0100 Subject: [PATCH] wip(108e): layout refactor --- Cargo.lock | 3 +- crates/tek_api/src/api_clock.rs | 31 +- crates/tek_api/src/api_player.rs | 16 +- crates/tek_api/src/api_timeline.ts | 0 crates/tek_api/src/lib.rs | 4 +- crates/tek_core/Cargo.toml | 1 - crates/tek_core/examples/demo.rs | 2 +- crates/tek_core/src/color.rs | 1 + crates/tek_core/src/layout.rs | 76 ++ crates/tek_core/src/lib.rs | 8 +- crates/tek_core/src/space.rs | 132 +++- crates/tek_core/src/time.rs | 14 +- crates/tek_core/src/tui.rs | 690 ----------------- crates/tek_tui/Cargo.toml | 2 + crates/tek_tui/src/lib.rs | 12 +- crates/tek_tui/src/tui.rs | 693 ++++++++++++++++++ crates/tek_tui/src/tui_app_sequencer.rs | 15 +- crates/tek_tui/src/tui_model_phrase_player.rs | 12 +- .../tek_tui/src/tui_view_phrase_selector.rs | 2 +- crates/tek_tui/src/tui_view_sequencer.rs | 151 +++- crates/tek_tui/src/tui_view_transport.rs | 101 ++- 21 files changed, 1141 insertions(+), 825 deletions(-) create mode 100644 crates/tek_api/src/api_timeline.ts create mode 100644 crates/tek_core/src/layout.rs create mode 100644 crates/tek_tui/src/tui.rs diff --git a/Cargo.lock b/Cargo.lock index d5775d8b..3091aae7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2711,7 +2711,6 @@ dependencies = [ "better-panic", "clap", "clojure-reader", - "crossterm", "jack", "midly", "once_cell", @@ -2726,6 +2725,8 @@ dependencies = [ name = "tek_tui" version = "0.1.0" dependencies = [ + "better-panic", + "crossterm", "livi", "suil-rs", "symphonia", diff --git a/crates/tek_api/src/api_clock.rs b/crates/tek_api/src/api_clock.rs index 2368cf21..f7792912 100644 --- a/crates/tek_api/src/api_clock.rs +++ b/crates/tek_api/src/api_clock.rs @@ -33,18 +33,35 @@ impl Command for ClockCommand { } } +#[derive(Clone)] +pub struct Timeline { + pub timebase: Arc, + pub started: Arc>>, + pub loopback: Arc>>, +} + +impl Default for Timeline { + fn default () -> Self { + Self { + timebase: Arc::new(Timebase::default()), + started: RwLock::new(None).into(), + loopback: RwLock::new(None).into(), + } + } +} + #[derive(Clone)] pub struct ClockModel { /// JACK transport handle. pub transport: Arc, - /// Global temporal resolution (shared by [Instant] fields) + /// Global temporal resolution (shared by [Moment] fields) pub timebase: Arc, /// Current global sample and usec (monotonic from JACK clock) - pub global: Arc, + pub global: Arc, /// Global sample and usec at which playback started - pub started: Arc>>, + pub started: Arc>>, /// Current playhead position - pub playhead: Arc, + pub playhead: Arc, /// Note quantization factor pub quant: Arc, /// Launch quantization factor @@ -64,8 +81,8 @@ impl From<&Arc>> for ClockModel { sync: Arc::new(384.into()), transport: Arc::new(transport), chunk: Arc::new((chunk as usize).into()), - global: Arc::new(Instant::zero(&timebase)), - playhead: Arc::new(Instant::zero(&timebase)), + global: Arc::new(Moment::zero(&timebase)), + playhead: Arc::new(Moment::zero(&timebase)), started: RwLock::new(None).into(), timebase, } @@ -147,7 +164,7 @@ impl ClockModel { match self.transport.query_state()? { TransportState::Rolling => { if started.is_none() { - *started = Some(Instant::from_sample(&self.timebase, current_frames as f64)); + *started = Some(Moment::from_sample(&self.timebase, current_frames as f64)); } }, TransportState::Stopped => { diff --git a/crates/tek_api/src/api_player.rs b/crates/tek_api/src/api_player.rs index 5bad0911..f728e3b8 100644 --- a/crates/tek_api/src/api_player.rs +++ b/crates/tek_api/src/api_player.rs @@ -10,10 +10,10 @@ pub trait MidiPlayerApi: MidiRecordApi + MidiPlaybackApi + Send + Sync {} pub trait HasPlayPhrase: HasClock { fn reset (&self) -> bool; fn reset_mut (&mut self) -> &mut bool; - fn play_phrase (&self) -> &Option<(Instant, Option>>)>; - fn play_phrase_mut (&mut self) -> &mut Option<(Instant, Option>>)>; - fn next_phrase (&self) -> &Option<(Instant, Option>>)>; - fn next_phrase_mut (&mut self) -> &mut Option<(Instant, Option>>)>; + fn play_phrase (&self) -> &Option<(Moment, Option>>)>; + fn play_phrase_mut (&mut self) -> &mut Option<(Moment, Option>>)>; + fn next_phrase (&self) -> &Option<(Moment, Option>>)>; + fn next_phrase_mut (&mut self) -> &mut Option<(Moment, Option>>)>; fn pulses_since_start (&self) -> Option { if let Some((started, Some(_))) = self.play_phrase().as_ref() { Some(self.clock().playhead.pulse.get() - started.pulse.get()) @@ -23,7 +23,7 @@ pub trait HasPlayPhrase: HasClock { } fn enqueue_next (&mut self, phrase: Option<&Arc>>) { let start = self.clock().next_launch_pulse() as f64; - let instant = Instant::from_pulse(&self.clock().timebase(), start); + let instant = Moment::from_pulse(&self.clock().timebase(), start); let phrase = phrase.map(|p|p.clone()); *self.next_phrase_mut() = Some((instant, phrase)); *self.reset_mut() = true; @@ -206,7 +206,7 @@ pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts { // Samples elapsed since phrase was supposed to start let skipped = sample0 - start; // Switch over to enqueued phrase - let started = Instant::from_sample(&self.clock().timebase(), start as f64); + let started = Moment::from_sample(&self.clock().timebase(), start as f64); *self.play_phrase_mut() = Some((started, phrase.clone())); // Unset enqueuement (TODO: where to implement looping?) *self.next_phrase_mut() = None @@ -306,9 +306,9 @@ impl<'a, T: MidiPlayerApi> Audio for PlayerAudio<'a, T> { ///// Global timebase //pub clock: Arc, ///// Start time and phrase being played - //pub play_phrase: Option<(Instant, Option>>)>, + //pub play_phrase: Option<(Moment, Option>>)>, ///// Start time and next phrase - //pub next_phrase: Option<(Instant, Option>>)>, + //pub next_phrase: Option<(Moment, Option>>)>, ///// Play input through output. //pub monitoring: bool, ///// Write input to sequence. diff --git a/crates/tek_api/src/api_timeline.ts b/crates/tek_api/src/api_timeline.ts new file mode 100644 index 00000000..e69de29b diff --git a/crates/tek_api/src/lib.rs b/crates/tek_api/src/lib.rs index a75ddf26..9908b835 100644 --- a/crates/tek_api/src/lib.rs +++ b/crates/tek_api/src/lib.rs @@ -455,8 +455,8 @@ fn query_ports(client: &Client, names: Vec) -> BTreeMap for Clock { - //fn from (current: Instant) -> Self { +//impl From for Clock { + //fn from (current: Moment) -> Self { //Self { //playing: Some(TransportState::Stopped).into(), //started: None.into(), diff --git a/crates/tek_core/Cargo.toml b/crates/tek_core/Cargo.toml index dbee0657..a4752597 100644 --- a/crates/tek_core/Cargo.toml +++ b/crates/tek_core/Cargo.toml @@ -9,7 +9,6 @@ backtrace = "0.3.72" better-panic = "0.3.0" clap = { version = "4.5.4", features = [ "derive" ] } clojure-reader = "0.1.0" -crossterm = "0.27" jack = "0.13" midly = "0.5" once_cell = "1.19.0" diff --git a/crates/tek_core/examples/demo.rs b/crates/tek_core/examples/demo.rs index 6ee99acf..ba0e022c 100644 --- a/crates/tek_core/examples/demo.rs +++ b/crates/tek_core/examples/demo.rs @@ -35,7 +35,7 @@ impl Demo { impl Content for Demo { type Engine = Tui; - fn content (&self) -> impl Render { + fn content (&self) -> dyn Render { let border_style = Style::default().fg(Color::Rgb(0,0,0)); Align::Center(Layers::new(move|add|{ diff --git a/crates/tek_core/src/color.rs b/crates/tek_core/src/color.rs index 5cee8353..9464e1f2 100644 --- a/crates/tek_core/src/color.rs +++ b/crates/tek_core/src/color.rs @@ -1,5 +1,6 @@ use crate::*; use rand::{thread_rng, distributions::uniform::UniformSampler}; +pub use ratatui::prelude::Color; /// A color in OKHSL and RGB representations. #[derive(Debug, Default, Copy, Clone, PartialEq)] diff --git a/crates/tek_core/src/layout.rs b/crates/tek_core/src/layout.rs new file mode 100644 index 00000000..e42e4cf5 --- /dev/null +++ b/crates/tek_core/src/layout.rs @@ -0,0 +1,76 @@ +use crate::*; + +pub enum Collect<'a, E: Engine, const N: usize> { + Callback(CallbackCollection<'a, E>), + //Iterator(IteratorCollection<'a, E>), + Array(ArrayCollection<'a, E, N>), + Slice(SliceCollection<'a, E>), +} + +impl<'a, E: Engine, const N: usize> Collect<'a, E, N> { + pub fn iter (&'a self) -> CollectIterator<'a, E, N> { + CollectIterator(0, &self) + } +} + +impl<'a, E: Engine, const N: usize> From> for Collect<'a, E, N> { + fn from (callback: CallbackCollection<'a, E>) -> Self { + Self::Callback(callback) + } +} + +impl<'a, E: Engine, const N: usize> From> for Collect<'a, E, N> { + fn from (slice: SliceCollection<'a, E>) -> Self { + Self::Slice(slice) + } +} + +impl<'a, E: Engine, const N: usize> From> for Collect<'a, E, N>{ + fn from (array: ArrayCollection<'a, E, N>) -> Self { + Self::Array(array) + } +} + +type CallbackCollection<'a, E> = + &'a dyn Fn(&'a mut dyn FnMut(&dyn Render)->Usually<()>); + +//type IteratorCollection<'a, E> = + //&'a mut dyn Iterator>; + +type SliceCollection<'a, E> = + &'a [&'a dyn Render]; + +type ArrayCollection<'a, E, const N: usize> = + [&'a dyn Render; N]; + +pub struct CollectIterator<'a, E: Engine, const N: usize>(usize, &'a Collect<'a, E, N>); + +impl<'a, E: Engine, const N: usize> Iterator for CollectIterator<'a, E, N> { + type Item = &'a dyn Render; + fn next (&mut self) -> Option { + match self.1 { + Collect::Callback(callback) => { + todo!() + }, + //Collection::Iterator(iterator) => { + //iterator.next() + //}, + Collect::Array(array) => { + if let Some(item) = array.get(self.0) { + self.0 += 1; + Some(item) + } else { + None + } + } + Collect::Slice(slice) => { + if let Some(item) = slice.get(self.0) { + self.0 += 1; + Some(item) + } else { + None + } + } + } + } +} diff --git a/crates/tek_core/src/lib.rs b/crates/tek_core/src/lib.rs index 0f60545c..5ce97392 100644 --- a/crates/tek_core/src/lib.rs +++ b/crates/tek_core/src/lib.rs @@ -1,5 +1,4 @@ pub use ratatui; -pub use crossterm; pub use jack; pub use midly; pub use clap; @@ -11,12 +10,8 @@ pub use std::rc::Rc; pub use std::cell::{Cell, RefCell}; pub use std::marker::PhantomData; pub(crate) use std::error::Error; -pub(crate) use std::io::{stdout}; -pub(crate) use std::thread::{spawn, JoinHandle}; -pub(crate) use std::time::Duration; pub(crate) use atomic_float::*; pub(crate) use palette::{*, convert::*, okhsl::*}; -use better_panic::{Settings, Verbosity}; use std::ops::{Add, Sub, Mul, Div, Rem}; use std::cmp::{Ord, Eq, PartialEq}; use std::fmt::{Debug, Display}; @@ -46,7 +41,8 @@ submod! { pitch space time - tui + //tui + layout } testmod! { diff --git a/crates/tek_core/src/space.rs b/crates/tek_core/src/space.rs index 0bb27802..04f89fcf 100644 --- a/crates/tek_core/src/space.rs +++ b/crates/tek_core/src/space.rs @@ -19,6 +19,9 @@ pub trait Coordinate: Send + Sync + Copy 0.into() } } + fn ZERO () -> Self { + 0.into() + } } impl Coordinate for T where T: Send + Sync + Copy @@ -702,6 +705,9 @@ impl< #[inline] pub fn down (build: F) -> Self { Self::new(Direction::Down, build) } + #[inline] pub fn up (build: F) -> Self { + Self::new(Direction::Up, build) + } } impl Render for Stack @@ -710,63 +716,119 @@ where { type Engine = E; fn min_size (&self, to: E::Size) -> Perhaps { - let mut w = 0.into(); - let mut h = 0.into(); match self.1 { + Direction::Down => { - (self.0)(&mut |component| { - if h >= to.h() { return Ok(()) } - let size = component.push_y(h).max_y(to.h() - h).min_size(to)?; - if let Some([width, height]) = size.map(|size|size.wh()) { - h = h + height.into(); - if width > w { w = width; } + let mut w: E::Unit = 0.into(); + let mut h: E::Unit = 0.into(); + (self.0)(&mut |component: &dyn Render| { + let max = to.h().minus(h); + if max > E::Unit::ZERO() { + let item = component.push_y(h).max_y(max); + let size = item.min_size(to)?.map(|size|size.wh()); + if let Some([width, height]) = size { + h = h + height.into(); + w = w.max(width); + } } Ok(()) })?; + Ok(Some([w, h].into())) }, + Direction::Right => { - (self.0)(&mut |component| { - if w >= to.w() { return Ok(()) } - let size = component.push_x(w).max_x(to.w() - w).min_size(to)?; - if let Some([width, height]) = size.map(|size|size.wh()) { - w = w + width.into(); - if height > h { h = height } + let mut w: E::Unit = 0.into(); + let mut h: E::Unit = 0.into(); + (self.0)(&mut |component: &dyn Render| { + let max = to.w().minus(w); + if max > E::Unit::ZERO() { + let item = component.push_x(w).max_x(max); + let size = item.min_size(to)?.map(|size|size.wh()); + if let Some([width, height]) = size { + w = w + width.into(); + h = h.max(height); + } } Ok(()) })?; + Ok(Some([w, h].into())) }, - _ => todo!() - }; - Ok(Some([w, h].into())) + + Direction::Up => { + let mut w: E::Unit = 0.into(); + let mut h: E::Unit = 0.into(); + (self.0)(&mut |component: &dyn Render| { + let max = to.h().minus(h); + if max > E::Unit::ZERO() { + let item = component.max_y(to.h() - h); + let size = item.min_size(to)?.map(|size|size.wh()); + if let Some([width, height]) = size { + h = h + height.into(); + w = w.max(width); + } + } + Ok(()) + })?; + Ok(Some([w, h].into())) + }, + + Direction::Left => { + let mut w: E::Unit = 0.into(); + let mut h: E::Unit = 0.into(); + (self.0)(&mut |component: &dyn Render| { + if w < to.w() { + todo!(); + } + Ok(()) + })?; + Ok(Some([w, h].into())) + }, + } } + fn render (&self, to: &mut E::Output) -> Usually<()> { let area = to.area(); let mut w = 0.into(); let mut h = 0.into(); match self.1 { Direction::Down => { - (self.0)(&mut |component| { - if h >= area.h() { return Ok(()) } - let item = component.push_y(h).max_y(area.h() - h); - let size = item.min_size(area.wh().into())?; - if let Some([width, height]) = size.map(|size|size.wh()) { - item.render(to)?; - h = h + height; - if width > w { w = width } - }; + (self.0)(&mut |item| { + if h < area.h() { + let item = item.push_y(h).max_y(area.h() - h); + let show = item.min_size(area.wh().into())?.map(|s|s.wh()); + if let Some([width, height]) = show { + item.render(to)?; + h = h + height; + if width > w { w = width } + }; + } Ok(()) })?; }, Direction::Right => { - (self.0)(&mut |component| { - if w >= area.w() { return Ok(()) } - let item = component.push_x(w).max_x(area.w() - w); - let size = item.min_size(area.wh().into())?; - if let Some([width, height]) = size.map(|size|size.wh()) { - item.render(to)?; - w = width + w; - if height > h { h = height } - }; + (self.0)(&mut |item| { + if w < area.w() { + let item = item.push_x(w).max_x(area.w() - w); + let show = item.min_size(area.wh().into())?.map(|s|s.wh()); + if let Some([width, height]) = show { + item.render(to)?; + w = width + w; + if height > h { h = height } + }; + } + Ok(()) + })?; + }, + Direction::Up => { + (self.0)(&mut |item| { + if h < area.h() { + let show = item.min_size([area.w(), area.h().minus(h)].into())?.map(|s|s.wh()); + if let Some([width, height]) = show { + item.push_y(area.h() - height).shrink_y(height).render(to)?; + h = h + height; + if width > w { w = width } + }; + } Ok(()) })?; }, diff --git a/crates/tek_core/src/time.rs b/crates/tek_core/src/time.rs index bede1fd5..536e403b 100644 --- a/crates/tek_core/src/time.rs +++ b/crates/tek_core/src/time.rs @@ -245,9 +245,19 @@ impl Timebase { impl Default for Timebase { fn default () -> Self { Self::new(48000f64, 150f64, DEFAULT_PPQ) } } + +#[derive(Debug, Clone)] +pub enum Moment2 { + None, + Zero, + Usec(Microsecond), + Sample(SampleCount), + Pulse(Pulse), +} + /// A point in time in all time scales (microsecond, sample, MIDI pulse) #[derive(Debug, Default, Clone)] -pub struct Instant { +pub struct Moment { pub timebase: Arc, /// Current time in microseconds pub usec: Microsecond, @@ -256,7 +266,7 @@ pub struct Instant { /// Current time in MIDI pulses pub pulse: Pulse, } -impl Instant { +impl Moment { pub fn zero (timebase: &Arc) -> Self { Self { usec: 0.into(), sample: 0.into(), pulse: 0.into(), timebase: timebase.clone() } } diff --git a/crates/tek_core/src/tui.rs b/crates/tek_core/src/tui.rs index 2e01ad69..e69de29b 100644 --- a/crates/tek_core/src/tui.rs +++ b/crates/tek_core/src/tui.rs @@ -1,690 +0,0 @@ -use crate::*; -pub(crate) use ratatui::buffer::Cell; -pub(crate) use crossterm::{ExecutableCommand}; -pub use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}; -pub use ratatui::prelude::{Rect, Style, Color, Buffer}; -pub use ratatui::style::{Stylize, Modifier}; -use ratatui::backend::{Backend, CrosstermBackend, ClearType}; -use std::io::Stdout; -use crossterm::terminal::{ - EnterAlternateScreen, LeaveAlternateScreen, - enable_raw_mode, disable_raw_mode -}; - -pub struct Tui { - pub exited: Arc, - pub buffer: Buffer, - pub backend: CrosstermBackend, - pub area: [u16;4], // FIXME auto resize -} -impl Engine for Tui { - type Unit = u16; - type Size = [Self::Unit;2]; - type Area = [Self::Unit;4]; - type Input = TuiInput; - type Handled = bool; - type Output = TuiOutput; - fn exited (&self) -> bool { - self.exited.fetch_and(true, Ordering::Relaxed) - } - fn setup (&mut self) -> Usually<()> { - let better_panic_handler = Settings::auto().verbosity(Verbosity::Full).create_panic_handler(); - std::panic::set_hook(Box::new(move |info: &std::panic::PanicHookInfo|{ - stdout().execute(LeaveAlternateScreen).unwrap(); - CrosstermBackend::new(stdout()).show_cursor().unwrap(); - disable_raw_mode().unwrap(); - better_panic_handler(info); - })); - stdout().execute(EnterAlternateScreen)?; - self.backend.hide_cursor()?; - enable_raw_mode().map_err(Into::into) - } - fn teardown (&mut self) -> Usually<()> { - stdout().execute(LeaveAlternateScreen)?; - self.backend.show_cursor()?; - disable_raw_mode().map_err(Into::into) - } -} -impl Tui { - /// Run the main loop. - pub fn run + Sized + 'static> ( - state: Arc> - ) -> Usually>> { - let backend = CrosstermBackend::new(stdout()); - let area = backend.size()?; - let engine = Self { - exited: Arc::new(AtomicBool::new(false)), - buffer: Buffer::empty(area), - area: area.xywh(), - backend, - }; - let engine = Arc::new(RwLock::new(engine)); - let _input_thread = Self::spawn_input_thread(&engine, &state, Duration::from_millis(100)); - engine.write().unwrap().setup()?; - let render_thread = Self::spawn_render_thread(&engine, &state, Duration::from_millis(10)); - render_thread.join().expect("main thread failed"); - engine.write().unwrap().teardown()?; - Ok(state) - } - fn spawn_input_thread + Sized + 'static> ( - engine: &Arc>, state: &Arc>, poll: Duration - ) -> JoinHandle<()> { - let exited = engine.read().unwrap().exited.clone(); - let state = state.clone(); - spawn(move || loop { - if exited.fetch_and(true, Ordering::Relaxed) { - break - } - if ::crossterm::event::poll(poll).is_ok() { - let event = TuiEvent::Input(::crossterm::event::read().unwrap()); - match event { - key!(Ctrl-KeyCode::Char('c')) => { - exited.store(true, Ordering::Relaxed); - }, - _ => { - let exited = exited.clone(); - if let Err(e) = state.write().unwrap().handle(&TuiInput { event, exited }) { - panic!("{e}") - } - } - } - } - }) - } - fn spawn_render_thread + Sized + 'static> ( - engine: &Arc>, state: &Arc>, sleep: Duration - ) -> JoinHandle<()> { - let exited = engine.read().unwrap().exited.clone(); - let engine = engine.clone(); - let state = state.clone(); - let size = engine.read().unwrap().backend.size().expect("get size failed"); - let mut buffer = Buffer::empty(size); - spawn(move || loop { - if exited.fetch_and(true, Ordering::Relaxed) { - break - } - let size = engine.read().unwrap().backend.size() - .expect("get size failed"); - if let Ok(state) = state.try_read() { - if buffer.area != size { - engine.write().unwrap().backend.clear_region(ClearType::All) - .expect("clear failed"); - buffer.resize(size); - buffer.reset(); - } - let mut output = TuiOutput { buffer, area: size.xywh() }; - state.render(&mut output).expect("render failed"); - buffer = engine.write().unwrap().flip(output.buffer, size); - } - std::thread::sleep(sleep); - }) - } - fn flip (&mut self, mut buffer: Buffer, size: ratatui::prelude::Rect) -> Buffer { - if self.buffer.area != size { - self.backend.clear_region(ClearType::All).unwrap(); - self.buffer.resize(size); - self.buffer.reset(); - } - let updates = self.buffer.diff(&buffer); - self.backend.draw(updates.into_iter()).expect("failed to render"); - self.backend.flush().expect("failed to flush output buffer"); - std::mem::swap(&mut self.buffer, &mut buffer); - buffer.reset(); - buffer - } -} -pub struct TuiInput { event: TuiEvent, exited: Arc, } -impl Input for TuiInput { - type Event = TuiEvent; - fn event (&self) -> &TuiEvent { &self.event } - fn is_done (&self) -> bool { self.exited.fetch_and(true, Ordering::Relaxed) } - fn done (&self) { self.exited.store(true, Ordering::Relaxed); } -} -impl TuiInput { - // TODO remove - pub fn handle_keymap (&self, state: &mut T, keymap: &KeyMap) -> Usually { - match self.event() { - TuiEvent::Input(crossterm::event::Event::Key(event)) => { - for (code, modifiers, _, _, command) in keymap.iter() { - if *code == event.code && modifiers.bits() == event.modifiers.bits() { - return command(state) - } - } - }, - _ => {} - }; - Ok(false) - } -} -pub type KeyHandler = &'static dyn Fn(&mut T)->Usually; -pub type KeyBinding = (KeyCode, KeyModifiers, &'static str, &'static str, KeyHandler); -pub type KeyMap = [KeyBinding]; -pub struct TuiOutput { pub buffer: Buffer, pub area: [u16;4] } -impl Output for TuiOutput { - #[inline] fn area (&self) -> [u16;4] { self.area } - #[inline] fn area_mut (&mut self) -> &mut [u16;4] { &mut self.area } - #[inline] fn render_in (&mut self, - area: [u16;4], - widget: &dyn Render - ) -> Usually<()> { - let last = self.area(); - *self.area_mut() = area; - widget.render(self)?; - *self.area_mut() = last; - Ok(()) - } -} -impl TuiOutput { - pub fn buffer_update (&mut self, - area: [u16;4], - callback: &impl Fn(&mut Cell, u16, u16) - ) { - buffer_update(&mut self.buffer, area, callback); - } - pub fn fill_bold (&mut self, area: [u16;4], on: bool) { - if on { - self.buffer_update(area, &|cell,_,_|cell.modifier.insert(Modifier::BOLD)) - } else { - self.buffer_update(area, &|cell,_,_|cell.modifier.remove(Modifier::BOLD)) - } - } - pub fn fill_bg (&mut self, area: [u16;4], color: Color) { - self.buffer_update(area, &|cell,_,_|{cell.set_bg(color);}) - } - pub fn fill_fg (&mut self, area: [u16;4], color: Color) { - self.buffer_update(area, &|cell,_,_|{cell.set_fg(color);}) - } - pub fn fill_ul (&mut self, area: [u16;4], color: Color) { - self.buffer_update(area, &|cell,_,_|{ - cell.modifier = ratatui::prelude::Modifier::UNDERLINED; - cell.underline_color = color; - }) - } - pub fn fill_char (&mut self, area: [u16;4], c: char) { - self.buffer_update(area, &|cell,_,_|{cell.set_char(c);}) - } - pub fn make_dim (&mut self) { - for cell in self.buffer.content.iter_mut() { - cell.bg = ratatui::style::Color::Rgb(30,30,30); - cell.fg = ratatui::style::Color::Rgb(100,100,100); - cell.modifier = ratatui::style::Modifier::DIM; - } - } - pub fn blit ( - &mut self, text: &impl AsRef, x: u16, y: u16, style: Option