Improve UX, add popover tour, metadata, and hicolor icons

- Redesign tutorial tour from modal dialogs to popovers pointing at actual UI elements
- Add beginner-friendly improvements: help buttons, tooltips, welcome wizard enhancements
- Add AppStream metainfo with screenshots, branding, categories, keywords, provides
- Update desktop file with GTK category and SingleMainWindow
- Add hicolor icon theme with all sizes (16-512px)
- Fix debounce SourceId panic in rename step
- Various step UI improvements and bug fixes
This commit is contained in:
2026-03-08 14:18:15 +02:00
parent 8d754017fa
commit f3668c45c3
26 changed files with 2292 additions and 473 deletions

View File

@@ -23,6 +23,7 @@ pub fn build_metadata_page(state: &AppState) -> adw::NavigationPage {
.title("Enable Metadata Handling")
.subtitle("Control what image metadata to keep or remove")
.active(cfg.metadata_enabled)
.tooltip_text("Toggle metadata handling on or off")
.build();
let enable_group = adw::PreferencesGroup::new();
@@ -39,7 +40,9 @@ pub fn build_metadata_page(state: &AppState) -> adw::NavigationPage {
.subtitle("Remove all metadata - smallest files, maximum privacy")
.activatable(true)
.build();
strip_all_row.add_prefix(&gtk::Image::from_icon_name("user-trash-symbolic"));
let strip_all_icon = gtk::Image::from_icon_name("user-trash-symbolic");
strip_all_icon.set_accessible_role(gtk::AccessibleRole::Presentation);
strip_all_row.add_prefix(&strip_all_icon);
let strip_all_check = gtk::CheckButton::new();
strip_all_check.set_active(cfg.metadata_mode == MetadataMode::StripAll);
strip_all_row.add_suffix(&strip_all_check);
@@ -50,7 +53,9 @@ pub fn build_metadata_page(state: &AppState) -> adw::NavigationPage {
.subtitle("Strip GPS and camera serial, keep copyright")
.activatable(true)
.build();
privacy_row.add_prefix(&gtk::Image::from_icon_name("security-medium-symbolic"));
let privacy_icon = gtk::Image::from_icon_name("security-medium-symbolic");
privacy_icon.set_accessible_role(gtk::AccessibleRole::Presentation);
privacy_row.add_prefix(&privacy_icon);
let privacy_check = gtk::CheckButton::new();
privacy_check.set_group(Some(&strip_all_check));
privacy_check.set_active(cfg.metadata_mode == MetadataMode::Privacy);
@@ -62,7 +67,9 @@ pub fn build_metadata_page(state: &AppState) -> adw::NavigationPage {
.subtitle("Preserve all original metadata")
.activatable(true)
.build();
keep_all_row.add_prefix(&gtk::Image::from_icon_name("emblem-ok-symbolic"));
let keep_all_icon = gtk::Image::from_icon_name("emblem-ok-symbolic");
keep_all_icon.set_accessible_role(gtk::AccessibleRole::Presentation);
keep_all_row.add_prefix(&keep_all_icon);
let keep_all_check = gtk::CheckButton::new();
keep_all_check.set_group(Some(&strip_all_check));
keep_all_check.set_active(cfg.metadata_mode == MetadataMode::KeepAll);
@@ -74,7 +81,9 @@ pub fn build_metadata_page(state: &AppState) -> adw::NavigationPage {
.subtitle("Keep copyright and camera model, strip GPS and software")
.activatable(true)
.build();
photographer_row.add_prefix(&gtk::Image::from_icon_name("camera-photo-symbolic"));
let photographer_icon = gtk::Image::from_icon_name("camera-photo-symbolic");
photographer_icon.set_accessible_role(gtk::AccessibleRole::Presentation);
photographer_row.add_prefix(&photographer_icon);
let photographer_check = gtk::CheckButton::new();
photographer_check.set_group(Some(&strip_all_check));
photographer_row.add_suffix(&photographer_check);
@@ -85,7 +94,9 @@ pub fn build_metadata_page(state: &AppState) -> adw::NavigationPage {
.subtitle("Choose exactly which metadata categories to strip")
.activatable(true)
.build();
custom_row.add_prefix(&gtk::Image::from_icon_name("emblem-system-symbolic"));
let custom_icon = gtk::Image::from_icon_name("emblem-system-symbolic");
custom_icon.set_accessible_role(gtk::AccessibleRole::Presentation);
custom_row.add_prefix(&custom_icon);
let custom_check = gtk::CheckButton::new();
custom_check.set_group(Some(&strip_all_check));
custom_check.set_active(cfg.metadata_mode == MetadataMode::Custom);
@@ -109,30 +120,35 @@ pub fn build_metadata_page(state: &AppState) -> adw::NavigationPage {
.title("GPS / Location")
.subtitle("GPS coordinates, location name, altitude")
.active(cfg.strip_gps)
.tooltip_text("Strip GPS coordinates, location name, and altitude")
.build();
let camera_row = adw::SwitchRow::builder()
.title("Camera Info")
.subtitle("Camera model, serial number, lens data")
.active(cfg.strip_camera)
.tooltip_text("Strip camera model, serial number, and lens data")
.build();
let software_row = adw::SwitchRow::builder()
.title("Software")
.subtitle("Editing software, processing history")
.active(cfg.strip_software)
.tooltip_text("Strip editing software and processing history")
.build();
let timestamps_row = adw::SwitchRow::builder()
.title("Timestamps")
.subtitle("Date taken, date modified, date digitized")
.active(cfg.strip_timestamps)
.tooltip_text("Strip date taken, date modified, date digitized")
.build();
let copyright_row = adw::SwitchRow::builder()
.title("Copyright / Author")
.subtitle("Copyright notice, artist name, credits")
.active(cfg.strip_copyright)
.tooltip_text("Strip copyright notice, artist name, and credits")
.build();
custom_group.add(&gps_row);
@@ -260,9 +276,21 @@ pub fn build_metadata_page(state: &AppState) -> adw::NavigationPage {
scrolled.set_child(Some(&content));
adw::NavigationPage::builder()
let page = adw::NavigationPage::builder()
.title("Metadata")
.tag("step-metadata")
.child(&scrolled)
.build()
.build();
// Sync enable toggle when navigating to this page
{
let jc = state.job_config.clone();
let er = enable_row.clone();
page.connect_map(move |_| {
let enabled = jc.borrow().metadata_enabled;
er.set_active(enabled);
});
}
page
}