arranger: theme trait

This commit is contained in:
🪞👃🪞 2024-11-09 17:43:08 +01:00
parent 89cb8d7bbe
commit 0b1193722d
6 changed files with 149 additions and 87 deletions

View file

@ -12,8 +12,8 @@ pub fn delegate <B, C: Command<S>, S> (
Ok(cmd.execute(state)?.map(|x|wrap(x))) Ok(cmd.execute(state)?.map(|x|wrap(x)))
} }
pub trait MatchInput<E: Engine, S>: Sized { pub trait InputToCommand<E: Engine, S>: Sized {
fn match_input (state: &S, input: &E::Input) -> Option<Self>; fn input_to_command (state: &S, input: &E::Input) -> Option<Self>;
} }
pub struct MenuBar<E: Engine, S, C: Command<S>> { pub struct MenuBar<E: Engine, S, C: Command<S>> {
pub menus: Vec<Menu<E, S, C>>, pub menus: Vec<Menu<E, S, C>>,

View file

@ -28,6 +28,8 @@ pub struct Arranger<E: Engine> {
pub size: Measure<E>, pub size: Measure<E>,
/// Menu bar /// Menu bar
pub menu: MenuBar<E, Self, ArrangerCommand>, pub menu: MenuBar<E, Self, ArrangerCommand>,
/// Command history
pub history: Vec<ArrangerCommand>,
} }
/// Sections in the arranger app that may be focused /// Sections in the arranger app that may be focused
#[derive(Copy, Clone, PartialEq, Eq)] #[derive(Copy, Clone, PartialEq, Eq)]
@ -142,6 +144,7 @@ impl<E: Engine> Arranger<E> {
transport: transport.clone(), transport: transport.clone(),
arrangement, arrangement,
phrases, phrases,
history: vec![],
size: Measure::new(), size: Measure::new(),
clock: if let Some(ref transport) = transport { clock: if let Some(ref transport) = transport {
transport.read().unwrap().clock.clone() transport.read().unwrap().clock.clone()

View file

@ -7,6 +7,7 @@ pub enum ArrangerCommand {
Phrases(PhrasePoolCommand), Phrases(PhrasePoolCommand),
Editor(PhraseEditorCommand), Editor(PhraseEditorCommand),
Arrangement(ArrangementCommand), Arrangement(ArrangementCommand),
EditPhrase,
} }
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub enum ArrangementCommand { pub enum ArrangementCommand {
@ -32,6 +33,7 @@ pub enum ArrangementCommand {
GoDown, GoDown,
GoLeft, GoLeft,
GoRight, GoRight,
Edit,
} }
impl<E: Engine> Command<Arranger<E>> for ArrangerCommand { impl<E: Engine> Command<Arranger<E>> for ArrangerCommand {
@ -54,6 +56,14 @@ impl<E: Engine> Command<Arranger<E>> for ArrangerCommand {
} else { } else {
Ok(None) 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.show_phrase();
state.update_status(); state.update_status();
@ -67,6 +77,7 @@ impl<E: Engine> Command<Arrangement<E>> for ArrangementCommand {
New => todo!(), New => todo!(),
Load => todo!(), Load => todo!(),
Save => todo!(), Save => todo!(),
Edit => todo!(),
ToggleViewMode => { state.mode.to_next(); }, ToggleViewMode => { state.mode.to_next(); },
Delete => { state.delete(); }, Delete => { state.delete(); },
Activate => { state.activate(); }, Activate => { state.activate(); },

View file

@ -1,12 +1,15 @@
use crate::*; use crate::*;
/// Layout for standalone arranger app. /// Layout for standalone arranger app.
impl Content for Arranger<Tui> { impl Content for Arranger<Tui> {
type Engine = Tui; type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> { fn content (&self) -> impl Widget<Engine = Tui> {
let focused = self.arrangement.focused; let focused = self.arrangement.focused;
let border_color = if focused{Color::Rgb(100, 110, 40)}else{Color::Rgb(70, 80, 50)}; let border_bg = Arranger::<Tui>::border_bg();
let title_color = if focused{Color::Rgb(150, 160, 90)}else{Color::Rgb(120, 130, 100)}; let border_fg = Arranger::<Tui>::border_fg(focused);
let border = Lozenge(Style::default().bg(Color::Rgb(40, 50, 30)).fg(border_color)); let title_fg = Arranger::<Tui>::title_fg(focused);
let border = Lozenge(Style::default().bg(border_bg).fg(border_fg));
let entered = if self.arrangement.entered { "" } else { " " };
Split::down( Split::down(
1, 1,
row!(menu in self.menu.menus.iter() => { row!(menu in self.menu.menus.iter() => {
@ -23,11 +26,7 @@ impl Content for Arranger<Tui> {
lay!( lay!(
widget(&self.arrangement).grow_y(1).border(border), widget(&self.arrangement).grow_y(1).border(border),
widget(&self.arrangement.size), widget(&self.arrangement.size),
widget(&format!("[{}] Arrangement", if self.arrangement.entered { widget(&format!("[{}] Arrangement", entered)).fg(title_fg).push_x(1),
""
} else {
" "
})).fg(title_color).push_x(1),
), ),
Split::right( Split::right(
self.phrases_split, self.phrases_split,
@ -53,9 +52,10 @@ impl Content for ArrangerStatusBar {
Self::PhraseView => "VIEW SEQ", Self::PhraseView => "VIEW SEQ",
Self::PhraseEdit => "EDIT SEQ", Self::PhraseEdit => "EDIT SEQ",
}; };
let mode = TuiStyle::bg(format!(" {label} "), Color::Rgb(150, 160, 90)) let status_bar_bg = Arranger::<Tui>::status_bar_bg();
.fg(Color::Rgb(0, 0, 0)) let mode_bg = Arranger::<Tui>::mode_bg();
.bold(true); let mode_fg = Arranger::<Tui>::mode_fg();
let mode = TuiStyle::bold(format!(" {label} "), true).bg(mode_bg).fg(mode_fg);
let commands = match self { let commands = match self {
Self::ArrangementMix => command(&[ Self::ArrangementMix => command(&[
["", "c", "olor"], ["", "c", "olor"],
@ -118,13 +118,18 @@ impl Content for ArrangerStatusBar {
_ => command(&[]) _ => command(&[])
}; };
//let commands = commands.iter().reduce(String::new(), |s, (a, b, c)| format!("{s} {a}{b}{c}")); //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<Engine = Tui>;3]]) -> impl Widget<Engine = Tui> + '_ { fn command (commands: &[[impl Widget<Engine = Tui>;3]]) -> impl Widget<Engine = Tui> + '_ {
Stack::right(|add|{ Stack::right(|add|{
Ok(for [a, b, c] in commands.iter() { 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::<Tui>::hotkey_fg()),
widget(c),
))?;
}) })
}) })
} }
@ -133,15 +138,9 @@ impl Content for Arrangement<Tui> {
fn content (&self) -> impl Widget<Engine = Tui> { fn content (&self) -> impl Widget<Engine = Tui> {
Layers::new(move |add|{ Layers::new(move |add|{
match self.mode { match self.mode {
ArrangementViewMode::Horizontal => { ArrangementViewMode::Horizontal => { add(&HorizontalArranger(&self)) },
add(&HorizontalArranger(&self)) ArrangementViewMode::Vertical(factor) => { add(&VerticalArranger(&self, factor)) },
},
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) add(&self.size)
}) })
} }
@ -155,7 +154,8 @@ impl<'a> Content for VerticalArranger<'a, Tui> {
let cols = state.track_widths(); let cols = state.track_widths();
let rows = Scene::ppqs(scenes, *factor); let rows = Scene::ppqs(scenes, *factor);
let bg = state.color; let bg = state.color;
let clip_bg = Color::Rgb(40, 50, 30); let clip_bg = Arranger::<Tui>::border_bg();
let sep_fg = Arranger::<Tui>::separator_fg(false);
let header_h = 3u16;//5u16; let header_h = 3u16;//5u16;
let scenes_w = 3 + Scene::longest_name(scenes) as u16; // x of 1st track let scenes_w = 3 + Scene::longest_name(scenes) as u16; // x of 1st track
let clock = &self.0.clock; let clock = &self.0.clock;
@ -165,7 +165,7 @@ impl<'a> Content for VerticalArranger<'a, Tui> {
let any_size = |_|Ok(Some([0,0])); let any_size = |_|Ok(Some([0,0]));
// column separators // column separators
add(&CustomWidget::new(any_size, move|to: &mut TuiOutput|{ 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) { Ok(for x in cols.iter().map(|col|col.1) {
let x = scenes_w + to.area().x() + x as u16; let x = scenes_w + to.area().x() + x as u16;
for y in to.area().y()..to.area().y2() { to.blit(&"", x, y, style); } 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 { if x < to.buffer.area.x && y < to.buffer.area.y {
let cell = to.buffer.get_mut(x, y); let cell = to.buffer.get_mut(x, y);
cell.modifier = Modifier::UNDERLINED; 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 area
}, },
}; };
let bg = Arranger::<Tui>::border_bg();
if let Some([x, y, width, height]) = track_area { 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, y, 1, height], bg);
to.fill_fg([x + width, y, 1, height], Color::Rgb(70, 80, 50)); to.fill_fg([x + width, y, 1, height], bg);
} }
if let Some([_, y, _, height]) = scene_area { 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 - 1, area.w(), 1], bg);
to.fill_ul([area.x(), y + height - 1, area.w(), 1], Color::Rgb(70, 80, 50)); to.fill_ul([area.x(), y + height - 1, area.w(), 1], bg);
} }
Ok(if focused { Ok(if focused {
to.render_in(if let Some(clip_area) = clip_area { clip_area } 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); }).bg(bg.rgb);
let color = if self.0.focused {Color::Rgb(150, 160, 90)} else {Color::Rgb(120, 130, 100)}; let color = Arranger::<Tui>::title_fg(self.0.focused);
let size = format!("{}x{}", self.0.size.w(), self.0.size.h()); 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(); let lower_right = TuiStyle::fg(size, color).pull_x(1).align_se().fill_xy();
lay!(arrangement, lower_right) lay!(arrangement, lower_right)
@ -335,7 +336,7 @@ impl<'a> Content for HorizontalArranger<'a, Tui> {
let Arrangement { tracks, focused, .. } = self.0; let Arrangement { tracks, focused, .. } = self.0;
let _tracks = tracks.as_slice(); let _tracks = tracks.as_slice();
lay!( lay!(
focused.then_some(Background(Color::Rgb(40, 50, 30))), focused.then_some(Background(Arranger::<Tui>::border_bg())),
row!( row!(
// name // name
CustomWidget::new(|_|{todo!()}, |_: &mut TuiOutput|{ CustomWidget::new(|_|{todo!()}, |_: &mut TuiOutput|{
@ -538,7 +539,7 @@ impl Handle<Tui> for Arranger<Tui> {
return Ok(Some(true)) 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)?; let _undo = command.execute(self)?;
Some(true) Some(true)
} else { } else {
@ -549,7 +550,7 @@ impl Handle<Tui> for Arranger<Tui> {
/// Handle events for arrangement. /// Handle events for arrangement.
impl Handle<Tui> for Arrangement<Tui> { impl Handle<Tui> for Arrangement<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> { fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
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)?; let _undo = command.execute(self)?;
Some(true) Some(true)
} else { } else {
@ -557,8 +558,8 @@ impl Handle<Tui> for Arrangement<Tui> {
}) })
} }
} }
impl MatchInput<Tui, Arranger<Tui>> for ArrangerCommand { impl InputToCommand<Tui, Arranger<Tui>> for ArrangerCommand {
fn match_input (state: &Arranger<Tui>, input: &TuiInput) -> Option<Self> { fn input_to_command (state: &Arranger<Tui>, input: &TuiInput) -> Option<Self> {
use FocusCommand::*; use FocusCommand::*;
use ArrangerCommand::*; use ArrangerCommand::*;
match input.event() { match input.event() {
@ -575,46 +576,50 @@ impl MatchInput<Tui, Arranger<Tui>> for ArrangerCommand {
key!(KeyCode::Char(' ')) => Some(Transport(TransportCommand::PlayToggle)), key!(KeyCode::Char(' ')) => Some(Transport(TransportCommand::PlayToggle)),
_ => match state.focused() { _ => match state.focused() {
ArrangerFocus::Transport => state.transport.as_ref() 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)) .map(Transport))
.flatten(), .flatten(),
ArrangerFocus::PhrasePool => ArrangerFocus::PhrasePool =>
PhrasePoolCommand::match_input(&*state.phrases.read().unwrap(), input) PhrasePoolCommand::input_to_command(&*state.phrases.read().unwrap(), input)
.map(Phrases), .map(Phrases),
ArrangerFocus::PhraseEditor => ArrangerFocus::PhraseEditor =>
PhraseEditorCommand::match_input(&state.editor, input) PhraseEditorCommand::input_to_command(&state.editor, input)
.map(Editor), .map(Editor),
ArrangerFocus::Arrangement => ArrangerFocus::Arrangement => match input.event() {
ArrangementCommand::match_input(&state.arrangement, &input) key!(KeyCode::Char('e')) => Some(EditPhrase),
_ => ArrangementCommand::input_to_command(&state.arrangement, &input)
.map(Arrangement) .map(Arrangement)
} }
} }
} }
}
} }
impl MatchInput<Tui, Arrangement<Tui>> for ArrangementCommand { impl InputToCommand<Tui, Arrangement<Tui>> for ArrangementCommand {
fn match_input (_: &Arrangement<Tui>, input: &TuiInput) -> Option<Self> { fn input_to_command (_: &Arrangement<Tui>, input: &TuiInput) -> Option<Self> {
use ArrangementCommand::*;
match input.event() { match input.event() {
key!(KeyCode::Char('`')) => Some(Self::ToggleViewMode), key!(KeyCode::Char('`')) => Some(ToggleViewMode),
key!(KeyCode::Delete) => Some(Self::Delete), key!(KeyCode::Delete) => Some(Delete),
key!(KeyCode::Enter) => Some(Self::Activate), key!(KeyCode::Enter) => Some(Activate),
key!(KeyCode::Char('.')) => Some(Self::Increment), key!(KeyCode::Char('.')) => Some(Increment),
key!(KeyCode::Char(',')) => Some(Self::Decrement), key!(KeyCode::Char(',')) => Some(Decrement),
key!(KeyCode::Char('+')) => Some(Self::ZoomIn), key!(KeyCode::Char('+')) => Some(ZoomIn),
key!(KeyCode::Char('=')) => Some(Self::ZoomOut), key!(KeyCode::Char('=')) => Some(ZoomOut),
key!(KeyCode::Char('_')) => Some(Self::ZoomOut), key!(KeyCode::Char('_')) => Some(ZoomOut),
key!(KeyCode::Char('-')) => Some(Self::ZoomOut), key!(KeyCode::Char('-')) => Some(ZoomOut),
key!(KeyCode::Char('<')) => Some(Self::MoveBack), key!(KeyCode::Char('<')) => Some(MoveBack),
key!(KeyCode::Char('>')) => Some(Self::MoveForward), key!(KeyCode::Char('>')) => Some(MoveForward),
key!(KeyCode::Char('c')) => Some(Self::RandomColor), key!(KeyCode::Char('c')) => Some(RandomColor),
key!(KeyCode::Char('s')) => Some(Self::Put), key!(KeyCode::Char('s')) => Some(Put),
key!(KeyCode::Char('g')) => Some(Self::Get), key!(KeyCode::Char('g')) => Some(Get),
key!(Ctrl-KeyCode::Char('a')) => Some(Self::AddScene), key!(KeyCode::Char('e')) => Some(Edit),
key!(Ctrl-KeyCode::Char('t')) => Some(Self::AddTrack), key!(Ctrl-KeyCode::Char('a')) => Some(AddScene),
key!(KeyCode::Char('l')) => Some(Self::ToggleLoop), key!(Ctrl-KeyCode::Char('t')) => Some(AddTrack),
key!(KeyCode::Up) => Some(Self::GoUp), key!(KeyCode::Char('l')) => Some(ToggleLoop),
key!(KeyCode::Down) => Some(Self::GoDown), key!(KeyCode::Up) => Some(GoUp),
key!(KeyCode::Left) => Some(Self::GoLeft), key!(KeyCode::Down) => Some(GoDown),
key!(KeyCode::Right) => Some(Self::GoRight), key!(KeyCode::Left) => Some(GoLeft),
key!(KeyCode::Right) => Some(GoRight),
_ => None _ => None
} }
} }
@ -670,3 +675,41 @@ impl MatchInput<Tui, Arrangement<Tui>> for ArrangementCommand {
//Ok(Some(true)) //Ok(Some(true))
//} //}
//} //}
trait ArrangerTheme<E: Engine> {
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<Tui> for Arranger<Tui> {
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)
}
}

View file

@ -22,15 +22,15 @@ impl Handle<Tui> for Sequencer<Tui> {
return Ok(Some(true)) 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)?; let _undo = command.execute(self)?;
return Ok(Some(true)) return Ok(Some(true))
} }
Ok(None) Ok(None)
} }
} }
impl MatchInput<Tui, Sequencer<Tui>> for SequencerCommand { impl InputToCommand<Tui, Sequencer<Tui>> for SequencerCommand {
fn match_input (state: &Sequencer<Tui>, input: &TuiInput) -> Option<Self> { fn input_to_command (state: &Sequencer<Tui>, input: &TuiInput) -> Option<Self> {
use SequencerCommand::*; use SequencerCommand::*;
use FocusCommand::*; use FocusCommand::*;
match input.event() { match input.event() {
@ -45,15 +45,15 @@ impl MatchInput<Tui, Sequencer<Tui>> for SequencerCommand {
key!(KeyCode::Char(' ')) => Some(Transport(TransportCommand::PlayToggle)), key!(KeyCode::Char(' ')) => Some(Transport(TransportCommand::PlayToggle)),
_ => match state.focused() { _ => match state.focused() {
SequencerFocus::Transport => if let Some(t) = state.transport.as_ref() { 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 { } else {
None None
}, },
SequencerFocus::PhrasePool => SequencerFocus::PhrasePool =>
PhrasePoolCommand::match_input(&*state.phrases.read().unwrap(), input) PhrasePoolCommand::input_to_command(&*state.phrases.read().unwrap(), input)
.map(Phrases), .map(Phrases),
SequencerFocus::PhraseEditor => SequencerFocus::PhraseEditor =>
PhraseEditorCommand::match_input(&state.editor, input) PhraseEditorCommand::input_to_command(&state.editor, input)
.map(Editor), .map(Editor),
} }
} }
@ -100,15 +100,15 @@ impl Content for PhrasePool<Tui> {
} }
impl Handle<Tui> for PhrasePool<Tui> { impl Handle<Tui> for PhrasePool<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> { fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
if let Some(command) = PhrasePoolCommand::match_input(self, from) { if let Some(command) = PhrasePoolCommand::input_to_command(self, from) {
let _undo = command.execute(self)?; let _undo = command.execute(self)?;
return Ok(Some(true)) return Ok(Some(true))
} }
Ok(None) Ok(None)
} }
} }
impl MatchInput<Tui, PhrasePool<Tui>> for PhrasePoolCommand { impl InputToCommand<Tui, PhrasePool<Tui>> for PhrasePoolCommand {
fn match_input (state: &PhrasePool<Tui>, input: &TuiInput) -> Option<Self> { fn input_to_command (state: &PhrasePool<Tui>, input: &TuiInput) -> Option<Self> {
match input.event() { match input.event() {
key!(KeyCode::Up) => Some(Self::Prev), key!(KeyCode::Up) => Some(Self::Prev),
key!(KeyCode::Down) => Some(Self::Next), key!(KeyCode::Down) => Some(Self::Next),
@ -122,17 +122,17 @@ impl MatchInput<Tui, PhrasePool<Tui>> for PhrasePoolCommand {
key!(KeyCode::Char('n')) => Some(Self::Rename(PhraseRenameCommand::Begin)), key!(KeyCode::Char('n')) => Some(Self::Rename(PhraseRenameCommand::Begin)),
key!(KeyCode::Char('t')) => Some(Self::Length(PhraseLengthCommand::Begin)), key!(KeyCode::Char('t')) => Some(Self::Length(PhraseLengthCommand::Begin)),
_ => match state.mode { _ => match state.mode {
Some(PhrasePoolMode::Rename(..)) => PhraseRenameCommand::match_input(state, input) Some(PhrasePoolMode::Rename(..)) => PhraseRenameCommand::input_to_command(state, input)
.map(Self::Rename), .map(Self::Rename),
Some(PhrasePoolMode::Length(..)) => PhraseLengthCommand::match_input(state, input) Some(PhrasePoolMode::Length(..)) => PhraseLengthCommand::input_to_command(state, input)
.map(Self::Length), .map(Self::Length),
_ => None _ => None
} }
} }
} }
} }
impl MatchInput<Tui, PhrasePool<Tui>> for PhraseRenameCommand { impl InputToCommand<Tui, PhrasePool<Tui>> for PhraseRenameCommand {
fn match_input (_: &PhrasePool<Tui>, from: &TuiInput) -> Option<Self> { fn input_to_command (_: &PhrasePool<Tui>, from: &TuiInput) -> Option<Self> {
match from.event() { match from.event() {
key!(KeyCode::Backspace) => Some(Self::Backspace), key!(KeyCode::Backspace) => Some(Self::Backspace),
key!(KeyCode::Enter) => Some(Self::Confirm), key!(KeyCode::Enter) => Some(Self::Confirm),
@ -142,8 +142,8 @@ impl MatchInput<Tui, PhrasePool<Tui>> for PhraseRenameCommand {
} }
} }
} }
impl MatchInput<Tui, PhrasePool<Tui>> for PhraseLengthCommand { impl InputToCommand<Tui, PhrasePool<Tui>> for PhraseLengthCommand {
fn match_input (_: &PhrasePool<Tui>, from: &TuiInput) -> Option<Self> { fn input_to_command (_: &PhrasePool<Tui>, from: &TuiInput) -> Option<Self> {
match from.event() { match from.event() {
key!(KeyCode::Up) => Some(Self::Inc), key!(KeyCode::Up) => Some(Self::Inc),
key!(KeyCode::Down) => Some(Self::Dec), key!(KeyCode::Down) => Some(Self::Dec),
@ -307,10 +307,15 @@ impl Content for PhraseEditor<Tui> {
//note_clamp.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!( lay!(
content, content,
TuiStyle::fg(upper_left.to_string(), title_color).push_x(1).align_nw().fill_xy(), 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(), 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<Tui> for PhraseEditor<Tui> { impl Handle<Tui> for PhraseEditor<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> { fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
if let Some(command) = PhraseEditorCommand::match_input(self, from) { if let Some(command) = PhraseEditorCommand::input_to_command(self, from) {
let _undo = command.execute(self)?; let _undo = command.execute(self)?;
return Ok(Some(true)) return Ok(Some(true))
} }
Ok(None) Ok(None)
} }
} }
impl MatchInput<Tui, PhraseEditor<Tui>> for PhraseEditorCommand { impl InputToCommand<Tui, PhraseEditor<Tui>> for PhraseEditorCommand {
fn match_input (_: &PhraseEditor<Tui>, from: &TuiInput) -> Option<Self> { fn input_to_command (_: &PhraseEditor<Tui>, from: &TuiInput) -> Option<Self> {
match from.event() { match from.event() {
key!(KeyCode::Char('`')) => Some(Self::ToggleDirection), key!(KeyCode::Char('`')) => Some(Self::ToggleDirection),
key!(KeyCode::Enter) => Some(Self::EnterEditMode), key!(KeyCode::Enter) => Some(Self::EnterEditMode),

View file

@ -47,15 +47,15 @@ impl TransportToolbarFocus {
} }
impl Handle<Tui> for TransportToolbar<Tui> { impl Handle<Tui> for TransportToolbar<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> { fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
if let Some(command) = TransportCommand::match_input(self, from) { if let Some(command) = TransportCommand::input_to_command(self, from) {
let _undo = command.execute(self)?; let _undo = command.execute(self)?;
return Ok(Some(true)) return Ok(Some(true))
} }
Ok(None) Ok(None)
} }
} }
impl MatchInput<Tui, TransportToolbar<Tui>> for TransportCommand { impl InputToCommand<Tui, TransportToolbar<Tui>> for TransportCommand {
fn match_input (_: &TransportToolbar<Tui>, input: &TuiInput) -> Option<Self> { fn input_to_command (_: &TransportToolbar<Tui>, input: &TuiInput) -> Option<Self> {
match input.event() { match input.event() {
key!(KeyCode::Char(' ')) => Some(Self::FocusPrev), key!(KeyCode::Char(' ')) => Some(Self::FocusPrev),
key!(Shift-KeyCode::Char(' ')) => Some(Self::FocusPrev), key!(Shift-KeyCode::Char(' ')) => Some(Self::FocusPrev),