Add session memory, resize mode tabs, improved output summary

This commit is contained in:
2026-03-06 12:44:16 +02:00
parent 9efcbd082e
commit 3284e066a0
4 changed files with 195 additions and 48 deletions

View File

@@ -167,6 +167,7 @@ impl Default for ConfigStore {
// --- Session Store ---
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct SessionState {
pub last_input_dir: Option<String>,
pub last_output_dir: Option<String>,
@@ -175,6 +176,18 @@ pub struct SessionState {
pub window_width: Option<i32>,
pub window_height: Option<i32>,
pub window_maximized: bool,
// Last-used wizard settings
pub resize_enabled: Option<bool>,
pub resize_width: Option<u32>,
pub resize_height: Option<u32>,
pub convert_enabled: Option<bool>,
pub convert_format: Option<String>,
pub compress_enabled: Option<bool>,
pub quality_preset: Option<String>,
pub metadata_enabled: Option<bool>,
pub metadata_mode: Option<String>,
pub watermark_enabled: Option<bool>,
pub rename_enabled: Option<bool>,
}
pub struct SessionStore {

View File

@@ -163,6 +163,7 @@ fn save_and_load_session() {
window_width: Some(1024),
window_height: Some(768),
window_maximized: false,
..Default::default()
};
session_store.save(&session).unwrap();

View File

@@ -124,39 +124,46 @@ fn setup_shortcuts(app: &adw::Application) {
}
fn build_ui(app: &adw::Application) {
// Restore last-used wizard settings from session
let sess = pixstrip_core::storage::SessionStore::new();
let sess_state = sess.load().unwrap_or_default();
let cfg_store = pixstrip_core::storage::ConfigStore::new();
let app_cfg = cfg_store.load().unwrap_or_default();
let remember = app_cfg.remember_settings;
let app_state = AppState {
wizard: Rc::new(RefCell::new(WizardState::new())),
loaded_files: Rc::new(RefCell::new(Vec::new())),
output_dir: Rc::new(RefCell::new(None)),
job_config: Rc::new(RefCell::new(JobConfig {
resize_enabled: true,
resize_width: 1200,
resize_height: 0,
resize_enabled: if remember { sess_state.resize_enabled.unwrap_or(true) } else { true },
resize_width: if remember { sess_state.resize_width.unwrap_or(1200) } else { 1200 },
resize_height: if remember { sess_state.resize_height.unwrap_or(0) } else { 0 },
allow_upscale: false,
rotation: 0,
flip: 0,
convert_enabled: false,
convert_enabled: if remember { sess_state.convert_enabled.unwrap_or(false) } else { false },
convert_format: None,
compress_enabled: true,
compress_enabled: if remember { sess_state.compress_enabled.unwrap_or(true) } else { true },
quality_preset: pixstrip_core::types::QualityPreset::Medium,
jpeg_quality: 85,
png_level: 3,
webp_quality: 80,
metadata_enabled: true,
metadata_enabled: if remember { sess_state.metadata_enabled.unwrap_or(true) } else { true },
metadata_mode: MetadataMode::StripAll,
strip_gps: true,
strip_camera: true,
strip_software: true,
strip_timestamps: true,
strip_copyright: true,
watermark_enabled: false,
watermark_enabled: if remember { sess_state.watermark_enabled.unwrap_or(false) } else { false },
watermark_text: String::new(),
watermark_image_path: None,
watermark_position: 8, // BottomRight
watermark_opacity: 0.5,
watermark_font_size: 24.0,
watermark_use_image: false,
rename_enabled: false,
rename_enabled: if remember { sess_state.rename_enabled.unwrap_or(false) } else { false },
rename_prefix: String::new(),
rename_suffix: String::new(),
rename_counter_start: 1,
@@ -254,8 +261,10 @@ fn build_ui(app: &adw::Application) {
window.maximize();
}
// Save window size on close
window.connect_close_request(|win| {
// Save window size and wizard settings on close
{
let app_state_for_close = app_state.clone();
window.connect_close_request(move |win| {
let session = pixstrip_core::storage::SessionStore::new();
let mut state = session.load().unwrap_or_default();
state.window_maximized = win.is_maximized();
@@ -266,9 +275,29 @@ fn build_ui(app: &adw::Application) {
state.window_height = Some(h);
}
}
// Save last-used wizard settings if remember_settings is enabled
let config_store = pixstrip_core::storage::ConfigStore::new();
let config = config_store.load().unwrap_or_default();
if config.remember_settings {
let cfg = app_state_for_close.job_config.borrow();
state.resize_enabled = Some(cfg.resize_enabled);
state.resize_width = Some(cfg.resize_width);
state.resize_height = Some(cfg.resize_height);
state.convert_enabled = Some(cfg.convert_enabled);
state.convert_format = cfg.convert_format.map(|f| format!("{:?}", f));
state.compress_enabled = Some(cfg.compress_enabled);
state.quality_preset = Some(format!("{:?}", cfg.quality_preset));
state.metadata_enabled = Some(cfg.metadata_enabled);
state.metadata_mode = Some(format!("{:?}", cfg.metadata_mode));
state.watermark_enabled = Some(cfg.watermark_enabled);
state.rename_enabled = Some(cfg.rename_enabled);
}
let _ = session.save(&state);
glib::Propagation::Proceed
});
}
let ui = WizardUi {
nav_view,

View File

@@ -29,16 +29,35 @@ pub fn build_resize_page(state: &AppState) -> adw::NavigationPage {
enable_group.add(&enable_row);
content.append(&enable_group);
// Resize dimensions
let mode_group = adw::PreferencesGroup::builder()
.title("Dimensions")
.description("Set target width and height. Set height to 0 to preserve aspect ratio.")
// Mode selector using GtkStack with a stack switcher
let mode_stack = gtk::Stack::builder()
.transition_type(gtk::StackTransitionType::Crossfade)
.build();
let switcher = gtk::StackSwitcher::builder()
.stack(&mode_stack)
.halign(gtk::Align::Center)
.margin_top(6)
.margin_bottom(6)
.build();
content.append(&switcher);
// --- Mode 1: Width/Height ---
let wh_box = gtk::Box::builder()
.orientation(gtk::Orientation::Vertical)
.spacing(12)
.build();
let wh_group = adw::PreferencesGroup::builder()
.title("Target Dimensions")
.description("Set width and/or height. Set either to 0 to maintain aspect ratio.")
.build();
let width_row = adw::SpinRow::builder()
.title("Width")
.subtitle("Target width in pixels")
.adjustment(&gtk::Adjustment::new(cfg.resize_width as f64, 1.0, 10000.0, 1.0, 100.0, 0.0))
.adjustment(&gtk::Adjustment::new(cfg.resize_width as f64, 0.0, 10000.0, 1.0, 100.0, 0.0))
.build();
let height_row = adw::SpinRow::builder()
@@ -47,17 +66,27 @@ pub fn build_resize_page(state: &AppState) -> adw::NavigationPage {
.adjustment(&gtk::Adjustment::new(cfg.resize_height as f64, 0.0, 10000.0, 1.0, 100.0, 0.0))
.build();
mode_group.add(&width_row);
mode_group.add(&height_row);
content.append(&mode_group);
wh_group.add(&width_row);
wh_group.add(&height_row);
wh_box.append(&wh_group);
// Social media presets
let presets_group = adw::PreferencesGroup::builder()
.title("Quick Dimension Presets")
.description("Click a preset to fill in the dimensions above")
mode_stack.add_titled(&wh_box, Some("width-height"), "Width / Height");
// --- Mode 2: Preset Dimensions ---
let preset_box = gtk::Box::builder()
.orientation(gtk::Orientation::Vertical)
.spacing(12)
.build();
// Helper to build preset expander sections
let presets_group = adw::PreferencesGroup::builder()
.title("Quick Dimension Presets")
.description("Select a preset to set the dimensions")
.build();
// Clone for closures
let width_for_preset = width_row.clone();
let height_for_preset = height_row.clone();
let build_preset_section = |title: &str, subtitle: &str, presets: &[(&str, u32, u32)]| -> adw::ExpanderRow {
let expander = adw::ExpanderRow::builder()
.title(title)
@@ -71,13 +100,16 @@ pub fn build_resize_page(state: &AppState) -> adw::NavigationPage {
.activatable(true)
.build();
row.add_suffix(&gtk::Image::from_icon_name("go-next-symbolic"));
let width_c = width_row.clone();
let height_c = height_row.clone();
let width_c = width_for_preset.clone();
let height_c = height_for_preset.clone();
let w = *w;
let h = *h;
let stack_c = mode_stack.clone();
row.connect_activated(move |_| {
width_c.set_value(w as f64);
height_c.set_value(h as f64);
// Switch to width/height tab to show the values
stack_c.set_visible_child_name("width-height");
});
expander.add_row(&row);
}
@@ -96,10 +128,11 @@ pub fn build_resize_page(state: &AppState) -> adw::NavigationPage {
("Pixelfed Story", 1080, 1920),
("Bluesky Post", 1200, 630),
("Bluesky Profile", 400, 400),
("Bluesky Banner", 1500, 500),
("Lemmy Post", 1200, 630),
("PeerTube Thumbnail", 1280, 720),
("Friendica Post", 1200, 630),
("Funkwhale Cover", 500, 500),
("Funkwhale Cover", 1400, 1400),
],
);
@@ -107,8 +140,8 @@ pub fn build_resize_page(state: &AppState) -> adw::NavigationPage {
"Mainstream Platforms",
"Instagram, YouTube, LinkedIn, Facebook, TikTok",
&[
("Instagram Post", 1080, 1080),
("Instagram Portrait", 1080, 1350),
("Instagram Post Square", 1080, 1080),
("Instagram Post Portrait", 1080, 1350),
("Instagram Story/Reel", 1080, 1920),
("Facebook Post", 1200, 630),
("Facebook Cover", 820, 312),
@@ -119,10 +152,8 @@ pub fn build_resize_page(state: &AppState) -> adw::NavigationPage {
("LinkedIn Cover", 1584, 396),
("LinkedIn Profile", 400, 400),
("Pinterest Pin", 1000, 1500),
("TikTok Profile", 200, 200),
("TikTok Video Cover", 1080, 1920),
("Threads Post", 1080, 1080),
("Twitter/X Post", 1200, 675),
("Twitter/X Header", 1500, 500),
],
);
@@ -133,8 +164,7 @@ pub fn build_resize_page(state: &AppState) -> adw::NavigationPage {
("4K UHD", 3840, 2160),
("Full HD", 1920, 1080),
("HD Ready", 1280, 720),
("Blog Wide", 800, 0),
("Blog Standard", 800, 600),
("Blog Standard", 800, 0),
("Email Header", 600, 200),
("Large Thumbnail", 300, 300),
("Small Thumbnail", 150, 150),
@@ -145,11 +175,64 @@ pub fn build_resize_page(state: &AppState) -> adw::NavigationPage {
presets_group.add(&fedi_expander);
presets_group.add(&mainstream_expander);
presets_group.add(&common_expander);
content.append(&presets_group);
preset_box.append(&presets_group);
// Advanced options
mode_stack.add_titled(&preset_box, Some("presets"), "Presets");
// --- Mode 3: Fit in Box ---
let fit_box = gtk::Box::builder()
.orientation(gtk::Orientation::Vertical)
.spacing(12)
.build();
let fit_group = adw::PreferencesGroup::builder()
.title("Fit in Bounding Box")
.description("Images are scaled down to fit within these maximum dimensions while maintaining their aspect ratio. Images smaller than the box are not enlarged.")
.build();
let max_width_row = adw::SpinRow::builder()
.title("Maximum Width")
.subtitle("Images wider than this are scaled down")
.adjustment(&gtk::Adjustment::new(1200.0, 1.0, 10000.0, 1.0, 100.0, 0.0))
.build();
let max_height_row = adw::SpinRow::builder()
.title("Maximum Height")
.subtitle("Images taller than this are scaled down")
.adjustment(&gtk::Adjustment::new(1200.0, 1.0, 10000.0, 1.0, 100.0, 0.0))
.build();
fit_group.add(&max_width_row);
fit_group.add(&max_height_row);
fit_box.append(&fit_group);
// Wire fit-in-box to update width/height
{
let width_c = width_row.clone();
let height_c = height_row.clone();
max_width_row.connect_value_notify(move |row| {
width_c.set_value(row.value());
});
let height_c2 = height_row.clone();
max_height_row.connect_value_notify(move |row| {
height_c2.set_value(row.value());
});
let _ = height_c; // suppress unused
}
mode_stack.add_titled(&fit_box, Some("fit-box"), "Fit in Box");
content.append(&mode_stack);
// Advanced options (AdwExpanderRow per design doc)
let advanced_group = adw::PreferencesGroup::builder()
.title("Advanced")
.build();
let advanced_expander = adw::ExpanderRow::builder()
.title("Advanced Options")
.subtitle("Resize algorithm, DPI, upscale behavior")
.show_enable_switch(false)
.build();
let upscale_row = adw::SwitchRow::builder()
@@ -158,7 +241,28 @@ pub fn build_resize_page(state: &AppState) -> adw::NavigationPage {
.active(cfg.allow_upscale)
.build();
advanced_group.add(&upscale_row);
let algorithm_row = adw::ComboRow::builder()
.title("Resize Algorithm")
.subtitle("Method used for pixel interpolation")
.build();
let algo_model = gtk::StringList::new(&[
"Lanczos3 (Best quality)",
"CatmullRom (Good quality, faster)",
"Bilinear (Fast, lower quality)",
"Nearest (Fastest, pixelated)",
]);
algorithm_row.set_model(Some(&algo_model));
let dpi_row = adw::SpinRow::builder()
.title("DPI")
.subtitle("Output resolution in dots per inch")
.adjustment(&gtk::Adjustment::new(72.0, 72.0, 600.0, 1.0, 10.0, 0.0))
.build();
advanced_expander.add_row(&upscale_row);
advanced_expander.add_row(&algorithm_row);
advanced_expander.add_row(&dpi_row);
advanced_group.add(&advanced_expander);
content.append(&advanced_group);
drop(cfg);