mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
wip: add TuiStyle, unifying Modal
This commit is contained in:
parent
7c555848e4
commit
29f11b5977
6 changed files with 467 additions and 434 deletions
|
|
@ -12,7 +12,7 @@
|
|||
>
|
||||
> (Especially if you know how to host LV2 plugin UIs in `winit` 😁)
|
||||
>
|
||||
> - the author ❤️
|
||||
> - the author
|
||||
|
||||
# tek
|
||||
|
||||
|
|
|
|||
|
|
@ -1,62 +1,59 @@
|
|||
use crate::*;
|
||||
|
||||
/// Entry point for main loop
|
||||
pub trait App<T: Engine> {
|
||||
fn run (self, context: T) -> Usually<T>;
|
||||
}
|
||||
|
||||
pub trait App<T: Engine> { fn run (self, context: T) -> Usually<T>; }
|
||||
/// 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: Number;
|
||||
type Area: Area<Self::Unit> + From<[Self::Unit;4]> + Debug + Copy;
|
||||
/// 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(()) }
|
||||
}
|
||||
|
||||
/// Current input state
|
||||
pub trait Input<E: Engine> {
|
||||
/// Type of input event
|
||||
type Event;
|
||||
fn event (&self)
|
||||
-> &Self::Event;
|
||||
fn is_done (&self)
|
||||
-> bool;
|
||||
/// Currently handled event
|
||||
fn event (&self) -> &Self::Event;
|
||||
/// Whether component should exit
|
||||
fn is_done (&self) -> bool;
|
||||
/// Mark component as done
|
||||
fn done (&self);
|
||||
}
|
||||
|
||||
/// Rendering target
|
||||
pub trait Output<E: Engine> {
|
||||
fn area (&self)
|
||||
-> E::Area;
|
||||
fn area_mut (&mut self)
|
||||
-> &mut E::Area;
|
||||
fn render_in (&mut self, area: E::Area, widget: &dyn Widget<Engine = E>)
|
||||
-> Usually<()>;
|
||||
/// 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 Widget<Engine = E>) -> Usually<()>;
|
||||
}
|
||||
|
||||
/// A renderable component
|
||||
pub trait Widget: Send + Sync {
|
||||
/// Engine for which this component is implemented
|
||||
type Engine: Engine;
|
||||
fn layout (&self,
|
||||
to: <Self::Engine as Engine>::Size
|
||||
) -> Perhaps<<Self::Engine as Engine>::Size> {
|
||||
/// Minimum size to use
|
||||
fn layout (&self, to: <Self::Engine as Engine>::Size)
|
||||
-> Perhaps<<Self::Engine as Engine>::Size>
|
||||
{
|
||||
Ok(Some(to))
|
||||
}
|
||||
fn render (&self,
|
||||
to: &mut <Self::Engine as Engine>::Output
|
||||
) -> Usually<()>;
|
||||
}
|
||||
impl<'a, E: Engine> Widget for Box<dyn Widget<Engine = E> + 'a> {
|
||||
type Engine = E;
|
||||
fn layout (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||
(**self).layout(to)
|
||||
}
|
||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||
(**self).render(to)
|
||||
}
|
||||
/// Draw to output render target
|
||||
fn render (&self,to: &mut <Self::Engine as Engine>::Output) -> Usually<()>;
|
||||
}
|
||||
impl<E: Engine> Widget for &dyn Widget<Engine = E> {
|
||||
type Engine = E;
|
||||
|
|
@ -76,6 +73,15 @@ impl<E: Engine> Widget for &mut dyn Widget<Engine = E> {
|
|||
(**self).render(to)
|
||||
}
|
||||
}
|
||||
impl<'a, E: Engine> Widget for Box<dyn Widget<Engine = E> + 'a> {
|
||||
type Engine = E;
|
||||
fn layout (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||
(**self).layout(to)
|
||||
}
|
||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||
(**self).render(to)
|
||||
}
|
||||
}
|
||||
impl<E: Engine, W: Widget<Engine = E>> Widget for Arc<W> {
|
||||
type Engine = E;
|
||||
fn layout (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||
|
|
@ -112,16 +118,12 @@ impl<E: Engine, W: Widget<Engine = E>> Widget for Option<W> {
|
|||
self.as_ref().map(|widget|widget.render(to)).unwrap_or(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
/// A component that consists of other components
|
||||
pub trait Content: Send + Sync {
|
||||
type Engine: Engine;
|
||||
fn content (&self) -> impl Widget<Engine = <Self as Content>::Engine>;
|
||||
}
|
||||
//impl<E> Content<E> for () where E: Engine {
|
||||
//fn content (&self) -> impl Widget<E> {
|
||||
//()
|
||||
//}
|
||||
//}
|
||||
/// Every component is renderable
|
||||
impl<E: Engine, W: Content<Engine = E>> Widget for W {
|
||||
type Engine = E;
|
||||
fn layout (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||
|
|
@ -134,41 +136,16 @@ impl<E: Engine, W: Content<Engine = E>> Widget for W {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A UI component.
|
||||
pub trait Component<E: Engine>: Widget<Engine = E> + Handle<E> {}
|
||||
|
||||
/// Everything that implements [Render] and [Handle] is a [Component].
|
||||
impl<E: Engine, C: Widget<Engine = E> + Handle<E>> Component<E> for C {}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine, C: Component<E> + Exit> ExitableComponent<E> for C {}
|
||||
|
||||
/// Handle input
|
||||
pub trait Handle<E: Engine>: Send + Sync {
|
||||
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled>;
|
||||
}
|
||||
impl<H, E: Engine> Handle<E> for &mut H where H: Handle<E> {
|
||||
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<H, E: Engine> Handle<E> for Option<H> where H: Handle<E> {
|
||||
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)
|
||||
|
|
@ -197,90 +174,25 @@ impl<H, E: Engine> Handle<E> for Arc<RwLock<H>> where H: Handle<E> {
|
|||
self.write().unwrap().handle(context)
|
||||
}
|
||||
}
|
||||
|
||||
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>];
|
||||
|
||||
pub fn handle_keymap <T> (
|
||||
state: &mut T, event: &TuiEvent, keymap: &KeyMap<T>,
|
||||
) -> Usually<bool> {
|
||||
match 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)
|
||||
}
|
||||
|
||||
/// 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>]
|
||||
/// A UI component that can render itself and handle input
|
||||
pub trait Component<E: Engine>: Widget<Engine = E> + Handle<E> {}
|
||||
/// Everything that implements [Render] and [Handle] is a [Component].
|
||||
impl<E: Engine, C: Widget<Engine = E> + Handle<E>> Component<E> for C {}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! map_key {
|
||||
($k:ident $(($char:literal))?, $m:ident, $n: literal, $d: literal, $f: expr) => {
|
||||
(KeyCode::$k $(($char))?, KeyModifiers::$m, $n, $d, &$f as &dyn Fn()->Usually<bool>)
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! key {
|
||||
($code:pat) => {
|
||||
TuiEvent::Input(crossterm::event::Event::Key(crossterm::event::KeyEvent {
|
||||
code: $code,
|
||||
modifiers: crossterm::event::KeyModifiers::NONE,
|
||||
kind: crossterm::event::KeyEventKind::Press,
|
||||
state: crossterm::event::KeyEventState::NONE
|
||||
}))
|
||||
};
|
||||
(Ctrl-$code:pat) => {
|
||||
TuiEvent::Input(crossterm::event::Event::Key(crossterm::event::KeyEvent {
|
||||
code: $code,
|
||||
modifiers: crossterm::event::KeyModifiers::CONTROL,
|
||||
kind: crossterm::event::KeyEventKind::Press,
|
||||
state: crossterm::event::KeyEventState::NONE
|
||||
}))
|
||||
};
|
||||
(Alt-$code:pat) => {
|
||||
TuiEvent::Input(crossterm::event::Event::Key(crossterm::event::KeyEvent {
|
||||
code: $code,
|
||||
modifiers: crossterm::event::KeyModifiers::ALT,
|
||||
kind: crossterm::event::KeyEventKind::Press,
|
||||
state: crossterm::event::KeyEventState::NONE
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! match_key {
|
||||
($event:expr, {
|
||||
$($key:pat=>$block:expr),* $(,)?
|
||||
}) => {
|
||||
match $event {
|
||||
$(crossterm::event::Event::Key(crossterm::event::KeyEvent {
|
||||
code: $key,
|
||||
modifiers: crossterm::event::KeyModifiers::NONE,
|
||||
kind: crossterm::event::KeyEventKind::Press,
|
||||
state: crossterm::event::KeyEventState::NONE
|
||||
}) => {
|
||||
$block
|
||||
})*
|
||||
_ => Ok(None)
|
||||
}
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
impl<E: Engine, C: Component<E> + Exit> ExitableComponent<E> for C {}
|
||||
|
||||
/// A component that may contain [Focusable] components.
|
||||
pub trait Focus <const N: usize, E: Engine>: Widget<Engine = E> + Handle<E> {
|
||||
|
|
|
|||
|
|
@ -108,26 +108,151 @@ impl<N: Number> Area<N> for [N;4] {
|
|||
#[inline] fn h (&self) -> N { self[3] }
|
||||
}
|
||||
|
||||
pub struct Split<
|
||||
E: Engine,
|
||||
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Widget<Engine = E>)->Usually<()>)->Usually<()>
|
||||
>(pub F, pub Direction, PhantomData<E>);
|
||||
pub trait Layout<E: Engine>: Widget<Engine = E> + Sized {
|
||||
fn align_center (self) -> Align<Self> {
|
||||
Align::Center(self)
|
||||
}
|
||||
fn align_x (self) -> Align<Self> {
|
||||
Align::X(self)
|
||||
}
|
||||
fn align_y (self) -> Align<Self> {
|
||||
Align::Y(self)
|
||||
}
|
||||
fn fixed_x (self, x: E::Unit) -> Fixed<E::Unit, Self> {
|
||||
Fixed::X(x, self)
|
||||
}
|
||||
fn fixed_y (self, y: E::Unit) -> Fixed<E::Unit, Self> {
|
||||
Fixed::Y(y, self)
|
||||
}
|
||||
fn fixed_xy (self, x: E::Unit, y: E::Unit) -> Fixed<E::Unit, Self> {
|
||||
Fixed::XY(x, y, self)
|
||||
}
|
||||
fn min_x (self, x: E::Unit) -> Min<E::Unit, Self> {
|
||||
Min::X(x, self)
|
||||
}
|
||||
fn min_y (self, y: E::Unit) -> Min<E::Unit, Self> {
|
||||
Min::Y(y, self)
|
||||
}
|
||||
fn min_xy (self, x: E::Unit, y: E::Unit) -> Min<E::Unit, Self> {
|
||||
Min::XY(x, y, self)
|
||||
}
|
||||
fn max_x (self, x: E::Unit) -> Max<E::Unit, Self> {
|
||||
Max::X(x, self)
|
||||
}
|
||||
fn max_y (self, y: E::Unit) -> Max<E::Unit, Self> {
|
||||
Max::Y(y, self)
|
||||
}
|
||||
fn max_xy (self, x: E::Unit, y: E::Unit) -> Max<E::Unit, Self> {
|
||||
Max::XY(x, y, self)
|
||||
}
|
||||
fn push_x (self, x: E::Unit) -> Push<E::Unit, Self> {
|
||||
Push::X(x, self)
|
||||
}
|
||||
fn push_y (self, y: E::Unit) -> Push<E::Unit, Self> {
|
||||
Push::Y(y, self)
|
||||
}
|
||||
fn push_xy (self, x: E::Unit, y: E::Unit) -> Push<E::Unit, Self> {
|
||||
Push::XY(x, y, self)
|
||||
}
|
||||
fn pull_x (self, x: E::Unit) -> Pull<E::Unit, Self> {
|
||||
Pull::X(x, self)
|
||||
}
|
||||
fn pull_y (self, y: E::Unit) -> Pull<E::Unit, Self> {
|
||||
Pull::Y(y, self)
|
||||
}
|
||||
fn pull_xy (self, x: E::Unit, y: E::Unit) -> Pull<E::Unit, Self> {
|
||||
Pull::XY(x, y, self)
|
||||
}
|
||||
fn grow_x (self, x: E::Unit) -> Grow<E::Unit, Self> {
|
||||
Grow::X(x, self)
|
||||
}
|
||||
fn grow_y (self, y: E::Unit) -> Grow<E::Unit, Self> {
|
||||
Grow::Y(y, self)
|
||||
}
|
||||
fn grow_xy (self, x: E::Unit, y: E::Unit) -> Grow<E::Unit, Self> {
|
||||
Grow::XY(x, y, self)
|
||||
}
|
||||
fn shrink_x (self, x: E::Unit) -> Shrink<E::Unit, Self> {
|
||||
Shrink::X(x, self)
|
||||
}
|
||||
fn shrink_y (self, y: E::Unit) -> Shrink<E::Unit, Self> {
|
||||
Shrink::Y(y, self)
|
||||
}
|
||||
fn shrink_xy (self, x: E::Unit, y: E::Unit) -> Shrink<E::Unit, Self> {
|
||||
Shrink::XY(x, y, self)
|
||||
}
|
||||
fn inset_x (self, x: E::Unit) -> Inset<E::Unit, Self> {
|
||||
Inset::X(x, self)
|
||||
}
|
||||
fn inset_y (self, y: E::Unit) -> Inset<E::Unit, Self> {
|
||||
Inset::Y(y, self)
|
||||
}
|
||||
fn inset_xy (self, x: E::Unit, y: E::Unit) -> Inset<E::Unit, Self> {
|
||||
Inset::XY(x, y, self)
|
||||
}
|
||||
fn outset_x (self, x: E::Unit) -> Outset<E::Unit, Self> {
|
||||
Outset::X(x, self)
|
||||
}
|
||||
fn outset_y (self, y: E::Unit) -> Outset<E::Unit, Self> {
|
||||
Outset::Y(y, self)
|
||||
}
|
||||
fn outset_xy (self, x: E::Unit, y: E::Unit) -> Outset<E::Unit, Self> {
|
||||
Outset::XY(x, y, self)
|
||||
}
|
||||
fn fill_x (self) -> Fill<E, Self> {
|
||||
Fill::X(self)
|
||||
}
|
||||
fn fill_y (self) -> Fill<E, Self> {
|
||||
Fill::Y(self)
|
||||
}
|
||||
fn fill_xy (self) -> Fill<E, Self> {
|
||||
Fill::XY(self)
|
||||
}
|
||||
fn debug (self) -> DebugOverlay<E, Self> {
|
||||
DebugOverlay(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
E: Engine,
|
||||
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Widget<Engine = E>)->Usually<()>)->Usually<()>
|
||||
> Split<E, F> {
|
||||
#[inline]
|
||||
pub fn new (direction: Direction, build: F) -> Self {
|
||||
Self(build, direction, Default::default())
|
||||
impl<E: Engine, W: Widget<Engine = E>> Layout<E> for W {}
|
||||
|
||||
pub struct DebugOverlay<E: Engine, W: Widget<Engine = E>>(pub W);
|
||||
|
||||
pub struct ModalHost<E: Engine, M: Widget<Engine = E>, W: Widget<Engine = E>>(
|
||||
pub bool, pub M, pub W
|
||||
);
|
||||
|
||||
pub enum Fill<E: Engine, W: Widget<Engine = E>> {
|
||||
X(W),
|
||||
Y(W),
|
||||
XY(W)
|
||||
}
|
||||
|
||||
impl<E: Engine, W: Widget<Engine = E>> Fill<E, W> {
|
||||
fn inner (&self) -> &W {
|
||||
match self {
|
||||
Self::X(inner) => &inner,
|
||||
Self::Y(inner) => &inner,
|
||||
Self::XY(inner) => &inner,
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn right (build: F) -> Self {
|
||||
Self::new(Direction::Right, build)
|
||||
}
|
||||
|
||||
impl<E: Engine, W: Widget<Engine = E>> Widget for Fill<E, W> {
|
||||
type Engine = E;
|
||||
fn layout (&self, to: E::Size) -> Perhaps<E::Size> {
|
||||
let area = self.inner().layout(to.into())?;
|
||||
if let Some(area) = area {
|
||||
Ok(Some(match self {
|
||||
Self::X(_) => [to.w().into(), area.h()],
|
||||
Self::Y(_) => [area.w(), to.h().into()],
|
||||
Self::XY(_) => [to.w().into(), to.h().into()],
|
||||
}.into()))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn down (build: F) -> Self {
|
||||
Self::new(Direction::Down, build)
|
||||
fn render (&self, to: &mut E::Output) -> Usually<()> {
|
||||
self.inner().render(to)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -172,17 +297,6 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
//pub fn collect <'a, E: Engine, const N: usize> (
|
||||
//items: &'a [&'a dyn Widget<Engine = E>;N]
|
||||
//) -> impl Send + Sync + Fn(&'a mut dyn FnMut(&'a dyn Widget<Engine = E>)->Usually<()>)->Usually<()> + '_ {
|
||||
//|add: &'a mut dyn FnMut(&'a dyn Widget<Engine = E>)->Usually<()>| {
|
||||
//for item in items.iter() {
|
||||
//add(item)?;
|
||||
//}
|
||||
//Ok(())
|
||||
//}
|
||||
//}
|
||||
|
||||
//`Layers<_, impl (Fn(&mut dyn FnMut(&dyn Widget<Engine = _>) -> Result<(), Box<...>>) -> ... ) + Send + Sync>`
|
||||
//`Layers<Tui, impl (Fn(&mut dyn FnMut(&dyn Widget<Engine = Tui>) -> Result<(), Box<(dyn std::error::Error + 'static)>>) -> Result<(), Box<(dyn std::error::Error + 'static)>>) + Send + Sync + '_>`
|
||||
|
||||
|
|
@ -446,7 +560,7 @@ impl<E: Engine, T: Widget<Engine = E>> Widget for Shrink<E::Unit, T> {
|
|||
],
|
||||
Self::Y(h, _) => [
|
||||
to.w(),
|
||||
if to.h() > h { to.h() - h } else { 0.into() }
|
||||
if to.h() > h { to.h() - h } else { 0.into() }
|
||||
],
|
||||
Self::XY(w, h, _) => [
|
||||
if to.w() > w { to.w() - w } else { 0.into() },
|
||||
|
|
@ -605,6 +719,26 @@ impl<E: Engine, T: Widget<Engine = E>> Widget for Pull<E::Unit, T> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct Split<
|
||||
E: Engine,
|
||||
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Widget<Engine = E>)->Usually<()>)->Usually<()>
|
||||
>(pub F, pub Direction, PhantomData<E>);
|
||||
|
||||
impl<
|
||||
E: Engine,
|
||||
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Widget<Engine = E>)->Usually<()>)->Usually<()>
|
||||
> Split<E, F> {
|
||||
#[inline] pub fn new (direction: Direction, build: F) -> Self {
|
||||
Self(build, direction, Default::default())
|
||||
}
|
||||
#[inline] pub fn right (build: F) -> Self {
|
||||
Self::new(Direction::Right, build)
|
||||
}
|
||||
#[inline] pub fn down (build: F) -> Self {
|
||||
Self::new(Direction::Down, build)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine, F> Widget for Split<E, F>
|
||||
where
|
||||
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Widget<Engine = E>)->Usually<()>)->Usually<()>
|
||||
|
|
@ -693,3 +827,13 @@ where
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! lay {
|
||||
($($expr:expr),* $(,)?) => { Layers::new(move|add|{ $(add(&$expr)?;)* Ok(()) }) }
|
||||
}
|
||||
#[macro_export] macro_rules! col {
|
||||
($($expr:expr),* $(,)?) => { Split::down(move|add|{ $(add(&$expr)?;)* Ok(()) }) }
|
||||
}
|
||||
#[macro_export] macro_rules! row {
|
||||
($($expr:expr),* $(,)?) => { Split::right(move|add|{ $(add(&$expr)?;)* Ok(()) }) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,6 +144,83 @@ impl Input<Tui> for TuiInput {
|
|||
self.exited.store(true, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
impl TuiInput {
|
||||
pub fn handle_keymap <T> (&self, state: &mut T, keymap: &KeyMap<T>) -> Usually<bool> {
|
||||
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<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>];
|
||||
/// 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>]
|
||||
}
|
||||
}
|
||||
/// Define a key in a keymap
|
||||
#[macro_export] macro_rules! map_key {
|
||||
($k:ident $(($char:literal))?, $m:ident, $n: literal, $d: literal, $f: expr) => {
|
||||
(KeyCode::$k $(($char))?, KeyModifiers::$m, $n, $d, &$f as &dyn Fn()->Usually<bool>)
|
||||
}
|
||||
}
|
||||
/// Shorthand for key match statement
|
||||
#[macro_export] macro_rules! match_key {
|
||||
($event:expr, {
|
||||
$($key:pat=>$block:expr),* $(,)?
|
||||
}) => {
|
||||
match $event {
|
||||
$(crossterm::event::Event::Key(crossterm::event::KeyEvent {
|
||||
code: $key,
|
||||
modifiers: crossterm::event::KeyModifiers::NONE,
|
||||
kind: crossterm::event::KeyEventKind::Press,
|
||||
state: crossterm::event::KeyEventState::NONE
|
||||
}) => {
|
||||
$block
|
||||
})*
|
||||
_ => Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Define key pattern in key match statement
|
||||
#[macro_export] macro_rules! key {
|
||||
($code:pat) => {
|
||||
TuiEvent::Input(crossterm::event::Event::Key(crossterm::event::KeyEvent {
|
||||
code: $code,
|
||||
modifiers: crossterm::event::KeyModifiers::NONE,
|
||||
kind: crossterm::event::KeyEventKind::Press,
|
||||
state: crossterm::event::KeyEventState::NONE
|
||||
}))
|
||||
};
|
||||
(Ctrl-$code:pat) => {
|
||||
TuiEvent::Input(crossterm::event::Event::Key(crossterm::event::KeyEvent {
|
||||
code: $code,
|
||||
modifiers: crossterm::event::KeyModifiers::CONTROL,
|
||||
kind: crossterm::event::KeyEventKind::Press,
|
||||
state: crossterm::event::KeyEventState::NONE
|
||||
}))
|
||||
};
|
||||
(Alt-$code:pat) => {
|
||||
TuiEvent::Input(crossterm::event::Event::Key(crossterm::event::KeyEvent {
|
||||
code: $code,
|
||||
modifiers: crossterm::event::KeyModifiers::ALT,
|
||||
kind: crossterm::event::KeyEventKind::Press,
|
||||
state: crossterm::event::KeyEventState::NONE
|
||||
}))
|
||||
}
|
||||
}
|
||||
pub struct TuiOutput {
|
||||
pub buffer: Buffer,
|
||||
pub area: [u16;4],
|
||||
|
|
@ -291,8 +368,19 @@ impl Widget for &str {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct Styled<T: Widget<Engine = Tui>>(pub Option<Style>, pub T);
|
||||
impl<T: Widget<Engine = Tui>> Widget for DebugOverlay<Tui, T> {
|
||||
type Engine = Tui;
|
||||
fn layout (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
|
||||
self.0.layout(to)
|
||||
}
|
||||
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
|
||||
let [x, y, w, h] = to.area();
|
||||
self.0.render(to)?;
|
||||
Ok(to.blit(&format!("{w}x{h}+{x}+{y}"), x, y, Some(Style::default().green())))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Styled<T: Widget<Engine = Tui>>(pub Option<Style>, pub T);
|
||||
impl Widget for Styled<&str> {
|
||||
type Engine = Tui;
|
||||
fn layout (&self, _: [u16;2]) -> Perhaps<[u16;2]> {
|
||||
|
|
@ -306,8 +394,49 @@ impl Widget for Styled<&str> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct Background(pub Color);
|
||||
#[macro_export] macro_rules! tui_style {
|
||||
($NAME:ident = $fg:expr, $bg:expr, $ul:expr, $add:expr, $sub:expr) => {
|
||||
pub const $NAME: Style = Style {
|
||||
fg: $fg, bg: $bg, underline_color: $ul, add_modifier: $add, sub_modifier: $sub,
|
||||
};
|
||||
}
|
||||
}
|
||||
pub trait TuiStyle: Widget<Engine = Tui> + Sized {
|
||||
fn foreground (self, color: Color) -> impl Widget<Engine = Tui> {
|
||||
Layers::new(move |add|{ add(&Foreground(color))?; add(&self) })
|
||||
}
|
||||
fn background (self, color: Color) -> impl Widget<Engine = Tui> {
|
||||
Layers::new(move |add|{ add(&Background(color))?; add(&self) })
|
||||
}
|
||||
}
|
||||
impl<W: Widget<Engine = Tui>> TuiStyle for W {}
|
||||
impl<M: Widget<Engine = Tui>, W: Widget<Engine = Tui>> Widget for ModalHost<Tui, M, W> {
|
||||
type Engine = Tui;
|
||||
fn layout (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
|
||||
Ok(Some(to))
|
||||
}
|
||||
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
|
||||
self.2.render(to)?;
|
||||
if self.0 {
|
||||
to.fill_bg(to.area(), COLOR_BG1);
|
||||
to.fill_fg(to.area(), COLOR_BG2);
|
||||
self.1.render(to)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Foreground(pub Color);
|
||||
impl Widget for Foreground {
|
||||
type Engine = Tui;
|
||||
fn layout (&self, _: [u16;2]) -> Perhaps<[u16;2]> {
|
||||
Ok(Some([0,0]))
|
||||
}
|
||||
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
|
||||
Ok(to.fill_fg(to.area(), self.0))
|
||||
}
|
||||
}
|
||||
pub struct Background(pub Color);
|
||||
impl Widget for Background {
|
||||
type Engine = Tui;
|
||||
fn layout (&self, _: [u16;2]) -> Perhaps<[u16;2]> {
|
||||
|
|
@ -318,34 +447,7 @@ impl Widget for Background {
|
|||
}
|
||||
}
|
||||
|
||||
//impl<F> Widget for Layers<Tui, F>
|
||||
//where
|
||||
//F: Send + Sync + Fn(&mut dyn FnMut(&dyn Widget<Engine = Tui>)->Usually<()>)->Usually<()>
|
||||
//{
|
||||
//type Engine = Tui;
|
||||
//fn layout (&self, area: [u16;2]) -> Perhaps<[u16;2]> {
|
||||
//let mut w = 0;
|
||||
//let mut h = 0;
|
||||
//(self.0)(&mut |layer| {
|
||||
//if let Some(layer_area) = layer.layout(area)? {
|
||||
//w = w.max(layer_area.w());
|
||||
//h = h.max(layer_area.h());
|
||||
//}
|
||||
//Ok(())
|
||||
//})?;
|
||||
//Ok(Some([w, h]))
|
||||
//}
|
||||
//fn render (&self, to: &mut TuiOutput) -> Usually<()> {
|
||||
//if let Some(size) = self.layout(to.area().wh())? {
|
||||
//(self.0)(&mut |layer|to.render_in(to.area().clip(size), &layer))
|
||||
//} else {
|
||||
//Ok(())
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
|
||||
pub struct Border<S: BorderStyle>(pub S);
|
||||
|
||||
impl<S: BorderStyle> Widget for Border<S> {
|
||||
type Engine = Tui;
|
||||
fn layout (&self, _: [u16;2]) -> Perhaps<[u16;2]> {
|
||||
|
|
@ -380,42 +482,25 @@ pub trait BorderStyle: Send + Sync {
|
|||
const S: &'static str = "";
|
||||
const SW: &'static str = "";
|
||||
const W: &'static str = "";
|
||||
|
||||
fn n (&self) -> &str {
|
||||
Self::N
|
||||
}
|
||||
fn s (&self) -> &str {
|
||||
Self::S
|
||||
}
|
||||
fn e (&self) -> &str {
|
||||
Self::E
|
||||
}
|
||||
fn w (&self) -> &str {
|
||||
Self::W
|
||||
}
|
||||
fn nw (&self) -> &str {
|
||||
Self::NW
|
||||
}
|
||||
fn ne (&self) -> &str {
|
||||
Self::NE
|
||||
}
|
||||
fn sw (&self) -> &str {
|
||||
Self::SW
|
||||
}
|
||||
fn se (&self) -> &str {
|
||||
Self::SE
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn draw <'a> (&self, to: &mut TuiOutput) -> Usually<()> {
|
||||
fn n (&self) -> &str { Self::N }
|
||||
fn s (&self) -> &str { Self::S }
|
||||
fn e (&self) -> &str { Self::E }
|
||||
fn w (&self) -> &str { Self::W }
|
||||
fn nw (&self) -> &str { Self::NW }
|
||||
fn ne (&self) -> &str { Self::NE }
|
||||
fn sw (&self) -> &str { Self::SW }
|
||||
fn se (&self) -> &str { Self::SE }
|
||||
#[inline] fn draw <'a> (
|
||||
&self, to: &mut TuiOutput
|
||||
) -> Usually<()> {
|
||||
self.draw_horizontal(to, None)?;
|
||||
self.draw_vertical(to, None)?;
|
||||
self.draw_corners(to, None)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn draw_horizontal (&self, to: &mut TuiOutput, style: Option<Style>) -> Usually<[u16;4]> {
|
||||
#[inline] fn draw_horizontal (
|
||||
&self, to: &mut TuiOutput, style: Option<Style>
|
||||
) -> Usually<[u16;4]> {
|
||||
let area = to.area();
|
||||
let style = style.or_else(||self.style_horizontal());
|
||||
let [x, x2, y, y2] = area.lrtb();
|
||||
|
|
@ -425,17 +510,19 @@ pub trait BorderStyle: Send + Sync {
|
|||
}
|
||||
Ok(area)
|
||||
}
|
||||
#[inline]
|
||||
fn draw_north (&self, to: &mut TuiOutput, x: u16, y: u16, style: Option<Style>) -> () {
|
||||
#[inline] fn draw_north (
|
||||
&self, to: &mut TuiOutput, x: u16, y: u16, style: Option<Style>
|
||||
) -> () {
|
||||
to.blit(&Self::N, x, y, style)
|
||||
}
|
||||
#[inline]
|
||||
fn draw_south (&self, to: &mut TuiOutput, x: u16, y: u16, style: Option<Style>) -> () {
|
||||
#[inline] fn draw_south (
|
||||
&self, to: &mut TuiOutput, x: u16, y: u16, style: Option<Style>
|
||||
) -> () {
|
||||
to.blit(&Self::S, x, y, style)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn draw_vertical (&self, to: &mut TuiOutput, style: Option<Style>) -> Usually<[u16;4]> {
|
||||
#[inline] fn draw_vertical (
|
||||
&self, to: &mut TuiOutput, style: Option<Style>
|
||||
) -> Usually<[u16;4]> {
|
||||
let area = to.area();
|
||||
let style = style.or_else(||self.style_vertical());
|
||||
let [x, x2, y, y2] = area.lrtb();
|
||||
|
|
@ -445,9 +532,9 @@ pub trait BorderStyle: Send + Sync {
|
|||
}
|
||||
Ok(area)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn draw_corners (&self, to: &mut TuiOutput, style: Option<Style>) -> Usually<[u16;4]> {
|
||||
#[inline] fn draw_corners (
|
||||
&self, to: &mut TuiOutput, style: Option<Style>
|
||||
) -> Usually<[u16;4]> {
|
||||
let area = to.area();
|
||||
let style = style.or_else(||self.style_corners());
|
||||
let [x, y, width, height] = area.xywh();
|
||||
|
|
@ -459,23 +546,10 @@ pub trait BorderStyle: Send + Sync {
|
|||
}
|
||||
Ok(area)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn style (&self) -> Option<Style> {
|
||||
None
|
||||
}
|
||||
#[inline]
|
||||
fn style_horizontal (&self) -> Option<Style> {
|
||||
self.style()
|
||||
}
|
||||
#[inline]
|
||||
fn style_vertical (&self) -> Option<Style> {
|
||||
self.style()
|
||||
}
|
||||
#[inline]
|
||||
fn style_corners (&self) -> Option<Style> {
|
||||
self.style()
|
||||
}
|
||||
#[inline] fn style (&self) -> Option<Style> { None }
|
||||
#[inline] fn style_horizontal (&self) -> Option<Style> { self.style() }
|
||||
#[inline] fn style_vertical (&self) -> Option<Style> { self.style() }
|
||||
#[inline] fn style_corners (&self) -> Option<Style> { self.style() }
|
||||
}
|
||||
|
||||
macro_rules! border {
|
||||
|
|
@ -497,12 +571,8 @@ macro_rules! border {
|
|||
pub struct $T(pub Style);
|
||||
impl Widget for $T {
|
||||
type Engine = Tui;
|
||||
fn layout (&self, _: [u16;2]) -> Perhaps<[u16;2]> {
|
||||
Ok(Some([0,0]))
|
||||
}
|
||||
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
|
||||
self.draw(to)
|
||||
}
|
||||
fn layout (&self, _: [u16;2]) -> Perhaps<[u16;2]> { Ok(Some([0,0])) }
|
||||
fn render (&self, to: &mut TuiOutput) -> Usually<()> { self.draw(to) }
|
||||
}
|
||||
)+}
|
||||
}
|
||||
|
|
@ -576,91 +646,14 @@ border! {
|
|||
}
|
||||
}
|
||||
|
||||
pub const COLOR_BG0: Color = Color::Rgb(30, 33, 36);
|
||||
pub const COLOR_BG1: Color = Color::Rgb(41, 46, 57);
|
||||
pub const COLOR_BG2: Color = Color::Rgb(46, 52, 64);
|
||||
pub const COLOR_BG3: Color = Color::Rgb(59, 66, 82);
|
||||
pub const COLOR_BG4: Color = Color::Rgb(67, 76, 94);
|
||||
pub const COLOR_BG5: Color = Color::Rgb(76, 86, 106);
|
||||
|
||||
pub trait Theme {
|
||||
const BG0: Color;
|
||||
const BG1: Color;
|
||||
const BG2: Color;
|
||||
const BG3: Color;
|
||||
const BG4: Color;
|
||||
const RED: Color;
|
||||
const YELLOW: Color;
|
||||
const GREEN: Color;
|
||||
|
||||
const PLAYING: Color;
|
||||
const SEPARATOR: Color;
|
||||
|
||||
fn bg_hier (focused: bool, entered: bool) -> Color {
|
||||
if focused && entered {
|
||||
Self::BG3
|
||||
} else if focused {
|
||||
Self::BG2
|
||||
} else {
|
||||
Self::BG1
|
||||
}
|
||||
}
|
||||
|
||||
fn bg_hi (focused: bool, entered: bool) -> Color {
|
||||
if focused && entered {
|
||||
Self::BG2
|
||||
} else if focused {
|
||||
Self::BG1
|
||||
} else {
|
||||
Self::BG0
|
||||
}
|
||||
}
|
||||
|
||||
fn bg_lo (focused: bool, entered: bool) -> Color {
|
||||
if focused && entered {
|
||||
Self::BG1
|
||||
} else if focused {
|
||||
Self::BG0
|
||||
} else {
|
||||
Color::Reset
|
||||
}
|
||||
}
|
||||
|
||||
fn style_hi (focused: bool, highlight: bool) -> Style {
|
||||
if highlight && focused {
|
||||
Style::default().yellow().not_dim()
|
||||
} else if highlight {
|
||||
Style::default().yellow().dim()
|
||||
} else {
|
||||
Style::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Nord;
|
||||
|
||||
impl Theme for Nord {
|
||||
const BG0: Color = Color::Rgb(41, 46, 57);
|
||||
const BG1: Color = Color::Rgb(46, 52, 64);
|
||||
const BG2: Color = Color::Rgb(59, 66, 82);
|
||||
const BG3: Color = Color::Rgb(67, 76, 94);
|
||||
const BG4: Color = Color::Rgb(76, 86, 106);
|
||||
const RED: Color = Color::Rgb(191, 97, 106);
|
||||
const YELLOW: Color = Color::Rgb(235, 203, 139);
|
||||
const GREEN: Color = Color::Rgb(163, 190, 140);
|
||||
|
||||
const PLAYING: Color = Color::Rgb(60, 100, 50);
|
||||
const SEPARATOR: Color = Color::Rgb(0, 0, 0);
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! tui_style {
|
||||
($NAME:ident = $fg:expr, $bg:expr, $ul:expr, $add:expr, $sub:expr) => {
|
||||
pub const $NAME: Style = Style {
|
||||
fg: $fg,
|
||||
bg: $bg,
|
||||
underline_color: $ul,
|
||||
add_modifier: $add,
|
||||
sub_modifier: $sub,
|
||||
};
|
||||
}
|
||||
}
|
||||
pub const COLOR_BG0: Color = Color::Rgb(30, 33, 36);
|
||||
pub const COLOR_BG1: Color = Color::Rgb(41, 46, 57);
|
||||
pub const COLOR_BG2: Color = Color::Rgb(46, 52, 64);
|
||||
pub const COLOR_BG3: Color = Color::Rgb(59, 66, 82);
|
||||
pub const COLOR_BG4: Color = Color::Rgb(67, 76, 94);
|
||||
pub const COLOR_BG5: Color = Color::Rgb(76, 86, 106);
|
||||
pub const COLOR_RED: Color = Color::Rgb(191, 97, 106);
|
||||
pub const COLOR_YELLOW: Color = Color::Rgb(235, 203, 139);
|
||||
pub const COLOR_GREEN: Color = Color::Rgb(163, 190, 140);
|
||||
pub const COLOR_PLAYING: Color = Color::Rgb(60, 100, 50);
|
||||
pub const COLOR_SEPARATOR: Color = Color::Rgb(0, 0, 0);
|
||||
|
|
|
|||
|
|
@ -48,13 +48,12 @@ impl ArrangerCli {
|
|||
//}
|
||||
}
|
||||
transport.set_focused(true);
|
||||
let state = Arc::new(RwLock::new(ArrangerStandalone {
|
||||
Tui::run(Arc::new(RwLock::new(ArrangerStandalone {
|
||||
transport,
|
||||
show_sequencer: Some(tek_core::Direction::Down),
|
||||
arranger,
|
||||
focus: 0
|
||||
}));
|
||||
Tui::run(state)?;
|
||||
})))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -73,26 +72,27 @@ struct ArrangerStandalone<E: Engine> {
|
|||
impl Content for ArrangerStandalone<Tui> {
|
||||
type Engine = Tui;
|
||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||
Split::down(|add|{
|
||||
add(&self.transport)?;
|
||||
let show = self.arranger.modal.is_some();
|
||||
let modal = self.arranger.modal.as_ref().map(|x|x as &dyn Content<Engine = Tui>);
|
||||
ModalHost(show, modal, Split::down(move|add|{
|
||||
add(&(&self.transport as &dyn Widget<Engine = Tui>).debug())?;
|
||||
if let (Some(direction), Some(sequencer)) = (
|
||||
self.show_sequencer,
|
||||
self.arranger.sequencer(),
|
||||
) {
|
||||
add(&Split::new(direction, |add|{
|
||||
add(&Shrink::Y(20, &self.arranger as &dyn Widget<Engine = Tui>))?;
|
||||
add(&Min::Y(20, sequencer as &dyn Widget<Engine = Tui>))?;
|
||||
add(&Split::new(direction, move|add|{
|
||||
add(&(&self.arranger as &dyn Widget<Engine = Tui>)
|
||||
.shrink_y(30)
|
||||
.debug())?;
|
||||
add(&(sequencer as &dyn Widget<Engine = Tui>)
|
||||
.min_y(20)
|
||||
.debug())?;
|
||||
Ok(())
|
||||
}))
|
||||
} else {
|
||||
add(&self.arranger)
|
||||
}
|
||||
})
|
||||
//if let Some(ref modal) = self.arranger.modal {
|
||||
//to.fill_bg(area, Nord::bg_lo(false, false));
|
||||
//to.fill_fg(area, Nord::bg_hi(false, false));
|
||||
//modal.render(to)?;
|
||||
//}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -482,6 +482,7 @@ impl<'a> Content for VerticalArranger<'a, Tui> {
|
|||
(0..=state.scenes.len()).map(|i|(factor*ppq, factor*ppq*i)).collect::<Vec<_>>(),
|
||||
),
|
||||
};
|
||||
//let height = rows.last().map(|(w,y)|(y+w)/ppq).unwrap_or(16);
|
||||
let tracks: &[Sequencer<Tui>] = state.tracks.as_ref();
|
||||
let scenes = state.scenes.as_ref();
|
||||
let offset = 4 + scene_name_max_len(scenes) as u16;
|
||||
|
|
@ -491,48 +492,48 @@ impl<'a> Content for VerticalArranger<'a, Tui> {
|
|||
add(&VerticalArrangerGrid(offset, &rows, &cols))?;
|
||||
add(&VerticalArrangerCursor(state.focused, state.selected, offset, &cols, &rows))?;
|
||||
add(&Split::down(|add|{
|
||||
add(&Push::X(offset, Split::right(move |add|{
|
||||
add(&Split::right(move |add|{
|
||||
for (track, (w, _)) in tracks.iter().zip(cols) {
|
||||
add(&Min::XY(*w as u16, 2, Layers::new(|add|{
|
||||
add(&Layers::new(|add|{
|
||||
add(&Background(COLOR_BG1))?;
|
||||
add(&track.name.read().unwrap().as_str())
|
||||
})))?;
|
||||
}).min_xy(*w as u16, 2))?;
|
||||
}
|
||||
Ok(())
|
||||
})))?;
|
||||
}).push_x(offset))?;
|
||||
add(&Split::down(move |add| {
|
||||
for (scene, (pulses, _)) in scenes.iter().zip(rows) {
|
||||
let height = 1.max((pulses / 96) as u16);
|
||||
let playing = scene.is_playing(tracks);
|
||||
add(&Fixed::Y(height, Split::right(move |add| {
|
||||
add(&Fixed::XY(offset.saturating_sub(1), height, Split::right(|add|{
|
||||
add(&Split::right(move |add| {
|
||||
add(&Split::right(|add|{
|
||||
add(&if playing { "▶ " } else { " " })?;
|
||||
add(&scene.name.read().unwrap().as_str())
|
||||
})))?;
|
||||
}).fixed_xy(offset.saturating_sub(1), height))?;
|
||||
for (track, (w, _x)) in cols.iter().enumerate() {
|
||||
add(&Fixed::XY(*w as u16, height, Layers::new(move |add|{
|
||||
add(&Layers::new(move |add|{
|
||||
let mut color = COLOR_BG0;
|
||||
if let (Some(track), Some(Some(clip))) = (
|
||||
tracks.get(track),
|
||||
scene.clips.get(track),
|
||||
) {
|
||||
if let Some(phrase) = track.phrases.get(*clip) {
|
||||
add(&Push::X(1, format!(
|
||||
add(&format!(
|
||||
"{clip:02} {}",
|
||||
phrase.read().unwrap().name.read().unwrap()
|
||||
).as_str()))?;
|
||||
).as_str().push_x(1))?;
|
||||
color = if track.sequence == Some(*clip) {
|
||||
Nord::PLAYING
|
||||
COLOR_PLAYING
|
||||
} else {
|
||||
COLOR_BG1
|
||||
};
|
||||
}
|
||||
}
|
||||
add(&Background(color))
|
||||
})))?;
|
||||
}).fixed_xy(*w as u16, height))?;
|
||||
}
|
||||
Ok(())
|
||||
})))?;
|
||||
}).fixed_y(height))?;
|
||||
}
|
||||
Ok(())
|
||||
}))
|
||||
|
|
@ -556,7 +557,7 @@ impl<'a> Widget for VerticalArrangerGrid<'a> {
|
|||
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
|
||||
let area = to.area();
|
||||
let Self(offset, rows, cols) = self;
|
||||
let style = Some(Style::default().fg(Nord::SEPARATOR));
|
||||
let style = Some(Style::default().fg(COLOR_SEPARATOR));
|
||||
for (_, x) in cols.iter() {
|
||||
let x = offset + area.x() + *x as u16 - 1;
|
||||
for y in area.y()..area.y2() {
|
||||
|
|
@ -572,7 +573,7 @@ impl<'a> Widget for VerticalArrangerGrid<'a> {
|
|||
if x < to.buffer.area.x && y < to.buffer.area.y {
|
||||
let cell = to.buffer.get_mut(x, y);
|
||||
cell.modifier = Modifier::UNDERLINED;
|
||||
cell.underline_color = Nord::SEPARATOR;
|
||||
cell.underline_color = COLOR_SEPARATOR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -982,7 +983,7 @@ impl Widget for ArrangerRenameModal<Tui> {
|
|||
let area = to.area();
|
||||
let y = area.y() + area.h() / 2;
|
||||
let bg_area = [1, y - 1, area.w() - 2, 3];
|
||||
to.fill_bg(bg_area, Nord::BG0);
|
||||
to.fill_bg(bg_area, COLOR_BG1);
|
||||
Lozenge(Style::default().bold().white().dim()).draw(to.with_rect(bg_area));
|
||||
let label = match self.target {
|
||||
ArrangerFocus::Mix => "Rename project:",
|
||||
|
|
@ -1333,22 +1334,23 @@ impl Sequencer<Tui> {
|
|||
impl Content for Sequencer<Tui> {
|
||||
type Engine = Tui;
|
||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||
Split::right(move |add|{
|
||||
add(&Split::down(|add|{
|
||||
add(&SequenceName(&self))?;
|
||||
add(&SequenceRange)?;
|
||||
add(&SequenceLoopRange)?;
|
||||
add(&SequenceNoteRange)?;
|
||||
Ok(())
|
||||
}))?;
|
||||
add(&Layers::new(|add|{
|
||||
add(&SequenceKeys(&self))?;
|
||||
add(&self.phrase.as_ref().map(|phrase|SequenceTimer(&self, phrase.clone())))?;
|
||||
add(&SequenceNotes(&self))?;
|
||||
add(&SequenceCursor(&self))?;
|
||||
add(&SequenceZoom(&self))
|
||||
}))
|
||||
})
|
||||
let toolbar = col!(
|
||||
SequenceName(&self),
|
||||
SequenceRange,
|
||||
SequenceLoopRange,
|
||||
SequenceNoteRange,
|
||||
);
|
||||
let content = lay!(
|
||||
SequenceKeys(&self),
|
||||
self.phrase.as_ref().map(|phrase|SequenceTimer(&self, phrase.clone())),
|
||||
SequenceNotes(&self),
|
||||
SequenceCursor(&self),
|
||||
SequenceZoom(&self),
|
||||
);
|
||||
row!(toolbar, content)
|
||||
.min_y(10)
|
||||
.inset_x(1)
|
||||
.background(Color::Red)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1544,13 +1546,7 @@ struct SequenceName<'a>(&'a Sequencer<Tui>);
|
|||
impl<'a> Content for SequenceName<'a> {
|
||||
type Engine = Tui;
|
||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||
Min::XY(10, 4, Split::down(|add|{
|
||||
let name: &str = &*self.0.name.read().unwrap();
|
||||
add(&"Name")?;
|
||||
add(&name)?;
|
||||
//Lozenge(Style::default().fg(Nord::BG2)).draw(to.with_rect(frame))?;
|
||||
Ok(())
|
||||
}))
|
||||
col! { "Name", self.0.name.read().unwrap().as_str(), }.min_xy(10, 4)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1560,14 +1556,8 @@ struct SequenceRange;
|
|||
impl Content for SequenceRange {
|
||||
type Engine = Tui;
|
||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||
Min::XY(10, 6, Split::down(|add|{
|
||||
add(&"Start: ")?;//Some(STYLE_LABEL));
|
||||
add(&" 1.1.1")?;//Some(STYLE_VALUE));
|
||||
add(&"End: ")?;//Some(STYLE_LABEL));
|
||||
add(&" 2.1.1")?;//Some(STYLE_VALUE));
|
||||
//Lozenge(Style::default().fg(Nord::BG2)).draw(to.with_rect(frame))?;
|
||||
Ok(())
|
||||
}))
|
||||
col! { "Start: ", " 1.1.1"
|
||||
, "End: ", " 2.1.1", }.min_xy(10, 6)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1577,15 +1567,9 @@ struct SequenceLoopRange;
|
|||
impl Content for SequenceLoopRange {
|
||||
type Engine = Tui;
|
||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||
Min::XY(10, 7, Split::down(|add|{
|
||||
//Lozenge(Style::default().fg(Nord::BG2)).draw(to.with_rect(range))?;
|
||||
add(&"Loop [ ]")?;//Some(STYLE_LABEL));
|
||||
add(&"From: ")?;//Some(STYLE_LABEL));
|
||||
add(&" 1.1.1")?;//Some(STYLE_VALUE));
|
||||
add(&"Length: ")?;//Some(STYLE_LABEL));
|
||||
add(&" 1.0.0")?;//Some(STYLE_VALUE));
|
||||
Ok(())
|
||||
}))
|
||||
col! { "Loop [ ]"
|
||||
, "From: ", " 1.1.1"
|
||||
, "Length: ", " 1.0.0", }.min_xy(10, 7)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1595,7 +1579,7 @@ struct SequenceNoteRange;
|
|||
impl Content for SequenceNoteRange {
|
||||
type Engine = Tui;
|
||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||
Min::XY(10, 9, Split::down(|add|{
|
||||
Split::down(|add|{
|
||||
//Lozenge(Style::default().fg(Nord::BG2)).draw(to.with_rect(range))?;
|
||||
add(&"Notes: ")?;//Some(STYLE_LABEL));
|
||||
add(&"C#0-C#9 ")?;//Some(STYLE_VALUE));
|
||||
|
|
@ -1605,7 +1589,7 @@ impl Content for SequenceNoteRange {
|
|||
add(&"[ Inv ]")?;//Some(STYLE_LABEL));
|
||||
add(&"[ Dup ]")?;//Some(STYLE_LABEL));
|
||||
Ok(())
|
||||
}))
|
||||
}).min_xy(10, 9)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2202,7 +2186,7 @@ impl Content for TransportPlayPauseButton<Tui> {
|
|||
type Engine = Tui;
|
||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||
Layers::new(|add|{
|
||||
add(&Push::X(1, Min::XY(11, 2, Styled(match self.value {
|
||||
add(&Styled(match self.value {
|
||||
Some(TransportState::Stopped) => Some(GRAY_DIM.bold()),
|
||||
Some(TransportState::Starting) => Some(GRAY_NOT_DIM_BOLD),
|
||||
Some(TransportState::Rolling) => Some(WHITE_NOT_DIM_BOLD),
|
||||
|
|
@ -2212,7 +2196,7 @@ impl Content for TransportPlayPauseButton<Tui> {
|
|||
Some(TransportState::Starting) => "READY ...",
|
||||
Some(TransportState::Stopped) => "⏹ STOPPED",
|
||||
_ => unreachable!(),
|
||||
}))))?;
|
||||
}).min_xy(11, 2).push_x(1))?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue