Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions src/gui/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pub struct AmplifierApp {
control_bar: Control,
settings: Settings,
settings_handler: SettingsHandler,
collapsed_stages: Vec<bool>,
dirty_chain: bool,
ir_cabinet_control: IrCabinetControl,
pitch_shift_control: PitchShiftControl,
Expand Down Expand Up @@ -104,6 +105,8 @@ impl AmplifierApp {

let hotkey_handler = HotkeyHandler::new(settings.hotkeys.clone());

let collapsed_stages = vec![false; preset.stages.len()];

(
Self {
audio_manager,
Expand All @@ -113,6 +116,7 @@ impl AmplifierApp {
control_bar,
settings,
settings_handler,
collapsed_stages,
// Set dirty chain to true to trigger initial rebuild
dirty_chain: true,
ir_cabinet_control,
Expand Down Expand Up @@ -150,9 +154,10 @@ impl AmplifierApp {
let main_content = column![
top_bar,
self.preset_handler.view(),
self.stage_list.view(),
self.stage_list.view(&self.collapsed_stages),
self.ir_cabinet_control.view(),
self.control_bar.view(self.is_recording),
self.control_bar
.view(self.is_recording, self.all_collapsed()),
]
.spacing(10)
.padding(20);
Expand Down Expand Up @@ -226,33 +231,47 @@ impl AmplifierApp {
pub fn update(&mut self, message: Message) -> Task<Message> {
match message {
Message::SetStages(stages) => {
self.collapsed_stages = vec![false; stages.len()];
self.stages = stages;
self.mark_stages_dirty();
}
Message::RebuildTick => self.rebuild_if_dirty(),
Message::AddStage => {
let new_stage = StageConfig::from(self.control_bar.selected());
self.stages.push(new_stage);
self.collapsed_stages.push(false);
self.mark_stages_dirty();
}
Message::RemoveStage(idx) => {
if idx < self.stages.len() {
self.stages.remove(idx);
self.collapsed_stages.remove(idx);
self.mark_stages_dirty();
}
}
Message::MoveStageUp(idx) => {
if idx > 0 && idx < self.stages.len() {
self.stages.swap(idx - 1, idx);
self.collapsed_stages.swap(idx - 1, idx);
self.mark_stages_dirty();
}
}
Message::MoveStageDown(idx) => {
if idx + 1 < self.stages.len() {
self.stages.swap(idx, idx + 1);
self.collapsed_stages.swap(idx, idx + 1);
self.mark_stages_dirty();
}
}
Message::ToggleStageCollapse(idx) => {
if let Some(collapsed) = self.collapsed_stages.get_mut(idx) {
*collapsed = !*collapsed;
}
}
Message::ToggleAllStagesCollapse => {
let any_expanded = self.collapsed_stages.iter().any(|&c| !c);
self.collapsed_stages.fill(any_expanded);
}
Message::StageTypeSelected(stage_type) => {
self.control_bar.set_selected_stage_type(stage_type);
}
Expand Down Expand Up @@ -416,6 +435,10 @@ impl AmplifierApp {
Task::none()
}

fn all_collapsed(&self) -> bool {
!self.collapsed_stages.is_empty() && self.collapsed_stages.iter().all(|&c| c)
}

fn any_dialog_visible(&self) -> bool {
self.settings_handler.is_visible()
|| self.tuner_handler.is_visible()
Expand Down
11 changes: 10 additions & 1 deletion src/gui/components/control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,23 @@ impl Control {
self.selected_stage_type = ty;
}

pub fn view(&self, is_recording: bool) -> Element<'_, Message> {
pub fn view(&self, is_recording: bool, all_collapsed: bool) -> Element<'_, Message> {
let collapse_label = if all_collapsed {
format!("▼ {}", tr!(expand_all))
} else {
format!("▶ {}", tr!(collapse_all))
};
Comment thread
OpenSauce marked this conversation as resolved.

let stage_controls = row![
pick_list(
STAGE_TYPES,
Some(self.selected_stage_type),
Message::StageTypeSelected
),
button(tr!(add_stage)).on_press(Message::AddStage),
button(text(collapse_label))
.on_press(Message::ToggleAllStagesCollapse)
.style(iced::widget::button::secondary),
]
.spacing(10)
.align_y(Alignment::Center);
Expand Down
5 changes: 3 additions & 2 deletions src/gui/components/stage_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ impl StageList {
self.stages = stages.to_vec();
}

pub fn view(&self) -> Element<'_, Message> {
pub fn view(&self, collapsed: &[bool]) -> Element<'_, Message> {
let mut col = column![].width(Length::Fill).padding(10);

for (idx, stage) in self.stages.iter().enumerate() {
col = col.push(stage.view(idx, self.stages.len()));
let is_collapsed = collapsed.get(idx).copied().unwrap_or(false);
col = col.push(stage.view(idx, self.stages.len(), is_collapsed));
}

scrollable(col).height(Length::FillPortion(9)).into()
Expand Down
55 changes: 50 additions & 5 deletions src/gui/components/widgets/common.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::gui::messages::Message;
use iced::widget::{button, row, slider, text};
use iced::widget::{button, column, container, row, slider, text};
use iced::{Alignment, Element, Length};

pub fn labeled_slider<'a, F: 'a + Fn(f32) -> Message>(
Expand Down Expand Up @@ -36,9 +36,21 @@ pub fn icon_button<'a>(
}
}

pub fn stage_header(stage_name: &str, idx: usize, total_stages: usize) -> Element<'_, Message> {
pub fn stage_header(
stage_name: &str,
idx: usize,
total_stages: usize,
is_collapsed: bool,
) -> Element<'_, Message> {
let header_text = format!("{} {}", stage_name, idx + 1);

let collapse_icon = if is_collapsed { "▶" } else { "▼" };
let collapse_btn = icon_button(
collapse_icon,
Some(Message::ToggleStageCollapse(idx)),
iced::widget::button::secondary,
);

let move_up_btn = if idx > 0 {
icon_button(
"↑",
Expand All @@ -65,8 +77,41 @@ pub fn stage_header(stage_name: &str, idx: usize, total_stages: usize) -> Elemen
iced::widget::button::danger,
);

row![move_up_btn, move_down_btn, remove_btn, text(header_text)]
.spacing(5)
.align_y(Alignment::Center)
row![
collapse_btn,
move_up_btn,
move_down_btn,
remove_btn,
text(header_text)
]
.spacing(5)
.align_y(Alignment::Center)
.into()
}

pub fn stage_card<'a>(
stage_name: &'a str,
idx: usize,
total_stages: usize,
is_collapsed: bool,
body: impl FnOnce() -> Element<'a, Message>,
) -> Element<'a, Message> {
let header = stage_header(stage_name, idx, total_stages, is_collapsed);

let mut content = column![header].spacing(5);

if !is_collapsed {
content = content.push(body());
}

let padding = if is_collapsed { 5 } else { 10 };

container(content.padding(padding))
.width(Length::Fill)
.style(|theme: &iced::Theme| {
container::Style::default()
.background(theme.palette().background)
.border(iced::Border::default().rounded(5))
})
.into()
}
2 changes: 2 additions & 0 deletions src/gui/messages/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ pub enum Message {
RemoveStage(usize),
MoveStageUp(usize),
MoveStageDown(usize),
ToggleStageCollapse(usize),
ToggleAllStagesCollapse,
StageTypeSelected(StageType),
RebuildTick,
SetStages(Vec<StageConfig>),
Expand Down
149 changes: 76 additions & 73 deletions src/gui/stages/compressor.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use iced::widget::{column, container};
use iced::{Element, Length};
use iced::widget::column;
use iced::Element;
use serde::{Deserialize, Serialize};

use crate::amp::stages::compressor::CompressorStage;
use crate::gui::components::widgets::common::{labeled_slider, stage_header};
use crate::gui::components::widgets::common::{labeled_slider, stage_card};
use crate::gui::messages::Message;
use crate::tr;

Expand Down Expand Up @@ -68,74 +68,77 @@ pub enum CompressorMessage {

// --- View ---

pub fn view(idx: usize, cfg: &CompressorConfig, total_stages: usize) -> Element<'_, Message> {
let header = stage_header(tr!(stage_compressor), idx, total_stages);

let body = column![
labeled_slider(
tr!(threshold),
-60.0..=0.0,
cfg.threshold_db,
move |v| Message::Stage(
idx,
StageMessage::Compressor(CompressorMessage::ThresholdChanged(v))
),
|v| format!("{v:.1} {}", tr!(db)),
1.0
),
labeled_slider(
tr!(ratio),
1.0..=20.0,
cfg.ratio,
move |v| Message::Stage(
idx,
StageMessage::Compressor(CompressorMessage::RatioChanged(v))
),
|v| format!("{v:.1}:1"),
0.1
),
labeled_slider(
tr!(attack),
0.1..=100.0,
cfg.attack_ms,
move |v| Message::Stage(
idx,
StageMessage::Compressor(CompressorMessage::AttackChanged(v))
),
|v| format!("{v:.1} {}", tr!(ms)),
0.1
),
labeled_slider(
tr!(release),
10.0..=1000.0,
cfg.release_ms,
move |v| Message::Stage(
idx,
StageMessage::Compressor(CompressorMessage::ReleaseChanged(v))
),
|v| format!("{v:.0} {}", tr!(ms)),
1.0
),
labeled_slider(
tr!(makeup),
-12.0..=24.0,
cfg.makeup_db,
move |v| Message::Stage(
idx,
StageMessage::Compressor(CompressorMessage::MakeupChanged(v))
),
|v| format!("{v:.1} {}", tr!(db)),
0.1
),
]
.spacing(5);

container(column![header, body].spacing(5).padding(10))
.width(Length::Fill)
.style(|theme: &iced::Theme| {
container::Style::default()
.background(theme.palette().background)
.border(iced::Border::default().rounded(5))
})
.into()
pub fn view(
idx: usize,
cfg: &CompressorConfig,
total_stages: usize,
is_collapsed: bool,
) -> Element<'_, Message> {
stage_card(
tr!(stage_compressor),
idx,
total_stages,
is_collapsed,
|| {
column![
labeled_slider(
tr!(threshold),
-60.0..=0.0,
cfg.threshold_db,
move |v| Message::Stage(
idx,
StageMessage::Compressor(CompressorMessage::ThresholdChanged(v))
),
|v| format!("{v:.1} {}", tr!(db)),
1.0
),
labeled_slider(
tr!(ratio),
1.0..=20.0,
cfg.ratio,
move |v| Message::Stage(
idx,
StageMessage::Compressor(CompressorMessage::RatioChanged(v))
),
|v| format!("{v:.1}:1"),
0.1
),
labeled_slider(
tr!(attack),
0.1..=100.0,
cfg.attack_ms,
move |v| Message::Stage(
idx,
StageMessage::Compressor(CompressorMessage::AttackChanged(v))
),
|v| format!("{v:.1} {}", tr!(ms)),
0.1
),
labeled_slider(
tr!(release),
10.0..=1000.0,
cfg.release_ms,
move |v| Message::Stage(
idx,
StageMessage::Compressor(CompressorMessage::ReleaseChanged(v))
),
|v| format!("{v:.0} {}", tr!(ms)),
1.0
),
labeled_slider(
tr!(makeup),
-12.0..=24.0,
cfg.makeup_db,
move |v| Message::Stage(
idx,
StageMessage::Compressor(CompressorMessage::MakeupChanged(v))
),
|v| format!("{v:.1} {}", tr!(db)),
0.1
),
]
.spacing(5)
.into()
},
)
}
Loading