Add What's New dialog, accessibility labels, focus management

This commit is contained in:
2026-03-06 13:13:39 +02:00
parent 36e291e486
commit dbc215f0ad
2 changed files with 106 additions and 0 deletions

View File

@@ -349,6 +349,7 @@ fn build_menu() -> gtk::gio::Menu {
menu.append(Some("Settings"), Some("win.show-settings"));
menu.append(Some("History"), Some("win.show-history"));
menu.append(Some("Keyboard Shortcuts"), Some("win.show-shortcuts"));
menu.append(Some("What's New"), Some("win.show-whats-new"));
menu
}
@@ -500,6 +501,16 @@ fn setup_window_actions(window: &adw::ApplicationWindow, ui: &WizardUi) {
action_group.add_action(&action);
}
// What's New action
{
let window = window.clone();
let action = gtk::gio::SimpleAction::new("show-whats-new", None);
action.connect_activate(move |_, _| {
show_whats_new_dialog(&window);
});
action_group.add_action(&action);
}
// Connect button clicks
ui.back_button.connect_clicked({
let action_group = action_group.clone();
@@ -541,6 +552,13 @@ fn navigate_to_step(ui: &WizardUi, target: usize) {
if target < ui.pages.len() {
ui.nav_view.replace(&ui.pages[..=target]);
ui.title.set_subtitle(&ui.pages[target].title());
// Focus management - move focus to first interactive element on new step
// Use idle callback to let the page fully render first
let page = ui.pages[target].clone();
glib::idle_add_local_once(move || {
page.child_focus(gtk::DirectionType::TabForward);
});
}
// Update dynamic content on certain steps
@@ -857,6 +875,78 @@ fn show_history_dialog(window: &adw::ApplicationWindow) {
dialog.present(Some(window));
}
fn show_whats_new_dialog(window: &adw::ApplicationWindow) {
let dialog = adw::Dialog::builder()
.title("What's New")
.content_width(450)
.content_height(350)
.build();
let toolbar_view = adw::ToolbarView::new();
let header = adw::HeaderBar::new();
toolbar_view.add_top_bar(&header);
let scrolled = gtk::ScrolledWindow::builder()
.hscrollbar_policy(gtk::PolicyType::Never)
.vexpand(true)
.build();
let content = gtk::Box::builder()
.orientation(gtk::Orientation::Vertical)
.spacing(12)
.margin_top(12)
.margin_bottom(12)
.margin_start(24)
.margin_end(24)
.build();
let version_label = gtk::Label::builder()
.label("Pixstrip 0.1.0")
.css_classes(["title-1"])
.halign(gtk::Align::Start)
.build();
let subtitle_label = gtk::Label::builder()
.label("First release")
.css_classes(["dim-label"])
.halign(gtk::Align::Start)
.build();
content.append(&version_label);
content.append(&subtitle_label);
let changes_group = adw::PreferencesGroup::builder()
.title("Changes in this version")
.build();
let features = [
("Wizard workflow", "Step-by-step batch image processing"),
("Resize", "Width/height, social media presets, fit-in-box"),
("Convert", "JPEG, PNG, WebP, AVIF, GIF, TIFF conversion"),
("Compress", "Quality presets with per-format controls"),
("Metadata", "Strip all, privacy mode, photographer mode"),
("Watermark", "Text and image watermarks with positioning"),
("Rename", "Prefix, suffix, counter, template engine"),
("Adjustments", "Brightness, contrast, crop, trim, effects"),
];
for (title, subtitle) in &features {
let row = adw::ActionRow::builder()
.title(*title)
.subtitle(*subtitle)
.build();
row.add_prefix(&gtk::Image::from_icon_name("emblem-ok-symbolic"));
changes_group.add(&row);
}
content.append(&changes_group);
scrolled.set_child(Some(&content));
toolbar_view.set_content(Some(&scrolled));
dialog.set_child(Some(&toolbar_view));
dialog.present(Some(window));
}
fn run_processing(_window: &adw::ApplicationWindow, ui: &WizardUi) {
let files = ui.state.loaded_files.borrow().clone();
if files.is_empty() {

View File

@@ -27,6 +27,10 @@ impl StepIndicator {
.margin_end(12)
.build();
container.update_property(&[
gtk::accessible::Property::Label("Wizard step indicator"),
]);
let mut dots = Vec::new();
for (i, name) in step_names.iter().enumerate() {
@@ -93,14 +97,26 @@ impl StepIndicator {
pub fn set_current(&self, index: usize) {
let dots = self.dots.borrow();
let total = dots.len();
for (i, dot) in dots.iter().enumerate() {
if i == index {
dot.icon.set_icon_name(Some("radio-checked-symbolic"));
dot.button.set_sensitive(true);
dot.label.add_css_class("accent");
// Update accessible description for screen readers
dot.button.update_property(&[
gtk::accessible::Property::Label(
&format!("Step {} of {}: {} (current)", i + 1, total, dot.label.label())
),
]);
} else if dot.icon.icon_name().as_deref() != Some("emblem-ok-symbolic") {
dot.icon.set_icon_name(Some("radio-symbolic"));
dot.label.remove_css_class("accent");
dot.button.update_property(&[
gtk::accessible::Property::Label(
&format!("Step {} of {}: {}", i + 1, total, dot.label.label())
),
]);
}
}
}