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:
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()
|
||||
}
|
||||
Reference in New Issue
Block a user