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);
|
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
|
// Advanced options
|
||||||
let advanced_group = adw::PreferencesGroup::builder()
|
let advanced_group = adw::PreferencesGroup::builder()
|
||||||
.title("Advanced")
|
.title("Advanced")
|
||||||
@@ -242,8 +324,11 @@ pub fn build_watermark_page(state: &AppState) -> adw::NavigationPage {
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
let jc = state.job_config.clone();
|
let jc = state.job_config.clone();
|
||||||
|
let wl = watermark_label.clone();
|
||||||
text_row.connect_changed(move |row| {
|
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 label = position_label.clone();
|
||||||
let names = position_names;
|
let names = position_names;
|
||||||
let all_buttons = buttons.clone();
|
let all_buttons = buttons.clone();
|
||||||
|
let wl = watermark_label.clone();
|
||||||
btn.connect_toggled(move |b| {
|
btn.connect_toggled(move |b| {
|
||||||
if b.is_active() {
|
if b.is_active() {
|
||||||
jc.borrow_mut().watermark_position = i as u32;
|
jc.borrow_mut().watermark_position = i as u32;
|
||||||
label.set_label(names[i]);
|
label.set_label(names[i]);
|
||||||
|
set_watermark_alignment(&wl, i as u32);
|
||||||
// Update icons
|
// Update icons
|
||||||
for (j, other) in all_buttons.iter().enumerate() {
|
for (j, other) in all_buttons.iter().enumerate() {
|
||||||
let icon_name = if j == i {
|
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 jc = state.job_config.clone();
|
||||||
|
let wl = watermark_label.clone();
|
||||||
opacity_row.connect_value_notify(move |row| {
|
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
|
// Wire image chooser button
|
||||||
|
|||||||
Reference in New Issue
Block a user