Add Adjustments, Watermark, Rename wizard steps; expand to 10-step wizard
- New step_adjustments: rotation (5 options) and flip (3 options) - New step_watermark: text/image watermark with position, opacity, font size - New step_rename: prefix/suffix/counter with live preview and template engine - Updated step_metadata: added Custom mode with per-category checkboxes (GPS, camera, software, timestamps, copyright) with show/hide toggle - Expanded JobConfig with all operation fields (watermark, rename, metadata custom) - Updated wizard from 7 to 10 steps in correct pipeline order - Fixed page index references from 6 to 9 for output step - Added MetadataMode::Custom handling in preset builder and output summary
This commit is contained in:
@@ -13,19 +13,47 @@ pub const APP_ID: &str = "live.lashman.Pixstrip";
|
||||
/// User's choices from the wizard steps, used to build the ProcessingJob
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct JobConfig {
|
||||
// Resize
|
||||
pub resize_enabled: bool,
|
||||
pub resize_width: u32,
|
||||
pub resize_height: u32,
|
||||
pub allow_upscale: bool,
|
||||
// Adjustments
|
||||
pub rotation: u32,
|
||||
pub flip: u32,
|
||||
// Convert
|
||||
pub convert_enabled: bool,
|
||||
pub convert_format: Option<pixstrip_core::types::ImageFormat>,
|
||||
// Compress
|
||||
pub compress_enabled: bool,
|
||||
pub quality_preset: pixstrip_core::types::QualityPreset,
|
||||
pub jpeg_quality: u8,
|
||||
pub png_level: u8,
|
||||
pub webp_quality: u8,
|
||||
// Metadata
|
||||
pub metadata_enabled: bool,
|
||||
pub metadata_mode: MetadataMode,
|
||||
pub strip_gps: bool,
|
||||
pub strip_camera: bool,
|
||||
pub strip_software: bool,
|
||||
pub strip_timestamps: bool,
|
||||
pub strip_copyright: bool,
|
||||
// Watermark
|
||||
pub watermark_enabled: bool,
|
||||
pub watermark_text: String,
|
||||
pub watermark_image_path: Option<std::path::PathBuf>,
|
||||
pub watermark_position: u32,
|
||||
pub watermark_opacity: f32,
|
||||
pub watermark_font_size: f32,
|
||||
pub watermark_use_image: bool,
|
||||
// Rename
|
||||
pub rename_enabled: bool,
|
||||
pub rename_prefix: String,
|
||||
pub rename_suffix: String,
|
||||
pub rename_counter_start: u32,
|
||||
pub rename_counter_padding: u32,
|
||||
pub rename_template: String,
|
||||
// Output
|
||||
pub preserve_dir_structure: bool,
|
||||
pub overwrite_behavior: u8,
|
||||
}
|
||||
@@ -36,6 +64,7 @@ pub enum MetadataMode {
|
||||
StripAll,
|
||||
Privacy,
|
||||
KeepAll,
|
||||
Custom,
|
||||
}
|
||||
|
||||
/// Shared app state accessible from all UI callbacks
|
||||
@@ -93,6 +122,8 @@ fn build_ui(app: &adw::Application) {
|
||||
resize_width: 1200,
|
||||
resize_height: 0,
|
||||
allow_upscale: false,
|
||||
rotation: 0,
|
||||
flip: 0,
|
||||
convert_enabled: false,
|
||||
convert_format: None,
|
||||
compress_enabled: true,
|
||||
@@ -102,6 +133,24 @@ fn build_ui(app: &adw::Application) {
|
||||
webp_quality: 80,
|
||||
metadata_enabled: true,
|
||||
metadata_mode: MetadataMode::StripAll,
|
||||
strip_gps: true,
|
||||
strip_camera: true,
|
||||
strip_software: true,
|
||||
strip_timestamps: true,
|
||||
strip_copyright: true,
|
||||
watermark_enabled: 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_prefix: String::new(),
|
||||
rename_suffix: String::new(),
|
||||
rename_counter_start: 1,
|
||||
rename_counter_padding: 3,
|
||||
rename_template: String::new(),
|
||||
preserve_dir_structure: false,
|
||||
overwrite_behavior: 0,
|
||||
})),
|
||||
@@ -398,10 +447,10 @@ fn navigate_to_step(ui: &WizardUi, target: usize) {
|
||||
}
|
||||
|
||||
// Update dynamic content on certain steps
|
||||
if target == 6 {
|
||||
if target == 9 {
|
||||
// Output step - update image count and operation summary
|
||||
let count = ui.state.loaded_files.borrow().len();
|
||||
if let Some(page) = ui.pages.get(6) {
|
||||
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"
|
||||
@@ -493,8 +542,8 @@ fn open_output_chooser(window: &adw::ApplicationWindow, ui: &WizardUi) {
|
||||
}
|
||||
|
||||
fn update_output_label(ui: &WizardUi, path: &std::path::Path) {
|
||||
// Find the output step page (index 6) and update the output location subtitle
|
||||
if let Some(page) = ui.pages.get(6) {
|
||||
// Find the output step page (index 9) and update the output location subtitle
|
||||
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() == "Output Location"
|
||||
@@ -704,6 +753,78 @@ fn run_processing(_window: &adw::ApplicationWindow, ui: &WizardUi) {
|
||||
MetadataMode::StripAll => pixstrip_core::operations::MetadataConfig::StripAll,
|
||||
MetadataMode::Privacy => pixstrip_core::operations::MetadataConfig::Privacy,
|
||||
MetadataMode::KeepAll => pixstrip_core::operations::MetadataConfig::KeepAll,
|
||||
MetadataMode::Custom => pixstrip_core::operations::MetadataConfig::Custom {
|
||||
strip_gps: cfg.strip_gps,
|
||||
strip_camera: cfg.strip_camera,
|
||||
strip_software: cfg.strip_software,
|
||||
strip_timestamps: cfg.strip_timestamps,
|
||||
strip_copyright: cfg.strip_copyright,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Rotation
|
||||
job.rotation = Some(match cfg.rotation {
|
||||
1 => pixstrip_core::operations::Rotation::Cw90,
|
||||
2 => pixstrip_core::operations::Rotation::Cw180,
|
||||
3 => pixstrip_core::operations::Rotation::Cw270,
|
||||
4 => pixstrip_core::operations::Rotation::AutoOrient,
|
||||
_ => pixstrip_core::operations::Rotation::None,
|
||||
});
|
||||
|
||||
// Flip
|
||||
job.flip = Some(match cfg.flip {
|
||||
1 => pixstrip_core::operations::Flip::Horizontal,
|
||||
2 => pixstrip_core::operations::Flip::Vertical,
|
||||
_ => pixstrip_core::operations::Flip::None,
|
||||
});
|
||||
|
||||
// Watermark
|
||||
if cfg.watermark_enabled {
|
||||
let position = match cfg.watermark_position {
|
||||
0 => pixstrip_core::operations::WatermarkPosition::TopLeft,
|
||||
1 => pixstrip_core::operations::WatermarkPosition::TopCenter,
|
||||
2 => pixstrip_core::operations::WatermarkPosition::TopRight,
|
||||
3 => pixstrip_core::operations::WatermarkPosition::MiddleLeft,
|
||||
4 => pixstrip_core::operations::WatermarkPosition::Center,
|
||||
5 => pixstrip_core::operations::WatermarkPosition::MiddleRight,
|
||||
6 => pixstrip_core::operations::WatermarkPosition::BottomLeft,
|
||||
7 => pixstrip_core::operations::WatermarkPosition::BottomCenter,
|
||||
_ => pixstrip_core::operations::WatermarkPosition::BottomRight,
|
||||
};
|
||||
|
||||
if cfg.watermark_use_image {
|
||||
if let Some(ref path) = cfg.watermark_image_path {
|
||||
job.watermark = Some(pixstrip_core::operations::WatermarkConfig::Image {
|
||||
path: path.clone(),
|
||||
position,
|
||||
opacity: cfg.watermark_opacity,
|
||||
scale: 0.2,
|
||||
});
|
||||
}
|
||||
} else if !cfg.watermark_text.is_empty() {
|
||||
job.watermark = Some(pixstrip_core::operations::WatermarkConfig::Text {
|
||||
text: cfg.watermark_text.clone(),
|
||||
position,
|
||||
font_size: cfg.watermark_font_size,
|
||||
opacity: cfg.watermark_opacity,
|
||||
color: [255, 255, 255, 255],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Rename
|
||||
if cfg.rename_enabled {
|
||||
job.rename = Some(pixstrip_core::operations::RenameConfig {
|
||||
prefix: cfg.rename_prefix.clone(),
|
||||
suffix: cfg.rename_suffix.clone(),
|
||||
counter_start: cfg.rename_counter_start,
|
||||
counter_padding: cfg.rename_counter_padding,
|
||||
template: if cfg.rename_template.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(cfg.rename_template.clone())
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1147,6 +1268,13 @@ fn build_preset_from_config(cfg: &JobConfig, name: &str) -> pixstrip_core::prese
|
||||
MetadataMode::StripAll => pixstrip_core::operations::MetadataConfig::StripAll,
|
||||
MetadataMode::Privacy => pixstrip_core::operations::MetadataConfig::Privacy,
|
||||
MetadataMode::KeepAll => pixstrip_core::operations::MetadataConfig::KeepAll,
|
||||
MetadataMode::Custom => pixstrip_core::operations::MetadataConfig::Custom {
|
||||
strip_gps: cfg.strip_gps,
|
||||
strip_camera: cfg.strip_camera,
|
||||
strip_software: cfg.strip_software,
|
||||
strip_timestamps: cfg.strip_timestamps,
|
||||
strip_copyright: cfg.strip_copyright,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
None
|
||||
@@ -1195,7 +1323,7 @@ fn build_preset_description(cfg: &JobConfig) -> String {
|
||||
|
||||
fn update_output_summary(ui: &WizardUi) {
|
||||
let cfg = ui.state.job_config.borrow();
|
||||
if let Some(page) = ui.pages.get(6) {
|
||||
if let Some(page) = ui.pages.get(9) {
|
||||
// Build summary lines
|
||||
let mut ops = Vec::new();
|
||||
if cfg.resize_enabled && cfg.resize_width > 0 {
|
||||
@@ -1216,6 +1344,7 @@ fn update_output_summary(ui: &WizardUi) {
|
||||
MetadataMode::StripAll => "Strip all metadata",
|
||||
MetadataMode::Privacy => "Privacy mode",
|
||||
MetadataMode::KeepAll => "Keep all metadata",
|
||||
MetadataMode::Custom => "Custom metadata stripping",
|
||||
};
|
||||
ops.push(mode.to_string());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user