mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
This commit is contained in:
parent
5696cbbebb
commit
2fd7d7b39f
18 changed files with 355 additions and 360 deletions
|
|
@ -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)))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue