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
|
/// User's choices from the wizard steps, used to build the ProcessingJob
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct JobConfig {
|
pub struct JobConfig {
|
||||||
|
// Resize
|
||||||
pub resize_enabled: bool,
|
pub resize_enabled: bool,
|
||||||
pub resize_width: u32,
|
pub resize_width: u32,
|
||||||
pub resize_height: u32,
|
pub resize_height: u32,
|
||||||
pub allow_upscale: bool,
|
pub allow_upscale: bool,
|
||||||
|
// Adjustments
|
||||||
|
pub rotation: u32,
|
||||||
|
pub flip: u32,
|
||||||
|
// Convert
|
||||||
pub convert_enabled: bool,
|
pub convert_enabled: bool,
|
||||||
pub convert_format: Option<pixstrip_core::types::ImageFormat>,
|
pub convert_format: Option<pixstrip_core::types::ImageFormat>,
|
||||||
|
// Compress
|
||||||
pub compress_enabled: bool,
|
pub compress_enabled: bool,
|
||||||
pub quality_preset: pixstrip_core::types::QualityPreset,
|
pub quality_preset: pixstrip_core::types::QualityPreset,
|
||||||
pub jpeg_quality: u8,
|
pub jpeg_quality: u8,
|
||||||
pub png_level: u8,
|
pub png_level: u8,
|
||||||
pub webp_quality: u8,
|
pub webp_quality: u8,
|
||||||
|
// Metadata
|
||||||
pub metadata_enabled: bool,
|
pub metadata_enabled: bool,
|
||||||
pub metadata_mode: MetadataMode,
|
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 preserve_dir_structure: bool,
|
||||||
pub overwrite_behavior: u8,
|
pub overwrite_behavior: u8,
|
||||||
}
|
}
|
||||||
@@ -36,6 +64,7 @@ pub enum MetadataMode {
|
|||||||
StripAll,
|
StripAll,
|
||||||
Privacy,
|
Privacy,
|
||||||
KeepAll,
|
KeepAll,
|
||||||
|
Custom,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shared app state accessible from all UI callbacks
|
/// Shared app state accessible from all UI callbacks
|
||||||
@@ -93,6 +122,8 @@ fn build_ui(app: &adw::Application) {
|
|||||||
resize_width: 1200,
|
resize_width: 1200,
|
||||||
resize_height: 0,
|
resize_height: 0,
|
||||||
allow_upscale: false,
|
allow_upscale: false,
|
||||||
|
rotation: 0,
|
||||||
|
flip: 0,
|
||||||
convert_enabled: false,
|
convert_enabled: false,
|
||||||
convert_format: None,
|
convert_format: None,
|
||||||
compress_enabled: true,
|
compress_enabled: true,
|
||||||
@@ -102,6 +133,24 @@ fn build_ui(app: &adw::Application) {
|
|||||||
webp_quality: 80,
|
webp_quality: 80,
|
||||||
metadata_enabled: true,
|
metadata_enabled: true,
|
||||||
metadata_mode: MetadataMode::StripAll,
|
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,
|
preserve_dir_structure: false,
|
||||||
overwrite_behavior: 0,
|
overwrite_behavior: 0,
|
||||||
})),
|
})),
|
||||||
@@ -398,10 +447,10 @@ fn navigate_to_step(ui: &WizardUi, target: usize) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update dynamic content on certain steps
|
// Update dynamic content on certain steps
|
||||||
if target == 6 {
|
if target == 9 {
|
||||||
// Output step - update image count and operation summary
|
// Output step - update image count and operation summary
|
||||||
let count = ui.state.loaded_files.borrow().len();
|
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| {
|
walk_widgets(&page.child(), &|widget| {
|
||||||
if let Some(row) = widget.downcast_ref::<adw::ActionRow>()
|
if let Some(row) = widget.downcast_ref::<adw::ActionRow>()
|
||||||
&& row.title().as_str() == "Images to process"
|
&& 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) {
|
fn update_output_label(ui: &WizardUi, path: &std::path::Path) {
|
||||||
// Find the output step page (index 6) and update the output location subtitle
|
// Find the output step page (index 9) and update the output location subtitle
|
||||||
if let Some(page) = ui.pages.get(6) {
|
if let Some(page) = ui.pages.get(9) {
|
||||||
walk_widgets(&page.child(), &|widget| {
|
walk_widgets(&page.child(), &|widget| {
|
||||||
if let Some(row) = widget.downcast_ref::<adw::ActionRow>()
|
if let Some(row) = widget.downcast_ref::<adw::ActionRow>()
|
||||||
&& row.title().as_str() == "Output Location"
|
&& 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::StripAll => pixstrip_core::operations::MetadataConfig::StripAll,
|
||||||
MetadataMode::Privacy => pixstrip_core::operations::MetadataConfig::Privacy,
|
MetadataMode::Privacy => pixstrip_core::operations::MetadataConfig::Privacy,
|
||||||
MetadataMode::KeepAll => pixstrip_core::operations::MetadataConfig::KeepAll,
|
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::StripAll => pixstrip_core::operations::MetadataConfig::StripAll,
|
||||||
MetadataMode::Privacy => pixstrip_core::operations::MetadataConfig::Privacy,
|
MetadataMode::Privacy => pixstrip_core::operations::MetadataConfig::Privacy,
|
||||||
MetadataMode::KeepAll => pixstrip_core::operations::MetadataConfig::KeepAll,
|
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 {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -1195,7 +1323,7 @@ fn build_preset_description(cfg: &JobConfig) -> String {
|
|||||||
|
|
||||||
fn update_output_summary(ui: &WizardUi) {
|
fn update_output_summary(ui: &WizardUi) {
|
||||||
let cfg = ui.state.job_config.borrow();
|
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
|
// Build summary lines
|
||||||
let mut ops = Vec::new();
|
let mut ops = Vec::new();
|
||||||
if cfg.resize_enabled && cfg.resize_width > 0 {
|
if cfg.resize_enabled && cfg.resize_width > 0 {
|
||||||
@@ -1216,6 +1344,7 @@ fn update_output_summary(ui: &WizardUi) {
|
|||||||
MetadataMode::StripAll => "Strip all metadata",
|
MetadataMode::StripAll => "Strip all metadata",
|
||||||
MetadataMode::Privacy => "Privacy mode",
|
MetadataMode::Privacy => "Privacy mode",
|
||||||
MetadataMode::KeepAll => "Keep all metadata",
|
MetadataMode::KeepAll => "Keep all metadata",
|
||||||
|
MetadataMode::Custom => "Custom metadata stripping",
|
||||||
};
|
};
|
||||||
ops.push(mode.to_string());
|
ops.push(mode.to_string());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
|
pub mod step_adjustments;
|
||||||
pub mod step_compress;
|
pub mod step_compress;
|
||||||
pub mod step_convert;
|
pub mod step_convert;
|
||||||
pub mod step_images;
|
pub mod step_images;
|
||||||
pub mod step_metadata;
|
pub mod step_metadata;
|
||||||
pub mod step_output;
|
pub mod step_output;
|
||||||
|
pub mod step_rename;
|
||||||
pub mod step_resize;
|
pub mod step_resize;
|
||||||
|
pub mod step_watermark;
|
||||||
pub mod step_workflow;
|
pub mod step_workflow;
|
||||||
|
|||||||
87
pixstrip-gtk/src/steps/step_adjustments.rs
Normal file
87
pixstrip-gtk/src/steps/step_adjustments.rs
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
use adw::prelude::*;
|
||||||
|
use crate::app::AppState;
|
||||||
|
|
||||||
|
pub fn build_adjustments_page(state: &AppState) -> 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(12)
|
||||||
|
.margin_top(12)
|
||||||
|
.margin_bottom(12)
|
||||||
|
.margin_start(24)
|
||||||
|
.margin_end(24)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let cfg = state.job_config.borrow();
|
||||||
|
|
||||||
|
// Rotate
|
||||||
|
let rotate_group = adw::PreferencesGroup::builder()
|
||||||
|
.title("Rotation")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let rotate_row = adw::ComboRow::builder()
|
||||||
|
.title("Rotate")
|
||||||
|
.subtitle("Rotation applied after resize")
|
||||||
|
.build();
|
||||||
|
let rotate_model = gtk::StringList::new(&[
|
||||||
|
"None",
|
||||||
|
"90 clockwise",
|
||||||
|
"180",
|
||||||
|
"270 clockwise",
|
||||||
|
"Auto-orient (EXIF)",
|
||||||
|
]);
|
||||||
|
rotate_row.set_model(Some(&rotate_model));
|
||||||
|
rotate_row.set_selected(cfg.rotation);
|
||||||
|
|
||||||
|
rotate_group.add(&rotate_row);
|
||||||
|
content.append(&rotate_group);
|
||||||
|
|
||||||
|
// Flip
|
||||||
|
let flip_group = adw::PreferencesGroup::builder()
|
||||||
|
.title("Flip")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let flip_row = adw::ComboRow::builder()
|
||||||
|
.title("Flip")
|
||||||
|
.subtitle("Mirror the image")
|
||||||
|
.build();
|
||||||
|
let flip_model = gtk::StringList::new(&["None", "Horizontal", "Vertical"]);
|
||||||
|
flip_row.set_model(Some(&flip_model));
|
||||||
|
flip_row.set_selected(cfg.flip);
|
||||||
|
|
||||||
|
flip_group.add(&flip_row);
|
||||||
|
content.append(&flip_group);
|
||||||
|
|
||||||
|
drop(cfg);
|
||||||
|
|
||||||
|
// Wire signals
|
||||||
|
{
|
||||||
|
let jc = state.job_config.clone();
|
||||||
|
rotate_row.connect_selected_notify(move |row| {
|
||||||
|
jc.borrow_mut().rotation = row.selected();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let jc = state.job_config.clone();
|
||||||
|
flip_row.connect_selected_notify(move |row| {
|
||||||
|
jc.borrow_mut().flip = row.selected();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
scrolled.set_child(Some(&content));
|
||||||
|
|
||||||
|
let clamp = adw::Clamp::builder()
|
||||||
|
.maximum_size(600)
|
||||||
|
.child(&scrolled)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
adw::NavigationPage::builder()
|
||||||
|
.title("Adjustments")
|
||||||
|
.tag("step-adjustments")
|
||||||
|
.child(&clamp)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
@@ -69,11 +69,71 @@ pub fn build_metadata_page(state: &AppState) -> adw::NavigationPage {
|
|||||||
keep_all_row.add_suffix(&keep_all_check);
|
keep_all_row.add_suffix(&keep_all_check);
|
||||||
keep_all_row.set_activatable_widget(Some(&keep_all_check));
|
keep_all_row.set_activatable_widget(Some(&keep_all_check));
|
||||||
|
|
||||||
|
let custom_row = adw::ActionRow::builder()
|
||||||
|
.title("Custom")
|
||||||
|
.subtitle("Choose exactly which metadata categories to strip")
|
||||||
|
.activatable(true)
|
||||||
|
.build();
|
||||||
|
custom_row.add_prefix(>k::Image::from_icon_name("emblem-system-symbolic"));
|
||||||
|
let custom_check = gtk::CheckButton::new();
|
||||||
|
custom_check.set_group(Some(&strip_all_check));
|
||||||
|
custom_check.set_active(cfg.metadata_mode == MetadataMode::Custom);
|
||||||
|
custom_row.add_suffix(&custom_check);
|
||||||
|
custom_row.set_activatable_widget(Some(&custom_check));
|
||||||
|
|
||||||
presets_group.add(&strip_all_row);
|
presets_group.add(&strip_all_row);
|
||||||
presets_group.add(&privacy_row);
|
presets_group.add(&privacy_row);
|
||||||
presets_group.add(&keep_all_row);
|
presets_group.add(&keep_all_row);
|
||||||
|
presets_group.add(&custom_row);
|
||||||
content.append(&presets_group);
|
content.append(&presets_group);
|
||||||
|
|
||||||
|
// Custom category checkboxes
|
||||||
|
let custom_group = adw::PreferencesGroup::builder()
|
||||||
|
.title("Custom Categories")
|
||||||
|
.description("Select which metadata categories to strip")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let gps_row = adw::SwitchRow::builder()
|
||||||
|
.title("GPS / Location")
|
||||||
|
.subtitle("GPS coordinates, location name, altitude")
|
||||||
|
.active(cfg.strip_gps)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let camera_row = adw::SwitchRow::builder()
|
||||||
|
.title("Camera Info")
|
||||||
|
.subtitle("Camera model, serial number, lens data")
|
||||||
|
.active(cfg.strip_camera)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let software_row = adw::SwitchRow::builder()
|
||||||
|
.title("Software")
|
||||||
|
.subtitle("Editing software, processing history")
|
||||||
|
.active(cfg.strip_software)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let timestamps_row = adw::SwitchRow::builder()
|
||||||
|
.title("Timestamps")
|
||||||
|
.subtitle("Date taken, date modified, date digitized")
|
||||||
|
.active(cfg.strip_timestamps)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let copyright_row = adw::SwitchRow::builder()
|
||||||
|
.title("Copyright / Author")
|
||||||
|
.subtitle("Copyright notice, artist name, credits")
|
||||||
|
.active(cfg.strip_copyright)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
custom_group.add(&gps_row);
|
||||||
|
custom_group.add(&camera_row);
|
||||||
|
custom_group.add(&software_row);
|
||||||
|
custom_group.add(×tamps_row);
|
||||||
|
custom_group.add(©right_row);
|
||||||
|
|
||||||
|
// Only show custom group when Custom mode is selected
|
||||||
|
custom_group.set_visible(cfg.metadata_mode == MetadataMode::Custom);
|
||||||
|
|
||||||
|
content.append(&custom_group);
|
||||||
|
|
||||||
drop(cfg);
|
drop(cfg);
|
||||||
|
|
||||||
// Wire signals
|
// Wire signals
|
||||||
@@ -85,28 +145,76 @@ pub fn build_metadata_page(state: &AppState) -> adw::NavigationPage {
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
let jc = state.job_config.clone();
|
let jc = state.job_config.clone();
|
||||||
|
let cg = custom_group.clone();
|
||||||
strip_all_check.connect_toggled(move |check| {
|
strip_all_check.connect_toggled(move |check| {
|
||||||
if check.is_active() {
|
if check.is_active() {
|
||||||
jc.borrow_mut().metadata_mode = MetadataMode::StripAll;
|
jc.borrow_mut().metadata_mode = MetadataMode::StripAll;
|
||||||
|
cg.set_visible(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let jc = state.job_config.clone();
|
let jc = state.job_config.clone();
|
||||||
|
let cg = custom_group.clone();
|
||||||
privacy_check.connect_toggled(move |check| {
|
privacy_check.connect_toggled(move |check| {
|
||||||
if check.is_active() {
|
if check.is_active() {
|
||||||
jc.borrow_mut().metadata_mode = MetadataMode::Privacy;
|
jc.borrow_mut().metadata_mode = MetadataMode::Privacy;
|
||||||
|
cg.set_visible(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let jc = state.job_config.clone();
|
let jc = state.job_config.clone();
|
||||||
|
let cg = custom_group.clone();
|
||||||
keep_all_check.connect_toggled(move |check| {
|
keep_all_check.connect_toggled(move |check| {
|
||||||
if check.is_active() {
|
if check.is_active() {
|
||||||
jc.borrow_mut().metadata_mode = MetadataMode::KeepAll;
|
jc.borrow_mut().metadata_mode = MetadataMode::KeepAll;
|
||||||
|
cg.set_visible(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
let jc = state.job_config.clone();
|
||||||
|
let cg = custom_group;
|
||||||
|
custom_check.connect_toggled(move |check| {
|
||||||
|
if check.is_active() {
|
||||||
|
jc.borrow_mut().metadata_mode = MetadataMode::Custom;
|
||||||
|
cg.set_visible(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wire custom category toggles
|
||||||
|
{
|
||||||
|
let jc = state.job_config.clone();
|
||||||
|
gps_row.connect_active_notify(move |row| {
|
||||||
|
jc.borrow_mut().strip_gps = row.is_active();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let jc = state.job_config.clone();
|
||||||
|
camera_row.connect_active_notify(move |row| {
|
||||||
|
jc.borrow_mut().strip_camera = row.is_active();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let jc = state.job_config.clone();
|
||||||
|
software_row.connect_active_notify(move |row| {
|
||||||
|
jc.borrow_mut().strip_software = row.is_active();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let jc = state.job_config.clone();
|
||||||
|
timestamps_row.connect_active_notify(move |row| {
|
||||||
|
jc.borrow_mut().strip_timestamps = row.is_active();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let jc = state.job_config.clone();
|
||||||
|
copyright_row.connect_active_notify(move |row| {
|
||||||
|
jc.borrow_mut().strip_copyright = row.is_active();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
scrolled.set_child(Some(&content));
|
scrolled.set_child(Some(&content));
|
||||||
|
|
||||||
|
|||||||
229
pixstrip-gtk/src/steps/step_rename.rs
Normal file
229
pixstrip-gtk/src/steps/step_rename.rs
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
use adw::prelude::*;
|
||||||
|
use crate::app::AppState;
|
||||||
|
|
||||||
|
pub fn build_rename_page(state: &AppState) -> 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(12)
|
||||||
|
.margin_top(12)
|
||||||
|
.margin_bottom(12)
|
||||||
|
.margin_start(24)
|
||||||
|
.margin_end(24)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let cfg = state.job_config.borrow();
|
||||||
|
|
||||||
|
// Enable toggle
|
||||||
|
let enable_row = adw::SwitchRow::builder()
|
||||||
|
.title("Enable Rename")
|
||||||
|
.subtitle("Rename output files with prefix, suffix, or template")
|
||||||
|
.active(cfg.rename_enabled)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let enable_group = adw::PreferencesGroup::new();
|
||||||
|
enable_group.add(&enable_row);
|
||||||
|
content.append(&enable_group);
|
||||||
|
|
||||||
|
// Simple mode: prefix + suffix + counter
|
||||||
|
let simple_group = adw::PreferencesGroup::builder()
|
||||||
|
.title("Simple Rename")
|
||||||
|
.description("Add prefix, suffix, and sequential counter")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let prefix_row = adw::EntryRow::builder()
|
||||||
|
.title("Prefix")
|
||||||
|
.text(&cfg.rename_prefix)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let suffix_row = adw::EntryRow::builder()
|
||||||
|
.title("Suffix")
|
||||||
|
.text(&cfg.rename_suffix)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let counter_start_row = adw::SpinRow::builder()
|
||||||
|
.title("Counter Start")
|
||||||
|
.subtitle("First number in sequence")
|
||||||
|
.adjustment(>k::Adjustment::new(cfg.rename_counter_start as f64, 0.0, 99999.0, 1.0, 10.0, 0.0))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let counter_padding_row = adw::SpinRow::builder()
|
||||||
|
.title("Counter Padding")
|
||||||
|
.subtitle("Minimum digits (e.g., 3 = 001, 002, 003)")
|
||||||
|
.adjustment(>k::Adjustment::new(cfg.rename_counter_padding as f64, 1.0, 10.0, 1.0, 1.0, 0.0))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
simple_group.add(&prefix_row);
|
||||||
|
simple_group.add(&suffix_row);
|
||||||
|
simple_group.add(&counter_start_row);
|
||||||
|
simple_group.add(&counter_padding_row);
|
||||||
|
content.append(&simple_group);
|
||||||
|
|
||||||
|
// Live preview
|
||||||
|
let preview_group = adw::PreferencesGroup::builder()
|
||||||
|
.title("Preview")
|
||||||
|
.description("Example of how files will be renamed")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let preview_label = gtk::Label::builder()
|
||||||
|
.label("photo.jpg -> photo_001.jpg")
|
||||||
|
.css_classes(["monospace", "dim-label"])
|
||||||
|
.halign(gtk::Align::Start)
|
||||||
|
.margin_top(8)
|
||||||
|
.margin_bottom(8)
|
||||||
|
.margin_start(12)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
preview_group.add(&preview_label);
|
||||||
|
content.append(&preview_group);
|
||||||
|
|
||||||
|
// Advanced: template engine
|
||||||
|
let advanced_group = adw::PreferencesGroup::builder()
|
||||||
|
.title("Advanced: Template Engine")
|
||||||
|
.description("Use variables in curly braces for full control")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let template_row = adw::EntryRow::builder()
|
||||||
|
.title("Template")
|
||||||
|
.text(&cfg.rename_template)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let help_label = gtk::Label::builder()
|
||||||
|
.label(
|
||||||
|
"Available variables:\n\
|
||||||
|
{name} - original filename (no extension)\n\
|
||||||
|
{ext} - output extension\n\
|
||||||
|
{counter} or {counter:3} - zero-padded counter\n\
|
||||||
|
{date} - today's date\n\
|
||||||
|
{exif_date} - EXIF date taken\n\
|
||||||
|
{camera} - camera model from EXIF\n\
|
||||||
|
{width} x {height} - output dimensions\n\
|
||||||
|
{original_ext} - original file extension"
|
||||||
|
)
|
||||||
|
.css_classes(["dim-label", "caption"])
|
||||||
|
.halign(gtk::Align::Start)
|
||||||
|
.wrap(true)
|
||||||
|
.margin_top(4)
|
||||||
|
.margin_bottom(8)
|
||||||
|
.margin_start(12)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
advanced_group.add(&template_row);
|
||||||
|
advanced_group.add(&help_label);
|
||||||
|
content.append(&advanced_group);
|
||||||
|
|
||||||
|
drop(cfg);
|
||||||
|
|
||||||
|
// Wire signals
|
||||||
|
{
|
||||||
|
let jc = state.job_config.clone();
|
||||||
|
enable_row.connect_active_notify(move |row| {
|
||||||
|
jc.borrow_mut().rename_enabled = row.is_active();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update preview helper
|
||||||
|
let update_preview = {
|
||||||
|
let files = state.loaded_files.clone();
|
||||||
|
let jc = state.job_config.clone();
|
||||||
|
let preview = preview_label.clone();
|
||||||
|
move || {
|
||||||
|
let cfg = jc.borrow();
|
||||||
|
let loaded = files.borrow();
|
||||||
|
let sample_name = loaded
|
||||||
|
.first()
|
||||||
|
.and_then(|p| p.file_stem())
|
||||||
|
.and_then(|s| s.to_str())
|
||||||
|
.unwrap_or("photo");
|
||||||
|
let sample_ext = loaded
|
||||||
|
.first()
|
||||||
|
.and_then(|p| p.extension())
|
||||||
|
.and_then(|e| e.to_str())
|
||||||
|
.unwrap_or("jpg");
|
||||||
|
|
||||||
|
if !cfg.rename_template.is_empty() {
|
||||||
|
// Template mode preview
|
||||||
|
let result = cfg.rename_template
|
||||||
|
.replace("{name}", sample_name)
|
||||||
|
.replace("{ext}", sample_ext)
|
||||||
|
.replace("{counter}", &format!("{:0>width$}", cfg.rename_counter_start, width = cfg.rename_counter_padding as usize))
|
||||||
|
.replace("{date}", "2026-03-06")
|
||||||
|
.replace("{width}", "1200")
|
||||||
|
.replace("{height}", "800");
|
||||||
|
preview.set_label(&format!("{}.{} -> {}", sample_name, sample_ext, result));
|
||||||
|
} else {
|
||||||
|
// Simple mode preview
|
||||||
|
let rename_cfg = 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: None,
|
||||||
|
};
|
||||||
|
let result = rename_cfg.apply_simple(sample_name, sample_ext, 1);
|
||||||
|
preview.set_label(&format!("{}.{} -> {}", sample_name, sample_ext, result));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Call once to set initial preview
|
||||||
|
update_preview();
|
||||||
|
|
||||||
|
{
|
||||||
|
let jc = state.job_config.clone();
|
||||||
|
let up = update_preview.clone();
|
||||||
|
prefix_row.connect_changed(move |row| {
|
||||||
|
jc.borrow_mut().rename_prefix = row.text().to_string();
|
||||||
|
up();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let jc = state.job_config.clone();
|
||||||
|
let up = update_preview.clone();
|
||||||
|
suffix_row.connect_changed(move |row| {
|
||||||
|
jc.borrow_mut().rename_suffix = row.text().to_string();
|
||||||
|
up();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let jc = state.job_config.clone();
|
||||||
|
let up = update_preview.clone();
|
||||||
|
counter_start_row.connect_value_notify(move |row| {
|
||||||
|
jc.borrow_mut().rename_counter_start = row.value() as u32;
|
||||||
|
up();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let jc = state.job_config.clone();
|
||||||
|
let up = update_preview.clone();
|
||||||
|
counter_padding_row.connect_value_notify(move |row| {
|
||||||
|
jc.borrow_mut().rename_counter_padding = row.value() as u32;
|
||||||
|
up();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let jc = state.job_config.clone();
|
||||||
|
let up = update_preview;
|
||||||
|
template_row.connect_changed(move |row| {
|
||||||
|
jc.borrow_mut().rename_template = row.text().to_string();
|
||||||
|
up();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
scrolled.set_child(Some(&content));
|
||||||
|
|
||||||
|
let clamp = adw::Clamp::builder()
|
||||||
|
.maximum_size(600)
|
||||||
|
.child(&scrolled)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
adw::NavigationPage::builder()
|
||||||
|
.title("Rename")
|
||||||
|
.tag("step-rename")
|
||||||
|
.child(&clamp)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
222
pixstrip-gtk/src/steps/step_watermark.rs
Normal file
222
pixstrip-gtk/src/steps/step_watermark.rs
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
use adw::prelude::*;
|
||||||
|
use crate::app::AppState;
|
||||||
|
|
||||||
|
pub fn build_watermark_page(state: &AppState) -> 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(12)
|
||||||
|
.margin_top(12)
|
||||||
|
.margin_bottom(12)
|
||||||
|
.margin_start(24)
|
||||||
|
.margin_end(24)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let cfg = state.job_config.borrow();
|
||||||
|
|
||||||
|
// Enable toggle
|
||||||
|
let enable_row = adw::SwitchRow::builder()
|
||||||
|
.title("Enable Watermark")
|
||||||
|
.subtitle("Add text or image watermark to processed images")
|
||||||
|
.active(cfg.watermark_enabled)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let enable_group = adw::PreferencesGroup::new();
|
||||||
|
enable_group.add(&enable_row);
|
||||||
|
content.append(&enable_group);
|
||||||
|
|
||||||
|
// Watermark type selection
|
||||||
|
let type_group = adw::PreferencesGroup::builder()
|
||||||
|
.title("Watermark Type")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let type_row = adw::ComboRow::builder()
|
||||||
|
.title("Type")
|
||||||
|
.subtitle("Choose text or image watermark")
|
||||||
|
.build();
|
||||||
|
let type_model = gtk::StringList::new(&["Text Watermark", "Image Watermark"]);
|
||||||
|
type_row.set_model(Some(&type_model));
|
||||||
|
type_row.set_selected(if cfg.watermark_use_image { 1 } else { 0 });
|
||||||
|
|
||||||
|
type_group.add(&type_row);
|
||||||
|
content.append(&type_group);
|
||||||
|
|
||||||
|
// Text watermark settings
|
||||||
|
let text_group = adw::PreferencesGroup::builder()
|
||||||
|
.title("Text Watermark")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let text_row = adw::EntryRow::builder()
|
||||||
|
.title("Watermark Text")
|
||||||
|
.text(&cfg.watermark_text)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let font_size_row = adw::SpinRow::builder()
|
||||||
|
.title("Font Size")
|
||||||
|
.subtitle("Size in pixels")
|
||||||
|
.adjustment(>k::Adjustment::new(cfg.watermark_font_size as f64, 8.0, 200.0, 1.0, 10.0, 0.0))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
text_group.add(&text_row);
|
||||||
|
text_group.add(&font_size_row);
|
||||||
|
content.append(&text_group);
|
||||||
|
|
||||||
|
// Image watermark settings
|
||||||
|
let image_group = adw::PreferencesGroup::builder()
|
||||||
|
.title("Image Watermark")
|
||||||
|
.visible(cfg.watermark_use_image)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let image_path_row = adw::ActionRow::builder()
|
||||||
|
.title("Logo Image")
|
||||||
|
.subtitle(
|
||||||
|
cfg.watermark_image_path
|
||||||
|
.as_ref()
|
||||||
|
.map(|p| p.display().to_string())
|
||||||
|
.unwrap_or_else(|| "No image selected".to_string()),
|
||||||
|
)
|
||||||
|
.activatable(true)
|
||||||
|
.build();
|
||||||
|
image_path_row.add_prefix(>k::Image::from_icon_name("image-x-generic-symbolic"));
|
||||||
|
|
||||||
|
let choose_image_button = gtk::Button::builder()
|
||||||
|
.icon_name("document-open-symbolic")
|
||||||
|
.tooltip_text("Choose logo image")
|
||||||
|
.valign(gtk::Align::Center)
|
||||||
|
.build();
|
||||||
|
choose_image_button.add_css_class("flat");
|
||||||
|
image_path_row.add_suffix(&choose_image_button);
|
||||||
|
|
||||||
|
image_group.add(&image_path_row);
|
||||||
|
content.append(&image_group);
|
||||||
|
|
||||||
|
// Position grid (9-point)
|
||||||
|
let position_group = adw::PreferencesGroup::builder()
|
||||||
|
.title("Position")
|
||||||
|
.description("Choose where the watermark appears on the image")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let position_names = [
|
||||||
|
"Top Left", "Top Center", "Top Right",
|
||||||
|
"Middle Left", "Center", "Middle Right",
|
||||||
|
"Bottom Left", "Bottom Center", "Bottom Right",
|
||||||
|
];
|
||||||
|
|
||||||
|
let position_row = adw::ComboRow::builder()
|
||||||
|
.title("Watermark Position")
|
||||||
|
.build();
|
||||||
|
let position_model = gtk::StringList::new(&position_names);
|
||||||
|
position_row.set_model(Some(&position_model));
|
||||||
|
position_row.set_selected(cfg.watermark_position);
|
||||||
|
|
||||||
|
position_group.add(&position_row);
|
||||||
|
content.append(&position_group);
|
||||||
|
|
||||||
|
// Advanced options
|
||||||
|
let advanced_group = adw::PreferencesGroup::builder()
|
||||||
|
.title("Advanced Options")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let opacity_row = adw::SpinRow::builder()
|
||||||
|
.title("Opacity")
|
||||||
|
.subtitle("0.0 (invisible) to 1.0 (fully opaque)")
|
||||||
|
.adjustment(>k::Adjustment::new(cfg.watermark_opacity as f64, 0.0, 1.0, 0.05, 0.1, 0.0))
|
||||||
|
.digits(2)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
advanced_group.add(&opacity_row);
|
||||||
|
content.append(&advanced_group);
|
||||||
|
|
||||||
|
drop(cfg);
|
||||||
|
|
||||||
|
// Wire signals
|
||||||
|
{
|
||||||
|
let jc = state.job_config.clone();
|
||||||
|
enable_row.connect_active_notify(move |row| {
|
||||||
|
jc.borrow_mut().watermark_enabled = row.is_active();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let jc = state.job_config.clone();
|
||||||
|
let text_group_c = text_group.clone();
|
||||||
|
let image_group_c = image_group.clone();
|
||||||
|
type_row.connect_selected_notify(move |row| {
|
||||||
|
let use_image = row.selected() == 1;
|
||||||
|
jc.borrow_mut().watermark_use_image = use_image;
|
||||||
|
text_group_c.set_visible(!use_image);
|
||||||
|
image_group_c.set_visible(use_image);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let jc = state.job_config.clone();
|
||||||
|
text_row.connect_changed(move |row| {
|
||||||
|
jc.borrow_mut().watermark_text = row.text().to_string();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let jc = state.job_config.clone();
|
||||||
|
font_size_row.connect_value_notify(move |row| {
|
||||||
|
jc.borrow_mut().watermark_font_size = row.value() as f32;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let jc = state.job_config.clone();
|
||||||
|
position_row.connect_selected_notify(move |row| {
|
||||||
|
jc.borrow_mut().watermark_position = row.selected();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let jc = state.job_config.clone();
|
||||||
|
opacity_row.connect_value_notify(move |row| {
|
||||||
|
jc.borrow_mut().watermark_opacity = row.value() as f32;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Wire image chooser button
|
||||||
|
{
|
||||||
|
let jc = state.job_config.clone();
|
||||||
|
let path_row = image_path_row.clone();
|
||||||
|
choose_image_button.connect_clicked(move |btn| {
|
||||||
|
let jc = jc.clone();
|
||||||
|
let path_row = path_row.clone();
|
||||||
|
let dialog = gtk::FileDialog::builder()
|
||||||
|
.title("Choose Watermark Image")
|
||||||
|
.modal(true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let filter = gtk::FileFilter::new();
|
||||||
|
filter.set_name(Some("PNG images"));
|
||||||
|
filter.add_mime_type("image/png");
|
||||||
|
let filters = gtk::gio::ListStore::new::<gtk::FileFilter>();
|
||||||
|
filters.append(&filter);
|
||||||
|
dialog.set_filters(Some(&filters));
|
||||||
|
|
||||||
|
if let Some(window) = btn.root().and_then(|r| r.downcast::<gtk::Window>().ok()) {
|
||||||
|
dialog.open(Some(&window), gtk::gio::Cancellable::NONE, move |result| {
|
||||||
|
if let Ok(file) = result
|
||||||
|
&& let Some(path) = file.path()
|
||||||
|
{
|
||||||
|
path_row.set_subtitle(&path.display().to_string());
|
||||||
|
jc.borrow_mut().watermark_image_path = Some(path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
scrolled.set_child(Some(&content));
|
||||||
|
|
||||||
|
let clamp = adw::Clamp::builder()
|
||||||
|
.maximum_size(600)
|
||||||
|
.child(&scrolled)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
adw::NavigationPage::builder()
|
||||||
|
.title("Watermark")
|
||||||
|
.tag("step-watermark")
|
||||||
|
.child(&clamp)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
@@ -14,9 +14,12 @@ impl WizardState {
|
|||||||
"Workflow".into(),
|
"Workflow".into(),
|
||||||
"Images".into(),
|
"Images".into(),
|
||||||
"Resize".into(),
|
"Resize".into(),
|
||||||
|
"Adjustments".into(),
|
||||||
"Convert".into(),
|
"Convert".into(),
|
||||||
"Compress".into(),
|
"Compress".into(),
|
||||||
"Metadata".into(),
|
"Metadata".into(),
|
||||||
|
"Watermark".into(),
|
||||||
|
"Rename".into(),
|
||||||
"Output".into(),
|
"Output".into(),
|
||||||
];
|
];
|
||||||
let total = names.len();
|
let total = names.len();
|
||||||
@@ -63,12 +66,15 @@ impl WizardState {
|
|||||||
|
|
||||||
pub fn build_wizard_pages(state: &AppState) -> Vec<adw::NavigationPage> {
|
pub fn build_wizard_pages(state: &AppState) -> Vec<adw::NavigationPage> {
|
||||||
vec![
|
vec![
|
||||||
steps::step_workflow::build_workflow_page(state),
|
steps::step_workflow::build_workflow_page(state), // 0: Workflow
|
||||||
steps::step_images::build_images_page(state),
|
steps::step_images::build_images_page(state), // 1: Images
|
||||||
steps::step_resize::build_resize_page(state),
|
steps::step_resize::build_resize_page(state), // 2: Resize
|
||||||
steps::step_convert::build_convert_page(state),
|
steps::step_adjustments::build_adjustments_page(state),// 3: Adjustments
|
||||||
steps::step_compress::build_compress_page(state),
|
steps::step_convert::build_convert_page(state), // 4: Convert
|
||||||
steps::step_metadata::build_metadata_page(state),
|
steps::step_compress::build_compress_page(state), // 5: Compress
|
||||||
steps::step_output::build_output_page(state),
|
steps::step_metadata::build_metadata_page(state), // 6: Metadata
|
||||||
|
steps::step_watermark::build_watermark_page(state), // 7: Watermark
|
||||||
|
steps::step_rename::build_rename_page(state), // 8: Rename
|
||||||
|
steps::step_output::build_output_page(state), // 9: Output
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user