wip: layered keymaps
Some checks failed
/ build (push) Has been cancelled

This commit is contained in:
🪞👃🪞 2025-04-28 23:43:12 +03:00
parent 5696cbbebb
commit 2fd7d7b39f
18 changed files with 355 additions and 360 deletions

View file

@ -2,6 +2,225 @@ use crate::*;
pub(crate) use std::fmt::Write;
pub(crate) use ::tengri::tui::ratatui::prelude::Position;
view!(TuiOut: |self: Tek| self.size.of(View(self, self.view)); {
":nil" => Box::new("nil"),
":modal" => self.view_modal(),
":status" => self.view_status(),
":transport" => self.view_transport(),
":arranger" => self.view_arranger(),
":pool" => self.view_pool(),
":editor" => self.editor().map(|e|Bsp::n(Bsp::e(e.clip_status(), e.edit_status()), e)),
":samples-keys" => self.sampler().map(|s|s.view_list(false, self.editor().unwrap())),
":samples-grid" => self.sampler().map(|s|s.view_grid()),
":sample-viewer" => self.sampler().map(|s|s.view_sample(self.editor().unwrap().note_pos())),
});
trait ScenesColors<'a> = Iterator<Item=SceneWithColor<'a>>;
type SceneWithColor<'a> = (usize, &'a Scene, usize, usize, Option<ItemTheme>);
impl Tek {
pub fn view_modal (&self) -> impl Content<TuiOut> + use<'_> {
When::new(self.modal.is_some(), Bsp::b(
Fill::xy(Tui::fg_bg(Color::Rgb(64,64,64), Color::Rgb(32,32,32), "")),
Fixed::xy(30, 15, Tui::fg_bg(Color::Rgb(255,255,255), Color::Rgb(16,16,16), Bsp::b(
Repeat(" "),
Outer(true, Style::default().fg(Tui::g(96)))
.enclose(self.modal.map(|modal|match modal {
Modal::Menu => self.view_modal_menu().boxed(),
Modal::Help => self.view_modal_help().boxed(),
}))
)))
))
}
fn view_modal_menu (&self) -> impl Content<TuiOut> {
let options = ||["Projects", "Settings", "Help", "Quit"].iter();
let option = |a,i|Tui::fg(Color::Rgb(255,255,255), format!("{}", a));
Bsp::s(Tui::bold(true, "tek!"), Bsp::s("", Map::south(1, options, option)))
}
fn view_modal_help (&self) -> impl Content<TuiOut> {
let bindings = ||TokenIter::new(include_str!("../../../config/keys_groovebox.edn"))
.filter_map(|x|if let Value::Exp(_, iter)=x.value{
Some(iter)
} else {
None
});
let binding = |mut binding: TokenIter, _|Bsp::e(
Tui::bold(true, Tui::fg(Color::Rgb(255,192,0), if let Some(Token {
value: Value::Sym(key), ..
}) = binding.next() {
Some(key.to_string())
} else {
None
})),
Bsp::e(" ", Tui::fg(Color::Rgb(255,255,255), if let Some(Token {
value: Value::Key(command), ..
}) = binding.next() {
Some(command.to_string())
} else {
None
})),
);
let layer = Map::south(1, bindings, binding);
Bsp::s(Tui::bold(true, "Help"), Bsp::s("", layer))
}
pub fn view_status (&self) -> impl Content<TuiOut> + use<'_> {
self.update_clock();
let cache = self.view_cache.read().unwrap();
view_status(
self.selected.describe(&self.tracks, &self.scenes),
cache.sr.view.clone(),
cache.buf.view.clone(),
cache.lat.view.clone(),
)
}
pub fn view_transport (&self) -> impl Content<TuiOut> + use<'_> {
self.update_clock();
let cache = self.view_cache.read().unwrap();
view_transport(
self.clock.is_rolling(),
cache.bpm.view.clone(),
cache.beat.view.clone(),
cache.time.view.clone(),
)
}
pub fn view_arranger (&self) -> impl Content<TuiOut> + use<'_> {
ArrangerView::new(self)
}
pub fn view_pool (&self) -> impl Content<TuiOut> + use<'_> {
self.pool().map(|p|Fixed::x(self.w_sidebar(), PoolView(self.is_editing(), p)))
}
/// Spacing between tracks.
pub(crate) const TRACK_SPACING: usize = 0;
/// Default scene height.
pub(crate) const H_SCENE: usize = 2;
/// Default editor height.
pub(crate) const H_EDITOR: usize = 15;
/// Width of display
pub(crate) fn w (&self) -> u16 {
self.size.w() as u16
}
pub(crate) fn w_sidebar (&self) -> u16 {
self.w() / if self.is_editing() { 16 } else { 8 } as u16
}
/// Width taken by all tracks.
pub(crate) fn w_tracks (&self) -> u16 {
self.tracks_with_sizes().last().map(|(_, _, _, x)|x as u16).unwrap_or(0)
}
/// Width available to display tracks.
pub(crate) fn w_tracks_area (&self) -> u16 {
self.w().saturating_sub(2 * self.w_sidebar())
}
/// Height of display
pub(crate) fn h (&self) -> u16 {
self.size.h() as u16
}
/// Height available to display track headers.
pub(crate) fn h_tracks_area (&self) -> u16 {
5
//self.h().saturating_sub(self.h_inputs() + self.h_outputs())
}
/// Height available to display tracks.
pub(crate) fn h_scenes_area (&self) -> u16 {
//15
self.h().saturating_sub(self.h_inputs() + self.h_outputs() + 11)
}
/// Height taken by all inputs.
pub(crate) fn h_inputs (&self) -> u16 {
1 + self.inputs_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0)
}
/// Height taken by all outputs.
pub(crate) fn h_outputs (&self) -> u16 {
1 + self.outputs_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0)
}
/// Height taken by all scenes.
pub(crate) fn h_scenes (&self) -> u16 {
self.scenes_with_sizes(self.is_editing(), Self::H_SCENE, Self::H_EDITOR).last()
.map(|(_, _, _, y)|y as u16).unwrap_or(0)
}
pub(crate) fn inputs_with_sizes (&self) -> impl PortsSizes<'_> {
let mut y = 0;
self.midi_ins.iter().enumerate().map(move|(i, input)|{
let height = 1 + input.conn().len();
let data = (i, input.name(), input.conn(), y, y + height);
y += height;
data
})
}
pub(crate) fn outputs_with_sizes (&self) -> impl PortsSizes<'_> {
let mut y = 0;
self.midi_outs.iter().enumerate().map(move|(i, output)|{
let height = 1 + output.conn().len();
let data = (i, output.name(), output.conn(), y, y + height);
y += height;
data
})
}
pub(crate) fn tracks_with_sizes (&self) -> impl TracksSizes<'_> {
let mut x = 0;
let editing = self.is_editing();
let active = match self.selected() {
Selection::Track(t) if editing => Some(t),
Selection::Clip(t, _) if editing => Some(t),
_ => None
};
let bigger = self.editor_w();
self.tracks().iter().enumerate().map(move |(index, track)|{
let width = if Some(index) == active.copied() { bigger } else { track.width.max(8) };
let data = (index, track, x, x + width);
x += width + Tek::TRACK_SPACING;
data
})
}
pub(crate) fn scenes_with_sizes (&self, editing: bool, height: usize, larger: usize)
-> impl ScenesSizes<'_>
{
let (selected_track, selected_scene) = match self.selected() {
Selection::Track(t) => (Some(*t), None),
Selection::Scene(s) => (None, Some(*s)),
Selection::Clip(t, s) => (Some(*t), Some(*s)),
_ => (None, None)
};
let mut y = 0;
self.scenes().iter().enumerate().map(move|(s, scene)|{
let active = editing && selected_track.is_some() && selected_scene == Some(s);
let height = if active { larger } else { height };
let data = (s, scene, y, y + height);
y += height;
data
})
}
pub fn update_clock (&self) {
ViewCache::update_clock(&self.view_cache, self.clock(), self.size.w() > 80)
}
}
pub(crate) struct ArrangerView<'a> {
app: &'a Tek,
@ -146,14 +365,8 @@ impl<'a> ArrangerView<'a> {
/// Render output matrix.
pub(crate) fn outputs (&'a self) -> impl Content<TuiOut> + 'a {
Tui::bg(Color::Reset, Align::n(Bsp::s(
Bsp::s(
self.output_nexts(),
self.output_froms(),
),
Bsp::s(
self.output_ports(),
self.output_conns(),
)
Bsp::s(self.output_nexts(), self.output_froms()),
Bsp::s(self.output_ports(), self.output_conns()),
)))
}
@ -369,160 +582,6 @@ impl<'a> ArrangerView<'a> {
}
trait ScenesColors<'a> = Iterator<Item=SceneWithColor<'a>>;
type SceneWithColor<'a> = (usize, &'a Scene, usize, usize, Option<ItemTheme>);
impl Tek {
/// Spacing between tracks.
pub(crate) const TRACK_SPACING: usize = 0;
/// Default scene height.
pub(crate) const H_SCENE: usize = 2;
/// Default editor height.
pub(crate) const H_EDITOR: usize = 15;
/// Width of display
pub(crate) fn w (&self) -> u16 {
self.size.w() as u16
}
pub(crate) fn w_sidebar (&self) -> u16 {
self.w() / if self.is_editing() { 16 } else { 8 } as u16
}
/// Width taken by all tracks.
pub(crate) fn w_tracks (&self) -> u16 {
self.tracks_with_sizes().last().map(|(_, _, _, x)|x as u16).unwrap_or(0)
}
/// Width available to display tracks.
pub(crate) fn w_tracks_area (&self) -> u16 {
self.w().saturating_sub(2 * self.w_sidebar())
}
/// Height of display
pub(crate) fn h (&self) -> u16 {
self.size.h() as u16
}
/// Height available to display track headers.
pub(crate) fn h_tracks_area (&self) -> u16 {
5
//self.h().saturating_sub(self.h_inputs() + self.h_outputs())
}
/// Height available to display tracks.
pub(crate) fn h_scenes_area (&self) -> u16 {
//15
self.h().saturating_sub(self.h_inputs() + self.h_outputs() + 11)
}
/// Height taken by all inputs.
pub(crate) fn h_inputs (&self) -> u16 {
1 + self.inputs_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0)
}
/// Height taken by all outputs.
pub(crate) fn h_outputs (&self) -> u16 {
1 + self.outputs_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0)
}
/// Height taken by all scenes.
pub(crate) fn h_scenes (&self) -> u16 {
self.scenes_with_sizes(self.is_editing(), Self::H_SCENE, Self::H_EDITOR).last()
.map(|(_, _, _, y)|y as u16).unwrap_or(0)
}
pub(crate) fn inputs_with_sizes (&self) -> impl PortsSizes<'_> {
let mut y = 0;
self.midi_ins.iter().enumerate().map(move|(i, input)|{
let height = 1 + input.conn().len();
let data = (i, input.name(), input.conn(), y, y + height);
y += height;
data
})
}
pub(crate) fn outputs_with_sizes (&self) -> impl PortsSizes<'_> {
let mut y = 0;
self.midi_outs.iter().enumerate().map(move|(i, output)|{
let height = 1 + output.conn().len();
let data = (i, output.name(), output.conn(), y, y + height);
y += height;
data
})
}
pub(crate) fn tracks_with_sizes (&self) -> impl TracksSizes<'_> {
let mut x = 0;
let editing = self.is_editing();
let active = match self.selected() {
Selection::Track(t) if editing => Some(t),
Selection::Clip(t, _) if editing => Some(t),
_ => None
};
let bigger = self.editor_w();
self.tracks().iter().enumerate().map(move |(index, track)|{
let width = if Some(index) == active.copied() { bigger } else { track.width.max(8) };
let data = (index, track, x, x + width);
x += width + Tek::TRACK_SPACING;
data
})
}
pub(crate) fn scenes_with_sizes (&self, editing: bool, height: usize, larger: usize)
-> impl ScenesSizes<'_>
{
let (selected_track, selected_scene) = match self.selected() {
Selection::Track(t) => (Some(*t), None),
Selection::Scene(s) => (None, Some(*s)),
Selection::Clip(t, s) => (Some(*t), Some(*s)),
_ => (None, None)
};
let mut y = 0;
self.scenes().iter().enumerate().map(move|(s, scene)|{
let active = editing && selected_track.is_some() && selected_scene == Some(s);
let height = if active { larger } else { height };
let data = (s, scene, y, y + height);
y += height;
data
})
}
pub fn update_clock (&self) {
ViewCache::update_clock(&self.view_cache, self.clock(), self.size.w() > 80)
}
pub fn view_transport (&self) -> impl Content<TuiOut> + use<'_> {
self.update_clock();
let cache = self.view_cache.read().unwrap();
view_transport(
self.clock.is_rolling(),
cache.bpm.view.clone(),
cache.beat.view.clone(),
cache.time.view.clone(),
)
}
pub fn view_status (&self) -> impl Content<TuiOut> + use<'_> {
self.update_clock();
let cache = self.view_cache.read().unwrap();
view_status(
self.selected.describe(&self.tracks, &self.scenes),
cache.sr.view.clone(),
cache.buf.view.clone(),
cache.lat.view.clone(),
)
}
pub fn view_modal (&self) -> impl Content<TuiOut> + use<'_> {
When::new(self.modal.is_some(), Bsp::b(
Fill::xy(Tui::fg_bg(Color::Rgb(64,64,64), Color::Rgb(32,32,32), "")),
Fixed::xy(30, 15, Tui::fg_bg(Color::Rgb(255,255,255), Color::Rgb(16,16,16), self.modal))
))
}
pub fn view_arranger (&self) -> impl Content<TuiOut> + use<'_> {
ArrangerView::new(self)
}
pub fn view_pool (&self) -> impl Content<TuiOut> + use<'_> {
self.pool().map(|p|Fixed::x(self.w_sidebar(), PoolView(self.is_editing(), p)))
}
}
/// Define a type alias for iterators of sized items (columns).
macro_rules! def_sizes_iter {
($Type:ident => $($Item:ty),+) => {
@ -577,8 +636,7 @@ fn view_status (
pub(crate) fn button_play_pause (playing: bool) -> impl Content<TuiOut> {
let compact = true;//self.is_editing();
Tui::bg(
if playing{Rgb(0,128,0)}else{Rgb(128,64,0)},
Tui::bg(if playing { Rgb(0, 128, 0) } else { Rgb(128, 64, 0) },
Either::new(compact,
Thunk::new(move||Fixed::x(9, Either::new(playing,
Tui::fg(Rgb(0, 255, 0), " PLAYING "),
@ -614,18 +672,15 @@ pub (crate) fn view_meter <'a> (label: &'a str, value: f32) -> impl Content<TuiO
}
pub(crate) fn view_meters (values: &[f32;2]) -> impl Content<TuiOut> + use<'_> {
Bsp::s(
format!("L/{:>+9.3}", values[0]),
format!("R/{:>+9.3}", values[1]),
)
let left = format!("L/{:>+9.3}", values[0]);
let right = format!("R/{:>+9.3}", values[1]);
Bsp::s(left, right)
}
pub(crate) fn wrap (
bg: Color, fg: Color, content: impl Content<TuiOut>
) -> impl Content<TuiOut> {
Bsp::e(Tui::fg_bg(bg, Reset, ""),
Bsp::w(Tui::fg_bg(bg, Reset, ""),
Tui::fg_bg(fg, bg, content)))
pub(crate) fn wrap (bg: Color, fg: Color, content: impl Content<TuiOut>) -> impl Content<TuiOut> {
let left = Tui::fg_bg(bg, Reset, "");
let right = Tui::fg_bg(bg, Reset, "");
Bsp::e(left, Bsp::w(right, Tui::fg_bg(fg, bg, content)))
}
pub(crate) fn button_2 <'a, K, L> (
@ -848,44 +903,3 @@ impl ViewCache {
}
}
}
impl Content<TuiOut> for Modal {
fn content (&self) -> impl Render<TuiOut> {
Bsp::b(
Repeat(" "),
Outer(true, Style::default().fg(Tui::g(96))).enclose(match self {
Self::Menu => self.view_menu().boxed(),
Self::Help => self.view_help().boxed(),
})
)
}
}
impl Modal {
fn view_menu (&self) -> impl Content<TuiOut> {
let options = ||["Projects", "Settings", "Help", "Quit"].iter();
let option = |a,i|Tui::fg(Color::Rgb(255,255,255), format!("{}", a));
Bsp::s(Tui::bold(true, "tek!"), Bsp::s("", Map::south(1, options, option)))
}
fn view_help (&self) -> impl Content<TuiOut> {
let bindings = ||TokenIter::new(include_str!("../../../config/keys_groovebox.edn"))
.filter_map(|x|if let Value::Exp(_, iter)=x.value{
Some(iter)
} else {
None
});
let binding = |mut binding: TokenIter, _|Bsp::e(
Tui::bold(true, Tui::fg(Color::Rgb(255,192,0), if let Some(Token { value: Value::Sym(key), .. }) = binding.next() {
Some(key.to_string())
} else {
None
})),
Bsp::e(" ", Tui::fg(Color::Rgb(255,255,255), if let Some(Token { value: Value::Key(command), .. }) = binding.next() {
Some(command.to_string())
} else {
None
})),
);
Bsp::s(Tui::bold(true, "Help"), Bsp::s("", Map::south(1, bindings, binding)))
}
}