Add What's New dialog, accessibility labels, focus management
Add What's New dialog accessible from hamburger menu showing version changelog. Add accessible property labels to step indicator for screen reader support with current step/total announcements. Add focus management on step transitions - focus moves to first interactive element when navigating to a new step.
This commit is contained in:
@@ -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(>k::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() {
|
||||
|
||||
@@ -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())
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user