Enhance rename preview to show 5 files, add watermark advanced expander

Rename step now shows preview for first 5 loaded files (or fallback
examples) with incrementing counters instead of a single line.

Watermark step gains an advanced expander with rotation, tiling,
margin, and scale options alongside the existing opacity control.
This commit is contained in:
2026-03-06 12:54:35 +02:00
parent 81f92e5b35
commit 29770be8b5
2 changed files with 125 additions and 40 deletions

View File

@@ -63,22 +63,31 @@ pub fn build_rename_page(state: &AppState) -> adw::NavigationPage {
simple_group.add(&counter_padding_row);
content.append(&simple_group);
// Live preview
// Live preview showing first 5 filenames
let preview_group = adw::PreferencesGroup::builder()
.title("Preview")
.description("Example of how files will be renamed")
.description("How your 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)
let preview_box = gtk::Box::builder()
.orientation(gtk::Orientation::Vertical)
.spacing(2)
.margin_top(8)
.margin_bottom(8)
.margin_start(12)
.margin_end(12)
.build();
preview_group.add(&preview_label);
// Create 5 preview labels
for _ in 0..5 {
let label = gtk::Label::builder()
.css_classes(["monospace", "dim-label", "caption"])
.halign(gtk::Align::Start)
.build();
preview_box.append(&label);
}
preview_group.add(&preview_box);
content.append(&preview_group);
// Advanced: template engine
@@ -126,37 +135,65 @@ pub fn build_rename_page(state: &AppState) -> adw::NavigationPage {
});
}
// Update preview helper
// Update preview helper - shows first 5 filenames from the batch
let update_preview = {
let files = state.loaded_files.clone();
let jc = state.job_config.clone();
let preview = preview_label.clone();
let preview = preview_box.clone();
move || {
let cfg = jc.borrow();
let loaded = files.borrow();
let sample_name = loaded
.first()
.and_then(|p| p.file_stem())
// Sample filenames: use actual loaded files, or fallback examples
let samples: Vec<(&str, &str)> = if loaded.is_empty() {
vec![
("photo", "jpg"),
("sunset", "png"),
("beach", "jpg"),
("portrait", "webp"),
("landscape", "jpg"),
]
} else {
loaded
.iter()
.take(5)
.map(|p| {
let name = p
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("photo");
let sample_ext = loaded
.first()
.and_then(|p| p.extension())
let ext = p
.extension()
.and_then(|e| e.to_str())
.unwrap_or("jpg");
(name, ext)
})
.collect()
};
let mut child = preview.first_child();
for (i, (name, ext)) in samples.iter().enumerate() {
let Some(widget) = child.clone() else { break };
if let Some(label) = widget.downcast_ref::<gtk::Label>() {
let counter = cfg.rename_counter_start + i as u32;
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))
let result = cfg
.rename_template
.replace("{name}", name)
.replace("{ext}", ext)
.replace(
"{counter}",
&format!(
"{:0>width$}",
counter,
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));
label.set_label(&format!("{}.{} -> {}", name, ext, result));
} else {
// Simple mode preview
let rename_cfg = pixstrip_core::operations::RenameConfig {
prefix: cfg.rename_prefix.clone(),
suffix: cfg.rename_suffix.clone(),
@@ -164,8 +201,19 @@ pub fn build_rename_page(state: &AppState) -> adw::NavigationPage {
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));
let result =
rename_cfg.apply_simple(name, ext, (i + 1) as u32);
label.set_label(&format!("{}.{} -> {}", name, ext, result));
}
label.set_visible(true);
}
child = widget.next_sibling();
}
// Hide unused labels
while let Some(widget) = child.clone() {
widget.set_visible(false);
child = widget.next_sibling();
}
}
};

View File

@@ -118,7 +118,13 @@ pub fn build_watermark_page(state: &AppState) -> adw::NavigationPage {
// Advanced options
let advanced_group = adw::PreferencesGroup::builder()
.title("Advanced")
.build();
let advanced_expander = adw::ExpanderRow::builder()
.title("Advanced Options")
.subtitle("Opacity, rotation, tiling, margin")
.show_enable_switch(false)
.build();
let opacity_row = adw::SpinRow::builder()
@@ -128,7 +134,38 @@ pub fn build_watermark_page(state: &AppState) -> adw::NavigationPage {
.digits(2)
.build();
advanced_group.add(&opacity_row);
let rotation_row = adw::ComboRow::builder()
.title("Rotation")
.subtitle("Rotate the watermark")
.build();
let rotation_model = gtk::StringList::new(&["None", "45 degrees", "-45 degrees", "90 degrees"]);
rotation_row.set_model(Some(&rotation_model));
let tiled_row = adw::SwitchRow::builder()
.title("Tiled / Repeated")
.subtitle("Repeat watermark across the entire image")
.active(false)
.build();
let margin_row = adw::SpinRow::builder()
.title("Margin from Edges")
.subtitle("Padding in pixels from image edges")
.adjustment(&gtk::Adjustment::new(10.0, 0.0, 200.0, 1.0, 10.0, 0.0))
.build();
let scale_row = adw::SpinRow::builder()
.title("Scale (% of image)")
.subtitle("Watermark size relative to image")
.adjustment(&gtk::Adjustment::new(20.0, 1.0, 100.0, 1.0, 5.0, 0.0))
.build();
advanced_expander.add_row(&opacity_row);
advanced_expander.add_row(&rotation_row);
advanced_expander.add_row(&tiled_row);
advanced_expander.add_row(&margin_row);
advanced_expander.add_row(&scale_row);
advanced_group.add(&advanced_expander);
content.append(&advanced_group);
drop(cfg);