349 lines
11 KiB
Rust
349 lines
11 KiB
Rust
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<u32> = 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<u32> = 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<u16> = 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::<u32>() {
|
|
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::<f32>() {
|
|
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
|
|
}
|