Add crop/trim/canvas padding to adjustments, wire all sliders to config
This commit is contained in:
@@ -21,6 +21,15 @@ pub struct JobConfig {
|
|||||||
// Adjustments
|
// Adjustments
|
||||||
pub rotation: u32,
|
pub rotation: u32,
|
||||||
pub flip: u32,
|
pub flip: u32,
|
||||||
|
pub brightness: i32,
|
||||||
|
pub contrast: i32,
|
||||||
|
pub saturation: i32,
|
||||||
|
pub sharpen: bool,
|
||||||
|
pub grayscale: bool,
|
||||||
|
pub sepia: bool,
|
||||||
|
pub crop_aspect_ratio: u32,
|
||||||
|
pub trim_whitespace: bool,
|
||||||
|
pub canvas_padding: u32,
|
||||||
// Convert
|
// Convert
|
||||||
pub convert_enabled: bool,
|
pub convert_enabled: bool,
|
||||||
pub convert_format: Option<pixstrip_core::types::ImageFormat>,
|
pub convert_format: Option<pixstrip_core::types::ImageFormat>,
|
||||||
@@ -142,6 +151,15 @@ fn build_ui(app: &adw::Application) {
|
|||||||
allow_upscale: false,
|
allow_upscale: false,
|
||||||
rotation: 0,
|
rotation: 0,
|
||||||
flip: 0,
|
flip: 0,
|
||||||
|
brightness: 0,
|
||||||
|
contrast: 0,
|
||||||
|
saturation: 0,
|
||||||
|
sharpen: false,
|
||||||
|
grayscale: false,
|
||||||
|
sepia: false,
|
||||||
|
crop_aspect_ratio: 0,
|
||||||
|
trim_whitespace: false,
|
||||||
|
canvas_padding: 0,
|
||||||
convert_enabled: if remember { sess_state.convert_enabled.unwrap_or(false) } else { false },
|
convert_enabled: if remember { sess_state.convert_enabled.unwrap_or(false) } else { false },
|
||||||
convert_format: None,
|
convert_format: None,
|
||||||
compress_enabled: if remember { sess_state.compress_enabled.unwrap_or(true) } else { true },
|
compress_enabled: if remember { sess_state.compress_enabled.unwrap_or(true) } else { true },
|
||||||
@@ -1705,6 +1723,43 @@ fn update_output_summary(ui: &WizardUi) {
|
|||||||
};
|
};
|
||||||
ops.push(fl.to_string());
|
ops.push(fl.to_string());
|
||||||
}
|
}
|
||||||
|
if cfg.brightness != 0 {
|
||||||
|
ops.push(format!("Brightness {:+}", cfg.brightness));
|
||||||
|
}
|
||||||
|
if cfg.contrast != 0 {
|
||||||
|
ops.push(format!("Contrast {:+}", cfg.contrast));
|
||||||
|
}
|
||||||
|
if cfg.saturation != 0 {
|
||||||
|
ops.push(format!("Saturation {:+}", cfg.saturation));
|
||||||
|
}
|
||||||
|
if cfg.sharpen {
|
||||||
|
ops.push("Sharpen".to_string());
|
||||||
|
}
|
||||||
|
if cfg.grayscale {
|
||||||
|
ops.push("Grayscale".to_string());
|
||||||
|
}
|
||||||
|
if cfg.sepia {
|
||||||
|
ops.push("Sepia".to_string());
|
||||||
|
}
|
||||||
|
if cfg.crop_aspect_ratio > 0 {
|
||||||
|
let ratio = match cfg.crop_aspect_ratio {
|
||||||
|
1 => "1:1",
|
||||||
|
2 => "4:3",
|
||||||
|
3 => "3:2",
|
||||||
|
4 => "16:9",
|
||||||
|
5 => "9:16",
|
||||||
|
6 => "3:4",
|
||||||
|
7 => "2:3",
|
||||||
|
_ => "Custom",
|
||||||
|
};
|
||||||
|
ops.push(format!("Crop {}", ratio));
|
||||||
|
}
|
||||||
|
if cfg.trim_whitespace {
|
||||||
|
ops.push("Trim whitespace".to_string());
|
||||||
|
}
|
||||||
|
if cfg.canvas_padding > 0 {
|
||||||
|
ops.push(format!("Padding {}px", cfg.canvas_padding));
|
||||||
|
}
|
||||||
|
|
||||||
let summary_text = if ops.is_empty() {
|
let summary_text = if ops.is_empty() {
|
||||||
"No operations configured".to_string()
|
"No operations configured".to_string()
|
||||||
|
|||||||
@@ -50,12 +50,51 @@ pub fn build_adjustments_page(state: &AppState) -> adw::NavigationPage {
|
|||||||
rotate_group.add(&flip_row);
|
rotate_group.add(&flip_row);
|
||||||
content.append(&rotate_group);
|
content.append(&rotate_group);
|
||||||
|
|
||||||
// Advanced adjustments in an expander
|
// Crop and canvas group
|
||||||
let advanced_group = adw::PreferencesGroup::builder()
|
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(>k::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")
|
.title("Image Adjustments")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let advanced_expander = adw::ExpanderRow::builder()
|
let adjust_expander = adw::ExpanderRow::builder()
|
||||||
.title("Advanced Adjustments")
|
.title("Advanced Adjustments")
|
||||||
.subtitle("Brightness, contrast, saturation, effects")
|
.subtitle("Brightness, contrast, saturation, effects")
|
||||||
.show_enable_switch(false)
|
.show_enable_switch(false)
|
||||||
@@ -64,83 +103,71 @@ pub fn build_adjustments_page(state: &AppState) -> adw::NavigationPage {
|
|||||||
// Brightness slider (-100 to +100)
|
// Brightness slider (-100 to +100)
|
||||||
let brightness_row = adw::ActionRow::builder()
|
let brightness_row = adw::ActionRow::builder()
|
||||||
.title("Brightness")
|
.title("Brightness")
|
||||||
.subtitle("0")
|
.subtitle(format!("{}", cfg.brightness))
|
||||||
.build();
|
.build();
|
||||||
let brightness_scale = gtk::Scale::with_range(gtk::Orientation::Horizontal, -100.0, 100.0, 1.0);
|
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_hexpand(true);
|
||||||
brightness_scale.set_valign(gtk::Align::Center);
|
brightness_scale.set_valign(gtk::Align::Center);
|
||||||
brightness_scale.set_size_request(200, -1);
|
brightness_scale.set_size_request(200, -1);
|
||||||
brightness_scale.set_draw_value(false);
|
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);
|
brightness_row.add_suffix(&brightness_scale);
|
||||||
advanced_expander.add_row(&brightness_row);
|
adjust_expander.add_row(&brightness_row);
|
||||||
|
|
||||||
// Contrast slider (-100 to +100)
|
// Contrast slider (-100 to +100)
|
||||||
let contrast_row = adw::ActionRow::builder()
|
let contrast_row = adw::ActionRow::builder()
|
||||||
.title("Contrast")
|
.title("Contrast")
|
||||||
.subtitle("0")
|
.subtitle(format!("{}", cfg.contrast))
|
||||||
.build();
|
.build();
|
||||||
let contrast_scale = gtk::Scale::with_range(gtk::Orientation::Horizontal, -100.0, 100.0, 1.0);
|
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_hexpand(true);
|
||||||
contrast_scale.set_valign(gtk::Align::Center);
|
contrast_scale.set_valign(gtk::Align::Center);
|
||||||
contrast_scale.set_size_request(200, -1);
|
contrast_scale.set_size_request(200, -1);
|
||||||
contrast_scale.set_draw_value(false);
|
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);
|
contrast_row.add_suffix(&contrast_scale);
|
||||||
advanced_expander.add_row(&contrast_row);
|
adjust_expander.add_row(&contrast_row);
|
||||||
|
|
||||||
// Saturation slider (-100 to +100)
|
// Saturation slider (-100 to +100)
|
||||||
let saturation_row = adw::ActionRow::builder()
|
let saturation_row = adw::ActionRow::builder()
|
||||||
.title("Saturation")
|
.title("Saturation")
|
||||||
.subtitle("0")
|
.subtitle(format!("{}", cfg.saturation))
|
||||||
.build();
|
.build();
|
||||||
let saturation_scale = gtk::Scale::with_range(gtk::Orientation::Horizontal, -100.0, 100.0, 1.0);
|
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_hexpand(true);
|
||||||
saturation_scale.set_valign(gtk::Align::Center);
|
saturation_scale.set_valign(gtk::Align::Center);
|
||||||
saturation_scale.set_size_request(200, -1);
|
saturation_scale.set_size_request(200, -1);
|
||||||
saturation_scale.set_draw_value(false);
|
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);
|
saturation_row.add_suffix(&saturation_scale);
|
||||||
advanced_expander.add_row(&saturation_row);
|
adjust_expander.add_row(&saturation_row);
|
||||||
|
|
||||||
// Sharpen after resize
|
// Sharpen after resize
|
||||||
let sharpen_row = adw::SwitchRow::builder()
|
let sharpen_row = adw::SwitchRow::builder()
|
||||||
.title("Sharpen after resize")
|
.title("Sharpen after resize")
|
||||||
.subtitle("Apply subtle sharpening to resized images")
|
.subtitle("Apply subtle sharpening to resized images")
|
||||||
.active(false)
|
.active(cfg.sharpen)
|
||||||
.build();
|
.build();
|
||||||
advanced_expander.add_row(&sharpen_row);
|
adjust_expander.add_row(&sharpen_row);
|
||||||
|
|
||||||
// Grayscale
|
// Grayscale
|
||||||
let grayscale_row = adw::SwitchRow::builder()
|
let grayscale_row = adw::SwitchRow::builder()
|
||||||
.title("Grayscale")
|
.title("Grayscale")
|
||||||
.subtitle("Convert images to black and white")
|
.subtitle("Convert images to black and white")
|
||||||
.active(false)
|
.active(cfg.grayscale)
|
||||||
.build();
|
.build();
|
||||||
advanced_expander.add_row(&grayscale_row);
|
adjust_expander.add_row(&grayscale_row);
|
||||||
|
|
||||||
// Sepia
|
// Sepia
|
||||||
let sepia_row = adw::SwitchRow::builder()
|
let sepia_row = adw::SwitchRow::builder()
|
||||||
.title("Sepia")
|
.title("Sepia")
|
||||||
.subtitle("Apply a warm vintage tone")
|
.subtitle("Apply a warm vintage tone")
|
||||||
.active(false)
|
.active(cfg.sepia)
|
||||||
.build();
|
.build();
|
||||||
advanced_expander.add_row(&sepia_row);
|
adjust_expander.add_row(&sepia_row);
|
||||||
|
|
||||||
advanced_group.add(&advanced_expander);
|
adjust_group.add(&adjust_expander);
|
||||||
content.append(&advanced_group);
|
content.append(&adjust_group);
|
||||||
|
|
||||||
drop(cfg);
|
drop(cfg);
|
||||||
|
|
||||||
@@ -157,6 +184,69 @@ pub fn build_adjustments_page(state: &AppState) -> adw::NavigationPage {
|
|||||||
jc.borrow_mut().flip = row.selected();
|
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));
|
scrolled.set_child(Some(&content));
|
||||||
|
|
||||||
|
|||||||
@@ -304,6 +304,18 @@ fn build_loaded_state(state: &AppState) -> gtk::Box {
|
|||||||
.build();
|
.build();
|
||||||
add_button.add_css_class("flat");
|
add_button.add_css_class("flat");
|
||||||
|
|
||||||
|
let select_all_button = gtk::Button::builder()
|
||||||
|
.icon_name("edit-select-all-symbolic")
|
||||||
|
.tooltip_text("Select all images (Ctrl+A)")
|
||||||
|
.build();
|
||||||
|
select_all_button.add_css_class("flat");
|
||||||
|
|
||||||
|
let deselect_all_button = gtk::Button::builder()
|
||||||
|
.icon_name("edit-clear-symbolic")
|
||||||
|
.tooltip_text("Deselect all images (Ctrl+Shift+A)")
|
||||||
|
.build();
|
||||||
|
deselect_all_button.add_css_class("flat");
|
||||||
|
|
||||||
let clear_button = gtk::Button::builder()
|
let clear_button = gtk::Button::builder()
|
||||||
.icon_name("edit-clear-all-symbolic")
|
.icon_name("edit-clear-all-symbolic")
|
||||||
.tooltip_text("Remove all images")
|
.tooltip_text("Remove all images")
|
||||||
@@ -326,6 +338,8 @@ fn build_loaded_state(state: &AppState) -> gtk::Box {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toolbar.append(&count_label);
|
toolbar.append(&count_label);
|
||||||
|
toolbar.append(&select_all_button);
|
||||||
|
toolbar.append(&deselect_all_button);
|
||||||
toolbar.append(&add_button);
|
toolbar.append(&add_button);
|
||||||
toolbar.append(&clear_button);
|
toolbar.append(&clear_button);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user