fix (?) Inset; add arranger cursor description

This commit is contained in:
🪞👃🪞 2024-09-30 09:41:43 +03:00
parent 7df9cc930d
commit 3c8d9668fe
4 changed files with 137 additions and 151 deletions

View file

@ -101,6 +101,12 @@ pub trait Area<N: Number>: Copy {
_ => todo!(), _ => todo!(),
} }
} }
#[inline] fn push_x (&self, x: N) -> [N;4] { [self.x() + x, self.y(), self.w(), self.h()] }
#[inline] fn push_y (&self, y: N) -> [N;4] { [self.x(), self.y() + y, self.w(), self.h()] }
#[inline] fn shrink_x (&self, x: N) -> [N;4] { [self.x(), self.y(), self.w() - x, self.h()] }
#[inline] fn shrink_y (&self, y: N) -> [N;4] { [self.x(), self.y(), self.w(), self.h() - y] }
#[inline] fn set_w (&self, w: N) -> [N;4] { [self.x(), self.y(), w, self.h()] }
#[inline] fn set_h (&self, h: N) -> [N;4] { [self.x(), self.y(), self.w(), h] }
} }
impl<N: Number> Area<N> for (N, N, N, N) { impl<N: Number> Area<N> for (N, N, N, N) {
@ -610,18 +616,14 @@ impl<N: Number, T: Widget> Outset<N, T> {
impl<E: Engine, T: Widget<Engine = E>> Widget for Inset<E::Unit, T> { impl<E: Engine, T: Widget<Engine = E>> Widget for Inset<E::Unit, T> {
type Engine = E; type Engine = E;
fn layout (&self, to: E::Size) -> Perhaps<E::Size> {
match *self {
Self::X(x, ref inner) => Shrink::X(x + x, inner as &dyn Widget<Engine = E>),
Self::Y(y, ref inner) => Shrink::Y(y + y, inner as &dyn Widget<Engine = E>),
Self::XY(x, y, ref inner) => Shrink::XY(x + x, y + y, inner as &dyn Widget<Engine = E>),
}.layout(to)
}
fn render (&self, to: &mut E::Output) -> Usually<()> { fn render (&self, to: &mut E::Output) -> Usually<()> {
match *self { match *self {
Self::X(x, ref inner) => Push::X(x, inner as &dyn Widget<Engine = E>), Self::X(x, ref inner) =>
Self::Y(y, ref inner) => Push::Y(y, inner as &dyn Widget<Engine = E>), Push::X(x, Shrink::X(x, inner as &dyn Widget<Engine = E>)),
Self::XY(x, y, ref inner) => Push::XY(x, y, inner as &dyn Widget<Engine = E>), Self::Y(y, ref inner) =>
Push::Y(y, Shrink::Y(y, inner as &dyn Widget<Engine = E>)),
Self::XY(x, y, ref inner) =>
Push::XY(x, y, Shrink::XY(x, y, inner as &dyn Widget<Engine = E>)),
}.render(to) }.render(to)
} }
} }

View file

@ -464,12 +464,8 @@ pub struct Bordered<S: BorderStyle, W: Widget<Engine = Tui>>(pub S, pub W);
impl<S: BorderStyle, W: Widget<Engine = Tui>> Content for Bordered<S, W> { impl<S: BorderStyle, W: Widget<Engine = Tui>> Content for Bordered<S, W> {
type Engine = Tui; type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> { fn content (&self) -> impl Widget<Engine = Tui> {
let style = self.0; let content: &dyn Widget<Engine = Tui> = &self.1;
Layers::new(move|add|{ lay! { content.inset_xy(1, 1), Border(self.0) }.fill_xy()
add(&Inset::XY(1, 1, &self.1 as &dyn Widget<Engine = Tui>))?;
add(&Border(style))?;
Ok(())
}).fill_xy()
} }
} }

View file

@ -81,7 +81,7 @@ impl Content for ArrangerStandalone<Tui> {
) { ) {
let arranger = &self.arranger as &dyn Widget<Engine = Tui>; let arranger = &self.arranger as &dyn Widget<Engine = Tui>;
let sequencer = sequencer as &dyn Widget<Engine = Tui>; let sequencer = sequencer as &dyn Widget<Engine = Tui>;
add(&Split::new(direction, 40, arranger, sequencer.min_y(20))) add(&Split::new(direction, 20, arranger, sequencer.min_y(20)))
} else { } else {
add(&self.arranger) add(&self.arranger)
} }

View file

@ -306,9 +306,15 @@ impl Handle<Tui> for Arranger<Tui> {
impl Content for Arranger<Tui> { impl Content for Arranger<Tui> {
type Engine = Tui; type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> { fn content (&self) -> impl Widget<Engine = Tui> {
Layers::new(move |add|match self.mode { Layers::new(move |add|{
match self.mode {
ArrangerViewMode::Horizontal => add(&HorizontalArranger(&self)), ArrangerViewMode::Horizontal => add(&HorizontalArranger(&self)),
ArrangerViewMode::Vertical(factor) => add(&VerticalArranger(&self, factor)) ArrangerViewMode::Vertical(factor) => add(&VerticalArranger(&self, factor))
}?;
add(&Align::SE(self.selected.description(
&self.tracks,
&self.scenes,
).as_str()))
}) })
} }
} }
@ -368,6 +374,41 @@ pub enum ArrangerFocus {
/// Focus identification methods /// Focus identification methods
impl ArrangerFocus { impl ArrangerFocus {
pub fn description <E: Engine> (
&self,
tracks: &Vec<Sequencer<E>>,
scenes: &Vec<Scene>,
) -> String {
format!("Selected: {}", match self {
Self::Mix => format!("Everything"),
Self::Track(t) => if let Some(track) = tracks.get(*t) {
format!("T{t}: {}", &track.name.read().unwrap())
} else {
format!("T??")
},
Self::Scene(s) => if let Some(scene) = scenes.get(*s) {
format!("S{s}: {}", &scene.name.read().unwrap())
} else {
format!("S??")
},
Self::Clip(t, s) => if let (Some(track), Some(scene)) = (
tracks.get(*t),
scenes.get(*s),
) {
if let Some(Some(slot)) = scene.clips.get(*t) {
if let Some(clip) = track.phrases.get(*slot) {
format!("T{t} S{s} C{slot} ({})", &clip.read().unwrap().name.read().unwrap())
} else {
format!("T{t} S{s}: Empty")
}
} else {
format!("T{t} S{s}: Empty")
}
} else {
format!("T{t} S{s}: Empty")
}
})
}
pub fn is_mix (&self) -> bool { pub fn is_mix (&self) -> bool {
match self { Self::Mix => true, _ => false } match self { Self::Mix => true, _ => false }
} }
@ -505,7 +546,7 @@ impl<'a> Content for VerticalArranger<'a, Tui> {
}).fixed_xy(offset.saturating_sub(1), height))?; }).fixed_xy(offset.saturating_sub(1), height))?;
for (track, (w, _x)) in cols.iter().enumerate() { for (track, (w, _x)) in cols.iter().enumerate() {
add(&Layers::new(move |add|{ add(&Layers::new(move |add|{
let mut color = COLOR_BG0; let mut color = Color::Rgb(40, 50, 30);
if let (Some(track), Some(Some(clip))) = ( if let (Some(track), Some(Some(clip))) = (
tracks.get(track), tracks.get(track),
scene.clips.get(track), scene.clips.get(track),
@ -612,7 +653,7 @@ impl<'a> Widget for VerticalArrangerCursor<'a> {
let area = match selected { let area = match selected {
ArrangerFocus::Mix => { ArrangerFocus::Mix => {
if focused { if focused {
to.fill_bg(area, COLOR_BG0); to.fill_bg(area, Color::Rgb(40, 50, 30));
} }
area area
}, },
@ -632,21 +673,21 @@ impl<'a> Widget for VerticalArrangerCursor<'a> {
}, },
}; };
if let Some([x, y, width, height]) = track_area { if let Some([x, y, width, height]) = track_area {
to.fill_fg([x, y, 1, height], COLOR_BG5); to.fill_fg([x, y, 1, height], Color::Rgb(70, 80, 50));
to.fill_fg([x + width, y, 1, height], COLOR_BG5); to.fill_fg([x + width, y, 1, height], Color::Rgb(70, 80, 50));
} }
if let Some([_, y, _, height]) = scene_area { if let Some([_, y, _, height]) = scene_area {
to.fill_ul([area.x(), y - 1, area.w(), 1], COLOR_BG5); to.fill_ul([area.x(), y - 1, area.w(), 1], Color::Rgb(70, 80, 50));
to.fill_ul([area.x(), y + height - 1, area.w(), 1], COLOR_BG5); to.fill_ul([area.x(), y + height - 1, area.w(), 1], Color::Rgb(70, 80, 50));
} }
if focused { if focused {
if let Some(clip_area) = clip_area { if let Some(clip_area) = clip_area {
to.render_in(clip_area, &CORNERS)?; to.render_in(clip_area, &CORNERS)?;
to.fill_bg(clip_area, COLOR_BG0); to.fill_bg(clip_area, Color::Rgb(40, 50, 30));
} else if let Some(track_area) = track_area { } else if let Some(track_area) = track_area {
to.fill_bg(track_area, COLOR_BG0); to.fill_bg(track_area, Color::Rgb(40, 50, 30));
} else if let Some(scene_area) = scene_area { } else if let Some(scene_area) = scene_area {
to.fill_bg(scene_area, COLOR_BG0); to.fill_bg(scene_area, Color::Rgb(40, 50, 30));
} }
} }
//Ok(Some(area)) //Ok(Some(area))
@ -666,7 +707,7 @@ impl<'a> Content for HorizontalArranger<'a, Tui> {
let Arranger { tracks, focused, selected, scenes, .. } = self.0; let Arranger { tracks, focused, selected, scenes, .. } = self.0;
let tracks = tracks.as_slice(); let tracks = tracks.as_slice();
Layers::new(|add|{ Layers::new(|add|{
add(&focused.then_some(Background(COLOR_BG0)))?; add(&focused.then_some(Background(Color::Rgb(40, 50, 30))))?;
add(&Stack::right(|add|{ add(&Stack::right(|add|{
add(&TrackNameColumn(tracks, *selected))?; add(&TrackNameColumn(tracks, *selected))?;
add(&TrackMonitorColumn(tracks))?; add(&TrackMonitorColumn(tracks))?;
@ -1330,38 +1371,17 @@ impl Content for Sequencer<Tui> {
type Engine = Tui; type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> { fn content (&self) -> impl Widget<Engine = Tui> {
let toolbar = col!( let toolbar = col!(
col! { "Name" col! { "Name", self.name.read().unwrap().as_str(), }.min_xy(10, 4),
, self.name.read().unwrap().as_str(), col! { "Start: ", " 1.1.1", "End: ", " 2.1.1", }.min_xy(10, 6),
}.min_xy(10, 4), col! { "Loop [ ]", "From: ", " 1.1.1", "Length: ", " 1.0.0", }.min_xy(10, 7),
col! { "Notes: ", "C#0-C#9 ", "[ /2 ]", "[ x2 ]"
col! { "Start: ", " 1.1.1" , "[ Rev ]", "[ Inv ]", "[ Dup ]" }.min_xy(10, 9),
, "End: ", " 2.1.1",
}.min_xy(10, 6),
col! { "Loop [ ]"
, "From: ", " 1.1.1"
, "Length: ", " 1.0.0",
}.min_xy(10, 7),
col! { "Notes: "
, "C#0-C#9 "
, "[ /2 ]"
, "[ x2 ]"
, "[ Rev ]"
, "[ Inv ]"
, "[ Dup ]"
}.min_xy(10, 9),
); );
let content = lay!( let content = lay!(
// keys // keys
CustomWidget::new(|to|Ok(Some([32,4])), |to: &mut TuiOutput|{ CustomWidget::new(|_|Ok(Some([32,4])), |to: &mut TuiOutput|{
let area = to.area(); if to.area().h() < 2 { return Ok(()) }
if area.h() < 2 { to.buffer_update(to.area().set_w(5).shrink_y(2), &|cell, x, y|{
return Ok(())
}
let area = [area.x(), area.y(), 5, area.h() - 2];
to.buffer_update(area, &|cell, x, y|{
let y = y + self.note_axis.start as u16; let y = y + self.note_axis.start as u16;
if x < self.keys.area.width && y < self.keys.area.height { if x < self.keys.area.width && y < self.keys.area.height {
*cell = self.keys.get(x, y).clone() *cell = self.keys.get(x, y).clone()
@ -1369,60 +1389,60 @@ impl Content for Sequencer<Tui> {
}); });
Ok(()) Ok(())
}).fill_y(), }).fill_y(),
// playhead
self.phrase.as_ref().map(|phrase|SequenceTimer(&self, phrase.clone()).fill_x()), CustomWidget::new(|_|Ok(Some([32,2])), |to: &mut TuiOutput|{
if let Some(phrase) = self.phrase.as_ref() {
// notes let time_0 = self.time_axis.start;
CustomWidget::new(|to|Ok(Some([32,4])), |to: &mut TuiOutput|{ let time_z = self.time_axis.scale;
let area = to.area(); let now = self.now % phrase.read().unwrap().length;
if area.h() < 2 { let [x, y, width, _] = to.area();
return Ok(())//Some(area)) let x2 = x as usize + Sequencer::H_KEYS_OFFSET;
let x3 = x as usize + width as usize;
for x in x2..x3 {
let step = (time_0 + x2) * time_z;
let next_step = (time_0 + x2 + 1) * time_z;
let style = Sequencer::<Tui>::style_timer_step(now, step as usize, next_step as usize);
to.blit(&"-", x as u16, y, Some(style));
} }
let area = [ }
area.x() + Sequencer::H_KEYS_OFFSET as u16, Ok(())
area.y() + 1, }).fill_x(),
area.w().saturating_sub(Sequencer::H_KEYS_OFFSET as u16), // notes
area.h().saturating_sub(2), CustomWidget::new(|_|Ok(Some([32,4])), |to: &mut TuiOutput|{
]; let offset = Sequencer::H_KEYS_OFFSET as u16;
if to.area().h() < 2 || to.area().w() < offset { return Ok(()) }
let area = to.area().push_x(offset).shrink_x(offset).shrink_y(2);
to.buffer_update(area, &move |cell, x, y|{ to.buffer_update(area, &move |cell, x, y|{
cell.set_bg(Color::Rgb(20, 20, 20));
let src_x = ((x as usize + self.time_axis.start) * self.time_axis.scale) as usize; let src_x = ((x as usize + self.time_axis.start) * self.time_axis.scale) as usize;
let src_y = (y as usize + self.note_axis.start) as usize; let src_y = (y as usize + self.note_axis.start) as usize;
if src_x < self.buffer.width && src_y < self.buffer.height - 1 { if src_x < self.buffer.width && src_y < self.buffer.height - 1 {
let src = self.buffer.get(src_x, self.buffer.height - src_y); let src = self.buffer.get(src_x, self.buffer.height - src_y);
src.map(|src|{ src.map(|src|{ cell.set_symbol(src.symbol()); cell.set_fg(src.fg); });
cell.set_symbol(src.symbol());
cell.set_fg(src.fg);
});
} }
}); });
Ok(())//Some(area)) Ok(())
}).fill_x(), }).fill_x(),
// note cursor
// cursor CustomWidget::new(|_|Ok(Some([1,1])), |to: &mut TuiOutput|{
CustomWidget::new(|to|Ok(Some([1,1])), |to: &mut TuiOutput|{
let area = to.area(); let area = to.area();
if let (Some(time), Some(note)) = (self.time_axis.point, self.note_axis.point) { if let (Some(time), Some(note)) = (self.time_axis.point, self.note_axis.point) {
let x = area.x() + Sequencer::H_KEYS_OFFSET as u16 + time as u16; let x = area.x() + Sequencer::H_KEYS_OFFSET as u16 + time as u16;
let y = area.y() + 1 + note as u16 / 2; let y = area.y() + 1 + note as u16 / 2;
let c = if note % 2 == 0 { "" } else { "" }; let c = if note % 2 == 0 { "" } else { "" };
to.blit(&c, x, y, self.style_focus()); to.blit(&c, x, y, self.style_focus());
Ok(())
} else {
//Ok(Some([0,0,0,0]))
Ok(())
} }
}),
//zoom
CustomWidget::new(|to|Ok(Some([10,1])), |to: &mut TuiOutput|{
let area = to.area();
let quant = ppq_to_name(self.time_axis.scale);
let quant_x = area.x() + area.w() - 1 - quant.len() as u16;
let quant_y = area.y() + area.h() - 2;
to.blit(&quant, quant_x, quant_y, self.style_focus());
Ok(()) Ok(())
}), }),
//zoom
CustomWidget::new(|_|Ok(Some([10,1])), |to: &mut TuiOutput|{
let [x, y, w, h] = to.area.xywh();
let quant = ppq_to_name(self.time_axis.scale);
let x = x + w - 1 - quant.len() as u16;
let y = y + h - 2;
to.blit(&quant, x, y, self.style_focus());
Ok(())
}),
); );
row!(toolbar, content).fill_x() row!(toolbar, content).fill_x()
.bg(Color::Rgb(40, 50, 30)) .bg(Color::Rgb(40, 50, 30))
@ -1496,12 +1516,12 @@ fn nth_octave (index: u16) -> &'static str {
fn key_colors (index: u16) -> (Color, Color) { fn key_colors (index: u16) -> (Color, Color) {
match index % 6 { match index % 6 {
0 => (Color::White, Color::Black), 0 => (Color::Rgb(255, 255, 255), Color::Rgb(0, 0, 0)),
1 => (Color::White, Color::Black), 1 => (Color::Rgb(255, 255, 255), Color::Rgb(0, 0, 0)),
2 => (Color::White, Color::White), 2 => (Color::Rgb(255, 255, 255), Color::Rgb(255, 255, 255)),
3 => (Color::Black, Color::White), 3 => (Color::Rgb(0, 0, 0), Color::Rgb(255, 255, 255)),
4 => (Color::Black, Color::White), 4 => (Color::Rgb(0, 0, 0), Color::Rgb(255, 255, 255)),
5 => (Color::Black, Color::White), 5 => (Color::Rgb(0, 0, 0), Color::Rgb(255, 255, 255)),
_ => unreachable!() _ => unreachable!()
} }
} }
@ -1620,34 +1640,6 @@ pub(crate) fn keys_vert () -> Buffer {
////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////
struct SequenceTimer<'a>(&'a Sequencer<Tui>, Arc<RwLock<Phrase>>);
impl<'a> Widget for SequenceTimer<'a> {
type Engine = Tui;
fn layout (&self, _to: [u16;2]) -> Perhaps<[u16;2]> {
Ok(Some([32,2]))
}
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
let area = to.area();
let phrase = self.1.read().unwrap();
let (time0, time_z, now) = (
self.0.time_axis.start, self.0.time_axis.scale, self.0.now % phrase.length
);
let [x, _, width, _] = area;
let x2 = x as usize + Sequencer::H_KEYS_OFFSET;
let x3 = x as usize + width as usize;
for x in x2..x3 {
let step = (time0 + x2) * time_z;
let next_step = (time0 + x2 + 1) * time_z;
let style = Sequencer::<Tui>::style_timer_step(now, step as usize, next_step as usize);
to.blit(&"-", x as u16, area.y(), Some(style));
}
//return Ok(Some([area.x(), area.y(), area.w(), 1]))
Ok(())
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////
/// A collection of phrases to play on each track. /// A collection of phrases to play on each track.
#[derive(Default)] #[derive(Default)]
pub struct Scene { pub struct Scene {
@ -2013,21 +2005,17 @@ impl Handle<Tui> for TransportToolbar<Tui> {
impl Content for TransportToolbar<Tui> { impl Content for TransportToolbar<Tui> {
type Engine = Tui; type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> { fn content (&self) -> impl Widget<Engine = Tui> {
Stack::right(|add|{
let focus_wrap = |focused, component|Layers::new(move |add|{ let focus_wrap = |focused, component|Layers::new(move |add|{
if focused { if focused { add(&CORNERS)?; add(&Background(COLOR_BG1))?; }
add(&CORNERS)?;
add(&Background(COLOR_BG1))?;
}
add(component) add(component)
}); });
add(&focus_wrap(self.focused && self.playing.focused, &self.playing))?; row! {
add(&focus_wrap(self.focused && self.bpm.focused, &self.bpm))?; focus_wrap(self.focused && self.playing.focused, &self.playing),
add(&focus_wrap(self.focused && self.quant.focused, &self.quant))?; focus_wrap(self.focused && self.bpm.focused, &self.bpm),
add(&focus_wrap(self.focused && self.sync.focused, &self.sync))?; focus_wrap(self.focused && self.quant.focused, &self.quant),
add(&focus_wrap(self.focused && self.clock.focused, &self.clock))?; focus_wrap(self.focused && self.sync.focused, &self.sync),
Ok(()) focus_wrap(self.focused && self.clock.focused, &self.clock),
}) }.fill_x().bg(Color::Rgb(25, 30, 20))
} }
} }