More robust midi implementation
This commit is contained in:
@@ -31,7 +31,6 @@ pub fn render(frame: &mut Frame, app: &App, link: &LinkState, area: Rect) {
|
||||
let focus = app.options.focus;
|
||||
let content_width = padded.width as usize;
|
||||
|
||||
// Build link header with status
|
||||
let enabled = link.is_enabled();
|
||||
let peers = link.peers();
|
||||
let (status_text, status_color) = if !enabled {
|
||||
@@ -64,7 +63,6 @@ pub fn render(frame: &mut Frame, app: &App, link: &LinkState, area: Rect) {
|
||||
Span::styled(peer_text, Style::new().fg(theme.ui.text_muted)),
|
||||
]);
|
||||
|
||||
// Prepare values
|
||||
let flash_str = format!("{:.0}%", app.ui.flash_brightness * 100.0);
|
||||
let quantum_str = format!("{:.0}", link.quantum());
|
||||
let tempo_str = format!("{:.1} BPM", link.tempo());
|
||||
@@ -74,35 +72,45 @@ pub fn render(frame: &mut Frame, app: &App, link: &LinkState, area: Rect) {
|
||||
let tempo_style = Style::new().fg(theme.values.tempo).add_modifier(Modifier::BOLD);
|
||||
let value_style = Style::new().fg(theme.values.value);
|
||||
|
||||
// MIDI device lists
|
||||
let midi_outputs = midi::list_midi_outputs();
|
||||
let midi_inputs = midi::list_midi_inputs();
|
||||
|
||||
let midi_out_display = if let Some(idx) = app.midi.selected_output {
|
||||
midi_outputs
|
||||
.get(idx)
|
||||
.map(|d| d.name.as_str())
|
||||
.unwrap_or("(disconnected)")
|
||||
} else if midi_outputs.is_empty() {
|
||||
"(none found)"
|
||||
} else {
|
||||
"(not connected)"
|
||||
let midi_out_display = |slot: usize| -> String {
|
||||
if let Some(idx) = app.midi.selected_outputs[slot] {
|
||||
midi_outputs
|
||||
.get(idx)
|
||||
.map(|d| d.name.clone())
|
||||
.unwrap_or_else(|| "(disconnected)".to_string())
|
||||
} else if midi_outputs.is_empty() {
|
||||
"(none found)".to_string()
|
||||
} else {
|
||||
"(not connected)".to_string()
|
||||
}
|
||||
};
|
||||
|
||||
let midi_in_display = if let Some(idx) = app.midi.selected_input {
|
||||
midi_inputs
|
||||
.get(idx)
|
||||
.map(|d| d.name.as_str())
|
||||
.unwrap_or("(disconnected)")
|
||||
} else if midi_inputs.is_empty() {
|
||||
"(none found)"
|
||||
} else {
|
||||
"(not connected)"
|
||||
let midi_in_display = |slot: usize| -> String {
|
||||
if let Some(idx) = app.midi.selected_inputs[slot] {
|
||||
midi_inputs
|
||||
.get(idx)
|
||||
.map(|d| d.name.clone())
|
||||
.unwrap_or_else(|| "(disconnected)".to_string())
|
||||
} else if midi_inputs.is_empty() {
|
||||
"(none found)".to_string()
|
||||
} else {
|
||||
"(not connected)".to_string()
|
||||
}
|
||||
};
|
||||
|
||||
// Build flat list of all lines
|
||||
let midi_out_0 = midi_out_display(0);
|
||||
let midi_out_1 = midi_out_display(1);
|
||||
let midi_out_2 = midi_out_display(2);
|
||||
let midi_out_3 = midi_out_display(3);
|
||||
let midi_in_0 = midi_in_display(0);
|
||||
let midi_in_1 = midi_in_display(1);
|
||||
let midi_in_2 = midi_in_display(2);
|
||||
let midi_in_3 = midi_in_display(3);
|
||||
|
||||
let lines: Vec<Line> = vec![
|
||||
// DISPLAY section (lines 0-8)
|
||||
render_section_header("DISPLAY", &theme),
|
||||
render_divider(content_width, &theme),
|
||||
render_option_line(
|
||||
@@ -146,9 +154,7 @@ pub fn render(frame: &mut Frame, app: &App, link: &LinkState, area: Rect) {
|
||||
&theme,
|
||||
),
|
||||
render_option_line("Flash brightness", &flash_str, focus == OptionsFocus::FlashBrightness, &theme),
|
||||
// Blank line (line 9)
|
||||
Line::from(""),
|
||||
// ABLETON LINK section (lines 10-15)
|
||||
link_header,
|
||||
render_divider(content_width, &theme),
|
||||
render_option_line(
|
||||
@@ -168,27 +174,31 @@ pub fn render(frame: &mut Frame, app: &App, link: &LinkState, area: Rect) {
|
||||
&theme,
|
||||
),
|
||||
render_option_line("Quantum", &quantum_str, focus == OptionsFocus::Quantum, &theme),
|
||||
// Blank line (line 16)
|
||||
Line::from(""),
|
||||
// SESSION section (lines 17-20)
|
||||
render_section_header("SESSION", &theme),
|
||||
render_divider(content_width, &theme),
|
||||
render_readonly_line("Tempo", &tempo_str, tempo_style, &theme),
|
||||
render_readonly_line("Beat", &beat_str, value_style, &theme),
|
||||
render_readonly_line("Phase", &phase_str, value_style, &theme),
|
||||
// Blank line (line 22)
|
||||
Line::from(""),
|
||||
// MIDI section (lines 23-26)
|
||||
render_section_header("MIDI", &theme),
|
||||
render_section_header("MIDI OUTPUTS", &theme),
|
||||
render_divider(content_width, &theme),
|
||||
render_option_line("Output", midi_out_display, focus == OptionsFocus::MidiOutput, &theme),
|
||||
render_option_line("Input", midi_in_display, focus == OptionsFocus::MidiInput, &theme),
|
||||
render_option_line("Output 0", &midi_out_0, focus == OptionsFocus::MidiOutput0, &theme),
|
||||
render_option_line("Output 1", &midi_out_1, focus == OptionsFocus::MidiOutput1, &theme),
|
||||
render_option_line("Output 2", &midi_out_2, focus == OptionsFocus::MidiOutput2, &theme),
|
||||
render_option_line("Output 3", &midi_out_3, focus == OptionsFocus::MidiOutput3, &theme),
|
||||
Line::from(""),
|
||||
render_section_header("MIDI INPUTS", &theme),
|
||||
render_divider(content_width, &theme),
|
||||
render_option_line("Input 0", &midi_in_0, focus == OptionsFocus::MidiInput0, &theme),
|
||||
render_option_line("Input 1", &midi_in_1, focus == OptionsFocus::MidiInput1, &theme),
|
||||
render_option_line("Input 2", &midi_in_2, focus == OptionsFocus::MidiInput2, &theme),
|
||||
render_option_line("Input 3", &midi_in_3, focus == OptionsFocus::MidiInput3, &theme),
|
||||
];
|
||||
|
||||
let total_lines = lines.len();
|
||||
let max_visible = padded.height as usize;
|
||||
|
||||
// Map focus to line index
|
||||
let focus_line: usize = match focus {
|
||||
OptionsFocus::ColorScheme => 2,
|
||||
OptionsFocus::RefreshRate => 3,
|
||||
@@ -200,11 +210,16 @@ pub fn render(frame: &mut Frame, app: &App, link: &LinkState, area: Rect) {
|
||||
OptionsFocus::LinkEnabled => 12,
|
||||
OptionsFocus::StartStopSync => 13,
|
||||
OptionsFocus::Quantum => 14,
|
||||
OptionsFocus::MidiOutput => 25,
|
||||
OptionsFocus::MidiInput => 26,
|
||||
OptionsFocus::MidiOutput0 => 25,
|
||||
OptionsFocus::MidiOutput1 => 26,
|
||||
OptionsFocus::MidiOutput2 => 27,
|
||||
OptionsFocus::MidiOutput3 => 28,
|
||||
OptionsFocus::MidiInput0 => 32,
|
||||
OptionsFocus::MidiInput1 => 33,
|
||||
OptionsFocus::MidiInput2 => 34,
|
||||
OptionsFocus::MidiInput3 => 35,
|
||||
};
|
||||
|
||||
// Calculate scroll offset to keep focused line visible (centered when possible)
|
||||
let scroll_offset = if total_lines <= max_visible {
|
||||
0
|
||||
} else {
|
||||
@@ -213,7 +228,6 @@ pub fn render(frame: &mut Frame, app: &App, link: &LinkState, area: Rect) {
|
||||
.min(total_lines.saturating_sub(max_visible))
|
||||
};
|
||||
|
||||
// Render visible portion
|
||||
let visible_end = (scroll_offset + max_visible).min(total_lines);
|
||||
let visible_lines: Vec<Line> = lines
|
||||
.into_iter()
|
||||
@@ -223,7 +237,6 @@ pub fn render(frame: &mut Frame, app: &App, link: &LinkState, area: Rect) {
|
||||
|
||||
frame.render_widget(Paragraph::new(visible_lines), padded);
|
||||
|
||||
// Render scroll indicators
|
||||
let indicator_style = Style::new().fg(theme.ui.text_dim);
|
||||
let indicator_x = padded.x + padded.width.saturating_sub(1);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user