From 0b1193722d0ce0f301403d1c81aec456626520ba Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 9 Nov 2024 17:43:08 +0100 Subject: [PATCH] arranger: theme trait --- crates/tek_core/src/command.rs | 4 +- crates/tek_sequencer/src/arranger.rs | 3 + crates/tek_sequencer/src/arranger_cmd.rs | 11 ++ crates/tek_sequencer/src/arranger_tui.rs | 169 ++++++++++++++-------- crates/tek_sequencer/src/sequencer_tui.rs | 43 +++--- crates/tek_sequencer/src/transport_tui.rs | 6 +- 6 files changed, 149 insertions(+), 87 deletions(-) diff --git a/crates/tek_core/src/command.rs b/crates/tek_core/src/command.rs index 1fd72494..6bba1f5d 100644 --- a/crates/tek_core/src/command.rs +++ b/crates/tek_core/src/command.rs @@ -12,8 +12,8 @@ pub fn delegate , S> ( Ok(cmd.execute(state)?.map(|x|wrap(x))) } -pub trait MatchInput: Sized { - fn match_input (state: &S, input: &E::Input) -> Option; +pub trait InputToCommand: Sized { + fn input_to_command (state: &S, input: &E::Input) -> Option; } pub struct MenuBar> { pub menus: Vec>, diff --git a/crates/tek_sequencer/src/arranger.rs b/crates/tek_sequencer/src/arranger.rs index a697fd59..1fdb5446 100644 --- a/crates/tek_sequencer/src/arranger.rs +++ b/crates/tek_sequencer/src/arranger.rs @@ -28,6 +28,8 @@ pub struct Arranger { pub size: Measure, /// Menu bar pub menu: MenuBar, + /// Command history + pub history: Vec, } /// Sections in the arranger app that may be focused #[derive(Copy, Clone, PartialEq, Eq)] @@ -142,6 +144,7 @@ impl Arranger { transport: transport.clone(), arrangement, phrases, + history: vec![], size: Measure::new(), clock: if let Some(ref transport) = transport { transport.read().unwrap().clock.clone() diff --git a/crates/tek_sequencer/src/arranger_cmd.rs b/crates/tek_sequencer/src/arranger_cmd.rs index 22decbce..53400e02 100644 --- a/crates/tek_sequencer/src/arranger_cmd.rs +++ b/crates/tek_sequencer/src/arranger_cmd.rs @@ -7,6 +7,7 @@ pub enum ArrangerCommand { Phrases(PhrasePoolCommand), Editor(PhraseEditorCommand), Arrangement(ArrangementCommand), + EditPhrase, } #[derive(Clone, PartialEq)] pub enum ArrangementCommand { @@ -32,6 +33,7 @@ pub enum ArrangementCommand { GoDown, GoLeft, GoRight, + Edit, } impl Command> for ArrangerCommand { @@ -54,6 +56,14 @@ impl Command> for ArrangerCommand { } else { Ok(None) }, + Self::EditPhrase => if let Some(phrase) = state.arrangement.phrase() { + state.editor.phrase = Some(phrase.clone()); + state.focus(ArrangerFocus::PhraseEditor); + state.focus_enter(); + Ok(None) + } else { + Ok(None) + } }?; state.show_phrase(); state.update_status(); @@ -67,6 +77,7 @@ impl Command> for ArrangementCommand { New => todo!(), Load => todo!(), Save => todo!(), + Edit => todo!(), ToggleViewMode => { state.mode.to_next(); }, Delete => { state.delete(); }, Activate => { state.activate(); }, diff --git a/crates/tek_sequencer/src/arranger_tui.rs b/crates/tek_sequencer/src/arranger_tui.rs index 123525e5..6279f802 100644 --- a/crates/tek_sequencer/src/arranger_tui.rs +++ b/crates/tek_sequencer/src/arranger_tui.rs @@ -1,12 +1,15 @@ use crate::*; + /// Layout for standalone arranger app. impl Content for Arranger { type Engine = Tui; fn content (&self) -> impl Widget { - let focused = self.arrangement.focused; - 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 focused = self.arrangement.focused; + let border_bg = Arranger::::border_bg(); + let border_fg = Arranger::::border_fg(focused); + let title_fg = Arranger::::title_fg(focused); + let border = Lozenge(Style::default().bg(border_bg).fg(border_fg)); + let entered = if self.arrangement.entered { "■" } else { " " }; Split::down( 1, row!(menu in self.menu.menus.iter() => { @@ -23,11 +26,7 @@ impl Content for Arranger { lay!( widget(&self.arrangement).grow_y(1).border(border), widget(&self.arrangement.size), - widget(&format!("[{}] Arrangement", if self.arrangement.entered { - "■" - } else { - " " - })).fg(title_color).push_x(1), + widget(&format!("[{}] Arrangement", entered)).fg(title_fg).push_x(1), ), Split::right( self.phrases_split, @@ -53,9 +52,10 @@ impl Content for ArrangerStatusBar { Self::PhraseView => "VIEW SEQ", Self::PhraseEdit => "EDIT SEQ", }; - let mode = TuiStyle::bg(format!(" {label} "), Color::Rgb(150, 160, 90)) - .fg(Color::Rgb(0, 0, 0)) - .bold(true); + let status_bar_bg = Arranger::::status_bar_bg(); + let mode_bg = Arranger::::mode_bg(); + let mode_fg = Arranger::::mode_fg(); + let mode = TuiStyle::bold(format!(" {label} "), true).bg(mode_bg).fg(mode_fg); let commands = match self { Self::ArrangementMix => command(&[ ["", "c", "olor"], @@ -118,13 +118,18 @@ impl Content for ArrangerStatusBar { _ => command(&[]) }; //let commands = commands.iter().reduce(String::new(), |s, (a, b, c)| format!("{s} {a}{b}{c}")); - row!(mode, commands).fill_x().bg(Color::Rgb(28, 35, 25)) + row!(mode, commands).fill_x().bg(status_bar_bg) } } fn command (commands: &[[impl Widget;3]]) -> impl Widget + '_ { Stack::right(|add|{ Ok(for [a, b, c] in commands.iter() { - add(&row!(" ", widget(a), widget(b).bold(true).fg(Color::Rgb(255,255,0)), widget(c)))?; + add(&row!( + " ", + widget(a), + widget(b).bold(true).fg(Arranger::::hotkey_fg()), + widget(c), + ))?; }) }) } @@ -133,15 +138,9 @@ impl Content for Arrangement { fn content (&self) -> impl Widget { Layers::new(move |add|{ match self.mode { - ArrangementViewMode::Horizontal => { - add(&HorizontalArranger(&self)) - }, - ArrangementViewMode::Vertical(factor) => { - add(&VerticalArranger(&self, factor)) - }, + ArrangementViewMode::Horizontal => { add(&HorizontalArranger(&self)) }, + ArrangementViewMode::Vertical(factor) => { add(&VerticalArranger(&self, factor)) }, }?; - let color = if self.focused{Color::Rgb(150, 160, 90)}else{Color::Rgb(120, 130, 100)}; - //add(&TuiStyle::fg("Project", color).push_x(2))?; add(&self.size) }) } @@ -155,7 +154,8 @@ impl<'a> Content for VerticalArranger<'a, Tui> { let cols = state.track_widths(); let rows = Scene::ppqs(scenes, *factor); let bg = state.color; - let clip_bg = Color::Rgb(40, 50, 30); + let clip_bg = Arranger::::border_bg(); + let sep_fg = Arranger::::separator_fg(false); let header_h = 3u16;//5u16; let scenes_w = 3 + Scene::longest_name(scenes) as u16; // x of 1st track let clock = &self.0.clock; @@ -165,7 +165,7 @@ impl<'a> Content for VerticalArranger<'a, Tui> { let any_size = |_|Ok(Some([0,0])); // column separators add(&CustomWidget::new(any_size, move|to: &mut TuiOutput|{ - let style = Some(Style::default().fg(Color::Rgb(0, 0, 0))); + let style = Some(Style::default().fg(sep_fg)); Ok(for x in cols.iter().map(|col|col.1) { let x = scenes_w + to.area().x() + x as u16; for y in to.area().y()..to.area().y2() { to.blit(&"▎", x, y, style); } @@ -180,7 +180,7 @@ impl<'a> Content for VerticalArranger<'a, Tui> { if x < to.buffer.area.x && y < to.buffer.area.y { let cell = to.buffer.get_mut(x, y); cell.modifier = Modifier::UNDERLINED; - cell.underline_color = Color::Rgb(0, 0, 0); + cell.underline_color = sep_fg; } } }) @@ -307,13 +307,14 @@ impl<'a> Content for VerticalArranger<'a, Tui> { area }, }; + let bg = Arranger::::border_bg(); if let Some([x, y, width, height]) = track_area { - to.fill_fg([x, y, 1, height], Color::Rgb(70, 80, 50)); - to.fill_fg([x + width, y, 1, height], Color::Rgb(70, 80, 50)); + to.fill_fg([x, y, 1, height], bg); + to.fill_fg([x + width, y, 1, height], bg); } if let Some([_, y, _, height]) = scene_area { - to.fill_ul([area.x(), y - 1, area.w(), 1], Color::Rgb(70, 80, 50)); - to.fill_ul([area.x(), y + height - 1, area.w(), 1], Color::Rgb(70, 80, 50)); + to.fill_ul([area.x(), y - 1, area.w(), 1], bg); + to.fill_ul([area.x(), y + height - 1, area.w(), 1], bg); } Ok(if focused { to.render_in(if let Some(clip_area) = clip_area { clip_area } @@ -323,7 +324,7 @@ impl<'a> Content for VerticalArranger<'a, Tui> { }) })) }).bg(bg.rgb); - let color = if self.0.focused {Color::Rgb(150, 160, 90)} else {Color::Rgb(120, 130, 100)}; + let color = Arranger::::title_fg(self.0.focused); let size = format!("{}x{}", self.0.size.w(), self.0.size.h()); let lower_right = TuiStyle::fg(size, color).pull_x(1).align_se().fill_xy(); lay!(arrangement, lower_right) @@ -335,7 +336,7 @@ impl<'a> Content for HorizontalArranger<'a, Tui> { let Arrangement { tracks, focused, .. } = self.0; let _tracks = tracks.as_slice(); lay!( - focused.then_some(Background(Color::Rgb(40, 50, 30))), + focused.then_some(Background(Arranger::::border_bg())), row!( // name CustomWidget::new(|_|{todo!()}, |_: &mut TuiOutput|{ @@ -538,7 +539,7 @@ impl Handle for Arranger { return Ok(Some(true)) } } - Ok(if let Some(command) = ArrangerCommand::match_input(self, i) { + Ok(if let Some(command) = ArrangerCommand::input_to_command(self, i) { let _undo = command.execute(self)?; Some(true) } else { @@ -549,7 +550,7 @@ impl Handle for Arranger { /// Handle events for arrangement. impl Handle for Arrangement { fn handle (&mut self, from: &TuiInput) -> Perhaps { - Ok(if let Some(command) = ArrangementCommand::match_input(self, from) { + Ok(if let Some(command) = ArrangementCommand::input_to_command(self, from) { let _undo = command.execute(self)?; Some(true) } else { @@ -557,8 +558,8 @@ impl Handle for Arrangement { }) } } -impl MatchInput> for ArrangerCommand { - fn match_input (state: &Arranger, input: &TuiInput) -> Option { +impl InputToCommand> for ArrangerCommand { + fn input_to_command (state: &Arranger, input: &TuiInput) -> Option { use FocusCommand::*; use ArrangerCommand::*; match input.event() { @@ -575,46 +576,50 @@ impl MatchInput> for ArrangerCommand { key!(KeyCode::Char(' ')) => Some(Transport(TransportCommand::PlayToggle)), _ => match state.focused() { ArrangerFocus::Transport => state.transport.as_ref() - .map(|t|TransportCommand::match_input(&*t.read().unwrap(), input) + .map(|t|TransportCommand::input_to_command(&*t.read().unwrap(), input) .map(Transport)) .flatten(), ArrangerFocus::PhrasePool => - PhrasePoolCommand::match_input(&*state.phrases.read().unwrap(), input) + PhrasePoolCommand::input_to_command(&*state.phrases.read().unwrap(), input) .map(Phrases), ArrangerFocus::PhraseEditor => - PhraseEditorCommand::match_input(&state.editor, input) + PhraseEditorCommand::input_to_command(&state.editor, input) .map(Editor), - ArrangerFocus::Arrangement => - ArrangementCommand::match_input(&state.arrangement, &input) + ArrangerFocus::Arrangement => match input.event() { + key!(KeyCode::Char('e')) => Some(EditPhrase), + _ => ArrangementCommand::input_to_command(&state.arrangement, &input) .map(Arrangement) + } } } } } -impl MatchInput> for ArrangementCommand { - fn match_input (_: &Arrangement, input: &TuiInput) -> Option { +impl InputToCommand> for ArrangementCommand { + fn input_to_command (_: &Arrangement, input: &TuiInput) -> Option { + use ArrangementCommand::*; match input.event() { - key!(KeyCode::Char('`')) => Some(Self::ToggleViewMode), - key!(KeyCode::Delete) => Some(Self::Delete), - key!(KeyCode::Enter) => Some(Self::Activate), - key!(KeyCode::Char('.')) => Some(Self::Increment), - key!(KeyCode::Char(',')) => Some(Self::Decrement), - key!(KeyCode::Char('+')) => Some(Self::ZoomIn), - key!(KeyCode::Char('=')) => Some(Self::ZoomOut), - key!(KeyCode::Char('_')) => Some(Self::ZoomOut), - key!(KeyCode::Char('-')) => Some(Self::ZoomOut), - key!(KeyCode::Char('<')) => Some(Self::MoveBack), - key!(KeyCode::Char('>')) => Some(Self::MoveForward), - key!(KeyCode::Char('c')) => Some(Self::RandomColor), - key!(KeyCode::Char('s')) => Some(Self::Put), - key!(KeyCode::Char('g')) => Some(Self::Get), - key!(Ctrl-KeyCode::Char('a')) => Some(Self::AddScene), - key!(Ctrl-KeyCode::Char('t')) => Some(Self::AddTrack), - key!(KeyCode::Char('l')) => Some(Self::ToggleLoop), - key!(KeyCode::Up) => Some(Self::GoUp), - key!(KeyCode::Down) => Some(Self::GoDown), - key!(KeyCode::Left) => Some(Self::GoLeft), - key!(KeyCode::Right) => Some(Self::GoRight), + key!(KeyCode::Char('`')) => Some(ToggleViewMode), + key!(KeyCode::Delete) => Some(Delete), + key!(KeyCode::Enter) => Some(Activate), + key!(KeyCode::Char('.')) => Some(Increment), + key!(KeyCode::Char(',')) => Some(Decrement), + key!(KeyCode::Char('+')) => Some(ZoomIn), + key!(KeyCode::Char('=')) => Some(ZoomOut), + key!(KeyCode::Char('_')) => Some(ZoomOut), + key!(KeyCode::Char('-')) => Some(ZoomOut), + key!(KeyCode::Char('<')) => Some(MoveBack), + key!(KeyCode::Char('>')) => Some(MoveForward), + key!(KeyCode::Char('c')) => Some(RandomColor), + key!(KeyCode::Char('s')) => Some(Put), + key!(KeyCode::Char('g')) => Some(Get), + key!(KeyCode::Char('e')) => Some(Edit), + key!(Ctrl-KeyCode::Char('a')) => Some(AddScene), + key!(Ctrl-KeyCode::Char('t')) => Some(AddTrack), + key!(KeyCode::Char('l')) => Some(ToggleLoop), + key!(KeyCode::Up) => Some(GoUp), + key!(KeyCode::Down) => Some(GoDown), + key!(KeyCode::Left) => Some(GoLeft), + key!(KeyCode::Right) => Some(GoRight), _ => None } } @@ -670,3 +675,41 @@ impl MatchInput> for ArrangementCommand { //Ok(Some(true)) //} //} + +trait ArrangerTheme { + fn border_bg () -> Color; + fn border_fg (focused: bool) -> Color; + fn title_fg (focused: bool) -> Color; + fn separator_fg (focused: bool) -> Color; + fn hotkey_fg () -> Color; + fn mode_bg () -> Color; + fn mode_fg () -> Color; + fn status_bar_bg () -> Color; +} + +impl ArrangerTheme for Arranger { + fn border_bg () -> Color { + Color::Rgb(40, 50, 30) + } + fn border_fg (focused: bool) -> Color { + if focused { Color::Rgb(100, 110, 40) } else { Color::Rgb(70, 80, 50) } + } + fn title_fg (focused: bool) -> Color { + if focused { Color::Rgb(150, 160, 90) } else { Color::Rgb(120, 130, 100) } + } + fn separator_fg (_: bool) -> Color { + Color::Rgb(0, 0, 0) + } + fn hotkey_fg () -> Color { + Color::Rgb(255, 255, 0) + } + fn mode_bg () -> Color { + Color::Rgb(150, 160, 90) + } + fn mode_fg () -> Color { + Color::Rgb(255, 255, 255) + } + fn status_bar_bg () -> Color { + Color::Rgb(28, 35, 25) + } +} diff --git a/crates/tek_sequencer/src/sequencer_tui.rs b/crates/tek_sequencer/src/sequencer_tui.rs index 5e7dc6fd..73815b4c 100644 --- a/crates/tek_sequencer/src/sequencer_tui.rs +++ b/crates/tek_sequencer/src/sequencer_tui.rs @@ -22,15 +22,15 @@ impl Handle for Sequencer { return Ok(Some(true)) } } - if let Some(command) = SequencerCommand::match_input(self, i) { + if let Some(command) = SequencerCommand::input_to_command(self, i) { let _undo = command.execute(self)?; return Ok(Some(true)) } Ok(None) } } -impl MatchInput> for SequencerCommand { - fn match_input (state: &Sequencer, input: &TuiInput) -> Option { +impl InputToCommand> for SequencerCommand { + fn input_to_command (state: &Sequencer, input: &TuiInput) -> Option { use SequencerCommand::*; use FocusCommand::*; match input.event() { @@ -45,15 +45,15 @@ impl MatchInput> for SequencerCommand { key!(KeyCode::Char(' ')) => Some(Transport(TransportCommand::PlayToggle)), _ => match state.focused() { SequencerFocus::Transport => if let Some(t) = state.transport.as_ref() { - TransportCommand::match_input(&*t.read().unwrap(), input).map(Transport) + TransportCommand::input_to_command(&*t.read().unwrap(), input).map(Transport) } else { None }, SequencerFocus::PhrasePool => - PhrasePoolCommand::match_input(&*state.phrases.read().unwrap(), input) + PhrasePoolCommand::input_to_command(&*state.phrases.read().unwrap(), input) .map(Phrases), SequencerFocus::PhraseEditor => - PhraseEditorCommand::match_input(&state.editor, input) + PhraseEditorCommand::input_to_command(&state.editor, input) .map(Editor), } } @@ -100,15 +100,15 @@ impl Content for PhrasePool { } impl Handle for PhrasePool { fn handle (&mut self, from: &TuiInput) -> Perhaps { - if let Some(command) = PhrasePoolCommand::match_input(self, from) { + if let Some(command) = PhrasePoolCommand::input_to_command(self, from) { let _undo = command.execute(self)?; return Ok(Some(true)) } Ok(None) } } -impl MatchInput> for PhrasePoolCommand { - fn match_input (state: &PhrasePool, input: &TuiInput) -> Option { +impl InputToCommand> for PhrasePoolCommand { + fn input_to_command (state: &PhrasePool, input: &TuiInput) -> Option { match input.event() { key!(KeyCode::Up) => Some(Self::Prev), key!(KeyCode::Down) => Some(Self::Next), @@ -122,17 +122,17 @@ impl MatchInput> for PhrasePoolCommand { key!(KeyCode::Char('n')) => Some(Self::Rename(PhraseRenameCommand::Begin)), key!(KeyCode::Char('t')) => Some(Self::Length(PhraseLengthCommand::Begin)), _ => match state.mode { - Some(PhrasePoolMode::Rename(..)) => PhraseRenameCommand::match_input(state, input) + Some(PhrasePoolMode::Rename(..)) => PhraseRenameCommand::input_to_command(state, input) .map(Self::Rename), - Some(PhrasePoolMode::Length(..)) => PhraseLengthCommand::match_input(state, input) + Some(PhrasePoolMode::Length(..)) => PhraseLengthCommand::input_to_command(state, input) .map(Self::Length), _ => None } } } } -impl MatchInput> for PhraseRenameCommand { - fn match_input (_: &PhrasePool, from: &TuiInput) -> Option { +impl InputToCommand> for PhraseRenameCommand { + fn input_to_command (_: &PhrasePool, from: &TuiInput) -> Option { match from.event() { key!(KeyCode::Backspace) => Some(Self::Backspace), key!(KeyCode::Enter) => Some(Self::Confirm), @@ -142,8 +142,8 @@ impl MatchInput> for PhraseRenameCommand { } } } -impl MatchInput> for PhraseLengthCommand { - fn match_input (_: &PhrasePool, from: &TuiInput) -> Option { +impl InputToCommand> for PhraseLengthCommand { + fn input_to_command (_: &PhrasePool, from: &TuiInput) -> Option { match from.event() { key!(KeyCode::Up) => Some(Self::Inc), key!(KeyCode::Down) => Some(Self::Dec), @@ -307,10 +307,15 @@ impl Content for PhraseEditor { //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(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(), ) } @@ -456,15 +461,15 @@ pub(crate) fn keys_vert () -> Buffer { } impl Handle for PhraseEditor { fn handle (&mut self, from: &TuiInput) -> Perhaps { - if let Some(command) = PhraseEditorCommand::match_input(self, from) { + if let Some(command) = PhraseEditorCommand::input_to_command(self, from) { let _undo = command.execute(self)?; return Ok(Some(true)) } Ok(None) } } -impl MatchInput> for PhraseEditorCommand { - fn match_input (_: &PhraseEditor, from: &TuiInput) -> Option { +impl InputToCommand> for PhraseEditorCommand { + fn input_to_command (_: &PhraseEditor, from: &TuiInput) -> Option { match from.event() { key!(KeyCode::Char('`')) => Some(Self::ToggleDirection), key!(KeyCode::Enter) => Some(Self::EnterEditMode), diff --git a/crates/tek_sequencer/src/transport_tui.rs b/crates/tek_sequencer/src/transport_tui.rs index d9f72b68..14490769 100644 --- a/crates/tek_sequencer/src/transport_tui.rs +++ b/crates/tek_sequencer/src/transport_tui.rs @@ -47,15 +47,15 @@ impl TransportToolbarFocus { } impl Handle for TransportToolbar { fn handle (&mut self, from: &TuiInput) -> Perhaps { - if let Some(command) = TransportCommand::match_input(self, from) { + if let Some(command) = TransportCommand::input_to_command(self, from) { let _undo = command.execute(self)?; return Ok(Some(true)) } Ok(None) } } -impl MatchInput> for TransportCommand { - fn match_input (_: &TransportToolbar, input: &TuiInput) -> Option { +impl InputToCommand> for TransportCommand { + fn input_to_command (_: &TransportToolbar, input: &TuiInput) -> Option { match input.event() { key!(KeyCode::Char(' ')) => Some(Self::FocusPrev), key!(Shift-KeyCode::Char(' ')) => Some(Self::FocusPrev),