mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
editor keycodes work
This commit is contained in:
parent
525923d057
commit
fc3ecfb241
4 changed files with 94 additions and 86 deletions
|
|
@ -1,20 +1,22 @@
|
|||
(:enter add :false)
|
||||
(:shift-enter add :true)
|
||||
(:del del :false)
|
||||
(:shift-del del :true)
|
||||
(:comma note/length :prev-note-length)
|
||||
(:period note/length :next-note-length)
|
||||
(:plus note/range :next-note-range)
|
||||
(:underscore note/range :prev-note-range)
|
||||
(:up note/point :next-note-point)
|
||||
(:down note/point :prev-note-point)
|
||||
(:left time/point :next-time-point)
|
||||
(:right time/point :prev-time-point)
|
||||
(:z time/lock :next-time-lock)
|
||||
(:equal time/zoom :next-time-zoom)
|
||||
(:minus time/zoom :prev-time-zoom)
|
||||
(:space clock/play :play-current)
|
||||
(:shift-space clock/play :play-start)
|
||||
(:u history :history-prev)
|
||||
(:r history :history-next)
|
||||
(:tab compact :next-compact)
|
||||
(@up note/pos :note-pos-next)
|
||||
(@down note/pos :note-pos-prev)
|
||||
|
||||
(@left time/pos :time-pos-next)
|
||||
(@right time/pos :time-pos-prev)
|
||||
|
||||
(@enter add :false)
|
||||
(@shift-enter add :true)
|
||||
(@del del :false)
|
||||
(@shift-del del :true)
|
||||
(@comma note/length :prev-note-length)
|
||||
(@period note/length :next-note-length)
|
||||
(@plus note/range :next-note-range)
|
||||
(@underscore note/range :prev-note-range)
|
||||
(@z time/lock :next-time-lock)
|
||||
(@equal time/zoom :next-time-zoom)
|
||||
(@minus time/zoom :prev-time-zoom)
|
||||
(@space clock/play :play-current)
|
||||
(@shift-space clock/play :play-start)
|
||||
(@u history :history-prev)
|
||||
(@r history :history-next)
|
||||
(@tab compact :next-compact)
|
||||
|
|
|
|||
|
|
@ -56,10 +56,17 @@ edn_provide!(bool: |self: MidiEditor| {
|
|||
":false" => false
|
||||
});
|
||||
edn_provide!(usize: |self: MidiEditor| {
|
||||
":note-length" => self.note_len(),
|
||||
":note-point" => self.note_point(),
|
||||
":time-point" => self.time_point(),
|
||||
":time-zoom" => self.time_zoom().get(),
|
||||
":note-length" => self.note_len(),
|
||||
|
||||
":note-pos" => self.note_point(),
|
||||
":note-pos-next" => self.note_point() + 1,
|
||||
":note-pos-prev" => self.note_point().saturating_sub(1),
|
||||
|
||||
":time-pos" => self.time_point(),
|
||||
":time-pos-next" => self.time_point() + self.time_zoom().get(),
|
||||
":time-pos-prev" => self.time_point().saturating_sub(self.time_zoom().get()),
|
||||
|
||||
":time-zoom" => self.time_zoom().get(),
|
||||
});
|
||||
impl std::fmt::Debug for MidiEditor {
|
||||
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||
|
|
|
|||
|
|
@ -5,4 +5,4 @@
|
|||
(@e editor show :pool-clip)
|
||||
(@ctrl-a scene add)
|
||||
(@ctrl-t track add)
|
||||
(@tab compact)
|
||||
(@tab edit)
|
||||
|
|
|
|||
121
tek/src/lib.rs
121
tek/src/lib.rs
|
|
@ -5,7 +5,7 @@
|
|||
#![feature(if_let_guard)]
|
||||
#![feature(impl_trait_in_assoc_type)]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::{self, *}}};
|
||||
pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::Relaxed}};
|
||||
/// Standard result type.
|
||||
pub type Usually<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
||||
/// Standard optional result type.
|
||||
|
|
@ -119,7 +119,6 @@ impl TekCli {
|
|||
pub edn: String,
|
||||
pub clock: Clock,
|
||||
pub color: ItemPalette,
|
||||
pub editing: AtomicBool,
|
||||
pub pool: Option<MidiPool>,
|
||||
pub editor: Option<MidiEditor>,
|
||||
pub player: Option<MidiPlayer>,
|
||||
|
|
@ -136,7 +135,7 @@ impl TekCli {
|
|||
pub splits: Vec<u16>,
|
||||
pub size: Measure<TuiOut>,
|
||||
pub perf: PerfModel,
|
||||
pub compact: bool,
|
||||
pub editing: AtomicBool,
|
||||
pub history: Vec<TekCommand>,
|
||||
}
|
||||
has_size!(<TuiOut>|self: Tek|&self.size);
|
||||
|
|
@ -174,10 +173,10 @@ edn_view!(TuiOut: |self: Tek| self.size.of(EdnView::from_source(self, self.edn.a
|
|||
// Provide sizes:
|
||||
u16 {
|
||||
":sidebar-w" => self.sidebar_w(),
|
||||
":sample-h" => if self.compact() { 0 } else { 5 },
|
||||
":samples-w" => if self.compact() { 4 } else { 11 },
|
||||
":samples-y" => if self.compact() { 1 } else { 0 },
|
||||
":pool-w" => if self.compact() { 5 } else {
|
||||
":sample-h" => if self.is_editing() { 0 } else { 5 },
|
||||
":samples-w" => if self.is_editing() { 4 } else { 11 },
|
||||
":samples-y" => if self.is_editing() { 1 } else { 0 },
|
||||
":pool-w" => if self.is_editing() { 5 } else {
|
||||
let w = self.size.w();
|
||||
if w > 60 { 20 } else if w > 40 { 15 } else { 10 } } };
|
||||
// Provide components:
|
||||
|
|
@ -193,7 +192,7 @@ edn_view!(TuiOut: |self: Tek| self.size.of(EdnView::from_source(self, self.edn.a
|
|||
":outputs" => self.view_row(self.w(), 3, self.output_header(), self.output_cells()).boxed(),
|
||||
":scenes" => Outer(Style::default().fg(TuiTheme::g(0))).enclose_bg(self.view_row(
|
||||
self.w(), self.size.h().saturating_sub(12) as u16,
|
||||
self.scene_header(), self.scene_cells()
|
||||
self.scene_header(), self.clip_columns()
|
||||
)).boxed() }});
|
||||
impl Tek {
|
||||
fn new_arranger (
|
||||
|
|
@ -289,8 +288,6 @@ impl Tek {
|
|||
fn sync_follow (&self) {
|
||||
// TODO
|
||||
}
|
||||
fn compact (&self) -> bool { self.compact }
|
||||
fn is_editing (&self) -> bool { self.editing.load(Relaxed) }
|
||||
fn editor (&self) -> impl Content<TuiOut> + '_ { &self.editor }
|
||||
fn view_clock (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
Outer(Style::default().fg(TuiTheme::g(0))).enclose(row!(
|
||||
|
|
@ -300,14 +297,14 @@ impl Tek {
|
|||
))
|
||||
}
|
||||
fn view_beat_stats (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
let Self { compact, ref clock, .. } = self;
|
||||
let clock = self.clock();
|
||||
let now = clock.started.read().unwrap().as_ref().map(|start|clock.global.usec.get() - start.usec.get());
|
||||
let beat = ||now.map(|now|clock.timebase.format_beats_1(clock.timebase.usecs_to_pulse(now)))
|
||||
.unwrap_or("-.-.--".into());
|
||||
let time = ||now.map(|now|format!("{:.3}s", now/1000000.))
|
||||
.unwrap_or("-.---s".into());
|
||||
let bpm = ||format!("{:.3}", clock.timebase.bpm.get());
|
||||
Either::new(self.compact,
|
||||
Either::new(self.is_editing(),
|
||||
row!(FieldV(TuiTheme::g(128).into(), "BPM", bpm()),
|
||||
FieldV(TuiTheme::g(128).into(), "Beat", beat()),
|
||||
FieldV(TuiTheme::g(128).into(), "Time", time()),),
|
||||
|
|
@ -316,13 +313,14 @@ impl Tek {
|
|||
Bsp::e("Time ", Tui::fg(TuiTheme::g(255), time()))))
|
||||
}
|
||||
fn view_engine_stats (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
let Self { compact, ref clock, .. } = self;
|
||||
let clock = self.clock();
|
||||
let compact = self.is_editing();
|
||||
let rate = clock.timebase.sr.get();
|
||||
let chunk = clock.chunk.load(Relaxed);
|
||||
let sr = move||format!("{}", if *compact {format!("{:.1}kHz", rate / 1000.)} else {format!("{:.0}Hz", rate)});
|
||||
let sr = move||format!("{}", if compact {format!("{:.1}kHz", rate / 1000.)} else {format!("{:.0}Hz", rate)});
|
||||
let buffer_size = move||format!("{chunk}");
|
||||
let latency = move||format!("{:.1}ms", chunk as f64 / rate * 1000.);
|
||||
Either::new(self.compact,
|
||||
Either::new(compact,
|
||||
row!(FieldV(TuiTheme::g(128).into(), "SR", sr()),
|
||||
FieldV(TuiTheme::g(128).into(), "Buf", buffer_size()),
|
||||
FieldV(TuiTheme::g(128).into(), "Lat", latency())),
|
||||
|
|
@ -360,7 +358,7 @@ impl Tek {
|
|||
let playing = self.clock.is_rolling();
|
||||
Tui::bg(
|
||||
if playing{Color::Rgb(0,128,0)}else{Color::Rgb(128,64,0)},
|
||||
Either::new(self.compact,
|
||||
Either::new(self.is_editing(),
|
||||
Thunk::new(move||Fixed::x(9, Either::new(playing,
|
||||
Tui::fg(Color::Rgb(0, 255, 0), " PLAYING "),
|
||||
Tui::fg(Color::Rgb(255, 128, 0), " STOPPED ")))),
|
||||
|
|
@ -372,11 +370,11 @@ impl Tek {
|
|||
self.editor.as_ref().map(|e|Bsp::e(e.clip_status(), e.edit_status()))
|
||||
}
|
||||
fn view_pool (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
self.pool.as_ref().map(|pool|PoolView(self.compact(), pool))
|
||||
self.pool.as_ref().map(|pool|PoolView(self.is_editing(), pool))
|
||||
}
|
||||
fn pool (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
self.pool.as_ref()
|
||||
.map(|pool|Align::e(Fixed::x(self.sidebar_w(), PoolView(self.compact(), pool))))
|
||||
.map(|pool|Align::e(Fixed::x(self.sidebar_w(), PoolView(self.is_editing(), pool))))
|
||||
}
|
||||
fn w (&self) -> u16 {
|
||||
self.tracks_sizes(self.is_editing(), self.editor_w())
|
||||
|
|
@ -406,6 +404,12 @@ impl Tek {
|
|||
clip.write().unwrap().toggle_loop()
|
||||
}
|
||||
}
|
||||
fn track_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
|
||||
(||Tui::bg(TuiTheme::g(32), Bsp::s(
|
||||
Fill::x(Align::w(button(" C-a ".to_string(), format!(" add scene ({})", self.scenes().len())))),
|
||||
Fill::x(Align::w(button(" C-t ".to_string(), format!(" add track ({})", self.tracks().len())))),
|
||||
)).boxed()).into()
|
||||
}
|
||||
fn track_add (
|
||||
&mut self,
|
||||
name: Option<&str>,
|
||||
|
|
@ -478,7 +482,7 @@ impl Tek {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
fn scene_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
|
||||
fn clip_columns <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
|
||||
let editing = self.is_editing();
|
||||
let tracks = move||self.tracks_sizes(editing, self.editor_w());
|
||||
let scenes = move||self.scenes_sizes(editing, 2, 15);
|
||||
|
|
@ -498,24 +502,22 @@ impl Tek {
|
|||
} else {
|
||||
("⏹ ".to_string(), TuiTheme::g(64), TuiTheme::g(32))
|
||||
};
|
||||
let last = last_color.read().unwrap().clone();
|
||||
let active = editing && selected_scene == Some(s) && selected_track == Some(t);
|
||||
let editor = Thunk::new(||self.editor());
|
||||
let cell = Thunk::new(move||phat_sel_3(
|
||||
selected_track == Some(t) && selected_scene == Some(s),
|
||||
Tui::fg(fg, Push::x(1, Tui::bold(true, name.to_string()))),
|
||||
Tui::fg(fg, Push::x(1, Tui::bold(true, name.to_string()))),
|
||||
if selected_track == Some(t) && selected_scene.map(|s|s+1) == Some(s) {
|
||||
None
|
||||
} else {
|
||||
Some(bg.into())
|
||||
},
|
||||
bg.into(),
|
||||
bg.into(),
|
||||
));
|
||||
let cell = Either::new(active, editor, cell);
|
||||
*last_color.write().unwrap() = bg.into();
|
||||
map_south(y1 as u16, h, Push::y(1, Fixed::y(h, cell)))
|
||||
map_south(y1 as u16, h, Push::y(1, Fixed::y(h, Either::new(
|
||||
editing && selected_scene == Some(s) && selected_track == Some(t),
|
||||
Thunk::new(||self.editor()),
|
||||
Thunk::new(move||phat_sel_3(
|
||||
selected_track == Some(t) && selected_scene == Some(s),
|
||||
Tui::fg(fg, Push::x(1, Tui::bold(true, name.to_string()))),
|
||||
Tui::fg(fg, Push::x(1, Tui::bold(true, name.to_string()))),
|
||||
if selected_track == Some(t) && selected_scene.map(|s|s+1) == Some(s) {
|
||||
None
|
||||
} else {
|
||||
Some(bg.into())
|
||||
},
|
||||
bg.into(),
|
||||
bg.into(),
|
||||
)),
|
||||
))))
|
||||
}))).boxed()
|
||||
})).boxed()).into()
|
||||
}
|
||||
|
|
@ -553,6 +555,12 @@ const KEYS_MIX: &str = include_str!("keys_mix.edn");
|
|||
handle!(TuiIn: |self: Tek, input|Ok({
|
||||
use EdnItem::*;
|
||||
let mut command: Option<TekCommand> = None;
|
||||
// If editing, editor keys take priority
|
||||
if self.is_editing() {
|
||||
if self.editor.handle(input)? == Some(true) {
|
||||
return Ok(Some(true))
|
||||
}
|
||||
}
|
||||
// Handle from root keymap
|
||||
if let Some(command) = EdnKeyMapToCommand::new(KEYS_APP)?.from::<_, TekCommand>(self, input) {
|
||||
if let Some(undo) = command.execute(self)? { self.history.push(undo); }
|
||||
|
|
@ -576,7 +584,7 @@ handle!(TuiIn: |self: Tek, input|Ok({
|
|||
Clip(ClipCommand),
|
||||
Clock(ClockCommand),
|
||||
Color(ItemPalette),
|
||||
Compact(Option<bool>),
|
||||
Edit(Option<bool>),
|
||||
Editor(MidiEditCommand),
|
||||
Enqueue(Option<Arc<RwLock<MidiClip>>>),
|
||||
History(isize),
|
||||
|
|
@ -589,8 +597,8 @@ handle!(TuiIn: |self: Tek, input|Ok({
|
|||
Zoom(Option<usize>),
|
||||
}
|
||||
edn_command!(TekCommand: |app: Tek| {
|
||||
("compact" [] Self::Compact(None))
|
||||
("compact" [c: bool] Self::Compact(c))
|
||||
("edit" [] Self::Edit(None))
|
||||
("edit" [c: bool] Self::Edit(c))
|
||||
|
||||
("color" [c: Color] Self::Color(c.map(ItemPalette::from).unwrap_or_default()))
|
||||
|
||||
|
|
@ -661,19 +669,16 @@ command!(|self: TekCommand, app: Tek|match self {
|
|||
} else {
|
||||
None
|
||||
},
|
||||
Self::Compact(compact) => match compact {
|
||||
Some(compact) => {
|
||||
if app.compact != compact {
|
||||
app.compact = compact;
|
||||
Some(Self::Compact(Some(!compact)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
None => {
|
||||
app.compact = !app.compact;
|
||||
Some(Self::Compact(Some(!app.compact)))
|
||||
Self::Edit(value) => if let Some(value) = value {
|
||||
if app.is_editing() != value {
|
||||
app.editing.store(value, Relaxed);
|
||||
Some(Self::Edit(Some(!value)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
app.editing.store(!app.is_editing(), Relaxed);
|
||||
Some(Self::Edit(Some(!app.is_editing())))
|
||||
}
|
||||
});
|
||||
/// Represents the current user selection in the arranger
|
||||
|
|
@ -817,12 +822,6 @@ trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync {
|
|||
fn track_mut (&mut self) -> Option<&mut Track> {
|
||||
self.selected().track().and_then(|s|self.tracks_mut().get_mut(s))
|
||||
}
|
||||
fn track_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
|
||||
(||Tui::bg(TuiTheme::g(32), Bsp::s(
|
||||
button(" C-t ", " add track "),
|
||||
button(" C-a ", " add scene "),
|
||||
)).boxed()).into()
|
||||
}
|
||||
fn track_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
|
||||
let iter = ||self.tracks_sizes(self.is_editing(), self.editor_w());
|
||||
(move||Align::x(Map::new(iter, move|(_, track, x1, x2), i| {
|
||||
|
|
@ -841,7 +840,7 @@ trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync {
|
|||
fn input_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
|
||||
let fg = TuiTheme::g(224);
|
||||
let bg = TuiTheme::g(64);
|
||||
(move||Bsp::s(button(" I ", " midi ins "), self.midi_ins().get(0).map(|inp|Bsp::s(
|
||||
(move||Bsp::s(Fill::x(Align::w(button(" I ".to_string(), format!(" midi ins ({})", self.midi_ins().len())))), self.midi_ins().get(0).map(|inp|Bsp::s(
|
||||
Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(inp.name.clone())))),
|
||||
inp.connect.get(0).map(|connect|Fill::x(Align::w(Tui::bold(false,
|
||||
Tui::fg_bg(fg, bg, connect.info()))))),
|
||||
|
|
@ -869,7 +868,7 @@ trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync {
|
|||
fn output_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
|
||||
let fg = TuiTheme::g(224);
|
||||
let bg = TuiTheme::g(64);
|
||||
(move||Bsp::s(button(" O ", " midi outs "), self.midi_outs().get(0).map(|out|Bsp::s(
|
||||
(move||Bsp::s(Fill::x(Align::w(button(" O ".to_string(), format!(" midi outs ({}) ", self.midi_outs().len())))), self.midi_outs().get(0).map(|out|Bsp::s(
|
||||
Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(out.name.clone())))),
|
||||
out.connect.get(0).map(|connect|Fill::x(Align::w(Tui::bold(false,
|
||||
Tui::fg_bg(fg, bg, connect.info()))))),
|
||||
|
|
@ -1138,7 +1137,7 @@ audio!(|self: Tek, client, scope|{
|
|||
self.perf.update(t0, scope);
|
||||
Control::Continue
|
||||
});
|
||||
fn button <'a> (key: &'a str, label: &'a str) -> impl Content<TuiOut> + 'a {
|
||||
fn button (key: String, label: String) -> impl Content<TuiOut> {
|
||||
Tui::bold(true, Bsp::e(
|
||||
Margin::x(1, Tui::fg_bg(TuiTheme::g(0), TuiTheme::orange(), key)),
|
||||
Margin::x(1, Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(96), label)),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue