Add custom workflow operation toggles, improve output step summary

Workflow step: replace the auto-advancing Custom card with a proper
operation checklist using SwitchRow toggles for each operation (Resize,
Adjustments, Convert, Compress, Metadata, Watermark, Rename). Wired
to job config so selections persist through the wizard.

Output step: show actual file size alongside image count. Refresh
both count and size dynamically when navigating to the output step.
This commit is contained in:
2026-03-06 13:00:52 +02:00
parent 8f6e4382c4
commit 0234f872bc
3 changed files with 104 additions and 62 deletions

View File

@@ -57,20 +57,97 @@ pub fn build_workflow_page(state: &AppState) -> adw::NavigationPage {
// Custom workflow section
let custom_group = adw::PreferencesGroup::builder()
.title("Custom Workflow")
.description("Choose which operations to include")
.description("Choose which operations to include, then click Next")
.build();
let custom_card = build_custom_card();
let custom_flow = gtk::FlowBox::builder()
.selection_mode(gtk::SelectionMode::None)
.max_children_per_line(4)
.min_children_per_line(2)
let resize_check = adw::SwitchRow::builder()
.title("Resize")
.subtitle("Scale images to new dimensions")
.active(state.job_config.borrow().resize_enabled)
.build();
custom_flow.append(&custom_card);
custom_flow.connect_child_activated(|flow, _child| {
flow.activate_action("win.next-step", None).ok();
});
custom_group.add(&custom_flow);
let adjustments_check = adw::SwitchRow::builder()
.title("Adjustments")
.subtitle("Rotate, flip, brightness, contrast, effects")
.active(false)
.build();
let convert_check = adw::SwitchRow::builder()
.title("Convert")
.subtitle("Change image format (JPEG, PNG, WebP, AVIF)")
.active(state.job_config.borrow().convert_enabled)
.build();
let compress_check = adw::SwitchRow::builder()
.title("Compress")
.subtitle("Reduce file size with quality control")
.active(state.job_config.borrow().compress_enabled)
.build();
let metadata_check = adw::SwitchRow::builder()
.title("Metadata")
.subtitle("Strip or modify EXIF, GPS, camera data")
.active(state.job_config.borrow().metadata_enabled)
.build();
let watermark_check = adw::SwitchRow::builder()
.title("Watermark")
.subtitle("Add text or image overlay")
.active(state.job_config.borrow().watermark_enabled)
.build();
let rename_check = adw::SwitchRow::builder()
.title("Rename")
.subtitle("Rename files with prefix, suffix, or template")
.active(state.job_config.borrow().rename_enabled)
.build();
custom_group.add(&resize_check);
custom_group.add(&adjustments_check);
custom_group.add(&convert_check);
custom_group.add(&compress_check);
custom_group.add(&metadata_check);
custom_group.add(&watermark_check);
custom_group.add(&rename_check);
// Wire custom operation toggles to job config
{
let jc = state.job_config.clone();
resize_check.connect_active_notify(move |row| {
jc.borrow_mut().resize_enabled = row.is_active();
});
}
{
let jc = state.job_config.clone();
convert_check.connect_active_notify(move |row| {
jc.borrow_mut().convert_enabled = row.is_active();
});
}
{
let jc = state.job_config.clone();
compress_check.connect_active_notify(move |row| {
jc.borrow_mut().compress_enabled = row.is_active();
});
}
{
let jc = state.job_config.clone();
metadata_check.connect_active_notify(move |row| {
jc.borrow_mut().metadata_enabled = row.is_active();
});
}
{
let jc = state.job_config.clone();
watermark_check.connect_active_notify(move |row| {
jc.borrow_mut().watermark_enabled = row.is_active();
});
}
{
let jc = state.job_config.clone();
rename_check.connect_active_notify(move |row| {
jc.borrow_mut().rename_enabled = row.is_active();
});
}
content.append(&custom_group);
// User presets section
@@ -276,49 +353,3 @@ fn build_preset_card(preset: &Preset) -> gtk::Box {
card
}
fn build_custom_card() -> gtk::Box {
let card = gtk::Box::builder()
.orientation(gtk::Orientation::Vertical)
.spacing(8)
.halign(gtk::Align::Center)
.valign(gtk::Align::Start)
.build();
card.add_css_class("card");
card.set_size_request(180, 120);
let inner = gtk::Box::builder()
.orientation(gtk::Orientation::Vertical)
.spacing(4)
.margin_top(16)
.margin_bottom(16)
.margin_start(12)
.margin_end(12)
.halign(gtk::Align::Center)
.valign(gtk::Align::Center)
.vexpand(true)
.build();
let icon = gtk::Image::builder()
.icon_name("applications-system-symbolic")
.pixel_size(32)
.build();
let name_label = gtk::Label::builder()
.label("Custom...")
.css_classes(["heading"])
.build();
let desc_label = gtk::Label::builder()
.label("Pick your own operations")
.css_classes(["caption", "dim-label"])
.wrap(true)
.justify(gtk::Justification::Center)
.build();
inner.append(&icon);
inner.append(&name_label);
inner.append(&desc_label);
card.append(&inner);
card
}