diff --git a/crates/paperback/src/accessibility.rs b/crates/paperback/src/accessibility.rs new file mode 100644 index 00000000..dc1159b6 --- /dev/null +++ b/crates/paperback/src/accessibility.rs @@ -0,0 +1,48 @@ +/// Notebook tab page role for a settings/property page. Pass as +/// `AccProps { role: Some(ROLE_PROPERTYPAGE), .. }` so screen readers announce +/// a page (not a bare "panel") on Ctrl+Tab. +pub use wxdragon::ffi::wxd_AccRole_WXD_ROLE_SYSTEM_PROPERTYPAGE as ROLE_PROPERTYPAGE; +use wxdragon::{ + accessible::{AccRole, AccStatus, Accessible, AccessibleImpl}, + ffi::{wxd_AccStatus_WXD_ACC_NOT_IMPLEMENTED as ACC_NOT_IMPLEMENTED, wxd_AccStatus_WXD_ACC_OK as ACC_OK}, + prelude::*, +}; + +/// Accessible properties to expose on a window. Leave a field `None` to report +/// NOT_IMPLEMENTED for it (screen reader falls back to wx defaults). +#[derive(Default)] +pub struct AccProps { + pub name: Option, + pub role: Option, +} + +struct PropsAccessible { + props: AccProps, +} + +impl AccessibleImpl for PropsAccessible { + fn get_name(&self, child_id: i32) -> (AccStatus, Option) { + match (child_id, &self.props.name) { + (0, Some(name)) => (ACC_OK, Some(name.clone())), + _ => (ACC_NOT_IMPLEMENTED, None), + } + } + + fn get_role(&self, _child_id: i32) -> (AccStatus, AccRole) { + match self.props.role { + Some(role) => (ACC_OK, role), + None => (ACC_NOT_IMPLEMENTED, wxdragon::ffi::wxd_AccRole_WXD_ROLE_NONE), + } + } +} + +/// Extension trait: attach accessible name/role to any window in one call. +pub trait AccessibleExt: WxWidget { + fn set_accessible_props(&self, props: AccProps) + where + Self: Sized, + { + self.set_accessible(Accessible::new(self, PropsAccessible { props })); + } +} +impl AccessibleExt for T {} diff --git a/crates/paperback/src/main.rs b/crates/paperback/src/main.rs index 100b8cca..d3882529 100644 --- a/crates/paperback/src/main.rs +++ b/crates/paperback/src/main.rs @@ -3,6 +3,7 @@ patois::embed_domain!(); +mod accessibility; mod config_ext; mod ipc; mod legacy_config; diff --git a/crates/paperback/src/ui/dialogs/options.rs b/crates/paperback/src/ui/dialogs/options.rs index 4c2f4484..964dc252 100644 --- a/crates/paperback/src/ui/dialogs/options.rs +++ b/crates/paperback/src/ui/dialogs/options.rs @@ -10,7 +10,11 @@ use patois::t; use wxdragon::prelude::*; use super::DIALOG_PADDING; -use crate::{config_ext::UpdateChannel, translation_manager::TranslationManager}; +use crate::{ + accessibility::{AccProps, AccessibleExt, ROLE_PROPERTYPAGE}, + config_ext::UpdateChannel, + translation_manager::TranslationManager, +}; #[derive(Clone, Debug)] pub struct OptionsDialogResult { @@ -277,9 +281,16 @@ fn build_options_dialog_ui(parent: &Frame, config: &ConfigManager) -> OptionsDia readability_panel.set_sizer(readability_sizer, true); general_panel.set_sizer(general_sizer, true); reading_panel.set_sizer(reading_sizer, true); - notebook.add_page(&general_panel, &t("General"), true, None); - notebook.add_page(&reading_panel, &t("Reading"), false, None); - notebook.add_page(&readability_panel, &t("Readability"), false, None); + let general_label = t("General"); + let reading_label = t("Reading"); + let readability_label = t("Readability"); + general_panel.set_accessible_props(AccProps { name: Some(general_label.clone()), role: Some(ROLE_PROPERTYPAGE) }); + reading_panel.set_accessible_props(AccProps { name: Some(reading_label.clone()), role: Some(ROLE_PROPERTYPAGE) }); + readability_panel + .set_accessible_props(AccProps { name: Some(readability_label.clone()), role: Some(ROLE_PROPERTYPAGE) }); + notebook.add_page(&general_panel, &general_label, true, None); + notebook.add_page(&reading_panel, &reading_label, false, None); + notebook.add_page(&readability_panel, &readability_label, false, None); restore_docs_check.set_value(config.get_app_bool("restore_previous_documents", true)); word_wrap_check.set_value(config.get_app_bool("word_wrap", false)); render_tables_inline_check.set_value(config.get_app_bool("render_tables_inline", true)); diff --git a/crates/paperback/src/ui/document_manager.rs b/crates/paperback/src/ui/document_manager.rs index 269c08b8..8260c469 100644 --- a/crates/paperback/src/ui/document_manager.rs +++ b/crates/paperback/src/ui/document_manager.rs @@ -25,6 +25,7 @@ use super::{ main_window::{SLEEP_TIMER_DURATION_MINUTES, SLEEP_TIMER_START_MS}, menu_ids, status, }; +use crate::accessibility::{AccProps, AccessibleExt}; pub struct DocumentTab { pub panel: Panel, @@ -220,6 +221,7 @@ impl DocumentManager { config.get_letter_spacing(), config.get_text_alignment(), ); + text_ctrl.set_accessible_props(AccProps { name: Some(title.clone()), role: None }); self.notebook.add_page(&panel, &title, true, None); let path_str = path.to_string_lossy(); let nav_history = config.get_navigation_history(&path_str);