mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
wip: big flat pt.2: extract engine crate
This commit is contained in:
parent
4a3de618d0
commit
a5628fb663
31 changed files with 1738 additions and 888 deletions
|
|
@ -99,29 +99,27 @@ impl ArrangerTui {
|
|||
}
|
||||
}
|
||||
}
|
||||
impl Render<Tui> for ArrangerTui {
|
||||
fn content (&self) -> Option<impl Render<Tui>> {
|
||||
let play = PlayPause(self.clock.is_rolling());
|
||||
let transport = TransportView::new(self, Some(ItemPalette::from(TuiTheme::g(96))), true);
|
||||
let with_transport = |x|col!([row!(![&play, &transport]), &x]);
|
||||
let pool_size = if self.phrases.visible { self.splits[1] } else { 0 };
|
||||
let with_pool = |x|Split::w(false, pool_size, PoolView(&self.phrases), x);
|
||||
let status = ArrangerStatus::from(self);
|
||||
let with_editbar = |x|Split::n(false, 1, MidiEditStatus(&self.editor), x);
|
||||
let with_status = |x|Split::n(false, 2, status, x);
|
||||
let with_size = |x|lay!([&self.size, x]);
|
||||
let arranger = ||lay!(|add|{
|
||||
let color = self.color;
|
||||
add(&Fill::wh(Tui::bg(color.darkest.rgb, ())))?;
|
||||
add(&Fill::wh(Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb))))?;
|
||||
add(&Self::render_mode(self))
|
||||
});
|
||||
Some(with_size(with_status(with_editbar(with_pool(with_transport(col!([
|
||||
Fill::w(Fixed::h(20, arranger())),
|
||||
Fill::wh(&self.editor),
|
||||
])))))))
|
||||
}
|
||||
}
|
||||
render!(<Tui>|self: ArrangerTui|{
|
||||
let play = PlayPause(self.clock.is_rolling());
|
||||
let transport = TransportView::new(self, Some(ItemPalette::from(TuiTheme::g(96))), true);
|
||||
let with_transport = |x|col!([row!(![&play, &transport]), &x]);
|
||||
let pool_size = if self.phrases.visible { self.splits[1] } else { 0 };
|
||||
let with_pool = |x|Split::w(false, pool_size, PoolView(&self.phrases), x);
|
||||
let status = ArrangerStatus::from(self);
|
||||
let with_editbar = |x|Split::n(false, 1, MidiEditStatus(&self.editor), x);
|
||||
let with_status = |x|Split::n(false, 2, status, x);
|
||||
let with_size = |x|lay!([&self.size, x]);
|
||||
let arranger = ||lay!(|add|{
|
||||
let color = self.color;
|
||||
add(&Fill::wh(Tui::bg(color.darkest.rgb, "")))?;
|
||||
add(&Fill::wh(Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb))))?;
|
||||
add(&Self::render_mode(self))
|
||||
});
|
||||
Some(with_size(with_status(with_editbar(with_pool(with_transport(col!([
|
||||
Fill::w(Fixed::h(20, arranger())),
|
||||
Fill::wh(&self.editor),
|
||||
])))))))
|
||||
});
|
||||
audio!(|self: ArrangerTui, client, scope|{
|
||||
// Start profiling cycle
|
||||
let t0 = self.perf.get_t0();
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ pub enum ArrangerMode {
|
|||
/// Tracks are rows
|
||||
H,
|
||||
}
|
||||
render!(<Tui>|self: ArrangerMode|{});
|
||||
render!(<Tui>|self: ArrangerMode|"");
|
||||
/// Arranger display mode can be cycled
|
||||
impl ArrangerMode {
|
||||
/// Cycle arranger display mode
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from!(<'a>|args: &'a ArrangerTui|ArrangerVIns<'a> = Self {
|
|||
tracks: &args.tracks,
|
||||
});
|
||||
|
||||
render!(<Tui>|self: ArrangerVIns<'a>|());
|
||||
render!(<Tui>|self: ArrangerVIns<'a>|"");
|
||||
|
||||
pub struct ArrangerVOuts<'a> {
|
||||
size: &'a Measure<Tui>,
|
||||
|
|
@ -23,4 +23,4 @@ from!(<'a>|args: &'a ArrangerTui|ArrangerVOuts<'a> = Self {
|
|||
tracks: &args.tracks,
|
||||
});
|
||||
|
||||
render!(<Tui>|self: ArrangerVOuts<'a>|());
|
||||
render!(<Tui>|self: ArrangerVOuts<'a>|"");
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use crate::*;
|
|||
pub struct Bordered<S: BorderStyle, W: Render<Tui>>(pub S, pub W);
|
||||
|
||||
render!(<Tui>|self: Bordered<S: BorderStyle, W: Render<Tui>>|{
|
||||
Fill::wh(lay!([Border(self.0), Padding::xy(1, 1, widget(&self.1))]))
|
||||
Fill::wh(lay!([Border(self.0), Padding::xy(1, 1, &self.1)]))
|
||||
});
|
||||
|
||||
pub struct Border<S: BorderStyle>(pub S);
|
||||
|
|
@ -1,49 +1,5 @@
|
|||
use crate::*;
|
||||
|
||||
/// Platform backend.
|
||||
pub trait Engine: Send + Sync + Sized {
|
||||
/// Input event type
|
||||
type Input: Input<Self>;
|
||||
/// Result of handling input
|
||||
type Handled;
|
||||
/// Render target
|
||||
type Output: Output<Self>;
|
||||
/// Unit of length
|
||||
type Unit: Coordinate;
|
||||
/// Rectangle without offset
|
||||
type Size: Size<Self::Unit> + From<[Self::Unit;2]> + Debug + Copy;
|
||||
/// Rectangle with offset
|
||||
type Area: Area<Self::Unit> + From<[Self::Unit;4]> + Debug + Copy;
|
||||
/// Prepare before run
|
||||
fn setup (&mut self) -> Usually<()> { Ok(()) }
|
||||
/// True if done
|
||||
fn exited (&self) -> bool;
|
||||
/// Clean up after run
|
||||
fn teardown (&mut self) -> Usually<()> { Ok(()) }
|
||||
}
|
||||
|
||||
/// A UI component that can render itself as a [Render], and [Handle] input.
|
||||
pub trait Component<E: Engine>: Render<E> + Handle<E> {}
|
||||
|
||||
/// Everything that implements [Render] and [Handle] is a [Component].
|
||||
impl<E: Engine, C: Render<E> + Handle<E>> Component<E> for C {}
|
||||
|
||||
/// A component that can exit.
|
||||
pub trait Exit: Send {
|
||||
fn exited (&self) -> bool;
|
||||
fn exit (&mut self);
|
||||
fn boxed (self) -> Box<dyn Exit> where Self: Sized + 'static {
|
||||
Box::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Marker trait for [Component]s that can [Exit].
|
||||
pub trait ExitableComponent<E>: Exit + Component<E> where E: Engine {
|
||||
/// Perform type erasure for collecting heterogeneous components.
|
||||
fn boxed (self) -> Box<dyn ExitableComponent<E>> where Self: Sized + 'static {
|
||||
Box::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// All [Components]s that implement [Exit] implement [ExitableComponent].
|
||||
impl<E: Engine, C: Component<E> + Exit> ExitableComponent<E> for C {}
|
||||
mod content; pub use self::content::*;
|
||||
mod render; pub use self::render::*;
|
||||
mod handle; pub use self::handle::*;
|
||||
|
|
|
|||
0
src/engine/render.rs
Normal file
0
src/engine/render.rs
Normal file
|
|
@ -142,9 +142,9 @@ render!(<Tui>|self:Groovebox|{
|
|||
});
|
||||
|
||||
struct EditStatus<'a, T: Render<Tui>>(&'a Sampler, &'a MidiEditor, usize, T);
|
||||
impl<'a, T: Render<Tui>> Render<Tui> for EditStatus<'a, T> {
|
||||
impl<'a, T: Render<Tui>> Content<Tui> for EditStatus<'a, T> {
|
||||
fn content (&self) -> impl Render<Tui> {
|
||||
Split::n(false, 9, col!([
|
||||
Some(Split::n(false, 9, col!([
|
||||
row!(|add|{
|
||||
if let Some(sample) = &self.0.mapped[self.2] {
|
||||
add(&format!("Sample {}", sample.read().unwrap().end))?;
|
||||
|
|
@ -162,7 +162,7 @@ impl<'a, T: Render<Tui>> Render<Tui> for EditStatus<'a, T> {
|
|||
SampleViewer(None)
|
||||
})),
|
||||
]),
|
||||
]), &self.3)
|
||||
]), &self.3))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
68
src/input.rs
68
src/input.rs
|
|
@ -1,68 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
/// Current input state
|
||||
pub trait Input<E: Engine> {
|
||||
/// Type of input event
|
||||
type Event;
|
||||
/// Currently handled event
|
||||
fn event (&self) -> &Self::Event;
|
||||
/// Whether component should exit
|
||||
fn is_done (&self) -> bool;
|
||||
/// Mark component as done
|
||||
fn done (&self);
|
||||
}
|
||||
|
||||
/// Handle input
|
||||
pub trait Handle<E: Engine>: Send + Sync {
|
||||
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled>;
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! handle {
|
||||
(<$E:ty>|$self:ident:$Struct:ty,$input:ident|$handler:expr) => {
|
||||
impl Handle<$E> for $Struct {
|
||||
fn handle (&mut $self, $input: &<$E as Engine>::Input) -> Perhaps<<$E as Engine>::Handled> {
|
||||
$handler
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine, H: Handle<E>> Handle<E> for &mut H {
|
||||
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled> {
|
||||
(*self).handle(context)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine, H: Handle<E>> Handle<E> for Option<H> {
|
||||
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled> {
|
||||
if let Some(ref mut handle) = self {
|
||||
handle.handle(context)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, E: Engine> Handle<E> for Mutex<H> where H: Handle<E> {
|
||||
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled> {
|
||||
self.get_mut().unwrap().handle(context)
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, E: Engine> Handle<E> for Arc<Mutex<H>> where H: Handle<E> {
|
||||
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled> {
|
||||
self.lock().unwrap().handle(context)
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, E: Engine> Handle<E> for RwLock<H> where H: Handle<E> {
|
||||
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled> {
|
||||
self.write().unwrap().handle(context)
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, E: Engine> Handle<E> for Arc<RwLock<H>> where H: Handle<E> {
|
||||
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled> {
|
||||
self.write().unwrap().handle(context)
|
||||
}
|
||||
}
|
||||
66
src/lib.rs
66
src/lib.rs
|
|
@ -1,6 +1,20 @@
|
|||
#![allow(unused)]
|
||||
#![allow(clippy::unit_arg)]
|
||||
|
||||
pub use ::tek_engine::*;
|
||||
pub(crate) use ::tek_engine::{
|
||||
crossterm::{
|
||||
ExecutableCommand,
|
||||
EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode};
|
||||
KeyCode, KeyModifiers, KeyEvent, KeyEventKind, KeyEventState
|
||||
},
|
||||
ratatui::{
|
||||
prelude::{Style, Buffer},
|
||||
style::{Stylize, Modifier},
|
||||
backend::{Backend, CrosstermBackend, ClearType}
|
||||
},
|
||||
};
|
||||
|
||||
pub(crate) use std::cmp::{Ord, Eq, PartialEq};
|
||||
pub(crate) use std::collections::BTreeMap;
|
||||
pub(crate) use std::error::Error;
|
||||
|
|
@ -16,6 +30,7 @@ pub(crate) use std::thread::{spawn, JoinHandle};
|
|||
pub(crate) use std::time::Duration;
|
||||
|
||||
pub mod arranger; pub use self::arranger::*;
|
||||
pub mod border; pub use self::border::*;
|
||||
pub mod color; pub use self::color::*;
|
||||
pub mod command; pub use self::command::*;
|
||||
pub mod engine; pub use self::engine::*;
|
||||
|
|
@ -23,12 +38,10 @@ pub mod event; pub use self::event::*;
|
|||
pub mod file; pub use self::file::*;
|
||||
pub mod focus; pub use self::focus::*;
|
||||
pub mod groovebox; pub use self::groovebox::*;
|
||||
pub mod input; pub use self::input::*;
|
||||
pub mod jack; pub use self::jack::*;
|
||||
pub mod meter; pub use self::meter::*;
|
||||
pub mod midi; pub use self::midi::*;
|
||||
pub mod mixer; pub use self::mixer::*;
|
||||
pub mod output; pub use self::output::*;
|
||||
pub mod piano_h; pub use self::piano_h::*;
|
||||
pub mod plugin; pub use self::plugin::*;
|
||||
pub mod pool; pub use self::pool::*;
|
||||
|
|
@ -36,28 +49,15 @@ pub mod sampler; pub use self::sampler::*;
|
|||
pub mod sequencer; pub use self::sequencer::*;
|
||||
pub mod space; pub use self::space::*;
|
||||
pub mod status; pub use self::status::*;
|
||||
pub mod style; pub use self::theme::*;
|
||||
pub mod theme; pub use self::theme::*;
|
||||
pub mod time; pub use self::time::*;
|
||||
pub mod transport; pub use self::transport::*;
|
||||
pub mod tui; pub use self::tui::*;
|
||||
|
||||
pub use ::better_panic;
|
||||
pub(crate) use better_panic::{Settings, Verbosity};
|
||||
|
||||
pub use ::atomic_float;
|
||||
pub(crate) use atomic_float::*;
|
||||
|
||||
pub use ::crossterm;
|
||||
pub(crate) use crossterm::{ExecutableCommand};
|
||||
pub(crate) use crossterm::terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode};
|
||||
pub(crate) use crossterm::event::{KeyCode, KeyModifiers, KeyEvent, KeyEventKind, KeyEventState};
|
||||
|
||||
pub use ::ratatui;
|
||||
pub(crate) use ratatui::{
|
||||
prelude::{Style, Buffer},
|
||||
style::{Stylize, Modifier},
|
||||
backend::{Backend, CrosstermBackend, ClearType}
|
||||
};
|
||||
|
||||
pub use ::midly::{self, num::u7};
|
||||
pub(crate) use ::midly::{
|
||||
Smf,
|
||||
|
|
@ -75,12 +75,6 @@ pub(crate) use ::palette::{
|
|||
|
||||
testmod! { test }
|
||||
|
||||
/// Standard result type.
|
||||
pub type Usually<T> = Result<T, Box<dyn Error>>;
|
||||
|
||||
/// Standard optional result type.
|
||||
pub type Perhaps<T> = Result<Option<T>, Box<dyn Error>>;
|
||||
|
||||
/// Define test modules.
|
||||
#[macro_export] macro_rules! testmod {
|
||||
($($name:ident)*) => { $(#[cfg(test)] mod $name;)* };
|
||||
|
|
@ -126,3 +120,29 @@ impl Gettable<usize> for AtomicUsize {
|
|||
impl InteriorMutable<usize> for AtomicUsize {
|
||||
fn set (&self, value: usize) -> usize { self.swap(value, Relaxed) }
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct BigBuffer {
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
pub content: Vec<Cell>
|
||||
}
|
||||
|
||||
impl BigBuffer {
|
||||
pub fn new (width: usize, height: usize) -> Self {
|
||||
Self { width, height, content: vec![Cell::default(); width*height] }
|
||||
}
|
||||
pub fn get (&self, x: usize, y: usize) -> Option<&Cell> {
|
||||
let i = self.index_of(x, y);
|
||||
self.content.get(i)
|
||||
}
|
||||
pub fn get_mut (&mut self, x: usize, y: usize) -> Option<&mut Cell> {
|
||||
let i = self.index_of(x, y);
|
||||
self.content.get_mut(i)
|
||||
}
|
||||
pub fn index_of (&self, x: usize, y: usize) -> usize {
|
||||
y * self.width + x
|
||||
}
|
||||
}
|
||||
|
||||
from!(|size:(usize, usize)| BigBuffer = Self::new(size.0, size.1));
|
||||
|
|
|
|||
|
|
@ -107,11 +107,13 @@ impl Default for MidiEditor {
|
|||
}
|
||||
|
||||
has_size!(<Tui>|self: MidiEditor|&self.size);
|
||||
|
||||
render!(<Tui>|self: MidiEditor|{
|
||||
self.autoscroll();
|
||||
self.autozoom();
|
||||
&self.mode
|
||||
});
|
||||
|
||||
//render!(<Tui>|self: MidiEditor|lay!(|add|{add(&self.size)?;add(self.mode)}));//bollocks
|
||||
|
||||
pub trait PhraseViewMode: Render<Tui> + HasSize<Tui> + MidiRange + MidiPoint + Debug + Send + Sync {
|
||||
|
|
@ -125,6 +127,12 @@ pub trait PhraseViewMode: Render<Tui> + HasSize<Tui> + MidiRange + MidiPoint + D
|
|||
}
|
||||
}
|
||||
|
||||
impl Content<Tui> for Box<dyn PhraseViewMode> {
|
||||
fn content (&self) -> impl Render<Tui> {
|
||||
Some(&(*self))
|
||||
}
|
||||
}
|
||||
|
||||
impl MidiView<Tui> for MidiEditor {}
|
||||
|
||||
impl TimeRange for MidiEditor {
|
||||
|
|
|
|||
177
src/output.rs
177
src/output.rs
|
|
@ -1,177 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
/// Ad-hoc widget with custom rendering.
|
||||
pub fn render <E, F> (render: F) -> impl Render<E>
|
||||
where E: Engine, F: Fn(&mut E::Output)->Usually<()>+Send+Sync
|
||||
{
|
||||
Widget::new(|_|Ok(Some([0.into(),0.into()].into())), render)
|
||||
}
|
||||
|
||||
/// Cast to dynamic pointer
|
||||
pub fn widget <E, T> (w: &T) -> &dyn Render<E>
|
||||
where E: Engine, T: Render<E>
|
||||
{
|
||||
w as &dyn Render<E>
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! render {
|
||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*E$(,$T:ident$(:$U:path)?)*$(,)?>)?|$cb:expr) => {
|
||||
impl<E: Engine, $($($L),*$($T $(: $U)?),*)?> Render<E> for $Struct $(<$($L,)* E, $($T),*>)? {
|
||||
fn content (&$self) -> Option<impl Render<$E>> {
|
||||
Some($cb)
|
||||
}
|
||||
}
|
||||
};
|
||||
(<$E:ty>|$self:ident:$Struct:ident$(<
|
||||
$($($L:lifetime),+)?
|
||||
$($($T:ident$(:$U:path)?),+)?
|
||||
>)?|$cb:expr) => {
|
||||
impl $(<
|
||||
$($($L),+)?
|
||||
$($($T$(:$U)?),+)?
|
||||
>)? Render<$E> for $Struct $(<
|
||||
$($($L),+)?
|
||||
$($($T),+)?
|
||||
>)? {
|
||||
fn content (&$self) -> Option<impl Render<$E>> {
|
||||
Some($cb)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Rendering target
|
||||
pub trait Output<E: Engine> {
|
||||
/// Current output area
|
||||
fn area (&self) -> E::Area;
|
||||
/// Mutable pointer to area
|
||||
fn area_mut (&mut self) -> &mut E::Area;
|
||||
/// Render widget in area
|
||||
fn render_in (&mut self, area: E::Area, widget: &dyn Render<E>) -> Usually<()>;
|
||||
}
|
||||
|
||||
/// A renderable component
|
||||
pub trait Render<E: Engine>: Send + Sync where (): Render<E> {
|
||||
fn content (&self) -> Option<impl Render<E>> where Self: Sized {
|
||||
None::<()>
|
||||
}
|
||||
/// Minimum size to use
|
||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||
self.content().map(|content|content.min_size(to)).unwrap_or(Ok(None))
|
||||
}
|
||||
/// Draw to output render target
|
||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||
self.content().map(|content|content.render(to)).unwrap_or(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine> Render<E> for () {
|
||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||
Ok(None)
|
||||
}
|
||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine, R: Render<E>> Render<E> for &R {
|
||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||
(*self).min_size(to)
|
||||
}
|
||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||
(*self).render(to)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine> Render<E> for &dyn Render<E> {
|
||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||
(*self).min_size(to)
|
||||
}
|
||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||
(*self).render(to)
|
||||
}
|
||||
}
|
||||
|
||||
//impl<E: Engine> Render<E> for &mut dyn Render<E> {
|
||||
//fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||
//(*self).min_size(to)
|
||||
//}
|
||||
//fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||
//(*self).render(to)
|
||||
//}
|
||||
//}
|
||||
|
||||
impl<E: Engine> Render<E> for Box<dyn Render<E> + '_> {
|
||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||
(**self).min_size(to)
|
||||
}
|
||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||
(**self).render(to)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine, W: Render<E>> Render<E> for Arc<W> {
|
||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||
self.as_ref().min_size(to)
|
||||
}
|
||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||
self.as_ref().render(to)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine, W: Render<E>> Render<E> for Mutex<W> {
|
||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||
self.lock().unwrap().min_size(to)
|
||||
}
|
||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||
self.lock().unwrap().render(to)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine, W: Render<E>> Render<E> for RwLock<W> {
|
||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||
self.read().unwrap().min_size(to)
|
||||
}
|
||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||
self.read().unwrap().render(to)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine, W: Render<E>> Render<E> for Option<W> {
|
||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||
Ok(self.as_ref().map(|widget|widget.min_size(to)).transpose()?.flatten())
|
||||
}
|
||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||
self.as_ref().map(|widget|widget.render(to)).unwrap_or(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
/// A custom [Render] defined by passing layout and render closures in place.
|
||||
pub struct Widget<
|
||||
E: Engine,
|
||||
L: Send + Sync + Fn(E::Size)->Perhaps<E::Size>,
|
||||
R: Send + Sync + Fn(&mut E::Output)->Usually<()>
|
||||
>(L, R, PhantomData<E>);
|
||||
|
||||
impl<
|
||||
E: Engine,
|
||||
L: Send + Sync + Fn(E::Size)->Perhaps<E::Size>,
|
||||
R: Send + Sync + Fn(&mut E::Output)->Usually<()>
|
||||
> Widget<E, L, R> {
|
||||
pub fn new (layout: L, render: R) -> Self {
|
||||
Self(layout, render, Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
E: Engine,
|
||||
L: Send + Sync + Fn(E::Size)->Perhaps<E::Size>,
|
||||
R: Send + Sync + Fn(&mut E::Output)->Usually<()>
|
||||
> Render<E> for Widget<E, L, R> {
|
||||
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||
self.0(to)
|
||||
}
|
||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||
self.1(to)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ use crate::*;
|
|||
use super::note_y_iter;
|
||||
|
||||
pub struct PianoHorizontalCursor<'a>(pub(crate) &'a PianoHorizontal);
|
||||
render!(<Tui>|self: PianoHorizontalCursor<'a>|render(|to|Ok({
|
||||
render!(<Tui>|self: PianoHorizontalCursor<'a>|render(|to: &mut TuiOutput|Ok({
|
||||
let style = Some(Style::default().fg(self.0.color.lightest.rgb));
|
||||
let note_hi = self.0.note_hi();
|
||||
let note_len = self.0.note_len();
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use crate::*;
|
|||
use super::note_y_iter;
|
||||
|
||||
pub struct PianoHorizontalNotes<'a>(pub(crate) &'a PianoHorizontal);
|
||||
render!(<Tui>|self: PianoHorizontalNotes<'a>|render(|to|Ok({
|
||||
render!(<Tui>|self: PianoHorizontalNotes<'a>|render(|to: &mut TuiOutput|Ok({
|
||||
let time_start = self.0.time_start().get();
|
||||
let note_lo = self.0.note_lo().get();
|
||||
let note_hi = self.0.note_hi();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use crate::*;
|
||||
|
||||
pub struct PianoHorizontalTimeline<'a>(pub(crate) &'a PianoHorizontal);
|
||||
render!(<Tui>|self: PianoHorizontalTimeline<'a>|render(|to|{
|
||||
render!(<Tui>|self: PianoHorizontalTimeline<'a>|render(|to: &mut TuiOutput|{
|
||||
let [x, y, w, h] = to.area();
|
||||
let style = Some(Style::default().dim());
|
||||
let length = self.0.phrase.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use ratatui::{prelude::Rect, widgets::{Widget, canvas::{Canvas, Points, Line}}};
|
|||
const EMPTY: &[(f64, f64)] = &[(0., 0.), (1., 1.), (2., 2.), (0., 2.), (2., 0.)];
|
||||
|
||||
pub struct SampleViewer(pub Option<Arc<RwLock<Sample>>>);
|
||||
render!(<Tui>|self: SampleViewer|render(|to|{
|
||||
render!(<Tui>|self: SampleViewer|render(|to: &mut TuiOutput|{
|
||||
let [x, y, width, height] = to.area();
|
||||
let area = Rect { x, y, width, height };
|
||||
let min_db = -40.0;
|
||||
|
|
|
|||
223
src/space.rs
223
src/space.rs
|
|
@ -14,126 +14,32 @@ mod split; pub use self::split::*;
|
|||
/// A cardinal direction.
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum Direction { North, South, West, East, }
|
||||
use self::Direction::*;
|
||||
pub use self::Direction::{self, *};
|
||||
|
||||
/// Has static methods for conditional rendering,
|
||||
/// in unary and binary forms.
|
||||
pub struct Cond;
|
||||
|
||||
impl Cond {
|
||||
/// Render `item` when `cond` is true.
|
||||
pub fn when <E: Engine, A: Render<E>> (cond: bool, item: A) -> When<E, A> {
|
||||
When(cond, item, Default::default())
|
||||
}
|
||||
/// Render `item` if `cond` is true, otherwise render `other`.
|
||||
pub fn either <E: Engine, A: Render<E>, B: Render<E>> (cond: bool, item: A, other: B) -> Either<E, A, B> {
|
||||
Either(cond, item, other, Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders `self.1` when `self.0` is true.
|
||||
pub struct When<E: Engine, A: Render<E>>(bool, A, PhantomData<E>);
|
||||
|
||||
/// Renders `self.1` when `self.0` is true, otherwise renders `self.2`
|
||||
pub struct Either<E: Engine, A: Render<E>, B: Render<E>>(bool, A, B, PhantomData<E>);
|
||||
|
||||
/// Renders multiple things on top of each other,
|
||||
/// in the order they are provided by the callback.
|
||||
/// Total size is largest width x largest height.
|
||||
pub struct Layers<
|
||||
E: Engine,
|
||||
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render<E>)->Usually<()>)->Usually<()>
|
||||
>(pub F, PhantomData<E>);
|
||||
|
||||
/// Shorthand for defining an instance of [Layers].
|
||||
#[macro_export] macro_rules! lay {
|
||||
([$($expr:expr),* $(,)?]) => {
|
||||
Layers::new(move|add|{ $(add(&$expr)?;)* Ok(()) })
|
||||
};
|
||||
(![$($expr:expr),* $(,)?]) => {
|
||||
Layers::new(|add|{ $(add(&$expr)?;)* Ok(()) })
|
||||
};
|
||||
($expr:expr) => {
|
||||
Layers::new($expr)
|
||||
};
|
||||
}
|
||||
|
||||
/// A linear coordinate.
|
||||
pub trait Coordinate: Send + Sync + Copy
|
||||
+ Add<Self, Output=Self>
|
||||
+ Sub<Self, Output=Self>
|
||||
+ Mul<Self, Output=Self>
|
||||
+ Div<Self, Output=Self>
|
||||
+ Ord + PartialEq + Eq
|
||||
+ Debug + Display + Default
|
||||
+ From<u16> + Into<u16>
|
||||
+ Into<usize>
|
||||
+ Into<f64>
|
||||
{
|
||||
fn minus (self, other: Self) -> Self {
|
||||
if self >= other {
|
||||
self - other
|
||||
} else {
|
||||
0.into()
|
||||
}
|
||||
}
|
||||
fn zero () -> Self {
|
||||
0.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Size<N: Coordinate> {
|
||||
fn x (&self) -> N;
|
||||
fn y (&self) -> N;
|
||||
#[inline] fn w (&self) -> N { self.x() }
|
||||
#[inline] fn h (&self) -> N { self.y() }
|
||||
#[inline] fn wh (&self) -> [N;2] { [self.x(), self.y()] }
|
||||
#[inline] fn clip_w (&self, w: N) -> [N;2] { [self.w().min(w), self.h()] }
|
||||
#[inline] fn clip_h (&self, h: N) -> [N;2] { [self.w(), self.h().min(h)] }
|
||||
#[inline] fn expect_min (&self, w: N, h: N) -> Usually<&Self> {
|
||||
if self.w() < w || self.h() < h {
|
||||
Err(format!("min {w}x{h}").into())
|
||||
} else {
|
||||
Ok(self)
|
||||
impl Direction {
|
||||
#[inline] fn split_fixed <N: Coordinate> (self, area: Area<N>, a: N) -> ([N;4],[N;4]) {
|
||||
match area {
|
||||
Direction::North => (
|
||||
[area.x(), (area.y()+area.h()).minus(a), area.w(), a],
|
||||
[area.x(), area.y(), area.w(), area.h().minus(a)],
|
||||
),
|
||||
Direction::South => (
|
||||
[area.x(), area.y(), area.w(), a],
|
||||
[area.x(), area.y() + a, area.w(), area.h().minus(a)],
|
||||
),
|
||||
Direction::East => (
|
||||
[area.x(), area.y(), a, area.h()],
|
||||
[area.x() + a, area.y(), area.w().minus(a), area.h()],
|
||||
),
|
||||
Direction::West => (
|
||||
[area.x() + area.w() - a, area.y(), a, area.h()],
|
||||
[area.x(), area.y(), area.w() - a, area.h()],
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HasSize<E: Engine> {
|
||||
fn size (&self) -> &Measure<E>;
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! has_size {
|
||||
(<$E:ty>|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||
impl $(<$($L),*$($T $(: $U)?),*>)? HasSize<$E> for $Struct $(<$($L),*$($T),*>)? {
|
||||
fn size (&$self) -> &Measure<$E> { $cb }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A widget that tracks its render width and height
|
||||
#[derive(Default)]
|
||||
pub struct Measure<E: Engine> {
|
||||
_engine: PhantomData<E>,
|
||||
pub x: Arc<AtomicUsize>,
|
||||
pub y: Arc<AtomicUsize>,
|
||||
}
|
||||
|
||||
pub struct ShowMeasure<'a>(&'a Measure<Tui>);
|
||||
|
||||
pub trait LayoutDebug<E: Engine> {
|
||||
fn debug <W: Render<E>> (other: W) -> DebugOverlay<E, W> {
|
||||
DebugOverlay(Default::default(), other)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DebugOverlay<E: Engine, W: Render<E>>(PhantomData<E>, pub W);
|
||||
|
||||
pub trait Area<N: Coordinate>: Copy {
|
||||
fn x (&self) -> N;
|
||||
fn y (&self) -> N;
|
||||
fn w (&self) -> N;
|
||||
fn h (&self) -> N;
|
||||
trait AreaMod<N: Coordinate>: Area<N> {
|
||||
fn x2 (&self) -> N {
|
||||
self.x() + self.w()
|
||||
}
|
||||
|
|
@ -176,35 +82,72 @@ pub trait Area<N: Coordinate>: Copy {
|
|||
#[inline] fn clip (&self, wh: impl Size<N>) -> [N;4] {
|
||||
[self.x(), self.y(), wh.w(), wh.h()]
|
||||
}
|
||||
#[inline] fn expect_min (&self, w: N, h: N) -> Usually<&Self> {
|
||||
if self.w() < w || self.h() < h {
|
||||
Err(format!("min {w}x{h}").into())
|
||||
} else {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
#[inline] fn split_fixed (&self, direction: Direction, a: N) -> ([N;4],[N;4]) {
|
||||
match direction {
|
||||
Direction::North => (
|
||||
[self.x(), (self.y()+self.h()).minus(a), self.w(), a],
|
||||
[self.x(), self.y(), self.w(), self.h().minus(a)],
|
||||
),
|
||||
Direction::South => (
|
||||
[self.x(), self.y(), self.w(), a],
|
||||
[self.x(), self.y() + a, self.w(), self.h().minus(a)],
|
||||
),
|
||||
Direction::East => (
|
||||
[self.x(), self.y(), a, self.h()],
|
||||
[self.x() + a, self.y(), self.w().minus(a), self.h()],
|
||||
),
|
||||
Direction::West => (
|
||||
[self.x() + self.w() - a, self.y(), a, self.h()],
|
||||
[self.x(), self.y(), self.w() - a, self.h()],
|
||||
),
|
||||
}
|
||||
|
||||
/// Renders multiple things on top of each other,
|
||||
/// in the order they are provided by the callback.
|
||||
/// Total size is largest width x largest height.
|
||||
pub struct Layers<
|
||||
E: Engine,
|
||||
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render<E>)->Usually<()>)->Usually<()>
|
||||
>(pub F, PhantomData<E>);
|
||||
|
||||
/// Shorthand for defining an instance of [Layers].
|
||||
#[macro_export] macro_rules! lay {
|
||||
([$($expr:expr),* $(,)?]) => {
|
||||
Layers::new(move|add|{ $(add(&$expr)?;)* Ok(()) })
|
||||
};
|
||||
(![$($expr:expr),* $(,)?]) => {
|
||||
Layers::new(|add|{ $(add(&$expr)?;)* Ok(()) })
|
||||
};
|
||||
($expr:expr) => {
|
||||
Layers::new($expr)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
pub trait HasSize<E: Engine> {
|
||||
fn size (&self) -> &Measure<E>;
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! has_size {
|
||||
(<$E:ty>|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||
impl $(<$($L),*$($T $(: $U)?),*>)? HasSize<$E> for $Struct $(<$($L),*$($T),*>)? {
|
||||
fn size (&$self) -> &Measure<$E> { $cb }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A widget that tracks its render width and height
|
||||
#[derive(Default)]
|
||||
pub struct Measure<E: Engine> {
|
||||
_engine: PhantomData<E>,
|
||||
pub x: Arc<AtomicUsize>,
|
||||
pub y: Arc<AtomicUsize>,
|
||||
}
|
||||
|
||||
pub struct ShowMeasure<'a>(&'a Measure<Tui>);
|
||||
|
||||
pub trait LayoutDebug<E: Engine> {
|
||||
fn debug <W: Render<E>> (other: W) -> DebugOverlay<E, W> {
|
||||
DebugOverlay(Default::default(), other)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DebugOverlay<E: Engine, W: Render<E>>(PhantomData<E>, pub W);
|
||||
|
||||
impl<T: Render<Tui>> Render<Tui> for DebugOverlay<Tui, T> {
|
||||
fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
|
||||
self.1.min_size(to)
|
||||
}
|
||||
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
|
||||
let [x, y, w, h] = to.area();
|
||||
self.1.render(to)?;
|
||||
Ok(to.blit(&format!("{w}x{h}+{x}+{y}"), x, y, Some(Style::default().green())))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
macro_rules! by_axis {
|
||||
(!$Enum:ident) => {
|
||||
impl<E: Engine, T: Render<E>> $Enum<E, T> {
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ impl Measure<Tui> {
|
|||
}
|
||||
}
|
||||
|
||||
render!(<Tui>|self: ShowMeasure<'a>|render(|to|Ok({
|
||||
render!(<Tui>|self: ShowMeasure<'a>|render(|to: &mut TuiOutput|Ok({
|
||||
let w = self.0.w();
|
||||
let h = self.0.h();
|
||||
to.blit(&format!(" {w} x {h} "), to.area.x(), to.area.y(), Some(
|
||||
|
|
|
|||
140
src/tui.rs
140
src/tui.rs
|
|
@ -1,143 +1,3 @@
|
|||
use crate::*;
|
||||
|
||||
mod tui_input; pub(crate) use self::tui_input::*;
|
||||
pub use self::tui_input::TuiInput;
|
||||
|
||||
mod tui_output; pub(crate) use self::tui_output::*;
|
||||
pub use self::tui_output::TuiOutput;
|
||||
|
||||
mod tui_style;
|
||||
|
||||
mod tui_theme; pub(crate) use self::tui_theme::*;
|
||||
|
||||
mod tui_border; pub(crate) use self::tui_border::*;
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
pub struct Tui {
|
||||
pub exited: Arc<AtomicBool>,
|
||||
pub buffer: Buffer,
|
||||
pub backend: CrosstermBackend<Stdout>,
|
||||
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, 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 <R: Component<Tui> + Sized + 'static> (
|
||||
state: Arc<RwLock<R>>
|
||||
) -> Usually<Arc<RwLock<R>>> {
|
||||
let backend = CrosstermBackend::new(stdout());
|
||||
let area = backend.size()?;
|
||||
let engine = Self {
|
||||
exited: Arc::new(AtomicBool::new(false)),
|
||||
buffer: Buffer::empty(area),
|
||||
area: [area.x, area.y, area.width, area.height],
|
||||
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 <R: Component<Tui> + Sized + 'static> (
|
||||
engine: &Arc<RwLock<Self>>, state: &Arc<RwLock<R>>, poll: Duration
|
||||
) -> JoinHandle<()> {
|
||||
let exited = engine.read().unwrap().exited.clone();
|
||||
let state = state.clone();
|
||||
spawn(move || loop {
|
||||
if exited.fetch_and(true, Relaxed) {
|
||||
break
|
||||
}
|
||||
if ::crossterm::event::poll(poll).is_ok() {
|
||||
let event = TuiEvent::Input(::crossterm::event::read().unwrap());
|
||||
match event {
|
||||
key_pat!(Ctrl-KeyCode::Char('c')) => {
|
||||
exited.store(true, Relaxed);
|
||||
},
|
||||
_ => {
|
||||
let exited = exited.clone();
|
||||
if let Err(e) = state.write().unwrap().handle(&TuiInput { event, exited }) {
|
||||
panic!("{e}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
fn spawn_render_thread <R: Component<Tui> + Sized + 'static> (
|
||||
engine: &Arc<RwLock<Self>>, state: &Arc<RwLock<R>>, 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, 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.x, size.y, size.width, size.height]
|
||||
};
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,160 +0,0 @@
|
|||
use crate::*;
|
||||
use ::crossterm::event::*;
|
||||
|
||||
pub struct TuiInput {
|
||||
pub(crate) exited: Arc<AtomicBool>,
|
||||
pub(crate) event: TuiEvent,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum TuiEvent {
|
||||
/// Terminal input
|
||||
Input(Event),
|
||||
/// JACK notification
|
||||
_Jack(JackEvent)
|
||||
}
|
||||
|
||||
impl Input<Tui> for TuiInput {
|
||||
type Event = TuiEvent;
|
||||
fn event (&self) -> &TuiEvent {
|
||||
&self.event
|
||||
}
|
||||
fn is_done (&self) -> bool {
|
||||
self.exited.fetch_and(true, Relaxed)
|
||||
}
|
||||
fn done (&self) {
|
||||
self.exited.store(true, Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! key_pat {
|
||||
(Ctrl-Alt-$code:pat) => { key_event_pat!($code, KeyModifiers::CONTROL | KeyModifiers::ALT) };
|
||||
(Ctrl-$code:pat) => { key_event_pat!($code, KeyModifiers::CONTROL) };
|
||||
(Alt-$code:pat) => { key_event_pat!($code, KeyModifiers::ALT) };
|
||||
(Shift-$code:pat) => { key_event_pat!($code, KeyModifiers::SHIFT) };
|
||||
($code:pat) => { TuiEvent::Input(crossterm::event::Event::Key(KeyEvent {
|
||||
code: $code,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE
|
||||
})) };
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! key_event_pat {
|
||||
($code:pat) => {
|
||||
TuiEvent::Input(crossterm::event::Event::Key(KeyEvent {
|
||||
code: $code,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE
|
||||
}))
|
||||
};
|
||||
($code:pat, $modifiers: pat) => {
|
||||
TuiEvent::Input(crossterm::event::Event::Key(KeyEvent {
|
||||
code: $code,
|
||||
modifiers: $modifiers,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) type KeyMapping<const N: usize, T> = [(TuiEvent, &'static dyn Fn(&T)->PhraseCommand);N];
|
||||
|
||||
#[macro_export] macro_rules! kexp {
|
||||
(Ctrl-Alt-$code:ident) => { key_event_expr!($code, KeyModifiers::from_bits(0b0000_0110).unwrap()) };
|
||||
(Ctrl-$code:ident) => { key_event_expr!($code, KeyModifiers::CONTROL) };
|
||||
(Alt-$code:ident) => { key_event_expr!($code, KeyModifiers::ALT) };
|
||||
(Shift-$code:ident) => { key_event_expr!($code, KeyModifiers::SHIFT) };
|
||||
($code:ident) => { key_event_expr!($code) };
|
||||
($code:expr) => { key_event_expr!($code) };
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! key_expr {
|
||||
(Ctrl-Alt-$code:ident) => { key_event_expr!($code, KeyModifiers::CONTROL | KeyModifiers::ALT) };
|
||||
(Ctrl-$code:ident) => { key_event_expr!($code, KeyModifiers::CONTROL) };
|
||||
(Alt-$code:ident) => { key_event_expr!($code, KeyModifiers::ALT) };
|
||||
(Shift-$code:ident) => { key_event_expr!($code, KeyModifiers::SHIFT) };
|
||||
($code:ident) => { key_event_expr!($code) };
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! key_event_expr {
|
||||
($code:expr, $modifiers: expr) => {
|
||||
TuiEvent::Input(crossterm::event::Event::Key(KeyEvent {
|
||||
code: $code,
|
||||
modifiers: $modifiers,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE
|
||||
}))
|
||||
};
|
||||
($code:expr) => {
|
||||
TuiEvent::Input(crossterm::event::Event::Key(KeyEvent {
|
||||
code: $code,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
/// Define a key
|
||||
pub const fn key (code: KeyCode) -> KeyEvent {
|
||||
let modifiers = KeyModifiers::NONE;
|
||||
let kind = KeyEventKind::Press;
|
||||
let state = KeyEventState::NONE;
|
||||
KeyEvent { code, modifiers, kind, state }
|
||||
}
|
||||
|
||||
/// Add Ctrl modifier to key
|
||||
pub const fn ctrl (key: KeyEvent) -> KeyEvent {
|
||||
KeyEvent { modifiers: key.modifiers.union(KeyModifiers::CONTROL), ..key }
|
||||
}
|
||||
|
||||
/// Add Alt modifier to key
|
||||
pub const fn alt (key: KeyEvent) -> KeyEvent {
|
||||
KeyEvent { modifiers: key.modifiers.union(KeyModifiers::ALT), ..key }
|
||||
}
|
||||
|
||||
/// Add Shift modifier to key
|
||||
pub const fn shift (key: KeyEvent) -> KeyEvent {
|
||||
KeyEvent { modifiers: key.modifiers.union(KeyModifiers::SHIFT), ..key }
|
||||
}
|
||||
|
||||
/// Define a keymap
|
||||
#[macro_export] macro_rules! keymap {
|
||||
($T:ty { $([$k:ident $(($char:literal))?, $m:ident, $n: literal, $d: literal, $f: expr]),* $(,)? }) => {
|
||||
&[
|
||||
$((KeyCode::$k $(($char))?, KeyModifiers::$m, $n, $d, &$f as KeyHandler<$T>)),*
|
||||
] as &'static [KeyBinding<$T>]
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
impl TuiInput {
|
||||
// TODO remove
|
||||
pub fn handle_keymap <T> (&self, state: &mut T, keymap: &KeyMap<T>) -> Usually<bool> {
|
||||
match self.event() {
|
||||
TuiEvent::Input(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<T> = &'static dyn Fn(&mut T)->Usually<bool>;
|
||||
|
||||
pub type KeyBinding<T> = (KeyCode, KeyModifiers, &'static str, &'static str, KeyHandler<T>);
|
||||
|
||||
pub type KeyMap<T> = [KeyBinding<T>];
|
||||
|
||||
*/
|
||||
|
|
@ -1,158 +0,0 @@
|
|||
use crate::*;
|
||||
use ratatui::buffer::Cell;
|
||||
|
||||
pub struct TuiOutput {
|
||||
pub buffer: Buffer,
|
||||
pub area: [u16;4]
|
||||
}
|
||||
|
||||
impl Output<Tui> 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<Tui>) -> 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<str>, x: u16, y: u16, style: Option<Style>
|
||||
) {
|
||||
let text = text.as_ref();
|
||||
let buf = &mut self.buffer;
|
||||
if x < buf.area.width && y < buf.area.height {
|
||||
buf.set_string(x, y, text, style.unwrap_or(Style::default()));
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn with_rect (&mut self, area: [u16;4]) -> &mut Self {
|
||||
self.area = area;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct BigBuffer {
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
pub content: Vec<Cell>
|
||||
}
|
||||
|
||||
impl BigBuffer {
|
||||
pub fn new (width: usize, height: usize) -> Self {
|
||||
Self { width, height, content: vec![Cell::default(); width*height] }
|
||||
}
|
||||
pub fn get (&self, x: usize, y: usize) -> Option<&Cell> {
|
||||
let i = self.index_of(x, y);
|
||||
self.content.get(i)
|
||||
}
|
||||
pub fn get_mut (&mut self, x: usize, y: usize) -> Option<&mut Cell> {
|
||||
let i = self.index_of(x, y);
|
||||
self.content.get_mut(i)
|
||||
}
|
||||
pub fn index_of (&self, x: usize, y: usize) -> usize {
|
||||
y * self.width + x
|
||||
}
|
||||
}
|
||||
|
||||
from!(|size:(usize, usize)| BigBuffer = Self::new(size.0, size.1));
|
||||
|
||||
pub fn buffer_update (buf: &mut Buffer, area: [u16;4], callback: &impl Fn(&mut Cell, u16, u16)) {
|
||||
for row in 0..area.h() {
|
||||
let y = area.y() + row;
|
||||
for col in 0..area.w() {
|
||||
let x = area.x() + col;
|
||||
if x < buf.area.width && y < buf.area.height {
|
||||
callback(buf.get_mut(x, y), col, row);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//impl Area<u16> for Rect {
|
||||
//fn x (&self) -> u16 { self.x }
|
||||
//fn y (&self) -> u16 { self.y }
|
||||
//fn w (&self) -> u16 { self.width }
|
||||
//fn h (&self) -> u16 { self.height }
|
||||
//}
|
||||
|
||||
pub fn half_block (lower: bool, upper: bool) -> Option<char> {
|
||||
match (lower, upper) {
|
||||
(true, true) => Some('█'),
|
||||
(true, false) => Some('▄'),
|
||||
(false, true) => Some('▀'),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
impl Render<Tui> for &str {
|
||||
fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> {
|
||||
// TODO: line breaks
|
||||
Ok(Some([self.chars().count() as u16, 1]))
|
||||
}
|
||||
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
|
||||
let [x, y, ..] = to.area();
|
||||
//let [w, h] = self.min_size(to.area().wh())?.unwrap();
|
||||
Ok(to.blit(&self, x, y, None))
|
||||
}
|
||||
}
|
||||
|
||||
impl Render<Tui> for String {
|
||||
fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> {
|
||||
// TODO: line breaks
|
||||
Ok(Some([self.chars().count() as u16, 1]))
|
||||
}
|
||||
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
|
||||
let [x, y, ..] = to.area();
|
||||
//let [w, h] = self.min_size(to.area().wh())?.unwrap();
|
||||
Ok(to.blit(&self, x, y, None))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Render<Tui>> Render<Tui> for DebugOverlay<Tui, T> {
|
||||
fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
|
||||
self.1.min_size(to)
|
||||
}
|
||||
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
|
||||
let [x, y, w, h] = to.area();
|
||||
self.1.render(to)?;
|
||||
Ok(to.blit(&format!("{w}x{h}+{x}+{y}"), x, y, Some(Style::default().green())))
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue