use crate::config::{AudioOxideConfig, RecordingFormat}; use iced::widget::{ button, column, container, horizontal_rule, pick_list, row, text, text_input, toggler, Space, }; use iced::{Alignment, Background, Border, Color, Element, Length, Theme}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SettingsTab { General, Audio, Display, Advanced, } impl SettingsTab { pub const ALL: [SettingsTab; 4] = [ SettingsTab::General, SettingsTab::Audio, SettingsTab::Display, SettingsTab::Advanced, ]; fn label(&self) -> &'static str { match self { SettingsTab::General => "General", SettingsTab::Audio => "Audio", SettingsTab::Display => "Display", SettingsTab::Advanced => "Advanced", } } } #[derive(Debug, Clone)] pub struct State { pub config: AudioOxideConfig, pub active_tab: SettingsTab, } impl State { pub fn new(config: &AudioOxideConfig) -> Self { Self { config: config.clone(), active_tab: SettingsTab::General, } } } #[derive(Debug, Clone)] pub enum Message { TabSelected(SettingsTab), // General ProjectDirChanged(String), AutoSaveToggled(bool), AutoSaveIntervalChanged(String), AskToSaveToggled(bool), // Audio DefaultSampleRateSelected(u32), DefaultBufferSizeSelected(u32), DefaultAudioDeviceChanged(String), RecordingFormatSelected(RecordingFormat), RecordingBitDepthSelected(u16), // Display DefaultTrackHeightChanged(String), ShowToolbarOnOpenToggled(bool), ShowInspectorOnOpenToggled(bool), // Actions Save, Cancel, } fn section_header(label: &str) -> Element<'_, Message> { column![ text(label) .size(13) .color(Color::from_rgb8(0x88, 0x88, 0x88)), horizontal_rule(1), ] .spacing(4) .into() } fn setting_row<'a>( label: &'a str, control: Element<'a, Message>, ) -> Element<'a, Message> { row![ text(label).size(13).width(200), control, ] .spacing(12) .align_y(Alignment::Center) .into() } fn tab_button<'a>(tab: SettingsTab, active: SettingsTab) -> Element<'a, Message> { let is_active = tab == active; let btn = button(text(tab.label()).size(13)) .on_press(Message::TabSelected(tab)) .padding([6, 16]); if is_active { btn.style(|_theme: &Theme, _status| button::Style { background: Some(Background::Color(Color::from_rgb8(0x00, 0x7A, 0xFF))), text_color: Color::WHITE, border: Border { radius: 4.0.into(), ..Border::default() }, ..button::Style::default() }) .into() } else { btn.style(|_theme: &Theme, _status| button::Style { background: Some(Background::Color(Color::from_rgb8(0x30, 0x32, 0x34))), text_color: Color::from_rgb8(0xAA, 0xAA, 0xAA), border: Border { radius: 4.0.into(), ..Border::default() }, ..button::Style::default() }) .into() } } fn view_general(config: &AudioOxideConfig) -> Element<'_, Message> { let project_dir_input = text_input( "Project directory", config.project_dir.to_str().unwrap_or(""), ) .on_input(Message::ProjectDirChanged) .width(300); let auto_save_toggle = toggler(config.auto_save) .on_toggle(Message::AutoSaveToggled) .width(Length::Shrink); let auto_save_interval = text_input( "seconds", &config.auto_save_interval_secs.to_string(), ) .on_input(Message::AutoSaveIntervalChanged) .width(80); let ask_save_toggle = toggler(config.ask_to_save_on_close) .on_toggle(Message::AskToSaveToggled) .width(Length::Shrink); column![ section_header("Project Handling"), setting_row("Default project directory", project_dir_input.into()), setting_row("Auto-save", auto_save_toggle.into()), setting_row("Auto-save interval (sec)", auto_save_interval.into()), setting_row("Ask to save on close", ask_save_toggle.into()), ] .spacing(10) .padding(16) .into() } fn view_audio(config: &AudioOxideConfig) -> Element<'_, Message> { let sample_rates: Vec = vec![22050, 44100, 48000, 88200, 96000, 176400, 192000]; let sr_picker = pick_list( sample_rates, Some(config.default_sample_rate), Message::DefaultSampleRateSelected, ) .width(120); let buffer_sizes: Vec = vec![32, 64, 128, 256, 512, 1024, 2048, 4096]; let bs_picker = pick_list( buffer_sizes, Some(config.default_buffer_size), Message::DefaultBufferSizeSelected, ) .width(120); let device_input = text_input("Audio device", &config.default_audio_device) .on_input(Message::DefaultAudioDeviceChanged) .width(200); let format_picker = pick_list( &RecordingFormat::ALL[..], Some(config.recording_format), Message::RecordingFormatSelected, ) .width(120); let bit_depths: Vec = vec![16, 24, 32]; let bd_picker = pick_list( bit_depths, Some(config.recording_bit_depth), Message::RecordingBitDepthSelected, ) .width(120); column![ section_header("Devices"), setting_row("Default audio device", device_input.into()), setting_row("Default sample rate", sr_picker.into()), setting_row("Default buffer size", bs_picker.into()), section_header("Recording"), setting_row("File format", format_picker.into()), setting_row("Bit depth", bd_picker.into()), ] .spacing(10) .padding(16) .into() } fn view_display(config: &AudioOxideConfig) -> Element<'_, Message> { let track_height_input = text_input("pixels", &format!("{:.0}", config.default_track_height)) .on_input(Message::DefaultTrackHeightChanged) .width(80); let toolbar_toggle = toggler(config.show_toolbar_on_open) .on_toggle(Message::ShowToolbarOnOpenToggled) .width(Length::Shrink); let inspector_toggle = toggler(config.show_inspector_on_open) .on_toggle(Message::ShowInspectorOnOpenToggled) .width(Length::Shrink); column![ section_header("Layout"), setting_row("Default track height (px)", track_height_input.into()), setting_row("Show toolbar on open", toolbar_toggle.into()), setting_row("Show inspector on open", inspector_toggle.into()), ] .spacing(10) .padding(16) .into() } fn view_advanced(_config: &AudioOxideConfig) -> Element<'_, Message> { column![ section_header("Engine"), text("Engine configuration options will appear here.") .size(12) .color(Color::from_rgb8(0x66, 0x66, 0x66)), section_header("Plugins"), text("Module/plugin scan paths and loading options will appear here.") .size(12) .color(Color::from_rgb8(0x66, 0x66, 0x66)), ] .spacing(10) .padding(16) .into() } pub fn view(state: &State) -> Element<'_, Message> { let title = text("Settings").size(20); // Tab bar let tabs = SettingsTab::ALL.iter().fold( row![].spacing(4), |r, tab| r.push(tab_button(*tab, state.active_tab)), ); // Tab content let content: Element<_> = match state.active_tab { SettingsTab::General => view_general(&state.config), SettingsTab::Audio => view_audio(&state.config), SettingsTab::Display => view_display(&state.config), SettingsTab::Advanced => view_advanced(&state.config), }; let content_area = container(content) .width(Length::Fill) .height(Length::Fill) .style(|_theme: &Theme| container::Style { background: Some(Background::Color(Color::from_rgb8(0x22, 0x24, 0x26))), border: Border { radius: 4.0.into(), color: Color::from_rgb8(0x35, 0x37, 0x39), width: 1.0, }, ..container::Style::default() }); // Action buttons let cancel_btn = button(text("Cancel").size(13)).on_press(Message::Cancel); let save_btn = button(text("Save").size(13)) .on_press(Message::Save) .style(|_theme: &Theme, _status| button::Style { background: Some(Background::Color(Color::from_rgb8(0x00, 0x7A, 0xFF))), text_color: Color::WHITE, border: Border { radius: 4.0.into(), ..Border::default() }, ..button::Style::default() }); let actions = row![Space::new(Length::Fill, 0), cancel_btn, save_btn] .spacing(8) .align_y(Alignment::Center); let dialog = column![title, tabs, content_area, actions] .spacing(12) .padding(20); container(dialog) .max_width(600) .max_height(500) .style(|theme: &Theme| container::Style { background: Some(Background::Color( theme.extended_palette().background.weak.color, )), border: Border { radius: 8.0.into(), color: Color::from_rgb8(0x40, 0x42, 0x44), width: 1.0, }, ..container::Style::default() }) .into() } pub fn handle_message(state: &mut State, message: Message) -> bool { match message { Message::TabSelected(tab) => state.active_tab = tab, Message::ProjectDirChanged(p) => state.config.project_dir = p.into(), Message::AutoSaveToggled(v) => state.config.auto_save = v, Message::AutoSaveIntervalChanged(s) => { if let Ok(v) = s.parse::() { state.config.auto_save_interval_secs = v; } } Message::AskToSaveToggled(v) => state.config.ask_to_save_on_close = v, Message::DefaultSampleRateSelected(sr) => state.config.default_sample_rate = sr, Message::DefaultBufferSizeSelected(bs) => state.config.default_buffer_size = bs, Message::DefaultAudioDeviceChanged(d) => state.config.default_audio_device = d, Message::RecordingFormatSelected(f) => state.config.recording_format = f, Message::RecordingBitDepthSelected(bd) => state.config.recording_bit_depth = bd, Message::DefaultTrackHeightChanged(s) => { if let Ok(v) = s.parse::() { if v > 0.0 { state.config.default_track_height = v; } } } Message::ShowToolbarOnOpenToggled(v) => state.config.show_toolbar_on_open = v, Message::ShowInspectorOnOpenToggled(v) => state.config.show_inspector_on_open = v, Message::Save => return true, Message::Cancel => return true, } false }