mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 04:06:45 +01:00
separate render/content macros; add has_jack
This commit is contained in:
parent
08184f9906
commit
e62e36d558
19 changed files with 183 additions and 226 deletions
|
|
@ -1,4 +1,8 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
/// Turns an EDN item sequence into a command enum variant.
|
||||||
|
pub trait EdnCommand<C>: Command<C> {
|
||||||
|
fn from_edn <'a> (state: &C, head: &EdnItem<&str>, tail: &'a [EdnItem<String>]) -> Self;
|
||||||
|
}
|
||||||
/** Implement `EdnCommand` for given `State` and `Command` */
|
/** Implement `EdnCommand` for given `State` and `Command` */
|
||||||
#[macro_export] macro_rules! edn_command {
|
#[macro_export] macro_rules! edn_command {
|
||||||
($Command:ty : |$state:ident:$State:ty| { $((
|
($Command:ty : |$state:ident:$State:ty| { $((
|
||||||
|
|
@ -47,16 +51,3 @@ use crate::*;
|
||||||
let $arg: $type = EdnProvide::<$type>::get_or_fail($state, $arg);
|
let $arg: $type = EdnProvide::<$type>::get_or_fail($state, $arg);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/// Turns an EDN symbol sequence into a command enum variant.
|
|
||||||
pub trait EdnCommand<C>: Command<C> {
|
|
||||||
fn from_edn <'a> (state: &C, head: &EdnItem<&str>, tail: &'a [EdnItem<String>]) -> Self;
|
|
||||||
fn get_isize (state: &C, item: &EdnItem<&str>) -> Option<isize> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
fn get_usize (state: &C, item: &EdnItem<&str>) -> Option<usize> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
fn get_bool (state: &C, item: &EdnItem<&str>) -> Option<usize> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use EdnItem::*;
|
|
||||||
|
|
||||||
pub trait EdnInput: Input {
|
pub trait EdnInput: Input {
|
||||||
fn matches (&self, token: &str) -> bool;
|
fn matches (&self, token: &str) -> bool;
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// Implement [TryFrom<&Arc<RwLock<JackConnection>>>]: create app state from wrapped JACK handle.
|
|
||||||
#[macro_export] macro_rules! from_jack {
|
|
||||||
(|$jack:ident|$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)? $cb:expr) => {
|
|
||||||
impl $(<$($L),*$($T $(: $U)?),*>)? TryFrom<&Arc<RwLock<JackConnection>>> for $Struct $(<$($L),*$($T),*>)? {
|
|
||||||
type Error = Box<dyn std::error::Error>;
|
|
||||||
fn try_from ($jack: &Arc<RwLock<JackConnection>>) -> Usually<Self> {
|
|
||||||
Ok($cb)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -3,3 +3,22 @@ use crate::*;
|
||||||
pub trait HasJack {
|
pub trait HasJack {
|
||||||
fn jack (&self) -> &Arc<RwLock<JackConnection>>;
|
fn jack (&self) -> &Arc<RwLock<JackConnection>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implement [HasJack].
|
||||||
|
#[macro_export] macro_rules! has_jack {
|
||||||
|
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||||
|
impl $(<$($L),*$($T $(: $U)?),*>)? HasJack for $Struct $(<$($L),*$($T),*>)? {
|
||||||
|
fn jack (&$self) -> &Arc<RwLock<JackConnection>> { $cb }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implement [TryFrom<&Arc<RwLock<JackConnection>>>]: create app state from wrapped JACK handle.
|
||||||
|
#[macro_export] macro_rules! from_jack {
|
||||||
|
(|$jack:ident|$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)? $cb:expr) => {
|
||||||
|
impl $(<$($L),*$($T $(: $U)?),*>)? TryFrom<&Arc<RwLock<JackConnection>>> for $Struct $(<$($L),*$($T),*>)? {
|
||||||
|
type Error = Box<dyn std::error::Error>;
|
||||||
|
fn try_from ($jack: &Arc<RwLock<JackConnection>>) -> Usually<Self> { Ok($cb) }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ pub(crate) use ::jack::{
|
||||||
Unowned, MidiIn, MidiOut, AudioIn, AudioOut,
|
Unowned, MidiIn, MidiOut, AudioIn, AudioOut,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod from_jack; pub use self::from_jack::*;
|
|
||||||
mod has_jack; pub use self::has_jack::*;
|
mod has_jack; pub use self::has_jack::*;
|
||||||
mod jack_audio; pub use self::jack_audio::*;
|
mod jack_audio; pub use self::jack_audio::*;
|
||||||
mod jack_connect; pub use self::jack_connect::*;
|
mod jack_connect; pub use self::jack_connect::*;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
has_size!(<TuiOut>|self: MidiEditor|&self.size);
|
has_size!(<TuiOut>|self: MidiEditor|&self.size);
|
||||||
render!(TuiOut: (self: MidiEditor) => {
|
content!(TuiOut: |self: MidiEditor| {
|
||||||
self.autoscroll();
|
self.autoscroll();
|
||||||
//self.autozoom();
|
//self.autozoom();
|
||||||
self.size.of(&self.mode)
|
self.size.of(&self.mode)
|
||||||
|
|
@ -17,7 +17,6 @@ impl MidiEditor {
|
||||||
FieldV(color, "Loop", looped.to_string())
|
FieldV(color, "Loop", looped.to_string())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn edit_status (&self) -> impl Content<TuiOut> + '_ {
|
pub fn edit_status (&self) -> impl Content<TuiOut> + '_ {
|
||||||
let (color, length) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) {
|
let (color, length) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) {
|
||||||
(clip.color, clip.length)
|
(clip.color, clip.length)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
pub struct PoolView<'a>(pub bool, pub &'a MidiPool);
|
pub struct PoolView<'a>(pub bool, pub &'a MidiPool);
|
||||||
render!(TuiOut: (self: PoolView<'a>) => {
|
content!(TuiOut: |self: PoolView<'a>| {
|
||||||
let Self(compact, model) = self;
|
let Self(compact, model) = self;
|
||||||
let MidiPool { clips, mode, .. } = self.1;
|
let MidiPool { clips, mode, .. } = self.1;
|
||||||
let color = self.1.clip().map(|c|c.read().unwrap().color).unwrap_or_else(||TuiTheme::g(32).into());
|
let color = self.1.clip().map(|c|c.read().unwrap().color).unwrap_or_else(||TuiTheme::g(32).into());
|
||||||
|
|
@ -24,7 +24,7 @@ render!(TuiOut: (self: PoolView<'a>) => {
|
||||||
))))
|
))))
|
||||||
})))))
|
})))))
|
||||||
});
|
});
|
||||||
render!(TuiOut: (self: ClipLength) => {
|
content!(TuiOut: |self: ClipLength| {
|
||||||
let bars = ||self.bars_string();
|
let bars = ||self.bars_string();
|
||||||
let beats = ||self.beats_string();
|
let beats = ||self.beats_string();
|
||||||
let ticks = ||self.ticks_string();
|
let ticks = ||self.ticks_string();
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,16 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
pub struct ClipSelected {
|
pub struct ClipSelected {
|
||||||
pub(crate) title: &'static str,
|
pub(crate) title: &'static str,
|
||||||
pub(crate) name: Arc<str>,
|
pub(crate) name: Arc<str>,
|
||||||
pub(crate) color: ItemPalette,
|
pub(crate) color: ItemPalette,
|
||||||
pub(crate) time: Arc<str>,
|
pub(crate) time: Arc<str>,
|
||||||
}
|
}
|
||||||
|
content!(TuiOut: |self: ClipSelected|FieldV(
|
||||||
render!(TuiOut: (self: ClipSelected) =>
|
self.color,
|
||||||
FieldV(self.color, self.title, format!("{} {}", self.time, self.name)));
|
self.title,
|
||||||
|
format!("{} {}", self.time, self.name)
|
||||||
|
));
|
||||||
impl ClipSelected {
|
impl ClipSelected {
|
||||||
|
|
||||||
/// Shows currently playing clip with beats elapsed
|
/// Shows currently playing clip with beats elapsed
|
||||||
pub fn play_clip <T: HasPlayClip + HasClock> (state: &T) -> Self {
|
pub fn play_clip <T: HasPlayClip + HasClock> (state: &T) -> Self {
|
||||||
let (name, color) = if let Some((_, Some(clip))) = state.play_clip() {
|
let (name, color) = if let Some((_, Some(clip))) = state.play_clip() {
|
||||||
|
|
@ -31,7 +30,6 @@ impl ClipSelected {
|
||||||
.unwrap_or_else(||String::from(" ")).into()
|
.unwrap_or_else(||String::from(" ")).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shows next clip with beats remaining until switchover
|
/// Shows next clip with beats remaining until switchover
|
||||||
pub fn next_clip <T: HasPlayClip> (state: &T) -> Self {
|
pub fn next_clip <T: HasPlayClip> (state: &T) -> Self {
|
||||||
let mut time: Arc<str> = String::from("--.-.--").into();
|
let mut time: Arc<str> = String::from("--.-.--").into();
|
||||||
|
|
@ -69,5 +67,4 @@ impl ClipSelected {
|
||||||
};
|
};
|
||||||
Self { title: "Next", time, name, color, }
|
Self { title: "Next", time, name, color, }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ impl PianoHorizontal {
|
||||||
pub(crate) fn note_y_iter (note_lo: usize, note_hi: usize, y0: u16) -> impl Iterator<Item=(usize, u16, usize)> {
|
pub(crate) fn note_y_iter (note_lo: usize, note_hi: usize, y0: u16) -> impl Iterator<Item=(usize, u16, usize)> {
|
||||||
(note_lo..=note_hi).rev().enumerate().map(move|(y, n)|(y, y0 + y as u16, n))
|
(note_lo..=note_hi).rev().enumerate().map(move|(y, n)|(y, y0 + y as u16, n))
|
||||||
}
|
}
|
||||||
render!(TuiOut: (self: PianoHorizontal) => Tui::bg(TuiTheme::g(40), Bsp::s(
|
content!(TuiOut:|self: PianoHorizontal| Tui::bg(TuiTheme::g(40), Bsp::s(
|
||||||
Bsp::e(
|
Bsp::e(
|
||||||
Fixed::x(5, format!("{}x{}", self.size.w(), self.size.h())),
|
Fixed::x(5, format!("{}x{}", self.size.w(), self.size.h())),
|
||||||
self.timeline()
|
self.timeline()
|
||||||
|
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
/// Build a [Render]able out of other [Render]ables,
|
|
||||||
/// then apply optional custom render/layout on top.
|
|
||||||
pub trait Content<E: Output>: Send + Sync + Sized {
|
|
||||||
fn content (&self) -> impl Render<E> { () }
|
|
||||||
fn layout (&self, area: E::Area) -> E::Area { self.content().layout(area) }
|
|
||||||
fn render (&self, output: &mut E) { self.content().render(output) }
|
|
||||||
}
|
|
||||||
impl<E: Output, C: Content<E>> Content<E> for &C {
|
|
||||||
fn content (&self) -> impl Render<E> { (*self).content() }
|
|
||||||
fn layout (&self, area: E::Area) -> E::Area { (*self).layout(area) }
|
|
||||||
fn render (&self, output: &mut E) { (*self).render(output) }
|
|
||||||
}
|
|
||||||
/// The platonic ideal unit of [Content]: total emptiness at dead center.
|
|
||||||
impl<E: Output> Content<E> for () {
|
|
||||||
fn layout (&self, area: E::Area) -> E::Area { area.center().to_area_pos().into() }
|
|
||||||
fn render (&self, _: &mut E) {}
|
|
||||||
}
|
|
||||||
impl<E: Output, T: Content<E>> Content<E> for Option<T> {
|
|
||||||
fn content (&self) -> impl Render<E> {
|
|
||||||
self.as_ref()
|
|
||||||
}
|
|
||||||
fn layout (&self, area: E::Area) -> E::Area {
|
|
||||||
self.as_ref()
|
|
||||||
.map(|content|content.layout(area))
|
|
||||||
.unwrap_or([0.into(), 0.into(), 0.into(), 0.into(),].into())
|
|
||||||
}
|
|
||||||
fn render (&self, output: &mut E) {
|
|
||||||
self.as_ref()
|
|
||||||
.map(|content|content.render(output));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//impl<E: Output, T: Content<E>, E: Content<E>> Content<E> for Option<T> {
|
|
||||||
//fn content (&self) -> impl Render<E> {
|
|
||||||
//self.as_ref()
|
|
||||||
//}
|
|
||||||
//fn layout (&self, area: E::Area) -> E::Area {
|
|
||||||
//self.as_ref()
|
|
||||||
//.map(|content|content.layout(area))
|
|
||||||
//.unwrap_or([0.into(), 0.into(), 0.into(), 0.into(),].into())
|
|
||||||
//}
|
|
||||||
//fn render (&self, output: &mut E) {
|
|
||||||
//self.as_ref()
|
|
||||||
//.map(|content|content.render(output));
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
|
|
@ -94,7 +94,6 @@ impl<E: Output, A: Content<E>, B: Content<E>> BspAreas<E, A, B> for Bsp<E, A, B>
|
||||||
impl<'a, E: Output + 'a, T: EdnViewData<'a, E>> TryFromEdn<'a, T> for Bsp<E, RenderBox<'a, E>, RenderBox<'a, E>> {
|
impl<'a, E: Output + 'a, T: EdnViewData<'a, E>> TryFromEdn<'a, T> for Bsp<E, RenderBox<'a, E>, RenderBox<'a, E>> {
|
||||||
fn try_from_edn (s: &'a T, head: &EdnItem<&str>, tail: &'a [EdnItem<&str>]) -> Option<Self> {
|
fn try_from_edn (s: &'a T, head: &EdnItem<&str>, tail: &'a [EdnItem<&str>]) -> Option<Self> {
|
||||||
use EdnItem::*;
|
use EdnItem::*;
|
||||||
panic!("({head} {} {})", tail[0], tail[1]);
|
|
||||||
Some(match (head, tail) {
|
Some(match (head, tail) {
|
||||||
(Key("bsp/n"), [a, b]) => Self::n(s.get_content(a).expect("no a"), s.get(b).expect("no b")),
|
(Key("bsp/n"), [a, b]) => Self::n(s.get_content(a).expect("no a"), s.get(b).expect("no b")),
|
||||||
(Key("bsp/s"), [a, b]) => Self::s(s.get_content(a).expect("no a"), s.get(b).expect("no b")),
|
(Key("bsp/s"), [a, b]) => Self::s(s.get_content(a).expect("no a"), s.get(b).expect("no b")),
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,8 @@ mod coordinate; pub use self::coordinate::*;
|
||||||
mod size; pub use self::size::*;
|
mod size; pub use self::size::*;
|
||||||
mod area; pub use self::area::*;
|
mod area; pub use self::area::*;
|
||||||
|
|
||||||
mod output; pub use self::output::*;
|
mod output; pub use self::output::*;
|
||||||
mod content; pub use self::content::*;
|
mod thunk; pub use self::thunk::*;
|
||||||
mod render; pub use self::render::*;
|
|
||||||
mod thunk; pub use self::thunk::*;
|
|
||||||
|
|
||||||
mod when; pub use self::when::*;
|
mod when; pub use self::when::*;
|
||||||
mod either; pub use self::either::*;
|
mod either; pub use self::either::*;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
use std::ops::Deref;
|
||||||
/// Render target
|
/// Render target.
|
||||||
pub trait Output: Send + Sync + Sized {
|
pub trait Output: Send + Sync + Sized {
|
||||||
/// Unit of length
|
/// Unit of length
|
||||||
type Unit: Coordinate;
|
type Unit: Coordinate;
|
||||||
|
|
@ -20,3 +20,113 @@ pub trait Output: Send + Sync + Sized {
|
||||||
#[inline] fn h (&self) -> Self::Unit { self.area().h() }
|
#[inline] fn h (&self) -> Self::Unit { self.area().h() }
|
||||||
#[inline] fn wh (&self) -> Self::Size { self.area().wh().into() }
|
#[inline] fn wh (&self) -> Self::Size { self.area().wh().into() }
|
||||||
}
|
}
|
||||||
|
/// Renderable with dynamic dispatch.
|
||||||
|
pub trait Render<E: Output>: Send + Sync {
|
||||||
|
/// Compute layout.
|
||||||
|
fn layout (&self, area: E::Area) -> E::Area;
|
||||||
|
/// Write data to display.
|
||||||
|
fn render (&self, output: &mut E);
|
||||||
|
/// Perform type erasure, turning `self` into an opaque [RenderBox].
|
||||||
|
fn boxed <'a> (self) -> RenderBox<'a, E> where Self: Sized + 'a {
|
||||||
|
Box::new(self) as RenderBox<'a, E>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Most importantly, every [Content] is also a [Render].
|
||||||
|
///
|
||||||
|
/// However, the converse does not hold true.
|
||||||
|
/// Instead, the [Content::content] method returns an
|
||||||
|
/// opaque [Render] pointer.
|
||||||
|
impl<E: Output, C: Content<E>> Render<E> for C {
|
||||||
|
fn layout (&self, area: E::Area) -> E::Area { Content::layout(self, area) }
|
||||||
|
fn render (&self, output: &mut E) { Content::render(self, output) }
|
||||||
|
}
|
||||||
|
/// Opaque pointer to a renderable living on the heap.
|
||||||
|
///
|
||||||
|
/// Return this from [Content::content] to use dynamic dispatch.
|
||||||
|
pub type RenderBox<'a, E> = Box<RenderDyn<'a, E>>;
|
||||||
|
/// You can render from a box.
|
||||||
|
impl<'a, E: Output> Content<E> for RenderBox<'a, E> {
|
||||||
|
fn content (&self) -> impl Render<E> { self.deref() }
|
||||||
|
//fn boxed <'b> (self) -> RenderBox<'b, E> where Self: Sized + 'b { self }
|
||||||
|
}
|
||||||
|
/// Opaque pointer to a renderable.
|
||||||
|
pub type RenderDyn<'a, E> = dyn Render<E> + 'a;
|
||||||
|
/// You can render from an opaque pointer.
|
||||||
|
impl<'a, E: Output> Content<E> for &RenderDyn<'a, E> where Self: Sized {
|
||||||
|
fn content (&self) -> impl Render<E> { self.deref() }
|
||||||
|
fn layout (&self, area: E::Area) -> E::Area { Render::layout(self.deref(), area) }
|
||||||
|
fn render (&self, output: &mut E) { Render::render(self.deref(), output) }
|
||||||
|
}
|
||||||
|
/// Composable renderable with static dispatch.
|
||||||
|
pub trait Content<E: Output>: Send + Sync + Sized {
|
||||||
|
/// Return a [Render]able of a specific type.
|
||||||
|
fn content (&self) -> impl Render<E> { () }
|
||||||
|
/// Perform layout. By default, delegates to [Self::content].
|
||||||
|
fn layout (&self, area: E::Area) -> E::Area { self.content().layout(area) }
|
||||||
|
/// Draw to output. By default, delegates to [Self::content].
|
||||||
|
fn render (&self, output: &mut E) { self.content().render(output) }
|
||||||
|
}
|
||||||
|
/// Every pointer to [Content] is a [Content].
|
||||||
|
impl<E: Output, C: Content<E>> Content<E> for &C {
|
||||||
|
fn content (&self) -> impl Render<E> { (*self).content() }
|
||||||
|
fn layout (&self, area: E::Area) -> E::Area { (*self).layout(area) }
|
||||||
|
fn render (&self, output: &mut E) { (*self).render(output) }
|
||||||
|
}
|
||||||
|
/// The platonic ideal unit of [Content]: total emptiness at dead center (e=1vg^sqrt(-1))
|
||||||
|
impl<E: Output> Content<E> for () {
|
||||||
|
fn layout (&self, area: E::Area) -> E::Area { area.center().to_area_pos().into() }
|
||||||
|
fn render (&self, _: &mut E) {}
|
||||||
|
}
|
||||||
|
impl<E: Output, T: Content<E>> Content<E> for Option<T> {
|
||||||
|
fn content (&self) -> impl Render<E> {
|
||||||
|
self.as_ref()
|
||||||
|
}
|
||||||
|
fn layout (&self, area: E::Area) -> E::Area {
|
||||||
|
self.as_ref()
|
||||||
|
.map(|content|content.layout(area))
|
||||||
|
.unwrap_or([0.into(), 0.into(), 0.into(), 0.into(),].into())
|
||||||
|
}
|
||||||
|
fn render (&self, output: &mut E) {
|
||||||
|
self.as_ref()
|
||||||
|
.map(|content|content.render(output));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Implement [Content] with composable content for a struct.
|
||||||
|
#[macro_export] macro_rules! content {
|
||||||
|
// Implement for all [Output]s.
|
||||||
|
(|$self:ident:$Struct:ty| $content:expr) => {
|
||||||
|
impl<E: Output> Content<E> for $Struct {
|
||||||
|
fn content (&$self) -> impl Render<E> { Some($content) }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Implement for specific [Output].
|
||||||
|
($Output:ty:|
|
||||||
|
$self:ident:
|
||||||
|
$Struct:ident$(<$($($L:lifetime)? $($T:ident)? $(:$Trait:path)?),+>)?
|
||||||
|
|$content:expr) => {
|
||||||
|
impl $(<$($($L)? $($T)? $(:$Trait)?),+>)? Content<$Output>
|
||||||
|
for $Struct $(<$($($L)? $($T)?),+>)? {
|
||||||
|
fn content (&$self) -> impl Render<$Output> { $content }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/// Implement [Content] with custom rendering for a struct.
|
||||||
|
#[macro_export] macro_rules! render {
|
||||||
|
(|$self:ident:$Struct:ident $(<
|
||||||
|
$($L:lifetime),* $($T:ident $(:$Trait:path)?),*
|
||||||
|
>)?, $to:ident | $render:expr) => {
|
||||||
|
impl <$($($L),*)? E: Output, $($T$(:$Trait)?),*> Content<E>
|
||||||
|
for $Struct $(<$($L),* $($T),*>>)? {
|
||||||
|
fn render (&$self, $to: &mut E) { $render }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($Output:ty:|
|
||||||
|
$self:ident:
|
||||||
|
$Struct:ident $(<$($($L:lifetime)? $($T:ident)? $(:$Trait:path)?),+>)?, $to:ident
|
||||||
|
|$render:expr) => {
|
||||||
|
impl $(<$($($L)? $($T)? $(:$Trait)?),+>)? Content<$Output>
|
||||||
|
for $Struct $(<$($($L)? $($T)?),+>)? {
|
||||||
|
fn render (&$self, $to: &mut $Output) { $render }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
use std::ops::Deref;
|
|
||||||
|
|
||||||
/// Custom layout and rendering.
|
|
||||||
pub trait Render<E: Output>: Send + Sync {
|
|
||||||
fn layout (&self, area: E::Area) -> E::Area;
|
|
||||||
fn render (&self, output: &mut E);
|
|
||||||
fn boxed <'a> (self) -> RenderBox<'a, E> where Self: Sized + 'a {
|
|
||||||
Box::new(self) as RenderBox<'a, E>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type RenderDyn<'a, Output> = dyn Render<Output> + 'a;
|
|
||||||
impl<'a, E: Output> Content<E> for &RenderDyn<'a, E> where Self: Sized {
|
|
||||||
fn content (&self) -> impl Render<E> { self.deref() }
|
|
||||||
fn layout (&self, area: E::Area) -> E::Area { Render::layout(self.deref(), area) }
|
|
||||||
fn render (&self, output: &mut E) { Render::render(self.deref(), output) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type RenderBox<'a, E: Output> = Box<RenderDyn<'a, E>>;
|
|
||||||
impl<'a, E: Output> Content<E> for RenderBox<'a, E> {
|
|
||||||
fn content (&self) -> impl Render<E> { self.deref() }
|
|
||||||
//fn boxed <'b> (self) -> RenderBox<'b, E> where Self: Sized + 'b { self }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: Output, C: Content<E>> Render<E> for C {
|
|
||||||
fn layout (&self, area: E::Area) -> E::Area { Content::layout(self, area) }
|
|
||||||
fn render (&self, output: &mut E) { Content::render(self, output) }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export] macro_rules! render {
|
|
||||||
(($self:ident:$Struct:ty) => $content:expr) => {
|
|
||||||
impl <E: Output> Content<E> for $Struct {
|
|
||||||
fn content (&$self) -> impl Render<E> { Some($content) }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
(|$self:ident:$Struct:ident $(<
|
|
||||||
$($L:lifetime),* $($T:ident $(:$Trait:path)?),*
|
|
||||||
>)?, $to:ident | $render:expr) => {
|
|
||||||
impl <$($($L),*)? E: Output, $($T$(:$Trait)?),*> Content<E>
|
|
||||||
for $Struct $(<$($L),* $($T),*>>)? {
|
|
||||||
fn render (&$self, $to: &mut E) { $render }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
($Output:ty:
|
|
||||||
($self:ident:$Struct:ident $(<$(
|
|
||||||
$($L:lifetime)? $($T:ident)? $(:$Trait:path)?
|
|
||||||
),+>)?) => $content:expr
|
|
||||||
) => {
|
|
||||||
impl $(<$($($L)? $($T)? $(:$Trait)?),+>)? Content<$Output>
|
|
||||||
for $Struct $(<$($($L)? $($T)?),+>)? {
|
|
||||||
fn content (&$self) -> impl Render<$Output> { $content }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
($Output:ty:
|
|
||||||
|$self:ident : $Struct:ident $(<$(
|
|
||||||
$($L:lifetime)? $($T:ident)? $(:$Trait:path)?
|
|
||||||
),+>)?, $to:ident| $render:expr
|
|
||||||
) => {
|
|
||||||
impl $(<$($($L)? $($T)? $(:$Trait)?),+>)? Content<$Output>
|
|
||||||
for $Struct $(<$($($L)? $($T)?),+>)? {
|
|
||||||
fn render (&$self, $to: &mut $Output) { $render }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -27,7 +27,7 @@ impl SamplerTui {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
render!(TuiOut: (self: SamplerTui) => {
|
content!(TuiOut: |self: SamplerTui| {
|
||||||
let keys_width = 5;
|
let keys_width = 5;
|
||||||
let keys = move||"";//SamplerKeys(self);
|
let keys = move||"";//SamplerKeys(self);
|
||||||
let fg = self.color.base.rgb;
|
let fg = self.color.base.rgb;
|
||||||
|
|
@ -54,8 +54,8 @@ struct SamplesTui {
|
||||||
note_pt: usize,
|
note_pt: usize,
|
||||||
height: usize,
|
height: usize,
|
||||||
}
|
}
|
||||||
render!(TuiOut: |self: SamplesTui, render|{
|
render!(TuiOut: |self: SamplesTui, to| {
|
||||||
let x = render.area.x();
|
let x = to.area.x();
|
||||||
let bg_base = self.color.darkest.rgb;
|
let bg_base = self.color.darkest.rgb;
|
||||||
let bg_selected = self.color.darker.rgb;
|
let bg_selected = self.color.darker.rgb;
|
||||||
let style_empty = Style::default().fg(self.color.base.rgb);
|
let style_empty = Style::default().fg(self.color.base.rgb);
|
||||||
|
|
@ -64,7 +64,7 @@ render!(TuiOut: |self: SamplesTui, render|{
|
||||||
let note = self.note_hi - y as usize;
|
let note = self.note_hi - y as usize;
|
||||||
let bg = if note == self.note_pt { bg_selected } else { bg_base };
|
let bg = if note == self.note_pt { bg_selected } else { bg_base };
|
||||||
let style = Some(style_empty.bg(bg));
|
let style = Some(style_empty.bg(bg));
|
||||||
render.blit(&" (no sample) ", x, render.area.y() + y as u16, style);
|
to.blit(&" (no sample) ", x, to.area.y() + y as u16, style);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
impl NoteRange for SamplerTui {
|
impl NoteRange for SamplerTui {
|
||||||
|
|
|
||||||
|
|
@ -56,9 +56,7 @@ has_size!(<TuiOut>|self: App|&self.size);
|
||||||
has_clock!(|self: App|&self.clock);
|
has_clock!(|self: App|&self.clock);
|
||||||
has_clips!(|self: App|self.pool.as_ref().expect("no clip pool").clips);
|
has_clips!(|self: App|self.pool.as_ref().expect("no clip pool").clips);
|
||||||
//has_editor!(|self: App|self.editor.as_ref().expect("no editor"));
|
//has_editor!(|self: App|self.editor.as_ref().expect("no editor"));
|
||||||
impl HasJack for App {
|
has_jack!(|self: App|&self.jack);
|
||||||
fn jack (&self) -> &Arc<RwLock<JackConnection>> { &self.jack }
|
|
||||||
}
|
|
||||||
impl HasSampler for App {
|
impl HasSampler for App {
|
||||||
fn sampler (&self) -> &Option<Sampler> { &self.sampler }
|
fn sampler (&self) -> &Option<Sampler> { &self.sampler }
|
||||||
fn sampler_mut (&mut self) -> &mut Option<Sampler> { &mut self.sampler }
|
fn sampler_mut (&mut self) -> &mut Option<Sampler> { &mut self.sampler }
|
||||||
|
|
@ -106,10 +104,10 @@ edn_provide!('a: Box<dyn Render<TuiOut> + 'a>: |self: App|{
|
||||||
":scenes" => self.row(self.w(), self.size.h().saturating_sub(9) as u16,
|
":scenes" => self.row(self.w(), self.size.h().saturating_sub(9) as u16,
|
||||||
self.scene_header(), self.scene_cells(self.is_editing())).boxed(),
|
self.scene_header(), self.scene_cells(self.is_editing())).boxed(),
|
||||||
});
|
});
|
||||||
render!(TuiOut: (self: App) => self.size.of(EdnView::from_source(self, self.edn.as_ref())));
|
content!(TuiOut: |self: App| self.size.of(EdnView::from_source(self, self.edn.as_ref())));
|
||||||
handle!(TuiIn: |self: App, input| Ok(None));
|
handle!(TuiIn: |self: App, input| Ok(None));
|
||||||
#[derive(Debug, Default)] struct Meter<'a>(pub &'a str, pub f32);
|
#[derive(Debug, Default)] struct Meter<'a>(pub &'a str, pub f32);
|
||||||
render!(TuiOut: (self: Meter<'a>) => col!(
|
content!(TuiOut: |self: Meter<'a>| col!(
|
||||||
Field(TuiTheme::g(128).into(), self.0, format!("{:>+9.3}", self.1)),
|
Field(TuiTheme::g(128).into(), self.0, format!("{:>+9.3}", self.1)),
|
||||||
Fixed::xy(if self.1 >= 0.0 { 13 }
|
Fixed::xy(if self.1 >= 0.0 { 13 }
|
||||||
else if self.1 >= -1.0 { 12 }
|
else if self.1 >= -1.0 { 12 }
|
||||||
|
|
@ -128,7 +126,7 @@ render!(TuiOut: (self: Meter<'a>) => col!(
|
||||||
else if self.1 >= -3.0 { Color::Yellow }
|
else if self.1 >= -3.0 { Color::Yellow }
|
||||||
else { Color::Green }, ()))));
|
else { Color::Green }, ()))));
|
||||||
#[derive(Debug, Default)] struct Meters<'a>(pub &'a[f32]);
|
#[derive(Debug, Default)] struct Meters<'a>(pub &'a[f32]);
|
||||||
render!(TuiOut: (self: Meters<'a>) => col!(
|
content!(TuiOut: |self: Meters<'a>| col!(
|
||||||
format!("L/{:>+9.3}", self.0[0]),
|
format!("L/{:>+9.3}", self.0[0]),
|
||||||
format!("R/{:>+9.3}", self.0[1])
|
format!("R/{:>+9.3}", self.0[1])
|
||||||
));
|
));
|
||||||
|
|
@ -182,7 +180,7 @@ pub trait HasSelection {
|
||||||
fn selected (&self) -> &Selection;
|
fn selected (&self) -> &Selection;
|
||||||
fn selected_mut (&mut self) -> &mut Selection;
|
fn selected_mut (&mut self) -> &mut Selection;
|
||||||
}
|
}
|
||||||
#[derive(Debug, Default)] struct Track {
|
#[derive(Debug, Default)] pub struct Track {
|
||||||
/// Name of track
|
/// Name of track
|
||||||
name: Arc<str>,
|
name: Arc<str>,
|
||||||
/// Preferred width of track column
|
/// Preferred width of track column
|
||||||
|
|
@ -332,7 +330,7 @@ pub trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync
|
||||||
trait Device: Send + Sync + std::fmt::Debug {}
|
trait Device: Send + Sync + std::fmt::Debug {}
|
||||||
impl Device for Sampler {}
|
impl Device for Sampler {}
|
||||||
impl Device for Plugin {}
|
impl Device for Plugin {}
|
||||||
#[derive(Debug, Default)] struct Scene {
|
#[derive(Debug, Default)] pub struct Scene {
|
||||||
/// Name of scene
|
/// Name of scene
|
||||||
name: Arc<str>,
|
name: Arc<str>,
|
||||||
/// Clips in scene, one per track
|
/// Clips in scene, one per track
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,7 @@ use crate::*;
|
||||||
use KeyCode::*;
|
use KeyCode::*;
|
||||||
use ClockCommand::{Play, Pause};
|
use ClockCommand::{Play, Pause};
|
||||||
/// Transport clock app.
|
/// Transport clock app.
|
||||||
pub struct ClockTui {
|
pub struct ClockTui { pub jack: Arc<RwLock<JackConnection>>, pub clock: Clock, }
|
||||||
pub jack: Arc<RwLock<JackConnection>>,
|
|
||||||
pub clock: Clock,
|
|
||||||
}
|
|
||||||
handle!(TuiIn: |self: ClockTui, input|ClockCommand::execute_with_state(self, input.event()));
|
handle!(TuiIn: |self: ClockTui, input|ClockCommand::execute_with_state(self, input.event()));
|
||||||
keymap!(TRANSPORT_KEYS = |state: ClockTui, input: Event| ClockCommand {
|
keymap!(TRANSPORT_KEYS = |state: ClockTui, input: Event| ClockCommand {
|
||||||
key(Char(' ')) =>
|
key(Char(' ')) =>
|
||||||
|
|
@ -15,29 +12,20 @@ keymap!(TRANSPORT_KEYS = |state: ClockTui, input: Event| ClockCommand {
|
||||||
});
|
});
|
||||||
has_clock!(|self: ClockTui|&self.clock);
|
has_clock!(|self: ClockTui|&self.clock);
|
||||||
audio!(|self: ClockTui, client, scope|ClockAudio(self).process(client, scope));
|
audio!(|self: ClockTui, client, scope|ClockAudio(self).process(client, scope));
|
||||||
render!(TuiOut: (self: ClockTui) => ClockView {
|
content!(TuiOut:|self: ClockTui|ClockView { compact: false, clock: &self.clock });
|
||||||
compact: false,
|
|
||||||
clock: &self.clock
|
|
||||||
});
|
|
||||||
|
|
||||||
pub struct ClockView<'a> { pub compact: bool, pub clock: &'a Clock }
|
pub struct ClockView<'a> { pub compact: bool, pub clock: &'a Clock }
|
||||||
impl<'a> ClockView<'a> {
|
content!(TuiOut: |self: ClockView<'a>| Outer(Style::default().fg(TuiTheme::g(255))).enclose(row!(
|
||||||
pub fn new (compact: bool, clock: &'a Clock) -> Self {
|
|
||||||
Self { compact, clock }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
render!(TuiOut: (self: ClockView<'a>) => Outer(
|
|
||||||
Style::default().fg(TuiTheme::g(255))
|
|
||||||
).enclose(row!(
|
|
||||||
OutputStats::new(self.compact, self.clock),
|
OutputStats::new(self.compact, self.clock),
|
||||||
" ",
|
" ",
|
||||||
PlayPause { compact: false, playing: self.clock.is_rolling() },
|
PlayPause { compact: false, playing: self.clock.is_rolling() },
|
||||||
" ",
|
" ",
|
||||||
BeatStats::new(self.compact, self.clock),
|
BeatStats::new(self.compact, self.clock),
|
||||||
)));
|
)));
|
||||||
|
impl<'a> ClockView<'a> {
|
||||||
|
pub fn new (compact: bool, clock: &'a Clock) -> Self { Self { compact, clock } }
|
||||||
|
}
|
||||||
pub struct PlayPause { pub compact: bool, pub playing: bool }
|
pub struct PlayPause { pub compact: bool, pub playing: bool }
|
||||||
render!(TuiOut: (self: PlayPause) => Tui::bg(
|
content!(TuiOut: |self: PlayPause| Tui::bg(
|
||||||
if self.playing{Color::Rgb(0,128,0)}else{Color::Rgb(128,64,0)},
|
if self.playing{Color::Rgb(0,128,0)}else{Color::Rgb(128,64,0)},
|
||||||
Either::new(self.compact,
|
Either::new(self.compact,
|
||||||
Thunk::new(||Fixed::x(9, Either::new(self.playing,
|
Thunk::new(||Fixed::x(9, Either::new(self.playing,
|
||||||
|
|
@ -46,8 +34,14 @@ render!(TuiOut: (self: PlayPause) => Tui::bg(
|
||||||
Thunk::new(||Fixed::x(5, Either::new(self.playing,
|
Thunk::new(||Fixed::x(5, Either::new(self.playing,
|
||||||
Tui::fg(Color::Rgb(0, 255, 0), Bsp::s(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)),
|
Tui::fg(Color::Rgb(0, 255, 0), Bsp::s(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)),
|
||||||
Tui::fg(Color::Rgb(255, 128, 0), Bsp::s(" ▗▄▖ ", " ▝▀▘ ",))))))));
|
Tui::fg(Color::Rgb(255, 128, 0), Bsp::s(" ▗▄▖ ", " ▝▀▘ ",))))))));
|
||||||
|
|
||||||
pub struct BeatStats { compact: bool, bpm: Arc<str>, beat: Arc<str>, time: Arc<str>, }
|
pub struct BeatStats { compact: bool, bpm: Arc<str>, beat: Arc<str>, time: Arc<str>, }
|
||||||
|
content!(TuiOut: |self: BeatStats| Either::new(self.compact,
|
||||||
|
row!(FieldV(TuiTheme::g(128).into(), "BPM", &self.bpm),
|
||||||
|
FieldV(TuiTheme::g(128).into(), "Beat", &self.beat),
|
||||||
|
FieldV(TuiTheme::g(128).into(), "Time", &self.time),),
|
||||||
|
col!(Bsp::e(Tui::fg(TuiTheme::g(255), &self.bpm), " BPM"),
|
||||||
|
Bsp::e("Beat ", Tui::fg(TuiTheme::g(255), &self.beat)),
|
||||||
|
Bsp::e("Time ", Tui::fg(TuiTheme::g(255), &self.time)))));
|
||||||
impl BeatStats {
|
impl BeatStats {
|
||||||
fn new (compact: bool, clock: &Clock) -> Self {
|
fn new (compact: bool, clock: &Clock) -> Self {
|
||||||
let bpm = format!("{:.3}", clock.timebase.bpm.get()).into();
|
let bpm = format!("{:.3}", clock.timebase.bpm.get()).into();
|
||||||
|
|
@ -63,46 +57,27 @@ impl BeatStats {
|
||||||
Self { compact, bpm, beat, time }
|
Self { compact, bpm, beat, time }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
render!(TuiOut: (self: BeatStats) => Either::new(self.compact,
|
|
||||||
row!(
|
|
||||||
FieldV(TuiTheme::g(128).into(), "BPM", &self.bpm),
|
|
||||||
FieldV(TuiTheme::g(128).into(), "Beat", &self.beat),
|
|
||||||
FieldV(TuiTheme::g(128).into(), "Time", &self.time),
|
|
||||||
),
|
|
||||||
col!(
|
|
||||||
Bsp::e(Tui::fg(TuiTheme::g(255), &self.bpm), " BPM"),
|
|
||||||
Bsp::e("Beat ", Tui::fg(TuiTheme::g(255), &self.beat)),
|
|
||||||
Bsp::e("Time ", Tui::fg(TuiTheme::g(255), &self.time)),
|
|
||||||
)));
|
|
||||||
|
|
||||||
pub struct OutputStats { compact: bool, sample_rate: Arc<str>, buffer_size: Arc<str>, latency: Arc<str>, }
|
pub struct OutputStats { compact: bool, sample_rate: Arc<str>, buffer_size: Arc<str>, latency: Arc<str>, }
|
||||||
|
content!(TuiOut: |self: OutputStats| Either::new(self.compact,
|
||||||
|
row!(FieldV(TuiTheme::g(128).into(), "SR", &self.sample_rate),
|
||||||
|
FieldV(TuiTheme::g(128).into(), "Buf", &self.buffer_size),
|
||||||
|
FieldV(TuiTheme::g(128).into(), "Lat", &self.latency)),
|
||||||
|
col!(Bsp::e(Tui::fg(TuiTheme::g(255), format!("{}", self.sample_rate)), " sample rate"),
|
||||||
|
Bsp::e(Tui::fg(TuiTheme::g(255), format!("{}", self.buffer_size)), " sample buffer"),
|
||||||
|
Bsp::e(Tui::fg(TuiTheme::g(255), format!("{:.3}ms", self.latency)), " latency"))));
|
||||||
impl OutputStats {
|
impl OutputStats {
|
||||||
fn new (compact: bool, clock: &Clock) -> Self {
|
fn new (compact: bool, clock: &Clock) -> Self {
|
||||||
let rate = clock.timebase.sr.get();
|
let rate = clock.timebase.sr.get();
|
||||||
let chunk = clock.chunk.load(Relaxed);
|
let chunk = clock.chunk.load(Relaxed);
|
||||||
|
let sr = if compact {format!("{:.1}kHz", rate / 1000.)} else {format!("{:.0}Hz", rate)};
|
||||||
Self {
|
Self {
|
||||||
compact,
|
compact,
|
||||||
sample_rate: if compact {
|
sample_rate: sr.into(),
|
||||||
format!("{:.1}kHz", rate / 1000.)
|
|
||||||
} else {
|
|
||||||
format!("{:.0}Hz", rate)
|
|
||||||
}.into(),
|
|
||||||
buffer_size: format!("{chunk}").into(),
|
buffer_size: format!("{chunk}").into(),
|
||||||
latency: format!("{:.1}ms", chunk as f64 / rate * 1000.).into(),
|
latency: format!("{:.1}ms", chunk as f64 / rate * 1000.).into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
render!(TuiOut: (self: OutputStats) => Either::new(self.compact,
|
|
||||||
row!(
|
|
||||||
FieldV(TuiTheme::g(128).into(), "SR", &self.sample_rate),
|
|
||||||
FieldV(TuiTheme::g(128).into(), "Buf", &self.buffer_size),
|
|
||||||
FieldV(TuiTheme::g(128).into(), "Lat", &self.latency),
|
|
||||||
),
|
|
||||||
col!(
|
|
||||||
Bsp::e(Tui::fg(TuiTheme::g(255), format!("{}", self.sample_rate)), " sample rate"),
|
|
||||||
Bsp::e(Tui::fg(TuiTheme::g(255), format!("{}", self.buffer_size)), " sample buffer"),
|
|
||||||
Bsp::e(Tui::fg(TuiTheme::g(255), format!("{:.3}ms", self.latency)), " latency"),
|
|
||||||
)));
|
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
//keymap!(TRANSPORT_BPM_KEYS = |state: Clock, input: Event| ClockCommand {
|
//keymap!(TRANSPORT_BPM_KEYS = |state: Clock, input: Event| ClockCommand {
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
pub struct Bordered<S: BorderStyle, W: Content<TuiOut>>(pub S, pub W);
|
pub struct Bordered<S: BorderStyle, W: Content<TuiOut>>(pub S, pub W);
|
||||||
|
content!(TuiOut: |self: Bordered<S: BorderStyle, W: Content<TuiOut>>|Fill::xy(
|
||||||
render!(TuiOut: (self: Bordered<S: BorderStyle, W: Content<TuiOut>>) => {
|
lay!(Border(self.0), Padding::xy(1, 1, &self.1))
|
||||||
Fill::xy(lay!(Border(self.0), Padding::xy(1, 1, &self.1)))
|
));
|
||||||
});
|
|
||||||
|
|
||||||
pub struct Border<S: BorderStyle>(pub S);
|
pub struct Border<S: BorderStyle>(pub S);
|
||||||
|
|
||||||
render!(TuiOut: |self: Border<S: BorderStyle>, to| {
|
render!(TuiOut: |self: Border<S: BorderStyle>, to| {
|
||||||
let area = to.area();
|
let area = to.area();
|
||||||
if area.w() > 0 && area.y() > 0 {
|
if area.w() > 0 && area.y() > 0 {
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ pub enum FileBrowserCommand {
|
||||||
Chdir(PathBuf),
|
Chdir(PathBuf),
|
||||||
Filter(Arc<str>),
|
Filter(Arc<str>),
|
||||||
}
|
}
|
||||||
render!(TuiOut: (self: FileBrowser) => /*Stack::down(|add|{
|
content!(TuiOut: |self: FileBrowser| /*Stack::down(|add|{
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
for (_, name) in self.dirs.iter() {
|
for (_, name) in self.dirs.iter() {
|
||||||
if i >= self.scroll {
|
if i >= self.scroll {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue