From 1587764b1e25fe29e5baf5c63d2bc08dc992eb7e Mon Sep 17 00:00:00 2001 From: lashman Date: Fri, 6 Mar 2026 11:10:38 +0200 Subject: [PATCH] Add processing, results, and settings UI screens Processing screen with progress bar, activity log, and pause/cancel. Results screen with stats summary, error section, and action buttons. Settings dialog with General, Processing, Accessibility, Notifications. --- pixstrip-gtk/src/main.rs | 2 + pixstrip-gtk/src/processing.rs | 232 +++++++++++++++++++++++++++++++++ pixstrip-gtk/src/settings.rs | 161 +++++++++++++++++++++++ 3 files changed, 395 insertions(+) create mode 100644 pixstrip-gtk/src/processing.rs create mode 100644 pixstrip-gtk/src/settings.rs diff --git a/pixstrip-gtk/src/main.rs b/pixstrip-gtk/src/main.rs index 99f055f..eb3d59b 100644 --- a/pixstrip-gtk/src/main.rs +++ b/pixstrip-gtk/src/main.rs @@ -1,4 +1,6 @@ mod app; +mod processing; +mod settings; mod step_indicator; mod steps; mod wizard; diff --git a/pixstrip-gtk/src/processing.rs b/pixstrip-gtk/src/processing.rs new file mode 100644 index 0000000..9d4cdd3 --- /dev/null +++ b/pixstrip-gtk/src/processing.rs @@ -0,0 +1,232 @@ +use adw::prelude::*; + +#[allow(dead_code)] +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(); + + 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); + + let clamp = adw::Clamp::builder() + .maximum_size(600) + .child(&content) + .build(); + + adw::NavigationPage::builder() + .title("Processing") + .tag("processing") + .child(&clamp) + .build() +} + +#[allow(dead_code)] +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(); + + 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(>k::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(>k::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(>k::Image::from_icon_name("drive-harddisk-symbolic")); + + let savings_row = adw::ActionRow::builder() + .title("Space saved") + .subtitle("0%") + .build(); + savings_row.add_prefix(>k::Image::from_icon_name("emblem-ok-symbolic")); + + let time_row = adw::ActionRow::builder() + .title("Processing time") + .subtitle("0s") + .build(); + time_row.add_prefix(>k::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(>k::Image::from_icon_name("folder-open-symbolic")); + open_row.add_suffix(>k::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(>k::Image::from_icon_name("view-refresh-symbolic")); + process_more_row.add_suffix(>k::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(>k::Image::from_icon_name("document-save-symbolic")); + save_preset_row.add_suffix(>k::Image::from_icon_name("go-next-symbolic")); + + action_group.add(&open_row); + action_group.add(&process_more_row); + action_group.add(&save_preset_row); + content.append(&action_group); + + scrolled.set_child(Some(&content)); + + let clamp = adw::Clamp::builder() + .maximum_size(600) + .child(&scrolled) + .build(); + + adw::NavigationPage::builder() + .title("Results") + .tag("results") + .child(&clamp) + .build() +} diff --git a/pixstrip-gtk/src/settings.rs b/pixstrip-gtk/src/settings.rs new file mode 100644 index 0000000..ae0b2ab --- /dev/null +++ b/pixstrip-gtk/src/settings.rs @@ -0,0 +1,161 @@ +use adw::prelude::*; + +#[allow(dead_code)] +pub fn build_settings_dialog() -> adw::PreferencesDialog { + let dialog = adw::PreferencesDialog::builder() + .title("Settings") + .build(); + + // General page + let general_page = adw::PreferencesPage::builder() + .title("General") + .icon_name("preferences-system-symbolic") + .build(); + + let output_group = adw::PreferencesGroup::builder() + .title("Output") + .build(); + + let subfolder_row = adw::EntryRow::builder() + .title("Default output subfolder") + .text("processed") + .build(); + + let overwrite_row = adw::ComboRow::builder() + .title("Default overwrite behavior") + .subtitle("What to do when output files already exist") + .build(); + let overwrite_model = gtk::StringList::new(&[ + "Ask before overwriting", + "Auto-rename with suffix", + "Always overwrite", + "Skip existing files", + ]); + overwrite_row.set_model(Some(&overwrite_model)); + + let remember_row = adw::SwitchRow::builder() + .title("Remember last-used settings") + .subtitle("Restore wizard state on next launch") + .active(true) + .build(); + + output_group.add(&subfolder_row); + output_group.add(&overwrite_row); + output_group.add(&remember_row); + general_page.add(&output_group); + + let ui_group = adw::PreferencesGroup::builder() + .title("Interface") + .build(); + + let skill_row = adw::ComboRow::builder() + .title("Detail level") + .subtitle("Controls how many options are visible by default") + .build(); + let skill_model = gtk::StringList::new(&["Simple", "Detailed"]); + skill_row.set_model(Some(&skill_model)); + + ui_group.add(&skill_row); + general_page.add(&ui_group); + dialog.add(&general_page); + + // Processing page + let processing_page = adw::PreferencesPage::builder() + .title("Processing") + .icon_name("system-run-symbolic") + .build(); + + let threads_group = adw::PreferencesGroup::builder() + .title("Performance") + .build(); + + let threads_row = adw::ComboRow::builder() + .title("Processing threads") + .subtitle("Auto uses all available CPU cores") + .build(); + let threads_model = gtk::StringList::new(&["Auto", "1", "2", "4", "8"]); + threads_row.set_model(Some(&threads_model)); + + let error_row = adw::ComboRow::builder() + .title("On error") + .subtitle("What to do when an image fails to process") + .build(); + let error_model = gtk::StringList::new(&["Skip and continue", "Pause on error"]); + error_row.set_model(Some(&error_model)); + + threads_group.add(&threads_row); + threads_group.add(&error_row); + processing_page.add(&threads_group); + dialog.add(&processing_page); + + // Accessibility page + let a11y_page = adw::PreferencesPage::builder() + .title("Accessibility") + .icon_name("preferences-desktop-accessibility-symbolic") + .build(); + + let a11y_group = adw::PreferencesGroup::builder() + .title("Visual Preferences") + .description("Override system settings for this app only") + .build(); + + let contrast_row = adw::SwitchRow::builder() + .title("High contrast") + .subtitle("Increase visual contrast throughout the app") + .active(false) + .build(); + + let large_text_row = adw::SwitchRow::builder() + .title("Large text") + .subtitle("Increase text size throughout the app") + .active(false) + .build(); + + let motion_row = adw::SwitchRow::builder() + .title("Reduced motion") + .subtitle("Minimize animations and transitions") + .active(false) + .build(); + + a11y_group.add(&contrast_row); + a11y_group.add(&large_text_row); + a11y_group.add(&motion_row); + a11y_page.add(&a11y_group); + dialog.add(&a11y_page); + + // Notifications page + let notify_page = adw::PreferencesPage::builder() + .title("Notifications") + .icon_name("preferences-system-notifications-symbolic") + .build(); + + let notify_group = adw::PreferencesGroup::builder() + .title("Completion") + .build(); + + let desktop_notify_row = adw::SwitchRow::builder() + .title("Desktop notification") + .subtitle("Show notification when processing completes") + .active(true) + .build(); + + let sound_row = adw::SwitchRow::builder() + .title("Completion sound") + .subtitle("Play a sound when processing completes") + .active(false) + .build(); + + let auto_open_row = adw::SwitchRow::builder() + .title("Auto-open output folder") + .subtitle("Open the output folder in file manager when done") + .active(false) + .build(); + + notify_group.add(&desktop_notify_row); + notify_group.add(&sound_row); + notify_group.add(&auto_open_row); + notify_page.add(¬ify_group); + dialog.add(¬ify_page); + + dialog +}