Fix edge cases and consistency issues

This commit is contained in:
2026-03-07 19:47:23 +02:00
parent 6bf9d60430
commit 9e1562c4c4
44 changed files with 5748 additions and 2221 deletions

View File

@@ -1,4 +1,5 @@
use adw::prelude::*;
use std::cell::Cell;
use pixstrip_core::config::{AppConfig, ErrorBehavior, OverwriteBehavior, SkillLevel};
use pixstrip_core::storage::ConfigStore;
@@ -8,7 +9,13 @@ pub fn build_settings_dialog() -> adw::PreferencesDialog {
.build();
let config_store = ConfigStore::new();
let config = config_store.load().unwrap_or_default();
let config = match config_store.load() {
Ok(c) => c,
Err(e) => {
eprintln!("Failed to load config, using defaults: {}", e);
AppConfig::default()
}
};
// General page
let general_page = adw::PreferencesPage::builder()
@@ -24,12 +31,14 @@ pub fn build_settings_dialog() -> adw::PreferencesDialog {
let output_mode_row = adw::ComboRow::builder()
.title("Default output location")
.subtitle("Where processed images are saved by default")
.use_subtitle(true)
.build();
let output_mode_model = gtk::StringList::new(&[
"Subfolder next to originals",
"Fixed output folder",
]);
output_mode_row.set_model(Some(&output_mode_model));
output_mode_row.set_list_factory(Some(&crate::steps::full_text_list_factory()));
output_mode_row.set_selected(if config.output_fixed_path.is_some() { 1 } else { 0 });
let subfolder_row = adw::EntryRow::builder()
@@ -101,6 +110,7 @@ pub fn build_settings_dialog() -> adw::PreferencesDialog {
let overwrite_row = adw::ComboRow::builder()
.title("Default overwrite behavior")
.subtitle("What to do when output files already exist")
.use_subtitle(true)
.build();
let overwrite_model = gtk::StringList::new(&[
"Ask before overwriting",
@@ -109,6 +119,7 @@ pub fn build_settings_dialog() -> adw::PreferencesDialog {
"Skip existing files",
]);
overwrite_row.set_model(Some(&overwrite_model));
overwrite_row.set_list_factory(Some(&crate::steps::full_text_list_factory()));
overwrite_row.set_selected(match config.overwrite_behavior {
OverwriteBehavior::Ask => 0,
OverwriteBehavior::AutoRename => 1,
@@ -136,9 +147,11 @@ pub fn build_settings_dialog() -> adw::PreferencesDialog {
let skill_row = adw::ComboRow::builder()
.title("Detail level")
.subtitle("Controls how many options are visible by default")
.use_subtitle(true)
.build();
let skill_model = gtk::StringList::new(&["Simple", "Detailed"]);
skill_row.set_model(Some(&skill_model));
skill_row.set_list_factory(Some(&crate::steps::full_text_list_factory()));
skill_row.set_selected(match config.skill_level {
SkillLevel::Simple => 0,
SkillLevel::Detailed => 1,
@@ -188,11 +201,22 @@ pub fn build_settings_dialog() -> adw::PreferencesDialog {
.build();
let fm_copy = *fm;
let reverting = std::rc::Rc::new(std::cell::Cell::new(false));
let reverting_clone = reverting.clone();
row.connect_active_notify(move |row| {
if row.is_active() {
let _ = fm_copy.install();
if reverting_clone.get() {
return;
}
let result = if row.is_active() {
fm_copy.install()
} else {
let _ = fm_copy.uninstall();
fm_copy.uninstall()
};
if let Err(e) = result {
eprintln!("File manager integration error for {}: {}", fm_copy.name(), e);
reverting_clone.set(true);
row.set_active(!row.is_active());
reverting_clone.set(false);
}
});
@@ -231,9 +255,11 @@ pub fn build_settings_dialog() -> adw::PreferencesDialog {
let threads_row = adw::ComboRow::builder()
.title("Processing threads")
.subtitle("Auto uses all available CPU cores")
.use_subtitle(true)
.build();
let threads_model = gtk::StringList::new(&["Auto", "1", "2", "4", "8"]);
threads_row.set_model(Some(&threads_model));
threads_row.set_list_factory(Some(&crate::steps::full_text_list_factory()));
threads_row.set_selected(match config.thread_count {
pixstrip_core::config::ThreadCount::Auto => 0,
pixstrip_core::config::ThreadCount::Manual(1) => 1,
@@ -245,9 +271,11 @@ pub fn build_settings_dialog() -> adw::PreferencesDialog {
let error_row = adw::ComboRow::builder()
.title("On error")
.subtitle("What to do when an image fails to process")
.use_subtitle(true)
.build();
let error_model = gtk::StringList::new(&["Skip and continue", "Pause on error"]);
error_row.set_model(Some(&error_model));
error_row.set_list_factory(Some(&crate::steps::full_text_list_factory()));
error_row.set_selected(match config.error_behavior {
ErrorBehavior::SkipAndContinue => 0,
ErrorBehavior::PauseOnError => 1,
@@ -287,6 +315,53 @@ pub fn build_settings_dialog() -> adw::PreferencesDialog {
.active(config.reduced_motion)
.build();
// Wire high contrast to apply immediately
{
contrast_row.connect_active_notify(move |row| {
if let Some(settings) = gtk::Settings::default() {
if row.is_active() {
settings.set_gtk_theme_name(Some("HighContrast"));
} else {
// Revert to the default Adwaita theme
settings.set_gtk_theme_name(Some("Adwaita"));
}
}
});
}
// Wire large text to apply immediately
{
let original_dpi: std::rc::Rc<Cell<i32>> = std::rc::Rc::new(Cell::new(0));
let orig_dpi = original_dpi.clone();
large_text_row.connect_active_notify(move |row| {
if let Some(settings) = gtk::Settings::default() {
if row.is_active() {
// Store original DPI before modifying
let current_dpi = settings.gtk_xft_dpi();
if current_dpi > 0 {
orig_dpi.set(current_dpi);
settings.set_gtk_xft_dpi(current_dpi * 5 / 4);
}
} else {
// Restore the original DPI (only if we actually changed it)
let saved = orig_dpi.get();
if saved > 0 {
settings.set_gtk_xft_dpi(saved);
}
}
}
});
}
// Wire reduced motion to apply immediately
{
motion_row.connect_active_notify(move |row| {
if let Some(settings) = gtk::Settings::default() {
settings.set_gtk_enable_animations(!row.is_active());
}
});
}
a11y_group.add(&contrast_row);
a11y_group.add(&large_text_row);
a11y_group.add(&motion_row);
@@ -443,6 +518,9 @@ pub fn build_settings_dialog() -> adw::PreferencesDialog {
let auto_open = auto_open_row.clone();
let output_mode = output_mode_row.clone();
let fps_reset = fixed_path_state.clone();
let wfs_reset = watch_folders_state.clone();
let wl_reset = watch_list.clone();
let el_reset = empty_label.clone();
reset_button.connect_clicked(move |_| {
let defaults = AppConfig::default();
subfolder.set_text(&defaults.output_subfolder);
@@ -459,6 +537,10 @@ pub fn build_settings_dialog() -> adw::PreferencesDialog {
auto_open.set_active(defaults.auto_open_output);
output_mode.set_selected(0);
*fps_reset.borrow_mut() = None;
// Clear watch folders
wfs_reset.borrow_mut().clear();
wl_reset.remove_all();
el_reset.set_visible(true);
});
}
@@ -539,11 +621,13 @@ fn build_watch_folder_row(
let preset_row = adw::ComboRow::builder()
.title("Linked Preset")
.subtitle("Preset to apply to new images")
.use_subtitle(true)
.build();
let preset_model = gtk::StringList::new(
&preset_names.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
);
preset_row.set_model(Some(&preset_model));
preset_row.set_list_factory(Some(&crate::steps::full_text_list_factory()));
// Set selected to matching preset
let selected_idx = preset_names.iter()