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:
@@ -527,14 +527,20 @@ fn navigate_to_step(ui: &WizardUi, target: usize) {
|
||||
|
||||
// Update dynamic content on certain steps
|
||||
if target == 9 {
|
||||
// Output step - update image count and operation summary
|
||||
let count = ui.state.loaded_files.borrow().len();
|
||||
// Output step - update image count, total size, and operation summary
|
||||
let files = ui.state.loaded_files.borrow();
|
||||
let count = files.len();
|
||||
let total_size: u64 = files.iter()
|
||||
.filter_map(|p| std::fs::metadata(p).ok())
|
||||
.map(|m| m.len())
|
||||
.sum();
|
||||
drop(files);
|
||||
if let Some(page) = ui.pages.get(9) {
|
||||
walk_widgets(&page.child(), &|widget| {
|
||||
if let Some(row) = widget.downcast_ref::<adw::ActionRow>()
|
||||
&& row.title().as_str() == "Images to process"
|
||||
{
|
||||
row.set_subtitle(&format!("{} images", count));
|
||||
row.set_subtitle(&format!("{} images ({})", count, format_bytes(total_size)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ pub fn build_output_page(state: &AppState) -> adw::NavigationPage {
|
||||
.margin_end(24)
|
||||
.build();
|
||||
|
||||
// Operation summary
|
||||
// Operation summary - dynamically updated when this step is shown
|
||||
let summary_group = adw::PreferencesGroup::builder()
|
||||
.title("Operation Summary")
|
||||
.description("Review your processing settings before starting")
|
||||
@@ -36,9 +36,14 @@ pub fn build_output_page(state: &AppState) -> adw::NavigationPage {
|
||||
.title("Output Directory")
|
||||
.build();
|
||||
|
||||
let default_output = state.output_dir.borrow()
|
||||
.as_ref()
|
||||
.map(|p| p.display().to_string())
|
||||
.unwrap_or_else(|| "processed/ (subfolder next to originals)".to_string());
|
||||
|
||||
let output_row = adw::ActionRow::builder()
|
||||
.title("Output Location")
|
||||
.subtitle("processed/ (subfolder next to originals)")
|
||||
.subtitle(&default_output)
|
||||
.activatable(true)
|
||||
.action_name("win.choose-output")
|
||||
.build();
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user