This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -5,13 +5,11 @@ use ratatui::widgets::{Block, Borders, Paragraph};
|
||||
use ratatui::Frame;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::engine::LinkState;
|
||||
use crate::midi;
|
||||
use crate::state::OptionsFocus;
|
||||
use crate::theme::{self, ThemeColors};
|
||||
use crate::widgets::{render_scroll_indicators, IndicatorAlign};
|
||||
|
||||
pub fn render(frame: &mut Frame, app: &App, link: &LinkState, area: Rect) {
|
||||
pub fn render(frame: &mut Frame, app: &App, area: Rect) {
|
||||
let theme = theme::get();
|
||||
|
||||
let block = Block::default()
|
||||
@@ -141,144 +139,24 @@ pub fn render(frame: &mut Frame, app: &App, link: &LinkState, area: Rect) {
|
||||
&theme,
|
||||
));
|
||||
}
|
||||
if !app.plugin_mode {
|
||||
let enabled = link.is_enabled();
|
||||
let peers = link.peers();
|
||||
let (status_text, status_color) = if !enabled {
|
||||
("DISABLED", theme.link_status.disabled)
|
||||
} else if peers > 0 {
|
||||
("CONNECTED", theme.link_status.connected)
|
||||
} else {
|
||||
("LISTENING", theme.link_status.listening)
|
||||
};
|
||||
let peer_text = if enabled && peers > 0 {
|
||||
if peers == 1 {
|
||||
" · 1 peer".to_string()
|
||||
} else {
|
||||
format!(" · {peers} peers")
|
||||
}
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let link_header = Line::from(vec![
|
||||
Span::styled(
|
||||
"ABLETON LINK",
|
||||
Style::new().fg(theme.ui.header).add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::raw(" "),
|
||||
Span::styled(
|
||||
status_text,
|
||||
Style::new().fg(status_color).add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::styled(peer_text, Style::new().fg(theme.ui.text_muted)),
|
||||
]);
|
||||
let quantum_str = format!("{:.0}", link.quantum());
|
||||
let tempo_str = format!("{:.1} BPM", link.tempo());
|
||||
let beat_str = format!("{:.2}", link.beat());
|
||||
let phase_str = format!("{:.2}", link.phase());
|
||||
let tempo_style = Style::new().fg(theme.values.tempo).add_modifier(Modifier::BOLD);
|
||||
let value_style = Style::new().fg(theme.values.value);
|
||||
|
||||
lines.push(Line::from(""));
|
||||
lines.extend([
|
||||
link_header,
|
||||
render_divider(content_width, &theme),
|
||||
render_option_line(
|
||||
"Enabled",
|
||||
if link.is_enabled() { "On" } else { "Off" },
|
||||
focus == OptionsFocus::LinkEnabled,
|
||||
&theme,
|
||||
),
|
||||
render_option_line(
|
||||
"Start/Stop sync",
|
||||
if link.is_start_stop_sync_enabled() {
|
||||
"On"
|
||||
} else {
|
||||
"Off"
|
||||
},
|
||||
focus == OptionsFocus::StartStopSync,
|
||||
&theme,
|
||||
),
|
||||
render_option_line("Quantum", &quantum_str, focus == OptionsFocus::Quantum, &theme),
|
||||
Line::from(""),
|
||||
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),
|
||||
]);
|
||||
|
||||
let midi_outputs = midi::list_midi_outputs();
|
||||
let midi_inputs = midi::list_midi_inputs();
|
||||
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 = |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()
|
||||
}
|
||||
};
|
||||
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);
|
||||
|
||||
lines.push(Line::from(""));
|
||||
lines.extend([
|
||||
render_section_header("MIDI OUTPUTS", &theme),
|
||||
render_divider(content_width, &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),
|
||||
]);
|
||||
}
|
||||
if !app.plugin_mode {
|
||||
lines.push(Line::from(""));
|
||||
lines.extend([
|
||||
render_section_header("ONBOARDING", &theme),
|
||||
render_divider(content_width, &theme),
|
||||
render_option_line(
|
||||
"Reset guides",
|
||||
&onboarding_str,
|
||||
focus == OptionsFocus::ResetOnboarding,
|
||||
&theme,
|
||||
),
|
||||
render_option_line(
|
||||
"Demo on startup",
|
||||
if app.ui.load_demo_on_startup { "On" } else { "Off" },
|
||||
focus == OptionsFocus::LoadDemoOnStartup,
|
||||
&theme,
|
||||
),
|
||||
]);
|
||||
}
|
||||
lines.push(Line::from(""));
|
||||
lines.extend([
|
||||
render_section_header("ONBOARDING", &theme),
|
||||
render_divider(content_width, &theme),
|
||||
render_option_line(
|
||||
"Reset guides",
|
||||
&onboarding_str,
|
||||
focus == OptionsFocus::ResetOnboarding,
|
||||
&theme,
|
||||
),
|
||||
render_option_line(
|
||||
"Demo on startup",
|
||||
if app.ui.load_demo_on_startup { "On" } else { "Off" },
|
||||
focus == OptionsFocus::LoadDemoOnStartup,
|
||||
&theme,
|
||||
),
|
||||
]);
|
||||
|
||||
// Insert description below focused option
|
||||
let focus_vec_idx = focus.line_index(app.plugin_mode);
|
||||
@@ -330,7 +208,7 @@ fn render_section_header(title: &str, theme: &theme::ThemeColors) -> Line<'stati
|
||||
|
||||
fn render_divider(width: usize, theme: &theme::ThemeColors) -> Line<'static> {
|
||||
Line::from(Span::styled(
|
||||
"─".repeat(width),
|
||||
"\u{2500}".repeat(width),
|
||||
Style::new().fg(theme.ui.border),
|
||||
))
|
||||
}
|
||||
@@ -377,17 +255,6 @@ fn option_description(focus: OptionsFocus) -> Option<&'static str> {
|
||||
OptionsFocus::Font => Some("Bitmap font for the plugin window"),
|
||||
OptionsFocus::ZoomFactor => Some("Scale factor for the plugin window"),
|
||||
OptionsFocus::WindowSize => Some("Default size for the plugin window"),
|
||||
OptionsFocus::LinkEnabled => Some("Join an Ableton Link session on the local network"),
|
||||
OptionsFocus::StartStopSync => Some("Sync transport start/stop with other Link peers"),
|
||||
OptionsFocus::Quantum => Some("Number of beats per phase cycle"),
|
||||
OptionsFocus::MidiOutput0 => Some("MIDI output device for channel group 1"),
|
||||
OptionsFocus::MidiOutput1 => Some("MIDI output device for channel group 2"),
|
||||
OptionsFocus::MidiOutput2 => Some("MIDI output device for channel group 3"),
|
||||
OptionsFocus::MidiOutput3 => Some("MIDI output device for channel group 4"),
|
||||
OptionsFocus::MidiInput0 => Some("MIDI input device for channel group 1"),
|
||||
OptionsFocus::MidiInput1 => Some("MIDI input device for channel group 2"),
|
||||
OptionsFocus::MidiInput2 => Some("MIDI input device for channel group 3"),
|
||||
OptionsFocus::MidiInput3 => Some("MIDI input device for channel group 4"),
|
||||
OptionsFocus::ResetOnboarding => Some("Re-enable all dismissed guide popups"),
|
||||
OptionsFocus::LoadDemoOnStartup => Some("Load a rotating demo song on fresh startup"),
|
||||
}
|
||||
@@ -403,15 +270,3 @@ fn render_description_line(desc: &str, theme: &ThemeColors) -> Line<'static> {
|
||||
fn gain_boost_label(gain: f32) -> String {
|
||||
format!("{:.0}x", gain)
|
||||
}
|
||||
|
||||
fn render_readonly_line(label: &str, value: &str, value_style: Style, theme: &theme::ThemeColors) -> Line<'static> {
|
||||
let label_style = Style::new().fg(theme.ui.text_muted);
|
||||
let label_width = 20;
|
||||
let padded_label = format!("{label:<label_width$}");
|
||||
|
||||
Line::from(vec![
|
||||
Span::styled(" ".to_string(), Style::new()),
|
||||
Span::styled(padded_label, label_style),
|
||||
Span::styled(format!(" {value}"), value_style),
|
||||
])
|
||||
}
|
||||
|
||||
@@ -185,8 +185,8 @@ pub fn render(
|
||||
match app.page {
|
||||
Page::Main => main_view::render(frame, app, snapshot, page_area),
|
||||
Page::Patterns => patterns_view::render(frame, app, snapshot, page_area),
|
||||
Page::Engine => engine_view::render(frame, app, page_area),
|
||||
Page::Options => options_view::render(frame, app, link, page_area),
|
||||
Page::Engine => engine_view::render(frame, app, link, page_area),
|
||||
Page::Options => options_view::render(frame, app, page_area),
|
||||
Page::Help => help_view::render(frame, app, page_area),
|
||||
Page::Dict => dict_view::render(frame, app, page_area),
|
||||
Page::Script => script_view::render(frame, app, snapshot, page_area),
|
||||
@@ -557,10 +557,10 @@ fn render_footer(frame: &mut Frame, app: &App, area: Rect) {
|
||||
],
|
||||
Page::Engine => vec![
|
||||
("Tab", "Section"),
|
||||
("←→", "Switch/Adjust"),
|
||||
("A", "Add Samples"),
|
||||
("D", "Remove"),
|
||||
("←→", "Adjust"),
|
||||
("R", "Restart"),
|
||||
("t", "Test"),
|
||||
("h/p", "Hush/Panic"),
|
||||
("?", "Keys"),
|
||||
],
|
||||
Page::Options => vec![
|
||||
|
||||
Reference in New Issue
Block a user