use super::CyclicEnum; #[derive(Clone, Copy, PartialEq, Eq, Default)] pub enum OptionsFocus { #[default] ColorScheme, HueRotation, RefreshRate, RuntimeHighlight, ShowScope, ShowSpectrum, ShowCompletion, ShowPreview, PerformanceMode, Font, ZoomFactor, WindowSize, LinkEnabled, StartStopSync, Quantum, MidiOutput0, MidiOutput1, MidiOutput2, MidiOutput3, MidiInput0, MidiInput1, MidiInput2, MidiInput3, ResetOnboarding, } impl CyclicEnum for OptionsFocus { const VARIANTS: &'static [Self] = &[ Self::ColorScheme, Self::HueRotation, Self::RefreshRate, Self::RuntimeHighlight, Self::ShowScope, Self::ShowSpectrum, Self::ShowCompletion, Self::ShowPreview, Self::PerformanceMode, Self::Font, Self::ZoomFactor, Self::WindowSize, Self::LinkEnabled, Self::StartStopSync, Self::Quantum, Self::MidiOutput0, Self::MidiOutput1, Self::MidiOutput2, Self::MidiOutput3, Self::MidiInput0, Self::MidiInput1, Self::MidiInput2, Self::MidiInput3, Self::ResetOnboarding, ]; } const PLUGIN_ONLY: &[OptionsFocus] = &[ OptionsFocus::Font, OptionsFocus::ZoomFactor, OptionsFocus::WindowSize, ]; const STANDALONE_ONLY: &[OptionsFocus] = &[ OptionsFocus::LinkEnabled, OptionsFocus::StartStopSync, OptionsFocus::Quantum, OptionsFocus::MidiOutput0, OptionsFocus::MidiOutput1, OptionsFocus::MidiOutput2, OptionsFocus::MidiOutput3, OptionsFocus::MidiInput0, OptionsFocus::MidiInput1, OptionsFocus::MidiInput2, OptionsFocus::MidiInput3, OptionsFocus::ResetOnboarding, ]; /// Section layout: header line, divider line, then option lines. /// Each entry gives the raw line offsets assuming ALL sections are visible /// (plugin mode with Font/Zoom/Window shown). const FULL_LAYOUT: &[(OptionsFocus, usize)] = &[ // DISPLAY section: header=0, divider=1 (OptionsFocus::ColorScheme, 2), (OptionsFocus::HueRotation, 3), (OptionsFocus::RefreshRate, 4), (OptionsFocus::RuntimeHighlight, 5), (OptionsFocus::ShowScope, 6), (OptionsFocus::ShowSpectrum, 7), (OptionsFocus::ShowCompletion, 8), (OptionsFocus::ShowPreview, 9), (OptionsFocus::PerformanceMode, 10), (OptionsFocus::Font, 11), (OptionsFocus::ZoomFactor, 12), (OptionsFocus::WindowSize, 13), // blank=14, ABLETON LINK header=15, divider=16 (OptionsFocus::LinkEnabled, 17), (OptionsFocus::StartStopSync, 18), (OptionsFocus::Quantum, 19), // blank=20, SESSION header=21, divider=22, Tempo=23, Beat=24, Phase=25 // blank=26, MIDI OUTPUTS header=27, divider=28 (OptionsFocus::MidiOutput0, 29), (OptionsFocus::MidiOutput1, 30), (OptionsFocus::MidiOutput2, 31), (OptionsFocus::MidiOutput3, 32), // blank=33, MIDI INPUTS header=34, divider=35 (OptionsFocus::MidiInput0, 36), (OptionsFocus::MidiInput1, 37), (OptionsFocus::MidiInput2, 38), (OptionsFocus::MidiInput3, 39), // blank=40, ONBOARDING header=41, divider=42 (OptionsFocus::ResetOnboarding, 43), ]; impl OptionsFocus { fn is_plugin_only(self) -> bool { PLUGIN_ONLY.contains(&self) } fn is_standalone_only(self) -> bool { STANDALONE_ONLY.contains(&self) } fn is_visible(self, plugin_mode: bool) -> bool { if self.is_plugin_only() && !plugin_mode { return false; } if self.is_standalone_only() && plugin_mode { return false; } true } pub fn line_index(self, plugin_mode: bool) -> usize { visible_layout(plugin_mode) .iter() .find(|(f, _)| *f == self) .map(|(_, l)| *l) .unwrap_or(0) } pub fn at_line(line: usize, plugin_mode: bool) -> Option { visible_layout(plugin_mode) .iter() .find(|(_, l)| *l == line) .map(|(f, _)| *f) } } /// Total number of rendered lines for the options view. pub fn total_lines(plugin_mode: bool) -> usize { visible_layout(plugin_mode) .last() .map(|(_, l)| *l + 1) .unwrap_or(0) } /// Compute (focus, line_index) pairs for only the visible options, /// with line indices adjusted to account for hidden sections. fn visible_layout(plugin_mode: bool) -> Vec<(OptionsFocus, usize)> { // Start from the full layout and compute adjusted line numbers. // Hidden items + their section headers/dividers/blanks shrink the layout. // We know the exact section structure, so we compute the offset to subtract // based on which sections are hidden. let mut offset: usize = 0; // Font/Zoom/Window lines (11,12,13) hidden when !plugin_mode if !plugin_mode { offset += 3; // 3 lines for Font, ZoomFactor, WindowSize } // Link + Session + MIDI sections hidden when plugin_mode // These span from blank(14) through MidiInput3(39) = 26 lines if plugin_mode { let link_section_lines = 26; offset += link_section_lines; } let mut result = Vec::new(); for &(focus, raw_line) in FULL_LAYOUT { if !focus.is_visible(plugin_mode) { continue; } // Lines at or below index 10 (PerformanceMode) are never shifted let adjusted = if raw_line <= 10 { raw_line } else if !plugin_mode && raw_line <= 13 { // Font/Zoom/Window — these are hidden, skip continue; } else { raw_line - offset }; result.push((focus, adjusted)); } result } #[derive(Default)] pub struct OptionsState { pub focus: OptionsFocus, } impl OptionsState { pub fn next_focus(&mut self, plugin_mode: bool) { let mut f = self.focus; loop { f = f.next(); if f.is_visible(plugin_mode) { break; } } self.focus = f; } pub fn prev_focus(&mut self, plugin_mode: bool) { let mut f = self.focus; loop { f = f.prev(); if f.is_visible(plugin_mode) { break; } } self.focus = f; } }