mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
wip: try to figure out saner semantics for arranger render modes (of which there are 1)
This commit is contained in:
parent
66e8acc811
commit
274316ccdd
4 changed files with 140 additions and 149 deletions
|
|
@ -19,6 +19,57 @@ pub struct ArrangerTui {
|
||||||
pub perf: PerfModel,
|
pub perf: PerfModel,
|
||||||
pub show_pool: bool,
|
pub show_pool: bool,
|
||||||
}
|
}
|
||||||
|
impl ArrangerTui {
|
||||||
|
pub fn selected (&self) -> ArrangerSelection {
|
||||||
|
self.selected
|
||||||
|
}
|
||||||
|
pub fn selected_mut (&mut self) -> &mut ArrangerSelection {
|
||||||
|
&mut self.selected
|
||||||
|
}
|
||||||
|
pub fn activate (&mut self) -> Usually<()> {
|
||||||
|
if let ArrangerSelection::Scene(s) = self.selected {
|
||||||
|
for (t, track) in self.tracks.iter_mut().enumerate() {
|
||||||
|
let phrase = self.scenes[s].clips[t].clone();
|
||||||
|
if track.player.play_phrase.is_some() || phrase.is_some() {
|
||||||
|
track.player.enqueue_next(phrase.as_ref());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.clock().is_stopped() {
|
||||||
|
self.clock().play_from(Some(0))?;
|
||||||
|
}
|
||||||
|
} else if let ArrangerSelection::Clip(t, s) = self.selected {
|
||||||
|
let phrase = self.scenes()[s].clips[t].clone();
|
||||||
|
self.tracks_mut()[t].player.enqueue_next(phrase.as_ref());
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn selected_phrase (&self) -> Option<Arc<RwLock<Phrase>>> {
|
||||||
|
self.selected_scene()?.clips.get(self.selected.track()?)?.clone()
|
||||||
|
}
|
||||||
|
pub fn toggle_loop (&mut self) {
|
||||||
|
if let Some(phrase) = self.selected_phrase() {
|
||||||
|
phrase.write().unwrap().toggle_loop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn randomize_color (&mut self) {
|
||||||
|
match self.selected {
|
||||||
|
ArrangerSelection::Mix => {
|
||||||
|
self.color = ItemPalette::random()
|
||||||
|
},
|
||||||
|
ArrangerSelection::Track(t) => {
|
||||||
|
self.tracks_mut()[t].color = ItemPalette::random()
|
||||||
|
},
|
||||||
|
ArrangerSelection::Scene(s) => {
|
||||||
|
self.scenes_mut()[s].color = ItemPalette::random()
|
||||||
|
},
|
||||||
|
ArrangerSelection::Clip(t, s) => {
|
||||||
|
if let Some(phrase) = &self.scenes_mut()[s].clips[t] {
|
||||||
|
phrase.write().unwrap().color = ItemPalette::random();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
from_jack!(|jack| ArrangerTui {
|
from_jack!(|jack| ArrangerTui {
|
||||||
let clock = ClockModel::from(jack);
|
let clock = ClockModel::from(jack);
|
||||||
let phrase = Arc::new(RwLock::new(Phrase::new(
|
let phrase = Arc::new(RwLock::new(Phrase::new(
|
||||||
|
|
@ -43,23 +94,20 @@ from_jack!(|jack| ArrangerTui {
|
||||||
jack: jack.clone(),
|
jack: jack.clone(),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
impl ArrangerTui {
|
||||||
|
fn render_mode (state: &Self) -> impl Render<Tui> {
|
||||||
|
match state.mode {
|
||||||
|
ArrangerMode::H => todo!("horizontal arranger"),
|
||||||
|
ArrangerMode::V(factor) => Self::render_mode_v(state, factor),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
render!(<Tui>|self: ArrangerTui|{
|
render!(<Tui>|self: ArrangerTui|{
|
||||||
let arranger = ||lay!(|add|{
|
let arranger = ||lay!(|add|{
|
||||||
let color = self.color;
|
let color = self.color;
|
||||||
add(&Fill::wh(Tui::bg(color.darkest.rgb, ())))?;
|
add(&Fill::wh(Tui::bg(color.darkest.rgb, ())))?;
|
||||||
add(&Fill::wh(Lozenge(Style::default().fg(color.light.rgb).bg(color.darker.rgb))))?;
|
add(&Fill::wh(Lozenge(Style::default().fg(color.light.rgb).bg(color.darker.rgb))))?;
|
||||||
match self.mode {
|
add(&Self::render_mode(self))
|
||||||
ArrangerMode::H => todo!("horizontal arranger"),
|
|
||||||
ArrangerMode::V(factor) => add(&lay!(![
|
|
||||||
ArrangerVColSep::from(self),
|
|
||||||
ArrangerVRowSep::from((self, factor)),
|
|
||||||
col!(![
|
|
||||||
ArrangerVHead::from(self),
|
|
||||||
ArrangerVBody::from((self, factor)),
|
|
||||||
]),
|
|
||||||
ArrangerVCursor::from((self, factor)),
|
|
||||||
])),
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
let play = Fixed::wh(5, 2, PlayPause(self.clock.is_rolling()));
|
let play = Fixed::wh(5, 2, PlayPause(self.clock.is_rolling()));
|
||||||
let transport = TransportView::from((self, None, true));
|
let transport = TransportView::from((self, None, true));
|
||||||
|
|
@ -261,6 +309,7 @@ pub enum ArrangerMode {
|
||||||
/// Tracks are rows
|
/// Tracks are rows
|
||||||
H,
|
H,
|
||||||
}
|
}
|
||||||
|
render!(<Tui>|self: ArrangerMode|{});
|
||||||
|
|
||||||
/// Arranger display mode can be cycled
|
/// Arranger display mode can be cycled
|
||||||
impl ArrangerMode {
|
impl ArrangerMode {
|
||||||
|
|
@ -279,57 +328,6 @@ impl ArrangerMode {
|
||||||
fn any_size <E: Engine> (_: E::Size) -> Perhaps<E::Size>{
|
fn any_size <E: Engine> (_: E::Size) -> Perhaps<E::Size>{
|
||||||
Ok(Some([0.into(),0.into()].into()))
|
Ok(Some([0.into(),0.into()].into()))
|
||||||
}
|
}
|
||||||
impl ArrangerTui {
|
|
||||||
pub fn selected (&self) -> ArrangerSelection {
|
|
||||||
self.selected
|
|
||||||
}
|
|
||||||
pub fn selected_mut (&mut self) -> &mut ArrangerSelection {
|
|
||||||
&mut self.selected
|
|
||||||
}
|
|
||||||
pub fn activate (&mut self) -> Usually<()> {
|
|
||||||
if let ArrangerSelection::Scene(s) = self.selected {
|
|
||||||
for (t, track) in self.tracks.iter_mut().enumerate() {
|
|
||||||
let phrase = self.scenes[s].clips[t].clone();
|
|
||||||
if track.player.play_phrase.is_some() || phrase.is_some() {
|
|
||||||
track.player.enqueue_next(phrase.as_ref());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if self.clock().is_stopped() {
|
|
||||||
self.clock().play_from(Some(0))?;
|
|
||||||
}
|
|
||||||
} else if let ArrangerSelection::Clip(t, s) = self.selected {
|
|
||||||
let phrase = self.scenes()[s].clips[t].clone();
|
|
||||||
self.tracks_mut()[t].player.enqueue_next(phrase.as_ref());
|
|
||||||
};
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
pub fn selected_phrase (&self) -> Option<Arc<RwLock<Phrase>>> {
|
|
||||||
self.selected_scene()?.clips.get(self.selected.track()?)?.clone()
|
|
||||||
}
|
|
||||||
pub fn toggle_loop (&mut self) {
|
|
||||||
if let Some(phrase) = self.selected_phrase() {
|
|
||||||
phrase.write().unwrap().toggle_loop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn randomize_color (&mut self) {
|
|
||||||
match self.selected {
|
|
||||||
ArrangerSelection::Mix => {
|
|
||||||
self.color = ItemPalette::random()
|
|
||||||
},
|
|
||||||
ArrangerSelection::Track(t) => {
|
|
||||||
self.tracks_mut()[t].color = ItemPalette::random()
|
|
||||||
},
|
|
||||||
ArrangerSelection::Scene(s) => {
|
|
||||||
self.scenes_mut()[s].color = ItemPalette::random()
|
|
||||||
},
|
|
||||||
ArrangerSelection::Clip(t, s) => {
|
|
||||||
if let Some(phrase) = &self.scenes_mut()[s].clips[t] {
|
|
||||||
phrase.write().unwrap().color = ItemPalette::random();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//impl<T: ArrangerApi> Command<T> for ArrangerClipCommand {
|
//impl<T: ArrangerApi> Command<T> for ArrangerClipCommand {
|
||||||
//fn execute (self, state: &mut T) -> Perhaps<Self> {
|
//fn execute (self, state: &mut T) -> Perhaps<Self> {
|
||||||
|
|
|
||||||
|
|
@ -34,21 +34,16 @@ impl std::fmt::Debug for TransportTui {
|
||||||
pub struct TransportView {
|
pub struct TransportView {
|
||||||
color: ItemPalette,
|
color: ItemPalette,
|
||||||
focused: bool,
|
focused: bool,
|
||||||
|
|
||||||
sr: String,
|
sr: String,
|
||||||
bpm: String,
|
bpm: String,
|
||||||
ppq: String,
|
ppq: String,
|
||||||
beat: String,
|
beat: String,
|
||||||
|
|
||||||
global_sample: String,
|
global_sample: String,
|
||||||
global_second: String,
|
global_second: String,
|
||||||
|
|
||||||
started: bool,
|
started: bool,
|
||||||
|
|
||||||
current_sample: f64,
|
current_sample: f64,
|
||||||
current_second: f64,
|
current_second: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: HasClock> From<(&T, Option<ItemPalette>, bool)> for TransportView {
|
impl<T: HasClock> From<(&T, Option<ItemPalette>, bool)> for TransportView {
|
||||||
fn from ((state, color, focused): (&T, Option<ItemPalette>, bool)) -> Self {
|
fn from ((state, color, focused): (&T, Option<ItemPalette>, bool)) -> Self {
|
||||||
let clock = state.clock();
|
let clock = state.clock();
|
||||||
|
|
@ -84,19 +79,14 @@ impl<T: HasClock> From<(&T, Option<ItemPalette>, bool)> for TransportView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
render!(<Tui>|self: TransportView|{
|
render!(<Tui>|self: TransportView|{
|
||||||
|
|
||||||
let color = self.color;
|
let color = self.color;
|
||||||
|
|
||||||
struct Field<'a>(&'a str, &'a str, &'a ItemPalette);
|
struct Field<'a>(&'a str, &'a str, &'a ItemPalette);
|
||||||
render!(<Tui>|self: Field<'a>|row!([
|
render!(<Tui>|self: Field<'a>|row!([
|
||||||
Tui::fg_bg(self.2.lightest.rgb, self.2.darkest.rgb, Tui::bold(true, self.0)),
|
Tui::fg_bg(self.2.lightest.rgb, self.2.darkest.rgb, Tui::bold(true, self.0)),
|
||||||
Tui::fg_bg(self.2.lighter.rgb, self.2.darkest.rgb, "│"),
|
Tui::fg_bg(self.2.lighter.rgb, self.2.darkest.rgb, "│"),
|
||||||
Tui::fg_bg(self.2.lighter.rgb, self.2.base.rgb, format!("{:>10}", self.1)),
|
Tui::fg_bg(self.2.lighter.rgb, self.2.base.rgb, format!("{:>10}", self.1)),
|
||||||
]));
|
]));
|
||||||
|
|
||||||
Tui::bg(color.base.rgb, Fill::w(row!([
|
Tui::bg(color.base.rgb, Fill::w(row!([
|
||||||
//PlayPause(self.started), " ",
|
//PlayPause(self.started), " ",
|
||||||
col!([
|
col!([
|
||||||
|
|
@ -109,9 +99,7 @@ render!(<Tui>|self: TransportView|{
|
||||||
Field(" Smpl", format!("{:.1}k", self.current_sample).as_str(), &color),
|
Field(" Smpl", format!("{:.1}k", self.current_sample).as_str(), &color),
|
||||||
]),
|
]),
|
||||||
])))
|
])))
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
pub struct PlayPause(pub bool);
|
pub struct PlayPause(pub bool);
|
||||||
render!(<Tui>|self: PlayPause|Tui::bg(
|
render!(<Tui>|self: PlayPause|Tui::bg(
|
||||||
if self.0{Color::Rgb(0,128,0)}else{Color::Rgb(128,64,0)},
|
if self.0{Color::Rgb(0,128,0)}else{Color::Rgb(128,64,0)},
|
||||||
|
|
@ -127,7 +115,6 @@ render!(<Tui>|self: PlayPause|Tui::bg(
|
||||||
])))
|
])))
|
||||||
}))
|
}))
|
||||||
));
|
));
|
||||||
|
|
||||||
impl HasFocus for TransportTui {
|
impl HasFocus for TransportTui {
|
||||||
type Item = TransportFocus;
|
type Item = TransportFocus;
|
||||||
fn focused (&self) -> Self::Item {
|
fn focused (&self) -> Self::Item {
|
||||||
|
|
@ -137,7 +124,6 @@ impl HasFocus for TransportTui {
|
||||||
self.focus = to
|
self.focus = to
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Which item of the transport toolbar is focused
|
/// Which item of the transport toolbar is focused
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
pub enum TransportFocus {
|
pub enum TransportFocus {
|
||||||
|
|
@ -157,7 +143,6 @@ impl FocusWrap<TransportFocus> for TransportFocus {
|
||||||
lay!([corners, /*highlight,*/ *content])
|
lay!([corners, /*highlight,*/ *content])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FocusWrap<TransportFocus> for Option<TransportFocus> {
|
impl FocusWrap<TransportFocus> for Option<TransportFocus> {
|
||||||
fn wrap <'a, W: Render<Tui>> (self, focus: TransportFocus, content: &'a W)
|
fn wrap <'a, W: Render<Tui>> (self, focus: TransportFocus, content: &'a W)
|
||||||
-> impl Render<Tui> + 'a
|
-> impl Render<Tui> + 'a
|
||||||
|
|
@ -168,41 +153,34 @@ impl FocusWrap<TransportFocus> for Option<TransportFocus> {
|
||||||
lay!([corners, /*highlight,*/ *content])
|
lay!([corners, /*highlight,*/ *content])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait TransportControl<T>: HasClock + {
|
pub trait TransportControl<T>: HasClock + {
|
||||||
fn transport_focused (&self) -> Option<TransportFocus>;
|
fn transport_focused (&self) -> Option<TransportFocus>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TransportControl<TransportFocus> for TransportTui {
|
impl TransportControl<TransportFocus> for TransportTui {
|
||||||
fn transport_focused (&self) -> Option<TransportFocus> {
|
fn transport_focused (&self) -> Option<TransportFocus> {
|
||||||
Some(self.focus)
|
Some(self.focus)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum TransportCommand {
|
pub enum TransportCommand {
|
||||||
Focus(FocusCommand<TransportFocus>),
|
Focus(FocusCommand<TransportFocus>),
|
||||||
Clock(ClockCommand),
|
Clock(ClockCommand),
|
||||||
}
|
}
|
||||||
|
|
||||||
command!(|self:TransportCommand,state:TransportTui|match self {
|
command!(|self:TransportCommand,state:TransportTui|match self {
|
||||||
//Self::Focus(cmd) => cmd.execute(state)?.map(Self::Focus),
|
//Self::Focus(cmd) => cmd.execute(state)?.map(Self::Focus),
|
||||||
Self::Clock(cmd) => cmd.execute(state)?.map(Self::Clock),
|
Self::Clock(cmd) => cmd.execute(state)?.map(Self::Clock),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
});
|
});
|
||||||
|
|
||||||
//command!(|self:TransportFocus,state:TransportTui|{
|
//command!(|self:TransportFocus,state:TransportTui|{
|
||||||
//if let FocusCommand::Set(to) = self { state.set_focused(to); }
|
//if let FocusCommand::Set(to) = self { state.set_focused(to); }
|
||||||
//Ok(None)
|
//Ok(None)
|
||||||
//});
|
//});
|
||||||
|
|
||||||
impl InputToCommand<Tui, TransportTui> for TransportCommand {
|
impl InputToCommand<Tui, TransportTui> for TransportCommand {
|
||||||
fn input_to_command (state: &TransportTui, input: &TuiInput) -> Option<Self> {
|
fn input_to_command (state: &TransportTui, input: &TuiInput) -> Option<Self> {
|
||||||
to_transport_command(state, input)
|
to_transport_command(state, input)
|
||||||
.or_else(||to_focus_command(input).map(TransportCommand::Focus))
|
.or_else(||to_focus_command(input).map(TransportCommand::Focus))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_transport_command <T, U> (state: &T, input: &TuiInput) -> Option<TransportCommand>
|
pub fn to_transport_command <T, U> (state: &T, input: &TuiInput) -> Option<TransportCommand>
|
||||||
where
|
where
|
||||||
T: TransportControl<U>,
|
T: TransportControl<U>,
|
||||||
|
|
@ -246,7 +224,6 @@ where
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_bpm_command (input: &TuiInput, bpm: f64) -> Option<TransportCommand> {
|
fn to_bpm_command (input: &TuiInput, bpm: f64) -> Option<TransportCommand> {
|
||||||
Some(match input.event() {
|
Some(match input.event() {
|
||||||
key_pat!(Char(',')) => Clock(SetBpm(bpm - 1.0)),
|
key_pat!(Char(',')) => Clock(SetBpm(bpm - 1.0)),
|
||||||
|
|
@ -256,7 +233,6 @@ fn to_bpm_command (input: &TuiInput, bpm: f64) -> Option<TransportCommand> {
|
||||||
_ => return None,
|
_ => return None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_quant_command (input: &TuiInput, quant: &Quantize) -> Option<TransportCommand> {
|
fn to_quant_command (input: &TuiInput, quant: &Quantize) -> Option<TransportCommand> {
|
||||||
Some(match input.event() {
|
Some(match input.event() {
|
||||||
key_pat!(Char(',')) => Clock(SetQuant(quant.prev())),
|
key_pat!(Char(',')) => Clock(SetQuant(quant.prev())),
|
||||||
|
|
@ -266,7 +242,6 @@ fn to_quant_command (input: &TuiInput, quant: &Quantize) -> Option<TransportComm
|
||||||
_ => return None,
|
_ => return None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_sync_command (input: &TuiInput, sync: &LaunchSync) -> Option<TransportCommand> {
|
fn to_sync_command (input: &TuiInput, sync: &LaunchSync) -> Option<TransportCommand> {
|
||||||
Some(match input.event() {
|
Some(match input.event() {
|
||||||
key_pat!(Char(',')) => Clock(SetSync(sync.prev())),
|
key_pat!(Char(',')) => Clock(SetSync(sync.prev())),
|
||||||
|
|
@ -276,7 +251,6 @@ fn to_sync_command (input: &TuiInput, sync: &LaunchSync) -> Option<TransportComm
|
||||||
_ => return None,
|
_ => return None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_seek_command (input: &TuiInput) -> Option<TransportCommand> {
|
fn to_seek_command (input: &TuiInput) -> Option<TransportCommand> {
|
||||||
Some(match input.event() {
|
Some(match input.event() {
|
||||||
key_pat!(Char(',')) => todo!("transport seek bar"),
|
key_pat!(Char(',')) => todo!("transport seek bar"),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,48 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
const HEADER_H: u16 = 3;
|
const HEADER_H: u16 = 3;
|
||||||
const SCENES_W_OFFSET: u16 = 3;
|
const SCENES_W_OFFSET: u16 = 3;
|
||||||
|
impl ArrangerTui {
|
||||||
|
pub fn render_mode_v (state: &ArrangerTui, factor: usize) -> impl Render<Tui> {
|
||||||
|
lay!([
|
||||||
|
ArrangerVColSep::from(state), ArrangerVRowSep::from((state, factor)),
|
||||||
|
col!([ArrangerVHead::from(state), ArrangerVBody::from((state, factor))]),
|
||||||
|
ArrangerVCursor::from((state, factor)),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub struct ArrangerVColSep { fg: Color, cols: Vec<(usize, usize)>, scenes_w: u16, }
|
||||||
|
from!(|state:&ArrangerTui|ArrangerVColSep = Self {
|
||||||
|
fg: TuiTheme::separator_fg(false),
|
||||||
|
cols: ArrangerTrack::widths(state.tracks()),
|
||||||
|
scenes_w: SCENES_W_OFFSET + ArrangerScene::longest_name(state.scenes()) as u16,
|
||||||
|
});
|
||||||
|
render!(<Tui>|self: ArrangerVColSep|render(move|to: &mut TuiOutput|{
|
||||||
|
let style = Some(Style::default().fg(self.fg));
|
||||||
|
Ok(for x in self.cols.iter().map(|col|col.1) {
|
||||||
|
let x = self.scenes_w + to.area().x() + x as u16;
|
||||||
|
for y in to.area().y()..to.area().y2() {
|
||||||
|
to.blit(&"▎", x, y, style);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}));
|
||||||
|
pub struct ArrangerVRowSep { fg: Color, rows: Vec<(usize, usize)>, }
|
||||||
|
from!(|args:(&ArrangerTui, usize)|ArrangerVRowSep = Self {
|
||||||
|
fg: TuiTheme::separator_fg(false),
|
||||||
|
rows: ArrangerScene::ppqs(args.0.scenes(), args.1),
|
||||||
|
});
|
||||||
|
render!(<Tui>|self: ArrangerVRowSep|render(move|to: &mut TuiOutput|{
|
||||||
|
Ok(for y in self.rows.iter().map(|row|row.1) {
|
||||||
|
let y = to.area().y() + (y / PPQ) as u16 + 1;
|
||||||
|
if y >= to.buffer.area.height { break }
|
||||||
|
for x in to.area().x()..to.area().x2().saturating_sub(2) {
|
||||||
|
if x < to.buffer.area.x && y < to.buffer.area.y {
|
||||||
|
let cell = to.buffer.get_mut(x, y);
|
||||||
|
cell.modifier = Modifier::UNDERLINED;
|
||||||
|
cell.underline_color = self.fg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}));
|
||||||
pub struct ArrangerVHead<'a> {
|
pub struct ArrangerVHead<'a> {
|
||||||
scenes_w: u16,
|
scenes_w: u16,
|
||||||
timebase: &'a Arc<Timebase>,
|
timebase: &'a Arc<Timebase>,
|
||||||
|
|
@ -46,74 +88,34 @@ impl<'a> ArrangerVHead<'a> {
|
||||||
}
|
}
|
||||||
/// beats elapsed
|
/// beats elapsed
|
||||||
fn format_elapsed (track: &ArrangerTrack, timebase: &Arc<Timebase>) -> impl Render<Tui> {
|
fn format_elapsed (track: &ArrangerTrack, timebase: &Arc<Timebase>) -> impl Render<Tui> {
|
||||||
|
let mut result = String::new();
|
||||||
if let Some((_, Some(phrase))) = track.player.play_phrase().as_ref() {
|
if let Some((_, Some(phrase))) = track.player.play_phrase().as_ref() {
|
||||||
let length = phrase.read().unwrap().length;
|
let length = phrase.read().unwrap().length;
|
||||||
let elapsed = track.player.pulses_since_start().unwrap();
|
let elapsed = track.player.pulses_since_start().unwrap();
|
||||||
let elapsed = timebase.format_beats_1_short(
|
let elapsed = timebase.format_beats_1_short(
|
||||||
(elapsed as usize % length) as f64
|
(elapsed as usize % length) as f64
|
||||||
);
|
);
|
||||||
format!("+{elapsed:>}")
|
result = format!("+{elapsed:>}")
|
||||||
} else {
|
|
||||||
String::new()
|
|
||||||
}
|
}
|
||||||
|
result
|
||||||
}
|
}
|
||||||
/// beats until switchover
|
/// beats until switchover
|
||||||
fn format_until_next (track: &ArrangerTrack, current: &Arc<Moment>)
|
fn format_until_next (track: &ArrangerTrack, current: &Arc<Moment>)
|
||||||
-> Option<impl Render<Tui>>
|
-> Option<impl Render<Tui>>
|
||||||
{
|
{
|
||||||
let timebase = ¤t.timebase;
|
let timebase = ¤t.timebase;
|
||||||
track.player.next_phrase().as_ref().map(|(t, _)|{
|
let mut result = String::new();
|
||||||
|
if let Some((t, _)) = track.player.next_phrase().as_ref() {
|
||||||
let target = t.pulse.get();
|
let target = t.pulse.get();
|
||||||
let current = current.pulse.get();
|
let current = current.pulse.get();
|
||||||
if target > current {
|
if target > current {
|
||||||
let remaining = target - current;
|
let remaining = target - current;
|
||||||
format!("-{:>}", timebase.format_beats_0_short(remaining))
|
result = format!("-{:>}", timebase.format_beats_0_short(remaining))
|
||||||
} else {
|
|
||||||
String::new()
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
Some(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub struct ArrangerVColSep {
|
|
||||||
cols: Vec<(usize, usize)>,
|
|
||||||
scenes_w: u16,
|
|
||||||
sep_fg: Color,
|
|
||||||
}
|
|
||||||
from!(|state:&ArrangerTui|ArrangerVColSep = Self {
|
|
||||||
cols: ArrangerTrack::widths(state.tracks()),
|
|
||||||
scenes_w: SCENES_W_OFFSET + ArrangerScene::longest_name(state.scenes()) as u16,
|
|
||||||
sep_fg: TuiTheme::separator_fg(false),
|
|
||||||
});
|
|
||||||
render!(<Tui>|self: ArrangerVColSep|render(move|to: &mut TuiOutput|{
|
|
||||||
let style = Some(Style::default().fg(self.sep_fg));
|
|
||||||
Ok(for x in self.cols.iter().map(|col|col.1) {
|
|
||||||
let x = self.scenes_w + to.area().x() + x as u16;
|
|
||||||
for y in to.area().y()..to.area().y2() {
|
|
||||||
to.blit(&"▎", x, y, style);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}));
|
|
||||||
pub struct ArrangerVRowSep {
|
|
||||||
rows: Vec<(usize, usize)>,
|
|
||||||
sep_fg: Color,
|
|
||||||
}
|
|
||||||
from!(|args:(&ArrangerTui, usize)|ArrangerVRowSep = Self {
|
|
||||||
rows: ArrangerScene::ppqs(args.0.scenes(), args.1),
|
|
||||||
sep_fg: TuiTheme::separator_fg(false),
|
|
||||||
});
|
|
||||||
render!(<Tui>|self: ArrangerVRowSep|render(move|to: &mut TuiOutput|{
|
|
||||||
Ok(for y in self.rows.iter().map(|row|row.1) {
|
|
||||||
let y = to.area().y() + (y / PPQ) as u16 + 1;
|
|
||||||
if y >= to.buffer.area.height { break }
|
|
||||||
for x in to.area().x()..to.area().x2().saturating_sub(2) {
|
|
||||||
if x < to.buffer.area.x && y < to.buffer.area.y {
|
|
||||||
let cell = to.buffer.get_mut(x, y);
|
|
||||||
cell.modifier = Modifier::UNDERLINED;
|
|
||||||
cell.underline_color = self.sep_fg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}));
|
|
||||||
pub struct ArrangerVCursor {
|
pub struct ArrangerVCursor {
|
||||||
cols: Vec<(usize, usize)>,
|
cols: Vec<(usize, usize)>,
|
||||||
rows: Vec<(usize, usize)>,
|
rows: Vec<(usize, usize)>,
|
||||||
|
|
@ -139,9 +141,9 @@ render!(<Tui>|self: ArrangerVCursor|render(move|to: &mut TuiOutput|{
|
||||||
area.w(), (self.rows[s].0 / PPQ) as u16
|
area.w(), (self.rows[s].0 / PPQ) as u16
|
||||||
];
|
];
|
||||||
let get_clip_area = |t: usize, s: usize| [
|
let get_clip_area = |t: usize, s: usize| [
|
||||||
self.scenes_w + area.x() + self.cols[t].1 as u16,
|
(self.scenes_w + area.x() + self.cols[t].1 as u16).saturating_sub(1),
|
||||||
HEADER_H + area.y() + (self.rows[s].1/PPQ) as u16,
|
HEADER_H + area.y() + (self.rows[s].1/PPQ) as u16,
|
||||||
self.cols[t].0 as u16,
|
self.cols[t].0 as u16 + 2,
|
||||||
(self.rows[s].0 / PPQ) as u16
|
(self.rows[s].0 / PPQ) as u16
|
||||||
];
|
];
|
||||||
let mut track_area: Option<[u16;4]> = None;
|
let mut track_area: Option<[u16;4]> = None;
|
||||||
|
|
@ -177,21 +179,20 @@ render!(<Tui>|self: ArrangerVCursor|render(move|to: &mut TuiOutput|{
|
||||||
to.render_in(if let Some(clip_area) = clip_area { clip_area }
|
to.render_in(if let Some(clip_area) = clip_area { clip_area }
|
||||||
else if let Some(track_area) = track_area { track_area.clip_h(HEADER_H) }
|
else if let Some(track_area) = track_area { track_area.clip_h(HEADER_H) }
|
||||||
else if let Some(scene_area) = scene_area { scene_area.clip_w(self.scenes_w) }
|
else if let Some(scene_area) = scene_area { scene_area.clip_w(self.scenes_w) }
|
||||||
else { area.clip_w(self.scenes_w).clip_h(HEADER_H) }, &CORNERS)?
|
else { area.clip_w(self.scenes_w).clip_h(HEADER_H) }, &RETICLE)?
|
||||||
})
|
})
|
||||||
}));
|
}));
|
||||||
|
|
||||||
pub struct ArrangerVBody<'a> {
|
pub struct ArrangerVBody<'a> {
|
||||||
size: &'a Measure<Tui>,
|
size: &'a Measure<Tui>,
|
||||||
scenes: &'a Vec<ArrangerScene>,
|
scenes: &'a Vec<ArrangerScene>,
|
||||||
tracks: &'a Vec<ArrangerTrack>,
|
tracks: &'a Vec<ArrangerTrack>,
|
||||||
rows: Vec<(usize, usize)>,
|
rows: Vec<(usize, usize)>,
|
||||||
}
|
}
|
||||||
from!(<'a>|args:(&'a ArrangerTui, usize)|ArrangerVBody<'a> = Self {
|
from!(<'a>|args:(&'a ArrangerTui, usize)|ArrangerVBody<'a> = Self {
|
||||||
size: &args.0.size,
|
size: &args.0.size,
|
||||||
scenes: &args.0.scenes,
|
scenes: &args.0.scenes,
|
||||||
tracks: &args.0.tracks,
|
tracks: &args.0.tracks,
|
||||||
rows: ArrangerScene::ppqs(args.0.scenes(), args.1),
|
rows: ArrangerScene::ppqs(args.0.scenes(), args.1),
|
||||||
});
|
});
|
||||||
render!(<Tui>|self: ArrangerVBody<'a>|Fixed::h(
|
render!(<Tui>|self: ArrangerVBody<'a>|Fixed::h(
|
||||||
(self.size.h() as u16).saturating_sub(HEADER_H),
|
(self.size.h() as u16).saturating_sub(HEADER_H),
|
||||||
|
|
|
||||||
|
|
@ -206,6 +206,16 @@ border! {
|
||||||
const N0: &'static str = "⎴";
|
const N0: &'static str = "⎴";
|
||||||
const S0: &'static str = "⎵";
|
const S0: &'static str = "⎵";
|
||||||
fn style (&self) -> Option<Style> { Some(self.0) }
|
fn style (&self) -> Option<Style> { Some(self.0) }
|
||||||
|
},
|
||||||
|
Reticle {
|
||||||
|
"⎡" "" "⎤"
|
||||||
|
"" ""
|
||||||
|
"⎣" "" "⎦"
|
||||||
|
const W0: &'static str = "╟";
|
||||||
|
const E0: &'static str = "╢";
|
||||||
|
const N0: &'static str = "┯";
|
||||||
|
const S0: &'static str = "┷";
|
||||||
|
fn style (&self) -> Option<Style> { Some(self.0) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -216,3 +226,11 @@ pub const CORNERS: Brackets = Brackets(Style {
|
||||||
add_modifier: Modifier::empty(),
|
add_modifier: Modifier::empty(),
|
||||||
sub_modifier: Modifier::DIM
|
sub_modifier: Modifier::DIM
|
||||||
});
|
});
|
||||||
|
|
||||||
|
pub const RETICLE: Reticle = Reticle(Style {
|
||||||
|
fg: Some(Color::Rgb(96, 255, 32)),
|
||||||
|
bg: None,
|
||||||
|
underline_color: None,
|
||||||
|
add_modifier: Modifier::empty(),
|
||||||
|
sub_modifier: Modifier::DIM
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue