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:
@@ -63,22 +63,31 @@ pub fn build_rename_page(state: &AppState) -> adw::NavigationPage {
|
|||||||
simple_group.add(&counter_padding_row);
|
simple_group.add(&counter_padding_row);
|
||||||
content.append(&simple_group);
|
content.append(&simple_group);
|
||||||
|
|
||||||
// Live preview
|
// Live preview showing first 5 filenames
|
||||||
let preview_group = adw::PreferencesGroup::builder()
|
let preview_group = adw::PreferencesGroup::builder()
|
||||||
.title("Preview")
|
.title("Preview")
|
||||||
.description("Example of how files will be renamed")
|
.description("How your files will be renamed")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let preview_label = gtk::Label::builder()
|
let preview_box = gtk::Box::builder()
|
||||||
.label("photo.jpg -> photo_001.jpg")
|
.orientation(gtk::Orientation::Vertical)
|
||||||
.css_classes(["monospace", "dim-label"])
|
.spacing(2)
|
||||||
.halign(gtk::Align::Start)
|
|
||||||
.margin_top(8)
|
.margin_top(8)
|
||||||
.margin_bottom(8)
|
.margin_bottom(8)
|
||||||
.margin_start(12)
|
.margin_start(12)
|
||||||
|
.margin_end(12)
|
||||||
.build();
|
.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);
|
content.append(&preview_group);
|
||||||
|
|
||||||
// Advanced: template engine
|
// Advanced: template engine
|
||||||
@@ -126,46 +135,85 @@ 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 update_preview = {
|
||||||
let files = state.loaded_files.clone();
|
let files = state.loaded_files.clone();
|
||||||
let jc = state.job_config.clone();
|
let jc = state.job_config.clone();
|
||||||
let preview = preview_label.clone();
|
let preview = preview_box.clone();
|
||||||
move || {
|
move || {
|
||||||
let cfg = jc.borrow();
|
let cfg = jc.borrow();
|
||||||
let loaded = files.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() {
|
// Sample filenames: use actual loaded files, or fallback examples
|
||||||
// Template mode preview
|
let samples: Vec<(&str, &str)> = if loaded.is_empty() {
|
||||||
let result = cfg.rename_template
|
vec![
|
||||||
.replace("{name}", sample_name)
|
("photo", "jpg"),
|
||||||
.replace("{ext}", sample_ext)
|
("sunset", "png"),
|
||||||
.replace("{counter}", &format!("{:0>width$}", cfg.rename_counter_start, width = cfg.rename_counter_padding as usize))
|
("beach", "jpg"),
|
||||||
.replace("{date}", "2026-03-06")
|
("portrait", "webp"),
|
||||||
.replace("{width}", "1200")
|
("landscape", "jpg"),
|
||||||
.replace("{height}", "800");
|
]
|
||||||
preview.set_label(&format!("{}.{} -> {}", sample_name, sample_ext, result));
|
|
||||||
} else {
|
} else {
|
||||||
// Simple mode preview
|
loaded
|
||||||
let rename_cfg = pixstrip_core::operations::RenameConfig {
|
.iter()
|
||||||
prefix: cfg.rename_prefix.clone(),
|
.take(5)
|
||||||
suffix: cfg.rename_suffix.clone(),
|
.map(|p| {
|
||||||
counter_start: cfg.rename_counter_start,
|
let name = p
|
||||||
counter_padding: cfg.rename_counter_padding,
|
.file_stem()
|
||||||
template: None,
|
.and_then(|s| s.to_str())
|
||||||
};
|
.unwrap_or("photo");
|
||||||
let result = rename_cfg.apply_simple(sample_name, sample_ext, 1);
|
let ext = p
|
||||||
preview.set_label(&format!("{}.{} -> {}", sample_name, sample_ext, result));
|
.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() {
|
||||||
|
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");
|
||||||
|
label.set_label(&format!("{}.{} -> {}", name, ext, result));
|
||||||
|
} else {
|
||||||
|
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(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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -118,7 +118,13 @@ pub fn build_watermark_page(state: &AppState) -> adw::NavigationPage {
|
|||||||
|
|
||||||
// Advanced options
|
// Advanced options
|
||||||
let advanced_group = adw::PreferencesGroup::builder()
|
let advanced_group = adw::PreferencesGroup::builder()
|
||||||
|
.title("Advanced")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let advanced_expander = adw::ExpanderRow::builder()
|
||||||
.title("Advanced Options")
|
.title("Advanced Options")
|
||||||
|
.subtitle("Opacity, rotation, tiling, margin")
|
||||||
|
.show_enable_switch(false)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let opacity_row = adw::SpinRow::builder()
|
let opacity_row = adw::SpinRow::builder()
|
||||||
@@ -128,7 +134,38 @@ pub fn build_watermark_page(state: &AppState) -> adw::NavigationPage {
|
|||||||
.digits(2)
|
.digits(2)
|
||||||
.build();
|
.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(>k::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(>k::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);
|
content.append(&advanced_group);
|
||||||
|
|
||||||
drop(cfg);
|
drop(cfg);
|
||||||
|
|||||||
Reference in New Issue
Block a user