mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
wip: p.46, e=26, still lotta todo!
This commit is contained in:
parent
627c7d8820
commit
2977247597
13 changed files with 392 additions and 443 deletions
|
|
@ -24,12 +24,15 @@ submod! {
|
|||
|
||||
//tui_mixer // TODO
|
||||
tui_phrase
|
||||
tui_phrase_cmd
|
||||
tui_phrase_view
|
||||
//tui_plugin // TODO
|
||||
//tui_plugin_lv2
|
||||
//tui_plugin_lv2_gui
|
||||
//tui_plugin_vst2
|
||||
//tui_plugin_vst3
|
||||
tui_pool
|
||||
tui_pool_cmd
|
||||
tui_pool_view
|
||||
//tui_sampler // TODO
|
||||
//tui_sampler_cmd
|
||||
|
|
@ -44,57 +47,7 @@ submod! {
|
|||
tui_transport_view
|
||||
}
|
||||
|
||||
//pub struct AppView<E, A, C, S>
|
||||
//where
|
||||
//E: Engine,
|
||||
//A: Widget<Engine = E> + Audio,
|
||||
//C: Command<Self>,
|
||||
//S: StatusBar,
|
||||
//{
|
||||
//pub app: A,
|
||||
//pub cursor: (usize, usize),
|
||||
//pub entered: bool,
|
||||
//pub menu_bar: Option<MenuBar<E, Self, C>>,
|
||||
//pub status_bar: Option<S>,
|
||||
//pub history: Vec<C>,
|
||||
//pub size: Measure<E>,
|
||||
//}
|
||||
|
||||
//#[derive(Debug, Clone)]
|
||||
//pub enum AppViewCommand<T: Debug + Clone> {
|
||||
//App(T)
|
||||
//}
|
||||
|
||||
//#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
//pub enum AppViewFocus<F: Debug + Copy + Clone + PartialEq> {
|
||||
//Menu,
|
||||
//Content(F),
|
||||
//}
|
||||
|
||||
//impl<E, A, C, S> AppView<E, A, C, S>
|
||||
//where
|
||||
//E: Engine,
|
||||
//A: Widget<Engine = E> + Audio,
|
||||
//C: Command<Self>,
|
||||
//S: StatusBar
|
||||
//{
|
||||
//pub fn new (
|
||||
//app: A,
|
||||
//menu_bar: Option<MenuBar<E, Self, C>>,
|
||||
//status_bar: Option<S>,
|
||||
//) -> Self {
|
||||
//Self {
|
||||
//app,
|
||||
//cursor: (0, 0),
|
||||
//entered: false,
|
||||
//history: vec![],
|
||||
//size: Measure::new(),
|
||||
//menu_bar,
|
||||
//status_bar,
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
|
||||
// TODO
|
||||
impl<A, C, S> Content for AppView<Tui, A, C, S>
|
||||
where
|
||||
A: Widget<Engine = Tui> + Audio,
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ pub enum ArrangerCommand {
|
|||
Select(ArrangerSelection),
|
||||
Zoom(usize),
|
||||
Phrases(PhrasePoolCommand),
|
||||
Editor(PhraseEditorCommand),
|
||||
Editor(PhraseCommand),
|
||||
EditPhrase(Option<Arc<RwLock<Phrase>>>),
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,19 +7,13 @@ impl JackApi for ArrangerTui {
|
|||
}
|
||||
|
||||
impl Audio for ArrangerTui {
|
||||
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||
TracksAudio(
|
||||
#[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||
if TracksAudio(
|
||||
&mut self.app.tracks,
|
||||
&mut self.app.note_buf,
|
||||
&mut self.app.midi_buf,
|
||||
Default::default(),
|
||||
).process(client, scope)
|
||||
}
|
||||
}
|
||||
|
||||
impl Audio for ArrangerTui {
|
||||
#[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||
if self.process(client, scope) == Control::Quit {
|
||||
).process(client, scope) == Control::Quit {
|
||||
return Control::Quit
|
||||
}
|
||||
// FIXME: one of these per playing track
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ impl Content for ArrangerTui {
|
|||
),
|
||||
Split::right(
|
||||
self.splits[1],
|
||||
widget(&self.phrases),
|
||||
widget(&PhrasesView(self)),
|
||||
widget(&PhraseView(self)),
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
use crate::*;
|
||||
|
||||
/// Contains state for viewing and editing a phrase
|
||||
pub struct PhraseTui<E: Engine> {
|
||||
_engine: PhantomData<E>,
|
||||
pub struct PhraseTui {
|
||||
/// Phrase being played
|
||||
pub phrase: Option<Arc<RwLock<Phrase>>>,
|
||||
/// Length of note that will be inserted, in pulses
|
||||
|
|
@ -28,10 +27,10 @@ pub struct PhraseTui<E: Engine> {
|
|||
/// Current position of global playhead
|
||||
pub now: Arc<Pulse>,
|
||||
/// Width and height of notes area at last render
|
||||
pub size: Measure<E>
|
||||
pub size: Measure<Tui>
|
||||
}
|
||||
|
||||
impl Widget for PhraseTui<Tui> {
|
||||
impl Widget for PhraseTui {
|
||||
type Engine = Tui;
|
||||
fn layout (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
|
||||
PhraseView(&self, Default::default()).layout(to)
|
||||
|
|
@ -41,192 +40,9 @@ impl Widget for PhraseTui<Tui> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct PhraseView<'a, T: PhraseTuiViewState>(pub &'a T);
|
||||
|
||||
pub trait PhraseTuiViewState: Send + Sync {
|
||||
fn focused (&self) -> bool;
|
||||
fn entered (&self) -> bool;
|
||||
fn keys (&self) -> &Buffer;
|
||||
fn phrase (&self) -> &Option<Arc<RwLock<Phrase>>>;
|
||||
fn buffer (&self) -> &BigBuffer;
|
||||
fn note_len (&self) -> usize;
|
||||
fn note_axis (&self) -> &RwLock<FixedAxis<usize>>;
|
||||
fn time_axis (&self) -> &RwLock<ScaledAxis<usize>>;
|
||||
fn size (&self) -> &Measure<Tui>;
|
||||
fn now (&self) -> &Arc<Pulse>;
|
||||
}
|
||||
|
||||
impl PhraseTuiViewState for PhraseTui<Tui> {
|
||||
fn focused (&self) -> bool {
|
||||
&self.focused
|
||||
}
|
||||
fn entered (&self) -> bool {
|
||||
&self.entered
|
||||
}
|
||||
fn keys (&self) -> &Buffer {
|
||||
&self.keys
|
||||
}
|
||||
fn phrase (&self) -> &Option<Arc<RwLock<Phrase>>> {
|
||||
&self.phrase
|
||||
}
|
||||
fn buffer (&self) -> &BigBuffer {
|
||||
&self.buffer
|
||||
}
|
||||
fn note_len (&self) -> usize {
|
||||
&self.note_len
|
||||
}
|
||||
fn note_axis (&self) -> &RwLock<FixedAxis<usize>> {
|
||||
&self.note_axis
|
||||
}
|
||||
fn time_axis (&self) -> &RwLock<ScaledAxis<usize>> {
|
||||
&self.time_axis
|
||||
}
|
||||
fn size (&self) -> &Measure<Tui> {
|
||||
&self.size
|
||||
}
|
||||
fn now (&self) -> &Arc<Pulse> {
|
||||
&self.now
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: PhraseTuiViewState> Content for PhraseView<'a, T> {
|
||||
type Engine = Tui;
|
||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||
let phrase = self.0.phrase();
|
||||
let size = self.0.size();
|
||||
let focused = self.0.focused();
|
||||
let entered = self.0.entered();
|
||||
let keys = self.0.keys();
|
||||
let buffer = self.0.buffer();
|
||||
let note_len = self.0.note_len();
|
||||
let note_axis = self.0.note_axis();
|
||||
let time_axis = self.0.time_axis();
|
||||
let FixedAxis { start: note_start, point: note_point, clamp: note_clamp }
|
||||
= *note_axis.read().unwrap();
|
||||
let ScaledAxis { start: time_start, point: time_point, clamp: time_clamp, scale: time_scale }
|
||||
= *time_axis.read().unwrap();
|
||||
//let color = Color::Rgb(0,255,0);
|
||||
//let color = phrase.as_ref().map(|p|p.read().unwrap().color.base.rgb).unwrap_or(color);
|
||||
let keys = CustomWidget::new(|to:[u16;2]|Ok(Some(to.clip_w(5))), move|to: &mut TuiOutput|{
|
||||
Ok(if to.area().h() >= 2 {
|
||||
to.buffer_update(to.area().set_w(5), &|cell, x, y|{
|
||||
let y = y + (note_start / 2) as u16;
|
||||
if x < keys.area.width && y < keys.area.height {
|
||||
*cell = keys.get(x, y).clone()
|
||||
}
|
||||
});
|
||||
})
|
||||
}).fill_y();
|
||||
let notes_bg_null = Color::Rgb(28, 35, 25);
|
||||
let notes = CustomWidget::new(|to|Ok(Some(to)), move|to: &mut TuiOutput|{
|
||||
let area = to.area();
|
||||
let h = area.h() as usize;
|
||||
size.set_wh(area.w(), h);
|
||||
let mut axis = note_axis.write().unwrap();
|
||||
if let Some(point) = axis.point {
|
||||
if point.saturating_sub(axis.start) > (h * 2).saturating_sub(1) {
|
||||
axis.start += 2;
|
||||
}
|
||||
}
|
||||
Ok(if to.area().h() >= 2 {
|
||||
let area = to.area();
|
||||
to.buffer_update(area, &move |cell, x, y|{
|
||||
cell.set_bg(notes_bg_null);
|
||||
let src_x = (x as usize + time_start) * time_scale;
|
||||
let src_y = y as usize + note_start / 2;
|
||||
if src_x < buffer.width && src_y < buffer.height - 1 {
|
||||
buffer.get(src_x, buffer.height - src_y - 2).map(|src|{
|
||||
cell.set_symbol(src.symbol());
|
||||
cell.set_fg(src.fg);
|
||||
cell.set_bg(src.bg);
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
}).fill_x();
|
||||
let cursor = CustomWidget::new(|to|Ok(Some(to)), move|to: &mut TuiOutput|{
|
||||
Ok(if focused && entered {
|
||||
let area = to.area();
|
||||
if let (Some(time), Some(note)) = (time_point, note_point) {
|
||||
let x1 = area.x() + (time / time_scale) as u16;
|
||||
let x2 = x1 + (note_len / time_scale) as u16;
|
||||
let y = area.y() + note.saturating_sub(note_start) as u16 / 2;
|
||||
let c = if note % 2 == 0 { "▀" } else { "▄" };
|
||||
for x in x1..x2 {
|
||||
to.blit(&c, x, y, Some(Style::default().fg(Color::Rgb(0,255,0))));
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
let playhead_inactive = Style::default().fg(Color::Rgb(255,255,255)).bg(Color::Rgb(40,50,30));
|
||||
let playhead_active = playhead_inactive.clone().yellow().bold().not_dim();
|
||||
let playhead = CustomWidget::new(
|
||||
|to:[u16;2]|Ok(Some(to.clip_h(1))),
|
||||
move|to: &mut TuiOutput|{
|
||||
if let Some(_) = phrase {
|
||||
let now = self.0.now().get() as usize; // TODO FIXME: self.now % phrase.read().unwrap().length;
|
||||
let time_clamp = time_clamp
|
||||
.expect("time_axis of sequencer expected to be clamped");
|
||||
for x in 0..(time_clamp/time_scale).saturating_sub(time_start) {
|
||||
let this_step = time_start + (x + 0) * time_scale;
|
||||
let next_step = time_start + (x + 1) * time_scale;
|
||||
let x = to.area().x() + x as u16;
|
||||
let active = this_step <= now && now < next_step;
|
||||
let character = if active { "|" } else { "·" };
|
||||
let style = if active { playhead_active } else { playhead_inactive };
|
||||
to.blit(&character, x, to.area.y(), Some(style));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
).push_x(6).align_sw();
|
||||
let border_color = if focused{Color::Rgb(100, 110, 40)}else{Color::Rgb(70, 80, 50)};
|
||||
let title_color = if focused{Color::Rgb(150, 160, 90)}else{Color::Rgb(120, 130, 100)};
|
||||
let border = Lozenge(Style::default().bg(Color::Rgb(40, 50, 30)).fg(border_color));
|
||||
let note_area = lay!(notes, cursor).fill_x();
|
||||
let piano_roll = row!(keys, note_area).fill_x();
|
||||
let content = piano_roll.bg(Color::Rgb(40, 50, 30)).border(border);
|
||||
let content = lay!(content, playhead);
|
||||
let mut upper_left = format!("[{}] Sequencer", if entered {"■"} else {" "});
|
||||
if let Some(phrase) = phrase {
|
||||
upper_left = format!("{upper_left}: {}", phrase.read().unwrap().name);
|
||||
}
|
||||
let mut lower_right = format!("┤{}├", size.format());
|
||||
lower_right = format!("┤Zoom: {}├─{lower_right}", pulses_to_name(time_scale));
|
||||
//lower_right = format!("Zoom: {} (+{}:{}*{}|{})",
|
||||
//pulses_to_name(time_scale),
|
||||
//time_start, time_point.unwrap_or(0),
|
||||
//time_scale, time_clamp.unwrap_or(0),
|
||||
//);
|
||||
if focused && entered {
|
||||
lower_right = format!("┤Note: {} {}├─{lower_right}",
|
||||
note_axis.read().unwrap().point.unwrap(),
|
||||
pulses_to_name(note_len));
|
||||
//lower_right = format!("Note: {} (+{}:{}|{}) {upper_right}",
|
||||
//pulses_to_name(*note_len),
|
||||
//note_start,
|
||||
//note_point.unwrap_or(0),
|
||||
//note_clamp.unwrap_or(0),
|
||||
//);
|
||||
}
|
||||
let upper_right = if let Some(phrase) = phrase {
|
||||
format!("┤Length: {}├", phrase.read().unwrap().length)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
lay!(
|
||||
content,
|
||||
TuiStyle::fg(upper_left.to_string(), title_color).push_x(1).align_nw().fill_xy(),
|
||||
TuiStyle::fg(upper_right.to_string(), title_color).pull_x(1).align_ne().fill_xy(),
|
||||
TuiStyle::fg(lower_right.to_string(), title_color).pull_x(1).align_se().fill_xy(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine> PhraseTui<E> {
|
||||
impl PhraseTui {
|
||||
pub fn new () -> Self {
|
||||
Self {
|
||||
_engine: Default::default(),
|
||||
phrase: None,
|
||||
note_len: 24,
|
||||
notes_in: Arc::new(RwLock::new([false;128])),
|
||||
|
|
@ -408,157 +224,3 @@ pub(crate) fn keys_vert () -> Buffer {
|
|||
const NTH_OCTAVE: [&'static str; 11] = [
|
||||
"-2", "-1", "0", "1", "2", "3", "4", "5", "6", "7", "8",
|
||||
];
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub enum PhraseCommand {
|
||||
// TODO: 1-9 seek markers that by default start every 8th of the phrase
|
||||
ToggleDirection,
|
||||
EnterEditMode,
|
||||
ExitEditMode,
|
||||
NoteAppend,
|
||||
NoteSet,
|
||||
NoteCursorSet(usize),
|
||||
NoteLengthSet(usize),
|
||||
NoteScrollSet(usize),
|
||||
TimeCursorSet(usize),
|
||||
TimeScrollSet(usize),
|
||||
TimeZoomSet(usize),
|
||||
}
|
||||
|
||||
impl Handle<Tui> for PhraseTui<Tui> {
|
||||
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
||||
PhraseCommand::execute_with_state(self, from)
|
||||
}
|
||||
}
|
||||
|
||||
impl InputToCommand<Tui, PhraseTui<Tui>> for PhraseCommand {
|
||||
fn input_to_command (state: &PhraseTui<Tui>, from: &TuiInput) -> Option<Self> {
|
||||
use PhraseCommand::*;
|
||||
Some(match from.event() {
|
||||
key!(KeyCode::Char('`')) => ToggleDirection,
|
||||
key!(KeyCode::Enter) => EnterEditMode,
|
||||
key!(KeyCode::Esc) => ExitEditMode,
|
||||
key!(KeyCode::Char('[')) => NoteLengthSet(0),
|
||||
key!(KeyCode::Char(']')) => NoteLengthSet(0),
|
||||
key!(KeyCode::Char('a')) => NoteAppend,
|
||||
key!(KeyCode::Char('s')) => NoteSet,
|
||||
key!(KeyCode::Char('-')) => TimeZoomSet(0),
|
||||
key!(KeyCode::Char('_')) => TimeZoomSet(0),
|
||||
key!(KeyCode::Char('=')) => TimeZoomSet(0),
|
||||
key!(KeyCode::Char('+')) => TimeZoomSet(0),
|
||||
key!(KeyCode::PageUp) => NoteScrollSet(0),
|
||||
key!(KeyCode::PageDown) => NoteScrollSet(0),
|
||||
key!(KeyCode::Up) => match state.entered {
|
||||
true => NoteCursorSet(0),
|
||||
false => NoteScrollSet(0),
|
||||
},
|
||||
key!(KeyCode::Down) => match state.entered {
|
||||
true => NoteCursorSet(0),
|
||||
false => NoteScrollSet(0),
|
||||
},
|
||||
key!(KeyCode::Left) => match state.entered {
|
||||
true => TimeCursorSet(0),
|
||||
false => TimeScrollSet(0),
|
||||
},
|
||||
key!(KeyCode::Right) => match state.entered {
|
||||
true => TimeCursorSet(0),
|
||||
false => TimeScrollSet(0),
|
||||
},
|
||||
_ => return None
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine> Command<PhraseTui<E>> for PhraseCommand {
|
||||
//fn translate (self, state: &PhraseTui<E>) -> Self {
|
||||
//use PhraseCommand::*;
|
||||
//match self {
|
||||
//GoUp => match state.entered { true => NoteCursorInc, false => NoteScrollInc, },
|
||||
//GoDown => match state.entered { true => NoteCursorDec, false => NoteScrollDec, },
|
||||
//GoLeft => match state.entered { true => TimeCursorDec, false => TimeScrollDec, },
|
||||
//GoRight => match state.entered { true => TimeCursorInc, false => TimeScrollInc, },
|
||||
//_ => self
|
||||
//}
|
||||
//}
|
||||
fn execute (self, state: &mut PhraseTui<E>) -> Perhaps<Self> {
|
||||
use PhraseCommand::*;
|
||||
match self.translate(state) {
|
||||
ToggleDirection => {
|
||||
state.mode = !state.mode;
|
||||
},
|
||||
EnterEditMode => {
|
||||
state.entered = true;
|
||||
},
|
||||
ExitEditMode => {
|
||||
state.entered = false;
|
||||
},
|
||||
TimeZoomOut => {
|
||||
let scale = state.time_axis.read().unwrap().scale;
|
||||
state.time_axis.write().unwrap().scale = next_note_length(scale)
|
||||
},
|
||||
TimeZoomIn => {
|
||||
let scale = state.time_axis.read().unwrap().scale;
|
||||
state.time_axis.write().unwrap().scale = prev_note_length(scale)
|
||||
},
|
||||
TimeCursorDec => {
|
||||
let scale = state.time_axis.read().unwrap().scale;
|
||||
state.time_axis.write().unwrap().point_dec(scale);
|
||||
},
|
||||
TimeCursorInc => {
|
||||
let scale = state.time_axis.read().unwrap().scale;
|
||||
state.time_axis.write().unwrap().point_inc(scale);
|
||||
},
|
||||
TimeScrollDec => {
|
||||
let scale = state.time_axis.read().unwrap().scale;
|
||||
state.time_axis.write().unwrap().start_dec(scale);
|
||||
},
|
||||
TimeScrollInc => {
|
||||
let scale = state.time_axis.read().unwrap().scale;
|
||||
state.time_axis.write().unwrap().start_inc(scale);
|
||||
},
|
||||
NoteCursorDec => {
|
||||
let mut axis = state.note_axis.write().unwrap();
|
||||
axis.point_inc(1);
|
||||
if let Some(point) = axis.point { if point > 73 { axis.point = Some(73); } }
|
||||
},
|
||||
NoteCursorInc => {
|
||||
let mut axis = state.note_axis.write().unwrap();
|
||||
axis.point_dec(1);
|
||||
if let Some(point) = axis.point { if point < axis.start { axis.start = (point / 2) * 2; } }
|
||||
},
|
||||
NoteScrollDec => {
|
||||
state.note_axis.write().unwrap().start_inc(1);
|
||||
},
|
||||
NoteScrollInc => {
|
||||
state.note_axis.write().unwrap().start_dec(1);
|
||||
},
|
||||
NoteLengthDec => {
|
||||
state.note_len = prev_note_length(state.note_len)
|
||||
},
|
||||
NoteLengthInc => {
|
||||
state.note_len = next_note_length(state.note_len)
|
||||
},
|
||||
NotePageUp => {
|
||||
let mut axis = state.note_axis.write().unwrap();
|
||||
axis.start_dec(3);
|
||||
axis.point_dec(3);
|
||||
},
|
||||
NotePageDown => {
|
||||
let mut axis = state.note_axis.write().unwrap();
|
||||
axis.start_inc(3);
|
||||
axis.point_inc(3);
|
||||
},
|
||||
NoteAppend => {
|
||||
if state.entered {
|
||||
state.put();
|
||||
state.time_cursor_advance();
|
||||
}
|
||||
},
|
||||
NoteSet => {
|
||||
if state.entered { state.put(); }
|
||||
},
|
||||
_ => unreachable!()
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
155
crates/tek_tui/src/tui_phrase_cmd.rs
Normal file
155
crates/tek_tui/src/tui_phrase_cmd.rs
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
use crate::*;
|
||||
|
||||
impl Handle<Tui> for PhraseTui {
|
||||
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
||||
PhraseCommand::execute_with_state(self, from)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub enum PhraseCommand {
|
||||
// TODO: 1-9 seek markers that by default start every 8th of the phrase
|
||||
ToggleDirection,
|
||||
EnterEditMode,
|
||||
ExitEditMode,
|
||||
NoteAppend,
|
||||
NoteSet,
|
||||
NoteCursorSet(usize),
|
||||
NoteLengthSet(usize),
|
||||
NoteScrollSet(usize),
|
||||
TimeCursorSet(usize),
|
||||
TimeScrollSet(usize),
|
||||
TimeZoomSet(usize),
|
||||
}
|
||||
|
||||
impl InputToCommand<Tui, PhraseTui> for PhraseCommand {
|
||||
fn input_to_command (state: &PhraseTui, from: &TuiInput) -> Option<Self> {
|
||||
use PhraseCommand::*;
|
||||
Some(match from.event() {
|
||||
key!(KeyCode::Char('`')) => ToggleDirection,
|
||||
key!(KeyCode::Enter) => EnterEditMode,
|
||||
key!(KeyCode::Esc) => ExitEditMode,
|
||||
key!(KeyCode::Char('[')) => NoteLengthSet(0),
|
||||
key!(KeyCode::Char(']')) => NoteLengthSet(0),
|
||||
key!(KeyCode::Char('a')) => NoteAppend,
|
||||
key!(KeyCode::Char('s')) => NoteSet,
|
||||
key!(KeyCode::Char('-')) => TimeZoomSet(0),
|
||||
key!(KeyCode::Char('_')) => TimeZoomSet(0),
|
||||
key!(KeyCode::Char('=')) => TimeZoomSet(0),
|
||||
key!(KeyCode::Char('+')) => TimeZoomSet(0),
|
||||
key!(KeyCode::PageUp) => NoteScrollSet(0),
|
||||
key!(KeyCode::PageDown) => NoteScrollSet(0),
|
||||
key!(KeyCode::Up) => match state.entered {
|
||||
true => NoteCursorSet(0),
|
||||
false => NoteScrollSet(0),
|
||||
},
|
||||
key!(KeyCode::Down) => match state.entered {
|
||||
true => NoteCursorSet(0),
|
||||
false => NoteScrollSet(0),
|
||||
},
|
||||
key!(KeyCode::Left) => match state.entered {
|
||||
true => TimeCursorSet(0),
|
||||
false => TimeScrollSet(0),
|
||||
},
|
||||
key!(KeyCode::Right) => match state.entered {
|
||||
true => TimeCursorSet(0),
|
||||
false => TimeScrollSet(0),
|
||||
},
|
||||
_ => return None
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Command<PhraseTui> for PhraseCommand {
|
||||
//fn translate (self, state: &PhraseTui<E>) -> Self {
|
||||
//use PhraseCommand::*;
|
||||
//match self {
|
||||
//GoUp => match state.entered { true => NoteCursorInc, false => NoteScrollInc, },
|
||||
//GoDown => match state.entered { true => NoteCursorDec, false => NoteScrollDec, },
|
||||
//GoLeft => match state.entered { true => TimeCursorDec, false => TimeScrollDec, },
|
||||
//GoRight => match state.entered { true => TimeCursorInc, false => TimeScrollInc, },
|
||||
//_ => self
|
||||
//}
|
||||
//}
|
||||
fn execute (self, state: &mut PhraseTui) -> Perhaps<Self> {
|
||||
use PhraseCommand::*;
|
||||
match self.translate(state) {
|
||||
ToggleDirection => {
|
||||
state.mode = !state.mode;
|
||||
},
|
||||
EnterEditMode => {
|
||||
state.entered = true;
|
||||
},
|
||||
ExitEditMode => {
|
||||
state.entered = false;
|
||||
},
|
||||
TimeZoomOut => {
|
||||
let scale = state.time_axis.read().unwrap().scale;
|
||||
state.time_axis.write().unwrap().scale = next_note_length(scale)
|
||||
},
|
||||
TimeZoomIn => {
|
||||
let scale = state.time_axis.read().unwrap().scale;
|
||||
state.time_axis.write().unwrap().scale = prev_note_length(scale)
|
||||
},
|
||||
TimeCursorDec => {
|
||||
let scale = state.time_axis.read().unwrap().scale;
|
||||
state.time_axis.write().unwrap().point_dec(scale);
|
||||
},
|
||||
TimeCursorInc => {
|
||||
let scale = state.time_axis.read().unwrap().scale;
|
||||
state.time_axis.write().unwrap().point_inc(scale);
|
||||
},
|
||||
TimeScrollDec => {
|
||||
let scale = state.time_axis.read().unwrap().scale;
|
||||
state.time_axis.write().unwrap().start_dec(scale);
|
||||
},
|
||||
TimeScrollInc => {
|
||||
let scale = state.time_axis.read().unwrap().scale;
|
||||
state.time_axis.write().unwrap().start_inc(scale);
|
||||
},
|
||||
NoteCursorDec => {
|
||||
let mut axis = state.note_axis.write().unwrap();
|
||||
axis.point_inc(1);
|
||||
if let Some(point) = axis.point { if point > 73 { axis.point = Some(73); } }
|
||||
},
|
||||
NoteCursorInc => {
|
||||
let mut axis = state.note_axis.write().unwrap();
|
||||
axis.point_dec(1);
|
||||
if let Some(point) = axis.point { if point < axis.start { axis.start = (point / 2) * 2; } }
|
||||
},
|
||||
NoteScrollDec => {
|
||||
state.note_axis.write().unwrap().start_inc(1);
|
||||
},
|
||||
NoteScrollInc => {
|
||||
state.note_axis.write().unwrap().start_dec(1);
|
||||
},
|
||||
NoteLengthDec => {
|
||||
state.note_len = prev_note_length(state.note_len)
|
||||
},
|
||||
NoteLengthInc => {
|
||||
state.note_len = next_note_length(state.note_len)
|
||||
},
|
||||
NotePageUp => {
|
||||
let mut axis = state.note_axis.write().unwrap();
|
||||
axis.start_dec(3);
|
||||
axis.point_dec(3);
|
||||
},
|
||||
NotePageDown => {
|
||||
let mut axis = state.note_axis.write().unwrap();
|
||||
axis.start_inc(3);
|
||||
axis.point_inc(3);
|
||||
},
|
||||
NoteAppend => {
|
||||
if state.entered {
|
||||
state.put();
|
||||
state.time_cursor_advance();
|
||||
}
|
||||
},
|
||||
NoteSet => {
|
||||
if state.entered { state.put(); }
|
||||
},
|
||||
_ => unreachable!()
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
183
crates/tek_tui/src/tui_phrase_view.rs
Normal file
183
crates/tek_tui/src/tui_phrase_view.rs
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
use crate::*;
|
||||
|
||||
pub struct PhraseView<'a, T: PhraseViewState>(pub &'a T);
|
||||
|
||||
pub trait PhraseViewState: Send + Sync {
|
||||
fn focused (&self) -> bool;
|
||||
fn entered (&self) -> bool;
|
||||
fn keys (&self) -> &Buffer;
|
||||
fn phrase (&self) -> &Option<Arc<RwLock<Phrase>>>;
|
||||
fn buffer (&self) -> &BigBuffer;
|
||||
fn note_len (&self) -> usize;
|
||||
fn note_axis (&self) -> &RwLock<FixedAxis<usize>>;
|
||||
fn time_axis (&self) -> &RwLock<ScaledAxis<usize>>;
|
||||
fn size (&self) -> &Measure<Tui>;
|
||||
fn now (&self) -> &Arc<Pulse>;
|
||||
}
|
||||
|
||||
impl PhraseViewState for PhraseTui {
|
||||
fn focused (&self) -> bool {
|
||||
&self.focused
|
||||
}
|
||||
fn entered (&self) -> bool {
|
||||
&self.entered
|
||||
}
|
||||
fn keys (&self) -> &Buffer {
|
||||
&self.keys
|
||||
}
|
||||
fn phrase (&self) -> &Option<Arc<RwLock<Phrase>>> {
|
||||
&self.phrase
|
||||
}
|
||||
fn buffer (&self) -> &BigBuffer {
|
||||
&self.buffer
|
||||
}
|
||||
fn note_len (&self) -> usize {
|
||||
&self.note_len
|
||||
}
|
||||
fn note_axis (&self) -> &RwLock<FixedAxis<usize>> {
|
||||
&self.note_axis
|
||||
}
|
||||
fn time_axis (&self) -> &RwLock<ScaledAxis<usize>> {
|
||||
&self.time_axis
|
||||
}
|
||||
fn size (&self) -> &Measure<Tui> {
|
||||
&self.size
|
||||
}
|
||||
fn now (&self) -> &Arc<Pulse> {
|
||||
&self.now
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: PhraseViewState> Content for PhraseView<'a, T> {
|
||||
type Engine = Tui;
|
||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||
let phrase = self.0.phrase();
|
||||
let size = self.0.size();
|
||||
let focused = self.0.focused();
|
||||
let entered = self.0.entered();
|
||||
let keys = self.0.keys();
|
||||
let buffer = self.0.buffer();
|
||||
let note_len = self.0.note_len();
|
||||
let note_axis = self.0.note_axis();
|
||||
let time_axis = self.0.time_axis();
|
||||
let FixedAxis { start: note_start, point: note_point, clamp: note_clamp }
|
||||
= *note_axis.read().unwrap();
|
||||
let ScaledAxis { start: time_start, point: time_point, clamp: time_clamp, scale: time_scale }
|
||||
= *time_axis.read().unwrap();
|
||||
//let color = Color::Rgb(0,255,0);
|
||||
//let color = phrase.as_ref().map(|p|p.read().unwrap().color.base.rgb).unwrap_or(color);
|
||||
let keys = CustomWidget::new(|to:[u16;2]|Ok(Some(to.clip_w(5))), move|to: &mut TuiOutput|{
|
||||
Ok(if to.area().h() >= 2 {
|
||||
to.buffer_update(to.area().set_w(5), &|cell, x, y|{
|
||||
let y = y + (note_start / 2) as u16;
|
||||
if x < keys.area.width && y < keys.area.height {
|
||||
*cell = keys.get(x, y).clone()
|
||||
}
|
||||
});
|
||||
})
|
||||
}).fill_y();
|
||||
let notes_bg_null = Color::Rgb(28, 35, 25);
|
||||
let notes = CustomWidget::new(|to|Ok(Some(to)), move|to: &mut TuiOutput|{
|
||||
let area = to.area();
|
||||
let h = area.h() as usize;
|
||||
size.set_wh(area.w(), h);
|
||||
let mut axis = note_axis.write().unwrap();
|
||||
if let Some(point) = axis.point {
|
||||
if point.saturating_sub(axis.start) > (h * 2).saturating_sub(1) {
|
||||
axis.start += 2;
|
||||
}
|
||||
}
|
||||
Ok(if to.area().h() >= 2 {
|
||||
let area = to.area();
|
||||
to.buffer_update(area, &move |cell, x, y|{
|
||||
cell.set_bg(notes_bg_null);
|
||||
let src_x = (x as usize + time_start) * time_scale;
|
||||
let src_y = y as usize + note_start / 2;
|
||||
if src_x < buffer.width && src_y < buffer.height - 1 {
|
||||
buffer.get(src_x, buffer.height - src_y - 2).map(|src|{
|
||||
cell.set_symbol(src.symbol());
|
||||
cell.set_fg(src.fg);
|
||||
cell.set_bg(src.bg);
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
}).fill_x();
|
||||
let cursor = CustomWidget::new(|to|Ok(Some(to)), move|to: &mut TuiOutput|{
|
||||
Ok(if focused && entered {
|
||||
let area = to.area();
|
||||
if let (Some(time), Some(note)) = (time_point, note_point) {
|
||||
let x1 = area.x() + (time / time_scale) as u16;
|
||||
let x2 = x1 + (note_len / time_scale) as u16;
|
||||
let y = area.y() + note.saturating_sub(note_start) as u16 / 2;
|
||||
let c = if note % 2 == 0 { "▀" } else { "▄" };
|
||||
for x in x1..x2 {
|
||||
to.blit(&c, x, y, Some(Style::default().fg(Color::Rgb(0,255,0))));
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
let playhead_inactive = Style::default().fg(Color::Rgb(255,255,255)).bg(Color::Rgb(40,50,30));
|
||||
let playhead_active = playhead_inactive.clone().yellow().bold().not_dim();
|
||||
let playhead = CustomWidget::new(
|
||||
|to:[u16;2]|Ok(Some(to.clip_h(1))),
|
||||
move|to: &mut TuiOutput|{
|
||||
if let Some(_) = phrase {
|
||||
let now = self.0.now().get() as usize; // TODO FIXME: self.now % phrase.read().unwrap().length;
|
||||
let time_clamp = time_clamp
|
||||
.expect("time_axis of sequencer expected to be clamped");
|
||||
for x in 0..(time_clamp/time_scale).saturating_sub(time_start) {
|
||||
let this_step = time_start + (x + 0) * time_scale;
|
||||
let next_step = time_start + (x + 1) * time_scale;
|
||||
let x = to.area().x() + x as u16;
|
||||
let active = this_step <= now && now < next_step;
|
||||
let character = if active { "|" } else { "·" };
|
||||
let style = if active { playhead_active } else { playhead_inactive };
|
||||
to.blit(&character, x, to.area.y(), Some(style));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
).push_x(6).align_sw();
|
||||
let border_color = if focused{Color::Rgb(100, 110, 40)}else{Color::Rgb(70, 80, 50)};
|
||||
let title_color = if focused{Color::Rgb(150, 160, 90)}else{Color::Rgb(120, 130, 100)};
|
||||
let border = Lozenge(Style::default().bg(Color::Rgb(40, 50, 30)).fg(border_color));
|
||||
let note_area = lay!(notes, cursor).fill_x();
|
||||
let piano_roll = row!(keys, note_area).fill_x();
|
||||
let content = piano_roll.bg(Color::Rgb(40, 50, 30)).border(border);
|
||||
let content = lay!(content, playhead);
|
||||
let mut upper_left = format!("[{}] Sequencer", if entered {"■"} else {" "});
|
||||
if let Some(phrase) = phrase {
|
||||
upper_left = format!("{upper_left}: {}", phrase.read().unwrap().name);
|
||||
}
|
||||
let mut lower_right = format!("┤{}├", size.format());
|
||||
lower_right = format!("┤Zoom: {}├─{lower_right}", pulses_to_name(time_scale));
|
||||
//lower_right = format!("Zoom: {} (+{}:{}*{}|{})",
|
||||
//pulses_to_name(time_scale),
|
||||
//time_start, time_point.unwrap_or(0),
|
||||
//time_scale, time_clamp.unwrap_or(0),
|
||||
//);
|
||||
if focused && entered {
|
||||
lower_right = format!("┤Note: {} {}├─{lower_right}",
|
||||
note_axis.read().unwrap().point.unwrap(),
|
||||
pulses_to_name(note_len));
|
||||
//lower_right = format!("Note: {} (+{}:{}|{}) {upper_right}",
|
||||
//pulses_to_name(*note_len),
|
||||
//note_start,
|
||||
//note_point.unwrap_or(0),
|
||||
//note_clamp.unwrap_or(0),
|
||||
//);
|
||||
}
|
||||
let upper_right = if let Some(phrase) = phrase {
|
||||
format!("┤Length: {}├", phrase.read().unwrap().length)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
lay!(
|
||||
content,
|
||||
TuiStyle::fg(upper_left.to_string(), title_color).push_x(1).align_nw().fill_xy(),
|
||||
TuiStyle::fg(upper_right.to_string(), title_color).pull_x(1).align_ne().fill_xy(),
|
||||
TuiStyle::fg(lower_right.to_string(), title_color).pull_x(1).align_se().fill_xy(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::*;
|
||||
|
||||
impl Handle<Tui> for PhrasePoolView<Tui> {
|
||||
impl Handle<Tui> for PhrasesTui {
|
||||
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
||||
PhrasesCommand::execute_with_state(self, from)
|
||||
}
|
||||
|
|
@ -33,8 +33,8 @@ pub enum PhraseLengthCommand {
|
|||
Cancel,
|
||||
}
|
||||
|
||||
impl InputToCommand<Tui, PhrasePoolView<Tui>> for PhrasesCommand {
|
||||
fn input_to_command (state: &PhrasePoolView<Tui>, input: &TuiInput) -> Option<Self> {
|
||||
impl InputToCommand<Tui, PhrasesTui> for PhrasesCommand {
|
||||
fn input_to_command (state: &PhrasesTui, input: &TuiInput) -> Option<Self> {
|
||||
use PhrasesCommand as Cmd;
|
||||
use PhrasePoolCommand as Edit;
|
||||
use PhraseRenameCommand as Rename;
|
||||
|
|
@ -52,10 +52,10 @@ impl InputToCommand<Tui, PhrasePoolView<Tui>> for PhrasesCommand {
|
|||
key!(KeyCode::Char('n')) => Some(Cmd::Rename(Rename::Begin)),
|
||||
key!(KeyCode::Char('t')) => Some(Cmd::Length(Length::Begin)),
|
||||
_ => match state.mode {
|
||||
Some(PhrasePoolMode::Rename(..)) => {
|
||||
Some(PhrasesMode::Rename(..)) => {
|
||||
Rename::input_to_command(state, input).map(Cmd::Rename)
|
||||
},
|
||||
Some(PhrasePoolMode::Length(..)) => {
|
||||
Some(PhrasesMode::Length(..)) => {
|
||||
Length::input_to_command(state, input).map(Cmd::Length)
|
||||
},
|
||||
_ => None
|
||||
|
|
@ -64,8 +64,8 @@ impl InputToCommand<Tui, PhrasePoolView<Tui>> for PhrasesCommand {
|
|||
}
|
||||
}
|
||||
|
||||
impl<E: Engine> Command<PhrasePoolView<E>> for PhrasesCommand {
|
||||
fn execute (self, view: &mut PhrasePoolView<E>) -> Perhaps<Self> {
|
||||
impl Command<PhrasesTui> for PhrasesCommand {
|
||||
fn execute (self, view: &mut PhrasesTui) -> Perhaps<Self> {
|
||||
use PhraseRenameCommand as Rename;
|
||||
use PhraseLengthCommand as Length;
|
||||
match self {
|
||||
|
|
@ -77,7 +77,7 @@ impl<E: Engine> Command<PhrasePoolView<E>> for PhrasesCommand {
|
|||
}
|
||||
Self::Rename(command) => match command {
|
||||
Rename::Begin => {
|
||||
view.mode = Some(PhrasePoolMode::Rename(
|
||||
view.mode = Some(PhrasesMode::Rename(
|
||||
view.phrase,
|
||||
view.phrases[view.phrase].read().unwrap().name.clone()
|
||||
))
|
||||
|
|
@ -88,7 +88,7 @@ impl<E: Engine> Command<PhrasePoolView<E>> for PhrasesCommand {
|
|||
},
|
||||
Self::Length(command) => match command {
|
||||
Length::Begin => {
|
||||
view.mode = Some(PhrasePoolMode::Length(
|
||||
view.mode = Some(PhrasesMode::Length(
|
||||
view.phrase,
|
||||
view.phrases[view.phrase].read().unwrap().length,
|
||||
PhraseLengthFocus::Bar
|
||||
|
|
@ -103,9 +103,9 @@ impl<E: Engine> Command<PhrasePoolView<E>> for PhrasesCommand {
|
|||
}
|
||||
}
|
||||
|
||||
impl InputToCommand<Tui, PhrasePoolView<Tui>> for PhraseLengthCommand {
|
||||
fn input_to_command (view: &PhrasePoolView<Tui>, from: &TuiInput) -> Option<Self> {
|
||||
if let Some(PhrasePoolMode::Length(_, length, _)) = view.mode {
|
||||
impl InputToCommand<Tui, PhrasesTui> for PhraseLengthCommand {
|
||||
fn input_to_command (view: &PhrasesTui, from: &TuiInput) -> Option<Self> {
|
||||
if let Some(PhrasesMode::Length(_, length, _)) = view.mode {
|
||||
Some(match from.event() {
|
||||
key!(KeyCode::Up) => Self::Inc,
|
||||
key!(KeyCode::Down) => Self::Dec,
|
||||
|
|
@ -121,11 +121,11 @@ impl InputToCommand<Tui, PhrasePoolView<Tui>> for PhraseLengthCommand {
|
|||
}
|
||||
}
|
||||
|
||||
impl<E: Engine> Command<PhrasePoolView<E>> for PhraseLengthCommand {
|
||||
fn execute (self, view: &mut PhrasePoolView<E>) -> Perhaps<Self> {
|
||||
impl Command<PhrasesTui> for PhraseLengthCommand {
|
||||
fn execute (self, view: &mut PhrasesTui) -> Perhaps<Self> {
|
||||
use PhraseLengthFocus::*;
|
||||
use PhraseLengthCommand::*;
|
||||
if let Some(PhrasePoolMode::Length(phrase, ref mut length, ref mut focus)) = view.mode {
|
||||
if let Some(PhrasesMode::Length(phrase, ref mut length, ref mut focus)) = view.mode {
|
||||
match self {
|
||||
Self::Cancel => {
|
||||
view.mode = None;
|
||||
|
|
@ -157,7 +157,7 @@ impl<E: Engine> Command<PhrasePoolView<E>> for PhraseLengthCommand {
|
|||
}
|
||||
Ok(None)
|
||||
} else if self == Begin {
|
||||
view.mode = Some(PhrasePoolMode::Length(
|
||||
view.mode = Some(PhrasesMode::Length(
|
||||
view.phrase,
|
||||
view.phrases[view.phrase].read().unwrap().length,
|
||||
PhraseLengthFocus::Bar
|
||||
|
|
@ -169,9 +169,9 @@ impl<E: Engine> Command<PhrasePoolView<E>> for PhraseLengthCommand {
|
|||
}
|
||||
}
|
||||
|
||||
impl InputToCommand<Tui, PhrasePoolView<Tui>> for PhraseRenameCommand {
|
||||
fn input_to_command (view: &PhrasePoolView<Tui>, from: &TuiInput) -> Option<Self> {
|
||||
if let Some(PhrasePoolMode::Rename(_, ref old_name)) = view.mode {
|
||||
impl InputToCommand<Tui, PhrasesTui> for PhraseRenameCommand {
|
||||
fn input_to_command (view: &PhrasesTui, from: &TuiInput) -> Option<Self> {
|
||||
if let Some(PhrasesMode::Rename(_, ref old_name)) = view.mode {
|
||||
Some(match from.event() {
|
||||
key!(KeyCode::Char(c)) => {
|
||||
let mut new_name = old_name.clone();
|
||||
|
|
@ -193,10 +193,10 @@ impl InputToCommand<Tui, PhrasePoolView<Tui>> for PhraseRenameCommand {
|
|||
}
|
||||
}
|
||||
|
||||
impl<E: Engine> Command<PhrasePoolView<E>> for PhraseRenameCommand {
|
||||
fn execute (self, view: &mut PhrasePoolView<E>) -> Perhaps<Self> {
|
||||
impl Command<PhrasesTui> for PhraseRenameCommand {
|
||||
fn execute (self, view: &mut PhrasesTui) -> Perhaps<Self> {
|
||||
use PhraseRenameCommand::*;
|
||||
if let Some(PhrasePoolMode::Rename(phrase, ref mut old_name)) = view.mode {
|
||||
if let Some(PhrasesMode::Rename(phrase, ref mut old_name)) = view.mode {
|
||||
match self {
|
||||
Set(s) => {
|
||||
view.phrases[phrase].write().unwrap().name = s.into();
|
||||
|
|
@ -215,7 +215,7 @@ impl<E: Engine> Command<PhrasePoolView<E>> for PhraseRenameCommand {
|
|||
};
|
||||
Ok(None)
|
||||
} else if self == Begin {
|
||||
view.mode = Some(PhrasePoolMode::Rename(
|
||||
view.mode = Some(PhrasesMode::Rename(
|
||||
view.phrase,
|
||||
view.phrases[view.phrase].read().unwrap().name.clone()
|
||||
));
|
||||
|
|
|
|||
|
|
@ -42,17 +42,17 @@ pub struct PhrasesView<'a, T: PhrasesViewState>(&'a T);
|
|||
impl<'a, T: PhrasesViewState> Content for PhrasesView<'a, T> {
|
||||
type Engine = Tui;
|
||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||
let focused = self.focused();
|
||||
let entered = self.entered();
|
||||
let phrases = self.phrases();
|
||||
let phrase = self.phrase();
|
||||
let mode = self.mode();
|
||||
let focused = self.0.focused();
|
||||
let entered = self.0.entered();
|
||||
let phrases = self.0.phrases();
|
||||
let phrase = self.0.phrase();
|
||||
let mode = self.0.mode();
|
||||
let content = col!(
|
||||
(i, phrase) in phrases.iter().enumerate() => Layers::new(|add|{
|
||||
let Phrase { ref name, color, length, .. } = *phrase.read().unwrap();
|
||||
let mut length = PhraseLength::new(length, None);
|
||||
if let Some(PhrasesMode::Length(phrase, new_length, focus)) = mode {
|
||||
if *focused && i == phrase {
|
||||
if focused && i == phrase {
|
||||
length.pulses = new_length;
|
||||
length.focus = Some(focus);
|
||||
}
|
||||
|
|
@ -61,17 +61,17 @@ impl<'a, T: PhrasesViewState> Content for PhrasesView<'a, T> {
|
|||
let row1 = lay!(format!(" {i}").align_w().fill_x(), length).fill_x();
|
||||
let mut row2 = format!(" {name}");
|
||||
if let Some(PhrasesMode::Rename(phrase, _)) = mode {
|
||||
if *focused && i == phrase { row2 = format!("{row2}▄"); }
|
||||
if focused && i == phrase { row2 = format!("{row2}▄"); }
|
||||
};
|
||||
let row2 = TuiStyle::bold(row2, true);
|
||||
add(&col!(row1, row2).fill_x().bg(color.base.rgb))?;
|
||||
Ok(if *focused && i == phrase { add(&CORNERS)?; })
|
||||
Ok(if focused && i == phrase { add(&CORNERS)?; })
|
||||
})
|
||||
);
|
||||
let border_color = if *focused {Color::Rgb(100, 110, 40)} else {Color::Rgb(70, 80, 50)};
|
||||
let border_color = if focused {Color::Rgb(100, 110, 40)} else {Color::Rgb(70, 80, 50)};
|
||||
let border = Lozenge(Style::default().bg(Color::Rgb(40, 50, 30)).fg(border_color));
|
||||
let content = content.fill_xy().bg(Color::Rgb(28, 35, 25)).border(border);
|
||||
let title_color = if *focused {Color::Rgb(150, 160, 90)} else {Color::Rgb(120, 130, 100)};
|
||||
let title_color = if focused {Color::Rgb(150, 160, 90)} else {Color::Rgb(120, 130, 100)};
|
||||
let upper_left = format!("[{}] Phrases", if entered {"■"} else {" "});
|
||||
let upper_right = format!("({})", phrases.len());
|
||||
lay!(
|
||||
|
|
|
|||
|
|
@ -83,8 +83,8 @@ impl Content for SequencerTui {
|
|||
col!(
|
||||
widget(&TransportRefView(self)),
|
||||
Split::right(20,
|
||||
widget(&self.phrases),
|
||||
widget(&self.editor)
|
||||
widget(&PhrasesView(self)),
|
||||
widget(&PhraseView(self)),
|
||||
).min_y(20)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,9 +49,10 @@ impl Command<SequencerTui> for SequencerCommand {
|
|||
use SequencerCommand::*;
|
||||
match self {
|
||||
Focus(cmd) => delegate(cmd, Focus, state),
|
||||
Phrases(cmd) => delegate(cmd, Phrases, &mut state.phrases),
|
||||
Editor(cmd) => delegate(cmd, Editor, &mut state.editor),
|
||||
Transport(cmd) => delegate(cmd, Transport, &mut state.transport)
|
||||
Phrases(cmd) => delegate(cmd, Phrases, &mut state.phrases),
|
||||
Editor(cmd) => delegate(cmd, Editor, &mut state.editor),
|
||||
Clock(cmd) => delegate(cmd, Clock, &mut state.transport),
|
||||
Playhead(cmd) => delegate(cmd, Playhead, &mut state.transport)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ pub struct TransportTui {
|
|||
metronome: bool,
|
||||
focus: TransportFocus,
|
||||
focused: bool,
|
||||
size: Measure<E>,
|
||||
size: Measure<Tui>,
|
||||
}
|
||||
|
||||
/// Create app state from JACK handle.
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ impl HasFocus for TransportTui {
|
|||
}
|
||||
|
||||
impl FocusEnter for TransportTui {
|
||||
type Item = TransportFocus;
|
||||
fn focus_enter (&mut self) {
|
||||
self.entered = true;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue