wip: component playground; Align primitive

This commit is contained in:
🪞👃🪞 2024-09-07 20:54:49 +03:00
parent 4cca03352a
commit 5fc7da3aca
12 changed files with 181 additions and 42 deletions

View file

@ -1,10 +1,10 @@
use crate::*; use crate::*;
/// A UI component. /// A UI component.
pub trait Component<E: Engine>: Render<E> + Handle<E> {} pub trait Component<E: Engine>: Render<E> + Handle<E> + Layout<E> {}
/// Everything that implements [Render] and [Handle] is a [Component]. /// Everything that implements [Render] and [Handle] is a [Component].
impl<E: Engine, C: Render<E> + Handle<E>> Component<E> for C {} impl<E: Engine, C: Render<E> + Handle<E> + Layout<E>> Component<E> for C {}
/// Marker trait for [Component]s that can [Exit] /// Marker trait for [Component]s that can [Exit]
pub trait ExitableComponent<E>: Exit + Component<E> where E: Engine { pub trait ExitableComponent<E>: Exit + Component<E> where E: Engine {

View file

@ -2,19 +2,18 @@ use crate::*;
// TODO: Convert to component // TODO: Convert to component
// pub enum Align { Center, NW, N, NE, E, SE, S, SW, W, } // pub enum Align { Center, NW, N, NE, E, SE, S, SW, W, }
pub fn center_box (area: Rect, w: u16, h: u16) -> Rect { pub fn center_box (area: [u16;4], w: u16, h: u16) -> [u16;4] {
let width = w.min(area.width * 3 / 5); let width = w.min(area.w() * 3 / 5);
let height = h.min(area.width * 3 / 5); let height = h.min(area.w() * 3 / 5);
let x = area.x + (area.width - width) / 2; let x = area.x() + (area.w() - width) / 2;
let y = area.y + (area.height - height) / 2; let y = area.y() + (area.h() - height) / 2;
Rect { x, y, width, height } [x, y, width, height]
} }
/// Trait for structs that compute drawing area before rendering /// Trait for structs that compute drawing area before rendering
pub trait Layout<E: Engine>: Render<E> { pub trait Layout<E: Engine>: Render<E> {
fn layout (&self, area: E::Area) -> Perhaps<E::Area>; fn layout (&self, area: E::Area) -> Perhaps<E::Area>;
} }
impl<E: Engine, T: Layout<E>> Layout<E> for &T { impl<E: Engine, T: Layout<E>> Layout<E> for &T {
fn layout (&self, area: E::Area) -> Perhaps<E::Area> { fn layout (&self, area: E::Area) -> Perhaps<E::Area> {
(*self).layout(area) (*self).layout(area)
@ -29,6 +28,8 @@ impl<E: Engine, T: Layout<E>> Layout<E> for Option<T> {
} }
} }
/// Override X and Y coordinates, aligning to corner, side, or center of area
pub enum Align<L> { Center(L), NW(L), N(L), NE(L), W(L), E(L), SW(L), S(L), SE(L) }
/// Enforce minimum size of drawing area /// Enforce minimum size of drawing area
pub enum Min<U: Number, L> { W(U, L), H(U, L), WH(U, U, L), } pub enum Min<U: Number, L> { W(U, L), H(U, L), WH(U, U, L), }
/// Enforce maximum size of drawing area /// Enforce maximum size of drawing area

View file

@ -15,7 +15,7 @@ pub(crate) use std::thread::{spawn, JoinHandle};
pub(crate) use std::time::Duration; pub(crate) use std::time::Duration;
pub(crate) use atomic_float::*; pub(crate) use atomic_float::*;
use better_panic::{Settings, Verbosity}; use better_panic::{Settings, Verbosity};
use std::ops::{Add, Sub}; use std::ops::{Add, Sub, Div};
use std::cmp::{Ord, Eq, PartialEq}; use std::cmp::{Ord, Eq, PartialEq};
use std::fmt::{Debug, Display}; use std::fmt::{Debug, Display};
@ -48,6 +48,7 @@ pub type Perhaps<T> = Result<Option<T>, Box<dyn Error>>;
pub trait Number: Send + Sync + Copy pub trait Number: Send + Sync + Copy
+ Add<Self, Output=Self> + Add<Self, Output=Self>
+ Sub<Self, Output=Self> + Sub<Self, Output=Self>
+ Div<Self, Output=Self>
+ Ord + PartialEq + Eq + Ord + PartialEq + Eq
+ Debug + Display {} + Debug + Display {}
@ -55,6 +56,7 @@ impl<T> Number for T where
T: Send + Sync + Copy T: Send + Sync + Copy
+ Add<Self, Output=Self> + Add<Self, Output=Self>
+ Sub<Self, Output=Self> + Sub<Self, Output=Self>
+ Div<Self, Output=Self>
+ Ord + PartialEq + Eq + Ord + PartialEq + Eq
+ Debug + Display + Debug + Display
{} {}

View file

@ -36,6 +36,7 @@ impl Engine for Tui {
let better_panic_handler = Settings::auto().verbosity(Verbosity::Full).create_panic_handler(); let better_panic_handler = Settings::auto().verbosity(Verbosity::Full).create_panic_handler();
std::panic::set_hook(Box::new(move |info: &std::panic::PanicInfo|{ std::panic::set_hook(Box::new(move |info: &std::panic::PanicInfo|{
stdout().execute(LeaveAlternateScreen).unwrap(); stdout().execute(LeaveAlternateScreen).unwrap();
CrosstermBackend::new(stdout()).show_cursor().unwrap();
disable_raw_mode().unwrap(); disable_raw_mode().unwrap();
better_panic_handler(info); better_panic_handler(info);
})); }));

View file

@ -72,3 +72,56 @@ impl<'a> Split<'a, Tui> {
}, areas)) }, areas))
} }
} }
impl<L: Layout<Tui>> Layout<Tui> for Align<L> where Self: Render<Tui> {
fn layout (&self, outer_area: [u16;4]) -> Perhaps<[u16;4]> {
Ok(match self {
Self::Center(inner) => inner,
Self::NW(inner) => inner,
Self::N(inner) => inner,
Self::NE(inner) => inner,
Self::W(inner) => inner,
Self::E(inner) => inner,
Self::SW(inner) => inner,
Self::S(inner) => inner,
Self::SE(inner) => inner,
}
.layout(outer_area)?
.map(|inner_area|match self {
Self::Center(_) => {
let [_, _, w, h] = inner_area.xywh();
let offset_x = (outer_area.w() - w) / 2;
let offset_y = (outer_area.h() - h) / 2;
[outer_area.x() + offset_x, outer_area.y() + offset_y, w, h]
},
Self::NW(_) => { todo!() },
Self::N(_) => { todo!() },
Self::NE(_) => { todo!() },
Self::W(_) => { todo!() },
Self::E(_) => { todo!() },
Self::SW(_) => { todo!() },
Self::S(_) => { todo!() },
Self::SE(_) => { todo!() },
}))
}
}
impl<R: Render<Tui> + Layout<Tui>> Render<Tui> for Align<R> {
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
self.layout(to.area())?
.map(|area|to.with_area(area.x(), area.y(), area.w(), area.h()))
.map(|to|match self {
Self::Center(inner) => inner,
Self::NW(inner) => inner,
Self::N(inner) => inner,
Self::NE(inner) => inner,
Self::W(inner) => inner,
Self::E(inner) => inner,
Self::SW(inner) => inner,
Self::S(inner) => inner,
Self::SE(inner) => inner,
}.render(to))
.transpose()
.map(|x|x.flatten())
}
}

View file

@ -211,3 +211,9 @@ pub const KEYMAP_PLUGIN: &'static [KeyBinding<Plugin>] = keymap!(Plugin {
Ok(true) Ok(true)
}], }],
}); });
impl Layout<Tui> for Plugin {
fn layout (&self, area: [u16;4]) -> Perhaps<[u16;4]> {
todo!()
}
}

View file

@ -28,27 +28,27 @@ impl Render<Tui> for AddSampleModal {
to.make_dim(); to.make_dim();
let area = center_box( let area = center_box(
area, area,
64.max(area.width.saturating_sub(8)), 64.max(area.w().saturating_sub(8)),
20.max(area.width.saturating_sub(8)), 20.max(area.w().saturating_sub(8)),
); );
to.fill_fg(area, Color::Reset); to.fill_fg(area, Color::Reset);
to.fill_bg(area, Nord::bg_lo(true, true)); to.fill_bg(area, Nord::bg_lo(true, true));
to.fill_char(area, ' '); to.fill_char(area, ' ');
to.blit(&format!("{}", &self.dir.to_string_lossy()), area.x+2, area.y+1, Some(Style::default().bold()))?; to.blit(&format!("{}", &self.dir.to_string_lossy()), area.x()+2, area.y()+1, Some(Style::default().bold()))?;
to.blit(&"Select sample:", area.x+2, area.y+2, Some(Style::default().bold()))?; to.blit(&"Select sample:", area.x()+2, area.y()+2, Some(Style::default().bold()))?;
for (i, (is_dir, name)) in self.subdirs.iter() for (i, (is_dir, name)) in self.subdirs.iter()
.map(|path|(true, path)) .map(|path|(true, path))
.chain(self.files.iter().map(|path|(false, path))) .chain(self.files.iter().map(|path|(false, path)))
.enumerate() .enumerate()
.skip(self.offset) .skip(self.offset)
{ {
if i >= area.height as usize - 4 { if i >= area.h() as usize - 4 {
break break
} }
let t = if is_dir { "" } else { "" }; let t = if is_dir { "" } else { "" };
let line = format!("{t} {}", name.to_string_lossy()); let line = format!("{t} {}", name.to_string_lossy());
let line = &line[..line.len().min(area.width as usize - 4)]; let line = &line[..line.len().min(area.w() as usize - 4)];
to.blit(&line, area.x + 2, area.y + 3 + i as u16, Some(if i == self.cursor { to.blit(&line, area.x() + 2, area.y() + 3 + i as u16, Some(if i == self.cursor {
Style::default().green() Style::default().green()
} else { } else {
Style::default().white() Style::default().white()

View file

@ -143,3 +143,9 @@ impl Sampler {
} }
} }
} }
impl Layout<Tui> for Sampler {
fn layout (&self, area: [u16;4]) -> Perhaps<[u16;4]> {
todo!()
}
}

View file

@ -1,6 +1,11 @@
use crate::*; use crate::*;
use tek_core::Direction; use tek_core::Direction;
impl Layout<Tui> for Track<Tui> {
fn layout (&self, area: [u16;4]) -> Perhaps<[u16;4]> {
todo!()
}
}
impl Render<Tui> for Track<Tui> { impl Render<Tui> for Track<Tui> {
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
TrackView { TrackView {
@ -32,30 +37,31 @@ pub struct TrackView<'a, E: Engine> {
} }
impl<'a> Render<Tui> for TrackView<'a, Tui> { impl<'a> Render<Tui> for TrackView<'a, Tui> {
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> { fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
let mut area = to.area(); todo!();
if let Some(chain) = self.chain { //let mut area = to.area();
match self.direction { //if let Some(chain) = self.chain {
Direction::Down => area.width = area.width.min(40), //match self.direction {
Direction::Right => area.width = area.width.min(10), //Direction::Down => area.width = area.width.min(40),
_ => { unimplemented!() }, //Direction::Right => area.width = area.width.min(10),
} //_ => { unimplemented!() },
to.fill_bg(to.area(), Nord::bg_lo(self.focused, self.entered)); //}
let mut split = Split::new(self.direction); //to.fill_bg(to.area(), Nord::bg_lo(self.focused, self.entered));
for device in chain.devices.as_slice().iter() { //let mut split = Split::new(self.direction);
split = split.add_ref(device); //for device in chain.devices.as_slice().iter() {
} //split = split.add_ref(device);
let (area, areas) = split.render_areas(to)?; //}
if self.focused && self.entered && areas.len() > 0 { //let (area, areas) = split.render_areas(to)?;
Corners(Style::default().green().not_dim()).draw(to.with_rect(areas[0]))?; //if self.focused && self.entered && areas.len() > 0 {
} //Corners(Style::default().green().not_dim()).draw(to.with_rect(areas[0]))?;
Ok(Some(area)) //}
} else { //Ok(Some(area))
let [x, y, width, height] = area; //} else {
let label = "No chain selected"; //let [x, y, width, height] = area;
let x = x + (width - label.len() as u16) / 2; //let label = "No chain selected";
let y = y + height / 2; //let x = x + (width - label.len() as u16) / 2;
to.blit(&label, x, y, Some(Style::default().dim().bold()))?; //let y = y + height / 2;
Ok(Some(area)) //to.blit(&label, x, y, Some(Style::default().dim().bold()))?;
} //Ok(Some(area))
//}
} }
} }

View file

@ -99,3 +99,8 @@ impl Exit for ArrangerRenameModal {
self.done = true self.done = true
} }
} }
impl Layout<Tui> for ArrangerRenameModal {
fn layout (&self, area: [u16;4]) -> Perhaps<[u16;4]> {
todo!()
}
}

View file

@ -0,0 +1,9 @@
[package]
name = "tek_test"
edition = "2021"
version = "0.1.0"
[dependencies]
tek_core = { path = "../tek_core" }
tek_mixer = { path = "../tek_mixer" }
tek_sequencer = { path = "../tek_sequencer" }

View file

@ -0,0 +1,50 @@
use tek_core::*;
pub fn main () -> Usually<()> {
Tui::run(Arc::new(RwLock::new(Demo::new())))?;
Ok(())
}
pub struct Demo {
index: usize,
items: Vec<Box<dyn Component<Tui>>>
}
impl Demo {
fn new () -> Self {
let items = vec![];
Self { index: 0, items }
}
}
impl Handle<Tui> for Demo {
fn handle (&mut self, from: &Tui) -> Perhaps<bool> {
match from.event() {
key!(KeyCode::PageUp) => {
self.index = (self.index + 1) % self.items.len();
Ok(Some(true))
},
key!(KeyCode::PageDown) => {
self.index = if self.index > 1 {
self.index - 1
} else {
self.items.len() - 1
};
Ok(Some(true))
},
_ => Ok(None)
}
}
}
impl Layout<Tui> for Demo {
fn layout (&self, area: [u16;4]) -> Perhaps<[u16;4]> {
Align::Center(self.items[self.index]).layout(area)
}
}
impl Render<Tui> for Demo {
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
Align::Center(self.items[self.index]).render(to)
}
}