mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
make transport focusable
This commit is contained in:
parent
a7a798b484
commit
449615eea8
14 changed files with 220 additions and 237 deletions
|
|
@ -24,6 +24,8 @@ handle!{
|
|||
|
||||
fn handle_focused (state: &mut App, e: &AppEvent) -> Usually<bool> {
|
||||
match state.section {
|
||||
AppSection::Transport =>
|
||||
Ok(false),
|
||||
AppSection::Arranger =>
|
||||
handle_keymap(state, e, crate::control::arranger::KEYMAP_ARRANGER),
|
||||
AppSection::Sequencer =>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
//#![feature(fn_traits)]
|
||||
//#![feature(unboxed_closures)]
|
||||
#![allow(macro_expanded_macro_exports_accessed_by_absolute_paths)]
|
||||
#![allow(ambiguous_glob_reexports)]
|
||||
|
||||
extern crate clap;
|
||||
extern crate jack as _jack;
|
||||
|
|
|
|||
12
src/model.rs
12
src/model.rs
|
|
@ -217,21 +217,23 @@ impl App {
|
|||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
pub enum AppSection { Arranger, Sequencer, Chain, }
|
||||
pub enum AppSection { Transport, Arranger, Sequencer, Chain, }
|
||||
impl Default for AppSection { fn default () -> Self { Self::Arranger } }
|
||||
impl AppSection {
|
||||
pub fn prev (&mut self) {
|
||||
*self = match self {
|
||||
Self::Arranger => Self::Chain,
|
||||
Self::Transport => Self::Chain,
|
||||
Self::Arranger => Self::Transport,
|
||||
Self::Sequencer => Self::Arranger,
|
||||
Self::Chain => Self::Sequencer,
|
||||
Self::Chain => Self::Sequencer,
|
||||
}
|
||||
}
|
||||
pub fn next (&mut self) {
|
||||
*self = match self {
|
||||
Self::Arranger => Self::Sequencer,
|
||||
Self::Transport => Self::Arranger,
|
||||
Self::Arranger => Self::Sequencer,
|
||||
Self::Sequencer => Self::Chain,
|
||||
Self::Chain => Self::Arranger,
|
||||
Self::Chain => Self::Transport,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,36 @@ pub struct Sampler {
|
|||
pub buffer: Vec<Vec<f32>>,
|
||||
pub output_gain: f32
|
||||
}
|
||||
render!(Sampler |self, buf, area| {
|
||||
let Rect { x, y, height, .. } = area;
|
||||
let style = Style::default().gray();
|
||||
let title = format!(" {} ({})", self.name, self.voices.len());
|
||||
title.blit(buf, x+1, y, Some(style.white().bold().not_dim()))?;
|
||||
let mut width = title.len() + 2;
|
||||
for (i, (note, sample)) in self.samples.iter().enumerate() {
|
||||
let style = if i == self.cursor.0 {
|
||||
Style::default().green()
|
||||
} else {
|
||||
Style::default()
|
||||
};
|
||||
let i = i as u16;
|
||||
let y1 = y+1+i;
|
||||
if y1 >= y + height {
|
||||
break
|
||||
}
|
||||
if i as usize == self.cursor.0 {
|
||||
"⯈".blit(buf, x+1, y1, Some(style.bold()))?;
|
||||
}
|
||||
let label1 = format!("{note:3} {:12}", sample.name);
|
||||
let label2 = format!("{:>6} {:>6} +0.0", sample.start, sample.end);
|
||||
label1.blit(buf, x+2, y1, Some(style.bold()))?;
|
||||
label2.blit(buf, x+3+label1.len()as u16, y1, Some(style))?;
|
||||
width = width.max(label1.len() + label2.len() + 4);
|
||||
}
|
||||
let height = ((2 + self.samples.len()) as u16).min(height);
|
||||
Ok(Rect { x, y, width: (width as u16).min(area.width), height })
|
||||
});
|
||||
|
||||
render!(Sampler = crate::view::sampler::render);
|
||||
handle!(Sampler = crate::control::sampler::handle);
|
||||
//jack!(Sampler {
|
||||
//process = Sampler::process,
|
||||
|
|
@ -184,3 +212,48 @@ impl Iterator for Voice {
|
|||
None
|
||||
}
|
||||
}
|
||||
|
||||
//fn render_table (
|
||||
//self: &mut Sampler,
|
||||
//stdout: &mut Stdout,
|
||||
//offset: (u16, u16),
|
||||
//) -> Result<(), Box<dyn Error>> {
|
||||
//let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row);
|
||||
//stdout.queue(move_to(0, 3))?.queue(
|
||||
//Print(" Name Rate Trigger Route")
|
||||
//)?;
|
||||
//for (i, sample) in self.samples.lock().unwrap().iter().enumerate() {
|
||||
//let row = 4 + i as u16;
|
||||
//for (j, (column, field)) in [
|
||||
//(0, format!(" {:7} ", sample.name)),
|
||||
//(9, format!(" {:.1}Hz ", sample.rate)),
|
||||
//(18, format!(" MIDI C{} {} ", sample.trigger.0, sample.trigger.1)),
|
||||
//(33, format!(" {:.1}dB -> Output ", sample.gain)),
|
||||
//(50, format!(" {} ", sample.playing.unwrap_or(0))),
|
||||
//].into_iter().enumerate() {
|
||||
//stdout.queue(move_to(column, row))?;
|
||||
//if self.selected_sample == i && self.selected_column == j {
|
||||
//stdout.queue(PrintStyledContent(field.to_string().bold().reverse()))?;
|
||||
//} else {
|
||||
//stdout.queue(PrintStyledContent(field.to_string().bold()))?;
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
//Ok(())
|
||||
//}
|
||||
|
||||
//fn render_meters (
|
||||
//self: &mut Sampler,
|
||||
//stdout: &mut Stdout,
|
||||
//offset: (u16, u16),
|
||||
//) -> Result<(), Box<dyn Error>> {
|
||||
//let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row);
|
||||
//for (i, sample) in self.samples.lock().iter().enumerate() {
|
||||
//let row = 4 + i as u16;
|
||||
//stdout.queue(move_to(32, row))?.queue(
|
||||
//PrintStyledContent("▁".green())
|
||||
//)?;
|
||||
//}
|
||||
//Ok(())
|
||||
//}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,17 +20,6 @@ impl App {
|
|||
Ok(&mut self.tracks[self.track_cursor - 1])
|
||||
}
|
||||
|
||||
fn add_track_with_cb (
|
||||
&mut self, name: Option<&str>, init: impl FnOnce(&Client, &mut Track)->Usually<()>,
|
||||
) -> Usually<&mut Track> {
|
||||
let name = name.ok_or_else(||self.new_track_name())?;
|
||||
let mut track = Track::new(&name, None, None)?;
|
||||
init(self.client(), &mut track)?;
|
||||
self.tracks.push(track);
|
||||
self.track_cursor = self.tracks.len();
|
||||
Ok(&mut self.tracks[self.track_cursor - 1])
|
||||
}
|
||||
|
||||
pub fn track (&self) -> Option<(usize, &Track)> {
|
||||
match self.track_cursor { 0 => None, _ => {
|
||||
let id = self.track_cursor as usize - 1;
|
||||
|
|
@ -61,6 +50,7 @@ pub struct Track {
|
|||
pub sequence: Option<usize>,
|
||||
/// Output from current sequence.
|
||||
pub midi_out: Option<Port<MidiOut>>,
|
||||
/// MIDI output buffer
|
||||
midi_out_buf: Vec<Vec<Vec<u8>>>,
|
||||
/// Device chain
|
||||
pub devices: Vec<JackDevice>,
|
||||
|
|
@ -74,6 +64,7 @@ pub struct Track {
|
|||
/// Highlight keys on piano roll.
|
||||
pub notes_out: [bool;128],
|
||||
}
|
||||
|
||||
impl Track {
|
||||
fn new (
|
||||
name: &str,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
pub mod chain;
|
||||
pub mod arranger;
|
||||
pub mod sampler;
|
||||
pub mod sequencer;
|
||||
pub mod transport;
|
||||
pub mod plugin;
|
||||
pub mod border;
|
||||
pub mod theme;
|
||||
|
||||
pub use self::border::*;
|
||||
pub use self::theme::*;
|
||||
pub use self::transport::TransportView;
|
||||
pub use self::arranger::*;
|
||||
pub use self::chain::ChainView;
|
||||
|
|
|
|||
|
|
@ -24,37 +24,6 @@ impl<'a> ArrangerView<'a> {
|
|||
vertical
|
||||
}
|
||||
}
|
||||
fn track_names (&self) -> Vec<&'a str> {
|
||||
let mut track_names = vec!["Mix"];
|
||||
for track in self.tracks.iter() {
|
||||
track_names.push(track.name.as_str());
|
||||
}
|
||||
track_names
|
||||
}
|
||||
fn track_widths (&self) -> Vec<u16> {
|
||||
self.track_names()
|
||||
.iter()
|
||||
.map(|name|(name.len() as u16).max(10) + 3)
|
||||
.collect()
|
||||
}
|
||||
fn bg_color_lo (&self) -> Color {
|
||||
if self.focused && self.entered {
|
||||
Color::Rgb(25, 60, 15)
|
||||
} else if self.focused {
|
||||
Color::Rgb(20, 45, 5)
|
||||
} else {
|
||||
Color::Reset
|
||||
}
|
||||
}
|
||||
fn bg_color_hi (&self) -> Color {
|
||||
if self.focused && self.entered {
|
||||
Color::Rgb(30, 90, 25)
|
||||
} else if self.focused {
|
||||
Color::Rgb(25, 60, 15)
|
||||
} else {
|
||||
Color::Reset
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Render for ArrangerView<'a> {
|
||||
|
|
@ -64,7 +33,7 @@ impl<'a> Render for ArrangerView<'a> {
|
|||
} else {
|
||||
self.tracks.len() as u16 * 2
|
||||
});
|
||||
fill_bg(buf, area, self.bg_color_lo());
|
||||
fill_bg(buf, area, Nord::bg_lo(self.focused, self.entered));
|
||||
area = if self.vertical {
|
||||
self.draw_vertical(buf, area)
|
||||
} else {
|
||||
|
|
@ -73,7 +42,7 @@ impl<'a> Render for ArrangerView<'a> {
|
|||
if self.focused {
|
||||
//HELP.blit(buf, area.x + 2, area.y + area.height - 1, Some(Style::default().dim()))?;
|
||||
if self.entered {
|
||||
QuarterV(Style::default().green().dim()).draw(buf, area)?;
|
||||
Corners(Style::default().green().not_dim()).draw(buf, area)?;
|
||||
}
|
||||
}
|
||||
Ok(area)
|
||||
|
|
@ -82,45 +51,6 @@ impl<'a> Render for ArrangerView<'a> {
|
|||
|
||||
impl<'a> ArrangerView<'a> {
|
||||
|
||||
fn vertical_clips (&self, buf: &mut Buffer, x: u16, y: u16, height: u16, track: usize) -> Usually<u16> {
|
||||
let mut index = 0;
|
||||
let mut width = 10;
|
||||
loop {
|
||||
if index >= self.scenes.len() {
|
||||
break
|
||||
}
|
||||
if y + index as u16 >= height {
|
||||
break
|
||||
}
|
||||
width = 10.max(if let Some(scene) = self.scenes.get(index) {
|
||||
let hi = (track + 1 == self.cursor.0) &&
|
||||
(index + 1 == self.cursor.1);
|
||||
let style = Some(highlight(self.focused, hi));
|
||||
let clip = scene.clips.get(track);
|
||||
let index = index as u16;
|
||||
let label = if let Some(Some(clip)) = clip {
|
||||
if let Some(phrase) = self.tracks[track].phrases.get(*clip) {
|
||||
format!("{} {}", if self.tracks[track].sequence == Some(*clip) {
|
||||
""
|
||||
} else {
|
||||
" "
|
||||
}, phrase.name)
|
||||
} else {
|
||||
format!(" ??? ")
|
||||
}
|
||||
} else {
|
||||
format!(" ·········")
|
||||
};
|
||||
label.blit(buf, x, y + index, style)?;
|
||||
label.len() as u16
|
||||
} else {
|
||||
0u16
|
||||
});
|
||||
index = index + 1;
|
||||
}
|
||||
Ok(width)
|
||||
}
|
||||
|
||||
fn draw_vertical (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||
|
||||
return Split::right([
|
||||
|
|
@ -140,7 +70,7 @@ impl<'a> ArrangerView<'a> {
|
|||
"⯈".blit(buf, x+1, y, style)?;
|
||||
scene.name.blit(buf, x + 2, y, style)?;
|
||||
}
|
||||
let width = 2+self.scenes.iter()
|
||||
let width = 2 + self.scenes.iter()
|
||||
.map(|x|&x.name).fold(0, |x,y|x.max(y.len() as u16+1));
|
||||
Ok(Rect { width, ..area })
|
||||
},
|
||||
|
|
@ -180,20 +110,13 @@ impl<'a> ArrangerView<'a> {
|
|||
//width = width.max(2label.len() as u16 + 3);
|
||||
}
|
||||
if track_index + 1 == self.cursor.0 {
|
||||
let bg = Nord::bg_hi(self.focused, self.entered);
|
||||
if self.cursor.1 == 0 {
|
||||
let y = area.y;
|
||||
fill_bg(buf, Rect { x, y, width, height: 1 }, if self.focused {
|
||||
Color::Rgb(30, 90, 25)
|
||||
} else {
|
||||
Color::Rgb(25, 60, 15)
|
||||
})
|
||||
fill_bg(buf, Rect { x, y, width, height: 1 }, bg)
|
||||
} else {
|
||||
let y = 1 + area.y + 2 * (self.cursor.1 as u16 - 1);
|
||||
fill_bg(buf, Rect { x, y, width, height: 2 }, if self.focused {
|
||||
Color::Rgb(30, 90, 25)
|
||||
} else {
|
||||
Color::Rgb(25, 60, 15)
|
||||
})
|
||||
fill_bg(buf, Rect { x, y, width, height: 2 }, bg)
|
||||
}
|
||||
};
|
||||
x = x + width as u16;
|
||||
|
|
@ -202,9 +125,7 @@ impl<'a> ArrangerView<'a> {
|
|||
}
|
||||
]).render(buf, area);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ArrangerView<'a> {
|
||||
fn draw_horizontal (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||
let style1 = Some(Style::default().bold().not_dim());
|
||||
let style2 = Some(Style::default().not_dim());
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ pub struct LozengeDotted(pub Style);
|
|||
pub struct Quarter(pub Style);
|
||||
pub struct QuarterV(pub Style);
|
||||
pub struct Chamfer(pub Style);
|
||||
pub struct Corners(pub Style);
|
||||
|
||||
border! {
|
||||
Lozenge {
|
||||
|
|
@ -148,5 +149,13 @@ border! {
|
|||
fn style (&self) -> Option<Style> {
|
||||
Some(self.0)
|
||||
}
|
||||
},
|
||||
Corners {
|
||||
"🬆" "" "🬊"
|
||||
"" ""
|
||||
"🬱" "" "🬵"
|
||||
fn style (&self) -> Option<Style> {
|
||||
Some(self.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ pub struct ChainView<'a> {
|
|||
pub track: Option<&'a Track>,
|
||||
pub direction: Direction,
|
||||
pub focused: bool,
|
||||
pub entered: bool,
|
||||
}
|
||||
|
||||
impl<'a> ChainView<'a> {
|
||||
|
|
@ -18,6 +19,7 @@ impl<'a> ChainView<'a> {
|
|||
pub fn new (app: &'a App, direction: Direction) -> Self {
|
||||
Self {
|
||||
direction,
|
||||
entered: app.entered,
|
||||
focused: app.section == AppSection::Chain,
|
||||
track: match app.track_cursor {
|
||||
0 => None,
|
||||
|
|
@ -34,14 +36,18 @@ impl<'a> Render for ChainView<'a> {
|
|||
Direction::Down => area.width = area.width.min(40),
|
||||
Direction::Right => area.width = area.width.min(10),
|
||||
}
|
||||
fill_bg(buf, area, if self.focused { Color::Rgb(20, 45, 5) } else { Color::Reset });
|
||||
self.direction
|
||||
fill_bg(buf, area, Nord::bg_lo(self.focused, self.entered));
|
||||
let area = self.direction
|
||||
.split_focus(0, track.devices.as_slice(), if self.focused {
|
||||
Style::default().green().dim()
|
||||
} else {
|
||||
Style::default().dim()
|
||||
})
|
||||
.render(buf, area)
|
||||
.render(buf, area)?;
|
||||
if self.focused && self.entered {
|
||||
Corners(Style::default().green().not_dim()).draw(buf, area)?;
|
||||
}
|
||||
Ok(area)
|
||||
} else {
|
||||
let Rect { x, y, width, height } = area;
|
||||
let label = "No track selected";
|
||||
|
|
@ -86,7 +92,7 @@ impl<'a> Render for SplitFocus<'a> {
|
|||
}
|
||||
results
|
||||
.get(self.1)
|
||||
.map(|focused|Lozenge(self.3).draw(buf, *focused))
|
||||
.map(|focused|Lozenge(self.3).draw(buf, Rect { width, ..*focused }))
|
||||
.transpose()?;
|
||||
Ok(area)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,78 +0,0 @@
|
|||
use crate::core::*;
|
||||
use crate::model::*;
|
||||
|
||||
pub fn render (state: &Sampler, buf: &mut Buffer, Rect { x, y, height, .. }: Rect)
|
||||
-> Usually<Rect>
|
||||
{
|
||||
let style = Style::default().gray();
|
||||
let title = format!(" {} ({})", state.name, state.voices.len());
|
||||
title.blit(buf, x+1, y, Some(style.white().bold().not_dim()))?;
|
||||
let mut width = title.len() + 2;
|
||||
for (i, (note, sample)) in state.samples.iter().enumerate() {
|
||||
let style = if i == state.cursor.0 {
|
||||
Style::default().green()
|
||||
} else {
|
||||
Style::default()
|
||||
};
|
||||
let i = i as u16;
|
||||
let y1 = y+1+i;
|
||||
if y1 >= y + height {
|
||||
break
|
||||
}
|
||||
if i as usize == state.cursor.0 {
|
||||
"⯈".blit(buf, x+1, y1, Some(style.bold()))?;
|
||||
}
|
||||
let label1 = format!("{note:3} {:8}", sample.name);
|
||||
let label2 = format!("{:>6} {:>6}", sample.start, sample.end);
|
||||
label1.blit(buf, x+2, y1, Some(style.bold()))?;
|
||||
label2.blit(buf, x+3+label1.len()as u16, y1, Some(style))?;
|
||||
width = width.max(label1.len() + label2.len() + 4);
|
||||
}
|
||||
let height = ((2 + state.samples.len()) as u16).min(height);
|
||||
Ok(Rect { x, y, width: width as u16, height })
|
||||
}
|
||||
|
||||
//fn render_table (
|
||||
//state: &mut Sampler,
|
||||
//stdout: &mut Stdout,
|
||||
//offset: (u16, u16),
|
||||
//) -> Result<(), Box<dyn Error>> {
|
||||
//let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row);
|
||||
//stdout.queue(move_to(0, 3))?.queue(
|
||||
//Print(" Name Rate Trigger Route")
|
||||
//)?;
|
||||
//for (i, sample) in state.samples.lock().unwrap().iter().enumerate() {
|
||||
//let row = 4 + i as u16;
|
||||
//for (j, (column, field)) in [
|
||||
//(0, format!(" {:7} ", sample.name)),
|
||||
//(9, format!(" {:.1}Hz ", sample.rate)),
|
||||
//(18, format!(" MIDI C{} {} ", sample.trigger.0, sample.trigger.1)),
|
||||
//(33, format!(" {:.1}dB -> Output ", sample.gain)),
|
||||
//(50, format!(" {} ", sample.playing.unwrap_or(0))),
|
||||
//].into_iter().enumerate() {
|
||||
//stdout.queue(move_to(column, row))?;
|
||||
//if state.selected_sample == i && state.selected_column == j {
|
||||
//stdout.queue(PrintStyledContent(field.to_string().bold().reverse()))?;
|
||||
//} else {
|
||||
//stdout.queue(PrintStyledContent(field.to_string().bold()))?;
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
//Ok(())
|
||||
//}
|
||||
|
||||
//fn render_meters (
|
||||
//state: &mut Sampler,
|
||||
//stdout: &mut Stdout,
|
||||
//offset: (u16, u16),
|
||||
//) -> Result<(), Box<dyn Error>> {
|
||||
//let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row);
|
||||
//for (i, sample) in state.samples.lock().iter().enumerate() {
|
||||
//let row = 4 + i as u16;
|
||||
//stdout.queue(move_to(32, row))?.queue(
|
||||
//PrintStyledContent("▁".green())
|
||||
//)?;
|
||||
//}
|
||||
//Ok(())
|
||||
//}
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{core::*,model::*};
|
||||
use crate::{core::*,model::*,view::*};
|
||||
|
||||
pub struct BufferedSequencerView {
|
||||
pub ppq: usize,
|
||||
|
|
@ -79,6 +79,7 @@ impl BufferedSequencerView {
|
|||
|
||||
pub struct SequencerView<'a> {
|
||||
focused: bool,
|
||||
entered: bool,
|
||||
/// Displayed phrase
|
||||
phrase: Option<&'a Phrase>,
|
||||
/// Resolution of MIDI sequence
|
||||
|
|
@ -111,6 +112,7 @@ impl<'a> SequencerView<'a> {
|
|||
Self {
|
||||
phrase: app.phrase(),
|
||||
focused: app.section == AppSection::Sequencer,
|
||||
entered: app.entered,
|
||||
ppq: app.timebase.ppq() as usize,
|
||||
now: app.timebase.frame_to_pulse(app.playhead as f64) as usize,
|
||||
time_cursor: app.time_cursor,
|
||||
|
|
@ -126,9 +128,11 @@ impl<'a> SequencerView<'a> {
|
|||
|
||||
impl<'a> Render for SequencerView<'a> {
|
||||
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||
let bg = if self.focused { Color::Rgb(20, 45, 5) } else { Color::Reset };
|
||||
fill_bg(buf, area, bg);
|
||||
fill_bg(buf, area, Nord::bg_lo(self.focused, self.entered));
|
||||
self.horizontal_draw(buf, area)?;
|
||||
if self.focused && self.entered {
|
||||
Corners(Style::default().green().not_dim()).draw(buf, area)?;
|
||||
}
|
||||
Ok(area)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
0
src/view/split.rs
Normal file
0
src/view/split.rs
Normal file
45
src/view/theme.rs
Normal file
45
src/view/theme.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
use crate::core::*;
|
||||
|
||||
pub trait Theme {
|
||||
const BG0: Color;
|
||||
const BG1: Color;
|
||||
const BG2: Color;
|
||||
const BG3: Color;
|
||||
const BG4: Color;
|
||||
const RED: Color;
|
||||
const YELLOW: Color;
|
||||
const GREEN: Color;
|
||||
|
||||
fn bg_hi (focused: bool, entered: bool) -> Color {
|
||||
if focused && entered {
|
||||
Self::BG2
|
||||
} else if focused {
|
||||
Self::BG1
|
||||
} else {
|
||||
Self::BG0
|
||||
}
|
||||
}
|
||||
|
||||
fn bg_lo (focused: bool, entered: bool) -> Color {
|
||||
if focused && entered {
|
||||
Self::BG1
|
||||
} else if focused {
|
||||
Self::BG0
|
||||
} else {
|
||||
Color::Reset
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Nord;
|
||||
|
||||
impl Theme for Nord {
|
||||
const BG0: Color = Color::Rgb(41, 46, 57);
|
||||
const BG1: Color = Color::Rgb(46, 52, 64);
|
||||
const BG2: Color = Color::Rgb(59, 66, 82);
|
||||
const BG3: Color = Color::Rgb(67, 76, 94);
|
||||
const BG4: Color = Color::Rgb(76, 86, 106);
|
||||
const RED: Color = Color::Rgb(191, 97, 106);
|
||||
const YELLOW: Color = Color::Rgb(235, 203, 139);
|
||||
const GREEN: Color = Color::Rgb(163, 190, 140);
|
||||
}
|
||||
|
|
@ -1,22 +1,20 @@
|
|||
use crate::{core::*, view::Split, model::App};
|
||||
use crate::{core::*,view::*,model::{App, AppSection}};
|
||||
pub struct TransportView {
|
||||
pub playing: TransportState,
|
||||
pub record: bool,
|
||||
pub overdub: bool,
|
||||
pub monitor: bool,
|
||||
pub quant: usize,
|
||||
pub ppq: usize,
|
||||
pub bpm: usize,
|
||||
pub pulse: usize,
|
||||
pub usecs: usize,
|
||||
focused: bool,
|
||||
entered: bool,
|
||||
playing: TransportState,
|
||||
quant: usize,
|
||||
ppq: usize,
|
||||
bpm: usize,
|
||||
pulse: usize,
|
||||
usecs: usize,
|
||||
}
|
||||
impl TransportView {
|
||||
pub fn new (app: &App) -> Self {
|
||||
Self {
|
||||
focused: app.section == AppSection::Transport,
|
||||
entered: app.entered,
|
||||
playing: *app.playing.as_ref().unwrap_or(&TransportState::Stopped),
|
||||
monitor: app.track().map(|t|t.1.monitoring).unwrap_or(false),
|
||||
record: app.track().map(|t|t.1.recording).unwrap_or(false),
|
||||
overdub: app.track().map(|t|t.1.overdub).unwrap_or(false),
|
||||
quant: app.quant,
|
||||
ppq: app.timebase.ppq() as usize,
|
||||
bpm: app.timebase.bpm() as usize,
|
||||
|
|
@ -27,66 +25,53 @@ impl TransportView {
|
|||
}
|
||||
render!(TransportView |self, buf, area| {
|
||||
let mut area = area;
|
||||
area.height = if area.width > 100 { 1 } else { 2 };
|
||||
area.height = 2;
|
||||
let Self { ppq, bpm, quant, pulse, usecs, .. } = self;
|
||||
fill_bg(buf, area, Color::Rgb(20, 45, 5));
|
||||
let dim = Style::default().dim();
|
||||
let not_dim = Style::default().not_dim();
|
||||
let red = not_dim.red();
|
||||
let green = not_dim.green();
|
||||
let yellow = not_dim.yellow();
|
||||
Split::right([
|
||||
|
||||
fill_bg(buf, area, Nord::bg_lo(self.focused, self.entered));
|
||||
|
||||
let gray = Style::default().gray();
|
||||
let not_dim = Style::default().not_dim();
|
||||
let not_dim_bold = not_dim.bold();
|
||||
|
||||
let area = Split::right([
|
||||
|
||||
// Play/Pause button
|
||||
&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
|
||||
let style = Style::default().gray();
|
||||
let style = Some(match &self.playing {
|
||||
TransportState::Stopped => style.dim().bold(),
|
||||
TransportState::Starting => style.not_dim().bold(),
|
||||
TransportState::Rolling => style.not_dim().white().bold()
|
||||
TransportState::Stopped => gray.dim().bold(),
|
||||
TransportState::Starting => gray.not_dim().bold(),
|
||||
TransportState::Rolling => gray.not_dim().white().bold()
|
||||
});
|
||||
let label = match &self.playing {
|
||||
TransportState::Rolling => "▶ PLAYING",
|
||||
TransportState::Starting => "READY ...",
|
||||
TransportState::Stopped => "⏹ STOPPED",
|
||||
};
|
||||
label.blit(buf, x, y, style)
|
||||
},
|
||||
|
||||
// Record button/indicator
|
||||
&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
|
||||
"⏺ REC".blit(buf, x, y, Some(if self.record { red } else { dim }))
|
||||
},
|
||||
|
||||
// Overdub button/indicator
|
||||
&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
|
||||
"⏺ DUB".blit(buf, x, y, Some(if self.overdub { yellow } else { dim }))
|
||||
},
|
||||
|
||||
// Monitor button/indicator
|
||||
&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
|
||||
"⏺ MON".blit(buf, x, y, Some(if self.monitor { green } else { dim }))
|
||||
let mut result = label.blit(buf, x + 1, y, style)?;
|
||||
result.width = result.width + 1;
|
||||
Ok(result)
|
||||
},
|
||||
|
||||
// Beats per minute
|
||||
&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
|
||||
"BPM".blit(buf, x, y, Some(not_dim))?;
|
||||
format!("{}.{:03}", bpm, bpm % 1).blit(buf, x + 4, y, Some(not_dim.bold()))?;
|
||||
Ok(Rect { x, y, width: 13, height: 1 })
|
||||
let width = format!("{}.{:03}", bpm, bpm % 1).blit(buf, x, y + 1, Some(not_dim_bold))?.width;
|
||||
Ok(Rect { x, y, width: (width + 2).max(10), height: 2 })
|
||||
},
|
||||
|
||||
// Quantization
|
||||
&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
|
||||
"QUANT".blit(buf, x, y, Some(not_dim))?;
|
||||
ppq_to_name(*quant).blit(buf, x + 6, y, Some(not_dim.bold()))?;
|
||||
Ok(Rect { x, y, width: 12, height: 1 })
|
||||
let width = ppq_to_name(*quant).blit(buf, x, y + 1, Some(not_dim_bold))?.width;
|
||||
Ok(Rect { x, y, width: (width + 2).max(10), height: 2 })
|
||||
},
|
||||
|
||||
// Clip launch sync
|
||||
&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
|
||||
"SYNC".blit(buf, x, y, Some(not_dim))?;
|
||||
"4/4".blit(buf, x + 5, y, Some(not_dim.bold()))?;
|
||||
Ok(Rect { x, y, width: 12, height: 1 })
|
||||
let width = "4/4".blit(buf, x, y + 1, Some(not_dim_bold))?.width;
|
||||
Ok(Rect { x, y, width: (width + 2).max(10), height: 2 })
|
||||
},
|
||||
|
||||
// Clock
|
||||
|
|
@ -96,8 +81,29 @@ render!(TransportView |self, buf, area| {
|
|||
let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000);
|
||||
let (minutes, seconds) = (seconds / 60, seconds % 60);
|
||||
let timer = format!("{minutes}:{seconds:02}:{msecs:03} {bars}.{beats}.{pulses:02}");
|
||||
timer.blit(buf, x + width - timer.len() as u16 - 1, y, Some(Style::default().not_dim()))
|
||||
timer.blit(buf, x + width - timer.len() as u16 - 1, y, Some(not_dim))
|
||||
}
|
||||
|
||||
]).render(buf, area)
|
||||
]).render(buf, area)?;
|
||||
|
||||
Ok(if self.focused && self.entered {
|
||||
Corners(Style::default().green().not_dim()).draw(buf, area)?
|
||||
} else {
|
||||
area
|
||||
})
|
||||
});
|
||||
|
||||
// Record button/indicator
|
||||
//&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
|
||||
//"⏺ REC".blit(buf, x, y, Some(if self.record { red } else { dim }))
|
||||
//},
|
||||
|
||||
//// Overdub button/indicator
|
||||
//&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
|
||||
//"⏺ DUB".blit(buf, x, y, Some(if self.overdub { yellow } else { dim }))
|
||||
//},
|
||||
|
||||
//// Monitor button/indicator
|
||||
//&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
|
||||
//"⏺ MON".blit(buf, x, y, Some(if self.monitor { green } else { dim }))
|
||||
//},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue