Add crop/trim/canvas padding to adjustments, wire all sliders to config

Add crop to aspect ratio (8 ratios), trim whitespace, and canvas padding
controls to the adjustments step per design doc. Wire brightness,
contrast, saturation, sharpen, grayscale, and sepia to JobConfig. Add
Select All / Deselect All toolbar buttons to images step. Include new
adjustment operations in output step summary.
This commit is contained in:
2026-03-06 13:09:45 +02:00
parent 7c260c3534
commit 3ae84297d5
3 changed files with 191 additions and 32 deletions

View File

@@ -50,12 +50,51 @@ pub fn build_adjustments_page(state: &AppState) -> adw::NavigationPage {
rotate_group.add(&flip_row);
content.append(&rotate_group);
// Advanced adjustments in an expander
let advanced_group = adw::PreferencesGroup::builder()
// Crop and canvas group
let crop_group = adw::PreferencesGroup::builder()
.title("Crop and Canvas")
.build();
let crop_row = adw::ComboRow::builder()
.title("Crop to Aspect Ratio")
.subtitle("Crop images to a specific aspect ratio from center")
.build();
let crop_model = gtk::StringList::new(&[
"None",
"1:1 (Square)",
"4:3",
"3:2",
"16:9",
"9:16 (Portrait)",
"3:4 (Portrait)",
"2:3 (Portrait)",
]);
crop_row.set_model(Some(&crop_model));
crop_row.set_selected(cfg.crop_aspect_ratio);
let trim_row = adw::SwitchRow::builder()
.title("Trim Whitespace")
.subtitle("Remove uniform borders around the image")
.active(cfg.trim_whitespace)
.build();
let padding_row = adw::SpinRow::builder()
.title("Canvas Padding")
.subtitle("Add uniform padding around the image (pixels)")
.adjustment(&gtk::Adjustment::new(cfg.canvas_padding as f64, 0.0, 500.0, 1.0, 10.0, 0.0))
.build();
crop_group.add(&crop_row);
crop_group.add(&trim_row);
crop_group.add(&padding_row);
content.append(&crop_group);
// Image adjustments
let adjust_group = adw::PreferencesGroup::builder()
.title("Image Adjustments")
.build();
let advanced_expander = adw::ExpanderRow::builder()
let adjust_expander = adw::ExpanderRow::builder()
.title("Advanced Adjustments")
.subtitle("Brightness, contrast, saturation, effects")
.show_enable_switch(false)
@@ -64,83 +103,71 @@ pub fn build_adjustments_page(state: &AppState) -> adw::NavigationPage {
// Brightness slider (-100 to +100)
let brightness_row = adw::ActionRow::builder()
.title("Brightness")
.subtitle("0")
.subtitle(format!("{}", cfg.brightness))
.build();
let brightness_scale = gtk::Scale::with_range(gtk::Orientation::Horizontal, -100.0, 100.0, 1.0);
brightness_scale.set_value(0.0);
brightness_scale.set_value(cfg.brightness as f64);
brightness_scale.set_hexpand(true);
brightness_scale.set_valign(gtk::Align::Center);
brightness_scale.set_size_request(200, -1);
brightness_scale.set_draw_value(false);
let br_label = brightness_row.clone();
brightness_scale.connect_value_changed(move |scale| {
br_label.set_subtitle(&format!("{:.0}", scale.value()));
});
brightness_row.add_suffix(&brightness_scale);
advanced_expander.add_row(&brightness_row);
adjust_expander.add_row(&brightness_row);
// Contrast slider (-100 to +100)
let contrast_row = adw::ActionRow::builder()
.title("Contrast")
.subtitle("0")
.subtitle(format!("{}", cfg.contrast))
.build();
let contrast_scale = gtk::Scale::with_range(gtk::Orientation::Horizontal, -100.0, 100.0, 1.0);
contrast_scale.set_value(0.0);
contrast_scale.set_value(cfg.contrast as f64);
contrast_scale.set_hexpand(true);
contrast_scale.set_valign(gtk::Align::Center);
contrast_scale.set_size_request(200, -1);
contrast_scale.set_draw_value(false);
let ct_label = contrast_row.clone();
contrast_scale.connect_value_changed(move |scale| {
ct_label.set_subtitle(&format!("{:.0}", scale.value()));
});
contrast_row.add_suffix(&contrast_scale);
advanced_expander.add_row(&contrast_row);
adjust_expander.add_row(&contrast_row);
// Saturation slider (-100 to +100)
let saturation_row = adw::ActionRow::builder()
.title("Saturation")
.subtitle("0")
.subtitle(format!("{}", cfg.saturation))
.build();
let saturation_scale = gtk::Scale::with_range(gtk::Orientation::Horizontal, -100.0, 100.0, 1.0);
saturation_scale.set_value(0.0);
saturation_scale.set_value(cfg.saturation as f64);
saturation_scale.set_hexpand(true);
saturation_scale.set_valign(gtk::Align::Center);
saturation_scale.set_size_request(200, -1);
saturation_scale.set_draw_value(false);
let sat_label = saturation_row.clone();
saturation_scale.connect_value_changed(move |scale| {
sat_label.set_subtitle(&format!("{:.0}", scale.value()));
});
saturation_row.add_suffix(&saturation_scale);
advanced_expander.add_row(&saturation_row);
adjust_expander.add_row(&saturation_row);
// Sharpen after resize
let sharpen_row = adw::SwitchRow::builder()
.title("Sharpen after resize")
.subtitle("Apply subtle sharpening to resized images")
.active(false)
.active(cfg.sharpen)
.build();
advanced_expander.add_row(&sharpen_row);
adjust_expander.add_row(&sharpen_row);
// Grayscale
let grayscale_row = adw::SwitchRow::builder()
.title("Grayscale")
.subtitle("Convert images to black and white")
.active(false)
.active(cfg.grayscale)
.build();
advanced_expander.add_row(&grayscale_row);
adjust_expander.add_row(&grayscale_row);
// Sepia
let sepia_row = adw::SwitchRow::builder()
.title("Sepia")
.subtitle("Apply a warm vintage tone")
.active(false)
.active(cfg.sepia)
.build();
advanced_expander.add_row(&sepia_row);
adjust_expander.add_row(&sepia_row);
advanced_group.add(&advanced_expander);
content.append(&advanced_group);
adjust_group.add(&adjust_expander);
content.append(&adjust_group);
drop(cfg);
@@ -157,6 +184,69 @@ pub fn build_adjustments_page(state: &AppState) -> adw::NavigationPage {
jc.borrow_mut().flip = row.selected();
});
}
{
let jc = state.job_config.clone();
crop_row.connect_selected_notify(move |row| {
jc.borrow_mut().crop_aspect_ratio = row.selected();
});
}
{
let jc = state.job_config.clone();
trim_row.connect_active_notify(move |row| {
jc.borrow_mut().trim_whitespace = row.is_active();
});
}
{
let jc = state.job_config.clone();
padding_row.connect_value_notify(move |row| {
jc.borrow_mut().canvas_padding = row.value() as u32;
});
}
{
let jc = state.job_config.clone();
let label = brightness_row;
brightness_scale.connect_value_changed(move |scale| {
let val = scale.value().round() as i32;
jc.borrow_mut().brightness = val;
label.set_subtitle(&format!("{}", val));
});
}
{
let jc = state.job_config.clone();
let label = contrast_row;
contrast_scale.connect_value_changed(move |scale| {
let val = scale.value().round() as i32;
jc.borrow_mut().contrast = val;
label.set_subtitle(&format!("{}", val));
});
}
{
let jc = state.job_config.clone();
let label = saturation_row;
saturation_scale.connect_value_changed(move |scale| {
let val = scale.value().round() as i32;
jc.borrow_mut().saturation = val;
label.set_subtitle(&format!("{}", val));
});
}
{
let jc = state.job_config.clone();
sharpen_row.connect_active_notify(move |row| {
jc.borrow_mut().sharpen = row.is_active();
});
}
{
let jc = state.job_config.clone();
grayscale_row.connect_active_notify(move |row| {
jc.borrow_mut().grayscale = row.is_active();
});
}
{
let jc = state.job_config.clone();
sepia_row.connect_active_notify(move |row| {
jc.borrow_mut().sepia = row.is_active();
});
}
scrolled.set_child(Some(&content));