Add What's New dialog, accessibility labels, focus management
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("Settings"), Some("win.show-settings"));
|
||||||
menu.append(Some("History"), Some("win.show-history"));
|
menu.append(Some("History"), Some("win.show-history"));
|
||||||
menu.append(Some("Keyboard Shortcuts"), Some("win.show-shortcuts"));
|
menu.append(Some("Keyboard Shortcuts"), Some("win.show-shortcuts"));
|
||||||
|
menu.append(Some("What's New"), Some("win.show-whats-new"));
|
||||||
menu
|
menu
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -500,6 +501,16 @@ fn setup_window_actions(window: &adw::ApplicationWindow, ui: &WizardUi) {
|
|||||||
action_group.add_action(&action);
|
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
|
// Connect button clicks
|
||||||
ui.back_button.connect_clicked({
|
ui.back_button.connect_clicked({
|
||||||
let action_group = action_group.clone();
|
let action_group = action_group.clone();
|
||||||
@@ -541,6 +552,13 @@ fn navigate_to_step(ui: &WizardUi, target: usize) {
|
|||||||
if target < ui.pages.len() {
|
if target < ui.pages.len() {
|
||||||
ui.nav_view.replace(&ui.pages[..=target]);
|
ui.nav_view.replace(&ui.pages[..=target]);
|
||||||
ui.title.set_subtitle(&ui.pages[target].title());
|
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
|
// Update dynamic content on certain steps
|
||||||
@@ -857,6 +875,78 @@ fn show_history_dialog(window: &adw::ApplicationWindow) {
|
|||||||
dialog.present(Some(window));
|
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) {
|
fn run_processing(_window: &adw::ApplicationWindow, ui: &WizardUi) {
|
||||||
let files = ui.state.loaded_files.borrow().clone();
|
let files = ui.state.loaded_files.borrow().clone();
|
||||||
if files.is_empty() {
|
if files.is_empty() {
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ impl StepIndicator {
|
|||||||
.margin_end(12)
|
.margin_end(12)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
container.update_property(&[
|
||||||
|
gtk::accessible::Property::Label("Wizard step indicator"),
|
||||||
|
]);
|
||||||
|
|
||||||
let mut dots = Vec::new();
|
let mut dots = Vec::new();
|
||||||
|
|
||||||
for (i, name) in step_names.iter().enumerate() {
|
for (i, name) in step_names.iter().enumerate() {
|
||||||
@@ -93,14 +97,26 @@ impl StepIndicator {
|
|||||||
|
|
||||||
pub fn set_current(&self, index: usize) {
|
pub fn set_current(&self, index: usize) {
|
||||||
let dots = self.dots.borrow();
|
let dots = self.dots.borrow();
|
||||||
|
let total = dots.len();
|
||||||
for (i, dot) in dots.iter().enumerate() {
|
for (i, dot) in dots.iter().enumerate() {
|
||||||
if i == index {
|
if i == index {
|
||||||
dot.icon.set_icon_name(Some("radio-checked-symbolic"));
|
dot.icon.set_icon_name(Some("radio-checked-symbolic"));
|
||||||
dot.button.set_sensitive(true);
|
dot.button.set_sensitive(true);
|
||||||
dot.label.add_css_class("accent");
|
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") {
|
} else if dot.icon.icon_name().as_deref() != Some("emblem-ok-symbolic") {
|
||||||
dot.icon.set_icon_name(Some("radio-symbolic"));
|
dot.icon.set_icon_name(Some("radio-symbolic"));
|
||||||
dot.label.remove_css_class("accent");
|
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