modal -> dialog; extract dialog, selection, editor

This commit is contained in:
🪞👃🪞 2025-05-04 18:57:31 +03:00
parent 6286d69824
commit a3beab0f36
7 changed files with 391 additions and 377 deletions

View file

@ -20,8 +20,8 @@ handle!(TuiIn: |self: Tek, input|Ok(if let Some(command) = self.config.keys.comm
expose!([self: Tek]
([bool]
(":mode-editor" self.is_editing())
(":mode-message" matches!(self.modal, Some(Modal::Message(..))))
(":mode-device-add" matches!(self.modal, Some(Modal::Device(..))))
(":mode-message" matches!(self.dialog, Some(Dialog::Message(..))))
(":mode-device-add" matches!(self.dialog, Some(Dialog::Device(..))))
(":mode-clip" !self.is_editing() && self.selected.is_clip())
(":mode-track" !self.is_editing() && self.selected.is_track())
(":mode-scene" !self.is_editing() && self.selected.is_scene())
@ -48,17 +48,17 @@ expose!([self: Tek]
([usize]
(":scene-last" self.scenes.len())
(":track-last" self.tracks.len())
(":device-kind" if let Some(Modal::Device(index)) = self.modal {
(":device-kind" if let Some(Dialog::Device(index)) = self.dialog {
index
} else {
0
})
(":device-kind-prev" if let Some(Modal::Device(index)) = self.modal {
(":device-kind-prev" if let Some(Dialog::Device(index)) = self.dialog {
index.overflowing_sub(1).0.min(self.device_kinds().len().saturating_sub(1))
} else {
0
})
(":device-kind-next" if let Some(Modal::Device(index)) = self.modal {
(":device-kind-next" if let Some(Dialog::Device(index)) = self.dialog {
(index + 1) % self.device_kinds().len()
} else {
0
@ -192,6 +192,18 @@ impose!([app: Tek]
("rec" [] Some(Self::ToggleRec))
("mon" [] Some(Self::ToggleMon))));
//#[tengri_proc::input(TuiIn)]
//impl Tek {
//#[tengri::command("sampler", TekCommand::Sampler)]
//fn cmd_sampler (&mut self, cmd: SamplerCommand) -> Perhaps<TekCommand> {
//self.sampler_mut().map(|s|cmd.delegate(s, Self::Sampler)).transpose()?.flatten())
//}
//#[tengri::command("scene", TekCommand::Scene)]
//fn cmd_scene (&mut self, cmd: SceneCommand) -> Perhaps<TekCommand> {
//cmd.delegate(self, scene)
//}
//}
defcom!([self, app: Tek]
(TekCommand
@ -206,8 +218,8 @@ defcom!([self, app: Tek]
(Message [cmd: MessageCommand] cmd.delegate(app, Self::Message)?)
(Editor [cmd: MidiEditCommand] delegate_to_editor(app, cmd)?)
(Pool [cmd: PoolCommand] delegate_to_pool(app, cmd)?)
(ToggleHelp [] cmd!(app.toggle_modal(Some(Modal::Help))))
(ToggleMenu [] cmd!(app.toggle_modal(Some(Modal::Menu))))
(ToggleHelp [] cmd!(app.toggle_dialog(Some(Dialog::Help))))
(ToggleMenu [] cmd!(app.toggle_dialog(Some(Dialog::Menu))))
(Color [p: ItemTheme] app.set_color(Some(p)).map(Self::Color))
(Enqueue [c: MaybeClip] cmd_todo!("\n\rtodo: enqueue {c:?}"))
(History [d: isize] cmd_todo!("\n\rtodo: history {d:?}"))

View file

@ -1,5 +1,9 @@
use crate::*;
mod dialog; pub use self::dialog::*;
mod editor; pub use self::editor::*;
mod selection; pub use self::selection::*;
#[derive(Default, Debug)]
pub struct Tek {
/// Must not be dropped for the duration of the process
@ -50,8 +54,8 @@ pub struct Tek {
pub ports: std::collections::BTreeMap<u32, Port<Unowned>>,
// Cache of formatted strings
pub view_cache: Arc<RwLock<ViewCache>>,
// Modal overlay
pub modal: Option<Modal>,
// Dialog overlay
pub dialog: Option<Dialog>,
// View and input definition
pub config: Configuration
}
@ -204,14 +208,6 @@ impl Tek {
}
}
pub fn toggle_modal (&mut self, modal: Option<Modal>) {
self.modal = if self.modal == modal {
None
} else {
modal
}
}
// Create new clip in pool when entering empty cell
pub fn clip_auto_create (&mut self) {
if let Some(ref pool) = self.pool
@ -401,11 +397,11 @@ impl Tek {
}
pub(crate) fn device_picker_show (&mut self) {
self.modal = Some(Modal::Device(0));
self.dialog = Some(Dialog::Device(0));
}
pub(crate) fn device_pick (&mut self, index: usize) {
self.modal = Some(Modal::Device(index));
self.dialog = Some(Dialog::Device(index));
}
pub(crate) fn device_add (&mut self, index: usize) -> Usually<()> {
@ -426,10 +422,10 @@ impl Tek {
&[&[], &[]],
&[&[], &[]]
) {
self.modal = None;
self.dialog = None;
Device::Sampler(sampler)
} else {
self.modal = Some(Modal::Message(Message::FailedToAddDevice));
self.dialog = Some(Dialog::Message(Message::FailedToAddDevice));
return Err("failed to add device".into())
};
self.track_mut().expect("no active track").devices.push(sampler);
@ -440,11 +436,6 @@ impl Tek {
todo!();
Ok(())
}
pub(crate) fn message_dismiss (&mut self) {
self.modal = None;
}
}
has_size!(<TuiOut>|self: Tek|&self.size);
@ -466,171 +457,6 @@ has_editor!(|self: Tek|{
is_editing = self.editing.load(Relaxed);
});
pub trait HasSelection {
fn selected (&self) -> &Selection;
fn selected_mut (&mut self) -> &mut Selection;
}
/// Various possible modal overlays
#[derive(PartialEq, Clone, Copy, Debug)]
pub enum Modal {
Help,
Menu,
Device(usize),
Message(Message)
}
/// Various possible messages
#[derive(PartialEq, Clone, Copy, Debug)]
pub enum Message {
FailedToAddDevice,
}
content!(TuiOut: |self: Message| match self {
Self::FailedToAddDevice => "Failed to add device."
});
/// Represents the current user selection in the arranger
#[derive(PartialEq, Clone, Copy, Debug, Default)]
pub enum Selection {
/// The whole mix is selected
#[default] Mix,
/// A MIDI input is selected.
Input(usize),
/// A MIDI output is selected.
Output(usize),
/// A scene is selected.
Scene(usize),
/// A track is selected.
Track(usize),
/// A clip (track × scene) is selected.
TrackClip { track: usize, scene: usize },
/// A track's MIDI input connection is selected.
TrackInput { track: usize, port: usize },
/// A track's MIDI output connection is selected.
TrackOutput { track: usize, port: usize },
/// A track device slot is selected.
TrackDevice { track: usize, device: usize },
}
/// Focus identification methods
impl Selection {
pub fn is_mix (&self) -> bool {
matches!(self, Self::Mix)
}
pub fn is_track (&self) -> bool {
matches!(self, Self::Track(_))
}
pub fn is_scene (&self) -> bool {
matches!(self, Self::Scene(_))
}
pub fn is_clip (&self) -> bool {
matches!(self, Self::TrackClip {..})
}
pub fn track (&self) -> Option<usize> {
use Selection::*;
match self {
Track(track)
| TrackClip { track, .. }
| TrackInput { track, .. }
| TrackOutput { track, .. }
| TrackDevice { track, .. } => Some(*track),
_ => None
}
}
pub fn track_next (&self, len: usize) -> Self {
use Selection::*;
match self {
Mix => Track(0),
Scene(s) => TrackClip { track: 0, scene: *s },
Track(t) => if t + 1 < len {
Track(t + 1)
} else {
Mix
},
TrackClip {track, scene} => if track + 1 < len {
TrackClip { track: track + 1, scene: *scene }
} else {
Scene(*scene)
},
_ => todo!()
}
}
pub fn track_prev (&self) -> Self {
use Selection::*;
match self {
Mix => Mix,
Scene(s) => Scene(*s),
Track(0) => Mix,
Track(t) => Track(t - 1),
TrackClip { track: 0, scene } => Scene(*scene),
TrackClip { track: t, scene } => TrackClip { track: t - 1, scene: *scene },
_ => todo!()
}
}
pub fn scene (&self) -> Option<usize> {
use Selection::*;
match self {
Scene(scene) | TrackClip { scene, .. } => Some(*scene),
_ => None
}
}
pub fn scene_next (&self, len: usize) -> Self {
use Selection::*;
match self {
Mix => Scene(0),
Track(t) => TrackClip { track: *t, scene: 0 },
Scene(s) => if s + 1 < len {
Scene(s + 1)
} else {
Mix
},
TrackClip { track, scene } => if scene + 1 < len {
TrackClip { track: *track, scene: scene + 1 }
} else {
Track(*track)
},
_ => todo!()
}
}
pub fn scene_prev (&self) -> Self {
use Selection::*;
match self {
Mix | Scene(0) => Mix,
Scene(s) => Scene(s - 1),
Track(t) => Track(*t),
TrackClip { track, scene: 0 } => Track(*track),
TrackClip { track, scene } => TrackClip { track: *track, scene: scene - 1 },
_ => todo!()
}
}
pub fn describe (&self, tracks: &[Track], scenes: &[Scene]) -> Arc<str> {
use Selection::*;
format!("{}", match self {
Mix => "Everything".to_string(),
Scene(s) => scenes.get(*s)
.map(|scene|format!("S{s}: {}", &scene.name))
.unwrap_or_else(||"S??".into()),
Track(t) => tracks.get(*t)
.map(|track|format!("T{t}: {}", &track.name))
.unwrap_or_else(||"T??".into()),
TrackClip { track, scene } => match (tracks.get(*track), scenes.get(*scene)) {
(Some(_), Some(s)) => match s.clip(*track) {
Some(clip) => format!("T{track} S{scene} C{}", &clip.read().unwrap().name),
None => format!("T{track} S{scene}: Empty")
},
_ => format!("T{track} S{scene}: Empty"),
},
_ => todo!()
}).into()
}
}
impl HasSelection for Tek {
fn selected (&self) -> &Selection { &self.selected }
fn selected_mut (&mut self) -> &mut Selection { &mut self.selected }
}
pub trait HasScenes: HasSelection + HasEditor + Send + Sync {
fn scenes (&self) -> &Vec<Scene>;
fn scenes_mut (&mut self) -> &mut Vec<Scene>;
@ -1053,172 +879,3 @@ pub trait HasClips {
}
}
}
pub trait HasEditor {
fn editor (&self) -> &Option<MidiEditor>;
fn editor_mut (&mut self) -> &Option<MidiEditor>;
fn is_editing (&self) -> bool { true }
fn editor_w (&self) -> usize { 0 }
fn editor_h (&self) -> usize { 0 }
}
#[macro_export] macro_rules! has_editor {
(|$self:ident: $Struct:ident|{
editor = $e0:expr;
editor_w = $e1:expr;
editor_h = $e2:expr;
is_editing = $e3:expr;
}) => {
impl HasEditor for $Struct {
fn editor (&$self) -> &Option<MidiEditor> { &$e0 }
fn editor_mut (&mut $self) -> &Option<MidiEditor> { &mut $e0 }
fn editor_w (&$self) -> usize { $e1 }
fn editor_h (&$self) -> usize { $e2 }
fn is_editing (&$self) -> bool { $e3 }
}
};
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? HasEditor for $Struct $(<$($L),*$($T),*>)? {
fn editor (&$self) -> &MidiEditor { &$cb }
}
};
}
/// Contains state for viewing and editing a clip
pub struct MidiEditor {
/// Size of editor on screen
pub size: Measure<TuiOut>,
/// View mode and state of editor
pub mode: PianoHorizontal,
}
impl std::fmt::Debug for MidiEditor {
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.debug_struct("MidiEditor")
.field("mode", &self.mode)
.finish()
}
}
impl Default for MidiEditor {
fn default () -> Self {
Self {
size: Measure::new(),
mode: PianoHorizontal::new(None),
}
}
}
has_size!(<TuiOut>|self: MidiEditor|&self.size);
content!(TuiOut: |self: MidiEditor| {
self.autoscroll();
//self.autozoom();
self.size.of(&self.mode)
});
from!(|clip: &Arc<RwLock<MidiClip>>|MidiEditor = {
let model = Self::from(Some(clip.clone()));
model.redraw();
model
});
from!(|clip: Option<Arc<RwLock<MidiClip>>>|MidiEditor = {
let mut model = Self::default();
*model.clip_mut() = clip;
model.redraw();
model
});
impl MidiEditor {
/// Put note at current position
pub fn put_note (&mut self, advance: bool) {
let mut redraw = false;
if let Some(clip) = self.clip() {
let mut clip = clip.write().unwrap();
let note_start = self.time_pos();
let note_pos = self.note_pos();
let note_len = self.note_len();
let note_end = note_start + (note_len.saturating_sub(1));
let key: u7 = u7::from(note_pos as u8);
let vel: u7 = 100.into();
let length = clip.length;
let note_end = note_end % length;
let note_on = MidiMessage::NoteOn { key, vel };
if !clip.notes[note_start].iter().any(|msg|*msg == note_on) {
clip.notes[note_start].push(note_on);
}
let note_off = MidiMessage::NoteOff { key, vel };
if !clip.notes[note_end].iter().any(|msg|*msg == note_off) {
clip.notes[note_end].push(note_off);
}
if advance {
self.set_time_pos(note_end);
}
redraw = true;
}
if redraw {
self.mode.redraw();
}
}
pub fn clip_status (&self) -> impl Content<TuiOut> + '_ {
let (color, name, length, looped) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) {
(clip.color, clip.name.clone(), clip.length, clip.looped)
} else { (ItemTheme::G[64], String::new().into(), 0, false) };
Bsp::e(
FieldH(color, "Edit", format!("{name} ({length})")),
FieldH(color, "Loop", looped.to_string())
)
}
pub fn edit_status (&self) -> impl Content<TuiOut> + '_ {
let (color, length) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) {
(clip.color, clip.length)
} else { (ItemTheme::G[64], 0) };
let time_pos = self.time_pos();
let time_zoom = self.time_zoom().get();
let time_lock = if self.time_lock().get() { "[lock]" } else { " " };
let note_pos = format!("{:>3}", self.note_pos());
let note_name = format!("{:4}", Note::pitch_to_name(self.note_pos()));
let note_len = format!("{:>4}", self.note_len());
Bsp::e(
FieldH(color, "Time", format!("{length}/{time_zoom}+{time_pos} {time_lock}")),
FieldH(color, "Note", format!("{note_name} {note_pos} {note_len}")),
)
}
}
impl TimeRange for MidiEditor {
fn time_len (&self) -> &AtomicUsize { self.mode.time_len() }
fn time_zoom (&self) -> &AtomicUsize { self.mode.time_zoom() }
fn time_lock (&self) -> &AtomicBool { self.mode.time_lock() }
fn time_start (&self) -> &AtomicUsize { self.mode.time_start() }
fn time_axis (&self) -> &AtomicUsize { self.mode.time_axis() }
}
impl NoteRange for MidiEditor {
fn note_lo (&self) -> &AtomicUsize { self.mode.note_lo() }
fn note_axis (&self) -> &AtomicUsize { self.mode.note_axis() }
}
impl NotePoint for MidiEditor {
fn note_len (&self) -> usize { self.mode.note_len() }
fn set_note_len (&self, x: usize) -> usize { self.mode.set_note_len(x) }
fn note_pos (&self) -> usize { self.mode.note_pos() }
fn set_note_pos (&self, x: usize) -> usize { self.mode.set_note_pos(x) }
}
impl TimePoint for MidiEditor {
fn time_pos (&self) -> usize { self.mode.time_pos() }
fn set_time_pos (&self, x: usize) -> usize { self.mode.set_time_pos(x) }
}
impl MidiViewer for MidiEditor {
fn buffer_size (&self, clip: &MidiClip) -> (usize, usize) { self.mode.buffer_size(clip) }
fn redraw (&self) { self.mode.redraw() }
fn clip (&self) -> &Option<Arc<RwLock<MidiClip>>> { self.mode.clip() }
fn clip_mut (&mut self) -> &mut Option<Arc<RwLock<MidiClip>>> { self.mode.clip_mut() }
fn set_clip (&mut self, p: Option<&Arc<RwLock<MidiClip>>>) { self.mode.set_clip(p) }
}

View file

@ -0,0 +1,33 @@
use crate::*;
impl Tek {
pub fn toggle_dialog (&mut self, dialog: Option<Dialog>) {
self.dialog = if self.dialog == dialog {
None
} else {
dialog
}
}
pub(crate) fn message_dismiss (&mut self) {
self.dialog = None;
}
}
/// Various possible dialog overlays
#[derive(PartialEq, Clone, Copy, Debug)]
pub enum Dialog {
Help,
Menu,
Device(usize),
Message(Message)
}
/// Various possible messages
#[derive(PartialEq, Clone, Copy, Debug)]
pub enum Message {
FailedToAddDevice,
}
content!(TuiOut: |self: Message| match self {
Self::FailedToAddDevice => "Failed to add device."
});

View file

@ -0,0 +1,170 @@
use crate::*;
/// Contains state for viewing and editing a clip
pub struct MidiEditor {
/// Size of editor on screen
pub size: Measure<TuiOut>,
/// View mode and state of editor
pub mode: PianoHorizontal,
}
impl std::fmt::Debug for MidiEditor {
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.debug_struct("MidiEditor")
.field("mode", &self.mode)
.finish()
}
}
impl Default for MidiEditor {
fn default () -> Self {
Self {
size: Measure::new(),
mode: PianoHorizontal::new(None),
}
}
}
has_size!(<TuiOut>|self: MidiEditor|&self.size);
content!(TuiOut: |self: MidiEditor| {
self.autoscroll();
//self.autozoom();
self.size.of(&self.mode)
});
from!(|clip: &Arc<RwLock<MidiClip>>|MidiEditor = {
let model = Self::from(Some(clip.clone()));
model.redraw();
model
});
from!(|clip: Option<Arc<RwLock<MidiClip>>>|MidiEditor = {
let mut model = Self::default();
*model.clip_mut() = clip;
model.redraw();
model
});
impl MidiEditor {
/// Put note at current position
pub fn put_note (&mut self, advance: bool) {
let mut redraw = false;
if let Some(clip) = self.clip() {
let mut clip = clip.write().unwrap();
let note_start = self.time_pos();
let note_pos = self.note_pos();
let note_len = self.note_len();
let note_end = note_start + (note_len.saturating_sub(1));
let key: u7 = u7::from(note_pos as u8);
let vel: u7 = 100.into();
let length = clip.length;
let note_end = note_end % length;
let note_on = MidiMessage::NoteOn { key, vel };
if !clip.notes[note_start].iter().any(|msg|*msg == note_on) {
clip.notes[note_start].push(note_on);
}
let note_off = MidiMessage::NoteOff { key, vel };
if !clip.notes[note_end].iter().any(|msg|*msg == note_off) {
clip.notes[note_end].push(note_off);
}
if advance {
self.set_time_pos(note_end);
}
redraw = true;
}
if redraw {
self.mode.redraw();
}
}
pub fn clip_status (&self) -> impl Content<TuiOut> + '_ {
let (color, name, length, looped) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) {
(clip.color, clip.name.clone(), clip.length, clip.looped)
} else { (ItemTheme::G[64], String::new().into(), 0, false) };
Bsp::e(
FieldH(color, "Edit", format!("{name} ({length})")),
FieldH(color, "Loop", looped.to_string())
)
}
pub fn edit_status (&self) -> impl Content<TuiOut> + '_ {
let (color, length) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) {
(clip.color, clip.length)
} else { (ItemTheme::G[64], 0) };
let time_pos = self.time_pos();
let time_zoom = self.time_zoom().get();
let time_lock = if self.time_lock().get() { "[lock]" } else { " " };
let note_pos = format!("{:>3}", self.note_pos());
let note_name = format!("{:4}", Note::pitch_to_name(self.note_pos()));
let note_len = format!("{:>4}", self.note_len());
Bsp::e(
FieldH(color, "Time", format!("{length}/{time_zoom}+{time_pos} {time_lock}")),
FieldH(color, "Note", format!("{note_name} {note_pos} {note_len}")),
)
}
}
impl TimeRange for MidiEditor {
fn time_len (&self) -> &AtomicUsize { self.mode.time_len() }
fn time_zoom (&self) -> &AtomicUsize { self.mode.time_zoom() }
fn time_lock (&self) -> &AtomicBool { self.mode.time_lock() }
fn time_start (&self) -> &AtomicUsize { self.mode.time_start() }
fn time_axis (&self) -> &AtomicUsize { self.mode.time_axis() }
}
impl NoteRange for MidiEditor {
fn note_lo (&self) -> &AtomicUsize { self.mode.note_lo() }
fn note_axis (&self) -> &AtomicUsize { self.mode.note_axis() }
}
impl NotePoint for MidiEditor {
fn note_len (&self) -> usize { self.mode.note_len() }
fn set_note_len (&self, x: usize) -> usize { self.mode.set_note_len(x) }
fn note_pos (&self) -> usize { self.mode.note_pos() }
fn set_note_pos (&self, x: usize) -> usize { self.mode.set_note_pos(x) }
}
impl TimePoint for MidiEditor {
fn time_pos (&self) -> usize { self.mode.time_pos() }
fn set_time_pos (&self, x: usize) -> usize { self.mode.set_time_pos(x) }
}
impl MidiViewer for MidiEditor {
fn buffer_size (&self, clip: &MidiClip) -> (usize, usize) { self.mode.buffer_size(clip) }
fn redraw (&self) { self.mode.redraw() }
fn clip (&self) -> &Option<Arc<RwLock<MidiClip>>> { self.mode.clip() }
fn clip_mut (&mut self) -> &mut Option<Arc<RwLock<MidiClip>>> { self.mode.clip_mut() }
fn set_clip (&mut self, p: Option<&Arc<RwLock<MidiClip>>>) { self.mode.set_clip(p) }
}
pub trait HasEditor {
fn editor (&self) -> &Option<MidiEditor>;
fn editor_mut (&mut self) -> &Option<MidiEditor>;
fn is_editing (&self) -> bool { true }
fn editor_w (&self) -> usize { 0 }
fn editor_h (&self) -> usize { 0 }
}
#[macro_export] macro_rules! has_editor {
(|$self:ident: $Struct:ident|{
editor = $e0:expr;
editor_w = $e1:expr;
editor_h = $e2:expr;
is_editing = $e3:expr;
}) => {
impl HasEditor for $Struct {
fn editor (&$self) -> &Option<MidiEditor> { &$e0 }
fn editor_mut (&mut $self) -> &Option<MidiEditor> { &mut $e0 }
fn editor_w (&$self) -> usize { $e1 }
fn editor_h (&$self) -> usize { $e2 }
fn is_editing (&$self) -> bool { $e3 }
}
};
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? HasEditor for $Struct $(<$($L),*$($T),*>)? {
fn editor (&$self) -> &MidiEditor { &$cb }
}
};
}

View file

@ -0,0 +1,147 @@
use crate::*;
pub trait HasSelection {
fn selected (&self) -> &Selection;
fn selected_mut (&mut self) -> &mut Selection;
}
/// Represents the current user selection in the arranger
#[derive(PartialEq, Clone, Copy, Debug, Default)]
pub enum Selection {
/// The whole mix is selected
#[default] Mix,
/// A MIDI input is selected.
Input(usize),
/// A MIDI output is selected.
Output(usize),
/// A scene is selected.
Scene(usize),
/// A track is selected.
Track(usize),
/// A clip (track × scene) is selected.
TrackClip { track: usize, scene: usize },
/// A track's MIDI input connection is selected.
TrackInput { track: usize, port: usize },
/// A track's MIDI output connection is selected.
TrackOutput { track: usize, port: usize },
/// A track device slot is selected.
TrackDevice { track: usize, device: usize },
}
/// Focus identification methods
impl Selection {
pub fn is_mix (&self) -> bool {
matches!(self, Self::Mix)
}
pub fn is_track (&self) -> bool {
matches!(self, Self::Track(_))
}
pub fn is_scene (&self) -> bool {
matches!(self, Self::Scene(_))
}
pub fn is_clip (&self) -> bool {
matches!(self, Self::TrackClip {..})
}
pub fn track (&self) -> Option<usize> {
use Selection::*;
match self {
Track(track)
| TrackClip { track, .. }
| TrackInput { track, .. }
| TrackOutput { track, .. }
| TrackDevice { track, .. } => Some(*track),
_ => None
}
}
pub fn track_next (&self, len: usize) -> Self {
use Selection::*;
match self {
Mix => Track(0),
Scene(s) => TrackClip { track: 0, scene: *s },
Track(t) => if t + 1 < len {
Track(t + 1)
} else {
Mix
},
TrackClip {track, scene} => if track + 1 < len {
TrackClip { track: track + 1, scene: *scene }
} else {
Scene(*scene)
},
_ => todo!()
}
}
pub fn track_prev (&self) -> Self {
use Selection::*;
match self {
Mix => Mix,
Scene(s) => Scene(*s),
Track(0) => Mix,
Track(t) => Track(t - 1),
TrackClip { track: 0, scene } => Scene(*scene),
TrackClip { track: t, scene } => TrackClip { track: t - 1, scene: *scene },
_ => todo!()
}
}
pub fn scene (&self) -> Option<usize> {
use Selection::*;
match self {
Scene(scene) | TrackClip { scene, .. } => Some(*scene),
_ => None
}
}
pub fn scene_next (&self, len: usize) -> Self {
use Selection::*;
match self {
Mix => Scene(0),
Track(t) => TrackClip { track: *t, scene: 0 },
Scene(s) => if s + 1 < len {
Scene(s + 1)
} else {
Mix
},
TrackClip { track, scene } => if scene + 1 < len {
TrackClip { track: *track, scene: scene + 1 }
} else {
Track(*track)
},
_ => todo!()
}
}
pub fn scene_prev (&self) -> Self {
use Selection::*;
match self {
Mix | Scene(0) => Mix,
Scene(s) => Scene(s - 1),
Track(t) => Track(*t),
TrackClip { track, scene: 0 } => Track(*track),
TrackClip { track, scene } => TrackClip { track: *track, scene: scene - 1 },
_ => todo!()
}
}
pub fn describe (&self, tracks: &[Track], scenes: &[Scene]) -> Arc<str> {
use Selection::*;
format!("{}", match self {
Mix => "Everything".to_string(),
Scene(s) => scenes.get(*s)
.map(|scene|format!("S{s}: {}", &scene.name))
.unwrap_or_else(||"S??".into()),
Track(t) => tracks.get(*t)
.map(|track|format!("T{t}: {}", &track.name))
.unwrap_or_else(||"T??".into()),
TrackClip { track, scene } => match (tracks.get(*track), scenes.get(*scene)) {
(Some(_), Some(s)) => match s.clip(*track) {
Some(clip) => format!("T{track} S{scene} C{}", &clip.read().unwrap().name),
None => format!("T{track} S{scene}: Empty")
},
_ => format!("T{track} S{scene}: Empty"),
},
_ => todo!()
}).into()
}
}
impl HasSelection for Tek {
fn selected (&self) -> &Selection { &self.selected }
fn selected_mut (&mut self) -> &mut Selection { &mut self.selected }
}

View file

@ -60,30 +60,30 @@ impl Tek {
self.sampler().map(|s|s.view_sample(self.editor().unwrap().note_pos()))
}
#[tengri::view(":modal")]
fn view_modal (&self) -> impl Content<TuiOut> + use<'_> {
When::new(self.modal.is_some(), Bsp::b(
#[tengri::view(":dialog")]
fn view_dialog (&self) -> impl Content<TuiOut> + use<'_> {
When::new(self.dialog.is_some(), Bsp::b(
Fill::xy(Tui::fg_bg(Rgb(64,64,64), Rgb(32,32,32), "")),
Fixed::xy(30, 15, Tui::fg_bg(Rgb(255,255,255), Rgb(16,16,16), Bsp::b(
Repeat(" "),
Outer(true, Style::default().fg(Tui::g(96)))
.enclose(self.modal.as_ref().map(|modal|match modal {
Modal::Menu => self.view_modal_menu().boxed(),
Modal::Help => self.view_modal_help().boxed(),
Modal::Device(index) => self.view_modal_device(*index).boxed(),
Modal::Message(message) => self.view_modal_message(message).boxed(),
.enclose(self.dialog.as_ref().map(|dialog|match dialog {
Dialog::Menu => self.view_dialog_menu().boxed(),
Dialog::Help => self.view_dialog_help().boxed(),
Dialog::Device(index) => self.view_dialog_device(*index).boxed(),
Dialog::Message(message) => self.view_dialog_message(message).boxed(),
}))
)))
))
}
fn view_modal_menu (&self) -> impl Content<TuiOut> {
fn view_dialog_menu (&self) -> impl Content<TuiOut> {
let options = ||["Projects", "Settings", "Help", "Quit"].iter();
let option = |a,i|Tui::fg(Rgb(255,255,255), format!("{}", a));
Bsp::s(Tui::bold(true, "tek!"), Bsp::s("", Map::south(1, options, option)))
}
fn view_modal_help (&self) -> impl Content<TuiOut> + use<'_> {
fn view_dialog_help (&self) -> impl Content<TuiOut> + use<'_> {
let bindings = ||self.config.keys.layers.iter()
.filter_map(|a|(a.0)(self).then_some(a.1))
.flat_map(|a|a)
@ -111,7 +111,7 @@ impl Tek {
Bsp::s(Tui::bold(true, "Help"), Bsp::s("", Map::south(1, bindings, binding)))
}
fn view_modal_device (&self, index: usize) -> impl Content<TuiOut> + use<'_> {
fn view_dialog_device (&self, index: usize) -> impl Content<TuiOut> + use<'_> {
let choices = ||self.device_kinds().iter();
let choice = move|label, i|
Fill::x(Tui::bg(if i == index { Rgb(64,128,32) } else { Rgb(0,0,0) },
@ -121,7 +121,7 @@ impl Tek {
Bsp::s(Tui::bold(true, "Add device"), Map::south(1, choices, choice))
}
fn view_modal_message <'a> (&'a self, message: &'a Message) -> impl Content<TuiOut> + use<'a> {
fn view_dialog_message <'a> (&'a self, message: &'a Message) -> impl Content<TuiOut> + use<'a> {
Bsp::s(message, Bsp::s("", "[ OK ]"))
}

View file

@ -6,8 +6,6 @@ impl Content<TuiOut> for Lv2 {
let area = to.area();
let [x, y, _, height] = area;
let mut width = 20u16;
//match &self.plugin {
//Some(PluginKind::LV2(LV2Plugin { port_list, instance, .. })) => {
let start = self.selected.saturating_sub((height as usize / 2).saturating_sub(1));
let end = start + height as usize - 2;
//draw_box(buf, Rect { x, y, width, height });
@ -31,9 +29,6 @@ impl Content<TuiOut> for Lv2 {
break
}
}
//}
//_ => {}
//};
draw_header(self, to, x, y, width);
}
}