Files
pixstrip/pixstrip-gtk/src/processing.rs
lashman b432cc7431 Fix 26 bugs, edge cases, and consistency issues from fifth audit pass
Critical: undo toast now trashes only batch output files (not entire dir),
JPEG scanline write errors propagated, selective metadata write result returned.

High: zero-dimension guards in ResizeConfig/fit_within, negative aspect ratio
rejection, FM integration toggle infinite recursion guard, saturating counter
arithmetic in executor.

Medium: PNG compression level passed to oxipng, pct mode updates job_config,
external file loading updates step indicator, CLI undo removes history entries,
watch config write failures reported, fast-copy path reads image dimensions for
rename templates, discovery excludes unprocessable formats (heic/svg/ico/jxl),
CLI warns on invalid algorithm/overwrite values, resolve_collision trailing dot
fix, generation guards on all preview threads to cancel stale results, default
DPI aligned to 0, watermark text width uses char count not byte length.

Low: binary path escaped in Nautilus extension, file dialog filter aligned with
discovery, reset_wizard clears preset_mode and output_dir.
2026-03-07 19:47:23 +02:00

236 lines
6.9 KiB
Rust

use adw::prelude::*;
pub fn build_processing_page() -> adw::NavigationPage {
let content = gtk::Box::builder()
.orientation(gtk::Orientation::Vertical)
.spacing(16)
.margin_top(24)
.margin_bottom(24)
.margin_start(24)
.margin_end(24)
.vexpand(true)
.build();
// Title
let title = gtk::Label::builder()
.label("Processing...")
.css_classes(["title-1"])
.halign(gtk::Align::Start)
.build();
// Progress info
let progress_label = gtk::Label::builder()
.label("0 / 0 images")
.css_classes(["heading"])
.halign(gtk::Align::Start)
.build();
let progress_bar = gtk::ProgressBar::builder()
.fraction(0.0)
.show_text(true)
.text("0%")
.build();
progress_bar.update_property(&[
gtk::accessible::Property::Label("Processing progress"),
]);
let eta_label = gtk::Label::builder()
.label("Estimating time remaining...")
.css_classes(["dim-label"])
.halign(gtk::Align::Start)
.build();
// Activity log
let log_group = adw::PreferencesGroup::builder()
.title("Activity")
.build();
let log_scrolled = gtk::ScrolledWindow::builder()
.hscrollbar_policy(gtk::PolicyType::Never)
.vexpand(true)
.min_content_height(200)
.build();
let log_box = gtk::Box::builder()
.orientation(gtk::Orientation::Vertical)
.spacing(2)
.build();
log_scrolled.set_child(Some(&log_box));
log_group.add(&log_scrolled);
// Control buttons
let button_box = gtk::Box::builder()
.orientation(gtk::Orientation::Horizontal)
.spacing(12)
.halign(gtk::Align::Center)
.margin_top(12)
.build();
let pause_button = gtk::Button::builder()
.label("Pause")
.tooltip_text("Pause after current image")
.build();
let cancel_button = gtk::Button::builder()
.label("Cancel")
.tooltip_text("Cancel processing")
.build();
cancel_button.add_css_class("destructive-action");
button_box.append(&pause_button);
button_box.append(&cancel_button);
content.append(&title);
content.append(&progress_label);
content.append(&progress_bar);
content.append(&eta_label);
content.append(&log_group);
content.append(&button_box);
adw::NavigationPage::builder()
.title("Processing")
.tag("processing")
.child(&content)
.build()
}
pub fn build_results_page() -> adw::NavigationPage {
let scrolled = gtk::ScrolledWindow::builder()
.hscrollbar_policy(gtk::PolicyType::Never)
.vexpand(true)
.build();
let content = gtk::Box::builder()
.orientation(gtk::Orientation::Vertical)
.spacing(16)
.margin_top(24)
.margin_bottom(24)
.margin_start(24)
.margin_end(24)
.build();
// Success icon and title
let status_icon = gtk::Image::builder()
.icon_name("emblem-ok-symbolic")
.pixel_size(48)
.halign(gtk::Align::Center)
.css_classes(["success"])
.build();
status_icon.update_property(&[
gtk::accessible::Property::Label("Success"),
]);
let title = gtk::Label::builder()
.label("Processing Complete")
.css_classes(["title-1"])
.halign(gtk::Align::Center)
.build();
content.append(&status_icon);
content.append(&title);
// Stats
let stats_group = adw::PreferencesGroup::builder()
.title("Results")
.build();
let images_row = adw::ActionRow::builder()
.title("Images processed")
.subtitle("0 images")
.build();
images_row.add_prefix(&gtk::Image::from_icon_name("image-x-generic-symbolic"));
let size_before_row = adw::ActionRow::builder()
.title("Original size")
.subtitle("0 B")
.build();
size_before_row.add_prefix(&gtk::Image::from_icon_name("drive-harddisk-symbolic"));
let size_after_row = adw::ActionRow::builder()
.title("Output size")
.subtitle("0 B")
.build();
size_after_row.add_prefix(&gtk::Image::from_icon_name("drive-harddisk-symbolic"));
let savings_row = adw::ActionRow::builder()
.title("Space saved")
.subtitle("0%")
.build();
savings_row.add_prefix(&gtk::Image::from_icon_name("emblem-ok-symbolic"));
let time_row = adw::ActionRow::builder()
.title("Processing time")
.subtitle("0s")
.build();
time_row.add_prefix(&gtk::Image::from_icon_name("preferences-system-time-symbolic"));
stats_group.add(&images_row);
stats_group.add(&size_before_row);
stats_group.add(&size_after_row);
stats_group.add(&savings_row);
stats_group.add(&time_row);
content.append(&stats_group);
// Errors section (initially hidden)
let errors_group = adw::PreferencesGroup::builder()
.title("Errors")
.visible(false)
.build();
let errors_expander = adw::ExpanderRow::builder()
.title("0 errors occurred")
.build();
errors_group.add(&errors_expander);
content.append(&errors_group);
// Action buttons
let action_group = adw::PreferencesGroup::new();
let open_row = adw::ActionRow::builder()
.title("Open Output Folder")
.subtitle("View processed images in file manager")
.activatable(true)
.build();
open_row.add_prefix(&gtk::Image::from_icon_name("folder-open-symbolic"));
open_row.add_suffix(&gtk::Image::from_icon_name("go-next-symbolic"));
let process_more_row = adw::ActionRow::builder()
.title("Process Another Batch")
.subtitle("Start over with new images")
.activatable(true)
.build();
process_more_row.add_prefix(&gtk::Image::from_icon_name("view-refresh-symbolic"));
process_more_row.add_suffix(&gtk::Image::from_icon_name("go-next-symbolic"));
let save_preset_row = adw::ActionRow::builder()
.title("Save as Preset")
.subtitle("Save this workflow for future use")
.activatable(true)
.build();
save_preset_row.add_prefix(&gtk::Image::from_icon_name("document-save-symbolic"));
save_preset_row.add_suffix(&gtk::Image::from_icon_name("go-next-symbolic"));
let add_queue_row = adw::ActionRow::builder()
.title("Add to Queue")
.subtitle("Queue another batch with different images")
.activatable(true)
.build();
add_queue_row.add_prefix(&gtk::Image::from_icon_name("view-list-symbolic"));
add_queue_row.add_suffix(&gtk::Image::from_icon_name("go-next-symbolic"));
action_group.add(&open_row);
action_group.add(&process_more_row);
action_group.add(&add_queue_row);
action_group.add(&save_preset_row);
content.append(&action_group);
scrolled.set_child(Some(&content));
adw::NavigationPage::builder()
.title("Results")
.tag("results")
.child(&scrolled)
.build()
}