Add live watermark preview with position and opacity updates
Shows a thumbnail of the first batch image with the watermark text overlaid. Preview updates in real-time as user changes text, position (9-point grid), and opacity.
This commit is contained in:
@@ -167,6 +167,88 @@ pub fn build_watermark_page(state: &AppState) -> adw::NavigationPage {
|
||||
|
||||
content.append(&position_group);
|
||||
|
||||
// Live preview section
|
||||
let preview_group = adw::PreferencesGroup::builder()
|
||||
.title("Preview")
|
||||
.description("Shows how the watermark will appear on your image")
|
||||
.build();
|
||||
|
||||
// Overlay container for image + watermark text
|
||||
let preview_overlay = gtk::Overlay::builder()
|
||||
.halign(gtk::Align::Center)
|
||||
.build();
|
||||
|
||||
let preview_picture = gtk::Picture::builder()
|
||||
.content_fit(gtk::ContentFit::Contain)
|
||||
.width_request(300)
|
||||
.height_request(200)
|
||||
.build();
|
||||
preview_picture.add_css_class("card");
|
||||
preview_overlay.set_child(Some(&preview_picture));
|
||||
|
||||
// Watermark text label overlay
|
||||
let watermark_label = gtk::Label::builder()
|
||||
.label(&cfg.watermark_text)
|
||||
.css_classes(["heading"])
|
||||
.opacity(cfg.watermark_opacity as f64)
|
||||
.build();
|
||||
preview_overlay.add_overlay(&watermark_label);
|
||||
|
||||
// Position the watermark label according to grid position
|
||||
fn set_watermark_alignment(label: >k::Label, position: u32) {
|
||||
let (h, v) = match position {
|
||||
0 => (gtk::Align::Start, gtk::Align::Start), // Top Left
|
||||
1 => (gtk::Align::Center, gtk::Align::Start), // Top Center
|
||||
2 => (gtk::Align::End, gtk::Align::Start), // Top Right
|
||||
3 => (gtk::Align::Start, gtk::Align::Center), // Middle Left
|
||||
4 => (gtk::Align::Center, gtk::Align::Center), // Center
|
||||
5 => (gtk::Align::End, gtk::Align::Center), // Middle Right
|
||||
6 => (gtk::Align::Start, gtk::Align::End), // Bottom Left
|
||||
7 => (gtk::Align::Center, gtk::Align::End), // Bottom Center
|
||||
_ => (gtk::Align::End, gtk::Align::End), // Bottom Right
|
||||
};
|
||||
label.set_halign(h);
|
||||
label.set_valign(v);
|
||||
label.set_margin_start(8);
|
||||
label.set_margin_end(8);
|
||||
label.set_margin_top(8);
|
||||
label.set_margin_bottom(8);
|
||||
}
|
||||
set_watermark_alignment(&watermark_label, cfg.watermark_position);
|
||||
|
||||
// Load first image from batch as preview background
|
||||
{
|
||||
let files = state.loaded_files.borrow();
|
||||
if let Some(first) = files.first() {
|
||||
preview_picture.set_filename(Some(first));
|
||||
}
|
||||
}
|
||||
|
||||
// "No preview" placeholder
|
||||
let no_preview_label = gtk::Label::builder()
|
||||
.label("Add images to see a preview")
|
||||
.css_classes(["dim-label"])
|
||||
.halign(gtk::Align::Center)
|
||||
.valign(gtk::Align::Center)
|
||||
.build();
|
||||
{
|
||||
let has_files = !state.loaded_files.borrow().is_empty();
|
||||
no_preview_label.set_visible(!has_files);
|
||||
preview_picture.set_visible(has_files);
|
||||
}
|
||||
|
||||
let preview_stack = gtk::Box::builder()
|
||||
.orientation(gtk::Orientation::Vertical)
|
||||
.spacing(4)
|
||||
.margin_top(8)
|
||||
.margin_bottom(8)
|
||||
.build();
|
||||
preview_stack.append(&preview_overlay);
|
||||
preview_stack.append(&no_preview_label);
|
||||
|
||||
preview_group.add(&preview_stack);
|
||||
content.append(&preview_group);
|
||||
|
||||
// Advanced options
|
||||
let advanced_group = adw::PreferencesGroup::builder()
|
||||
.title("Advanced")
|
||||
@@ -242,8 +324,11 @@ pub fn build_watermark_page(state: &AppState) -> adw::NavigationPage {
|
||||
}
|
||||
{
|
||||
let jc = state.job_config.clone();
|
||||
let wl = watermark_label.clone();
|
||||
text_row.connect_changed(move |row| {
|
||||
jc.borrow_mut().watermark_text = row.text().to_string();
|
||||
let text = row.text().to_string();
|
||||
wl.set_label(&text);
|
||||
jc.borrow_mut().watermark_text = text;
|
||||
});
|
||||
}
|
||||
{
|
||||
@@ -258,10 +343,12 @@ pub fn build_watermark_page(state: &AppState) -> adw::NavigationPage {
|
||||
let label = position_label.clone();
|
||||
let names = position_names;
|
||||
let all_buttons = buttons.clone();
|
||||
let wl = watermark_label.clone();
|
||||
btn.connect_toggled(move |b| {
|
||||
if b.is_active() {
|
||||
jc.borrow_mut().watermark_position = i as u32;
|
||||
label.set_label(names[i]);
|
||||
set_watermark_alignment(&wl, i as u32);
|
||||
// Update icons
|
||||
for (j, other) in all_buttons.iter().enumerate() {
|
||||
let icon_name = if j == i {
|
||||
@@ -276,8 +363,11 @@ pub fn build_watermark_page(state: &AppState) -> adw::NavigationPage {
|
||||
}
|
||||
{
|
||||
let jc = state.job_config.clone();
|
||||
let wl = watermark_label.clone();
|
||||
opacity_row.connect_value_notify(move |row| {
|
||||
jc.borrow_mut().watermark_opacity = row.value() as f32;
|
||||
let val = row.value() as f32;
|
||||
wl.set_opacity(val as f64);
|
||||
jc.borrow_mut().watermark_opacity = val;
|
||||
});
|
||||
}
|
||||
// Wire image chooser button
|
||||
|
||||
Reference in New Issue
Block a user