diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 38a8211b87e5..21c56b864f03 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -15,7 +15,10 @@ libloading = { version = "0.8.5", optional = true } im-rc = { version = "15", optional = true } async-channel = { version = "2.3", optional = true } tokio = { version = "1", features = ["full"], optional = true } -reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false, optional = true } +reqwest = { version = "0.12", features = [ + "json", + "rustls-tls", +], default-features = false, optional = true } serde = { version = "1.0", features = ["derive"], optional = true } gtk.workspace = true @@ -36,12 +39,18 @@ v4_6 = ["gtk/v4_6"] v4_10 = ["gtk/v4_10"] v4_12 = ["gtk/v4_12"] v4_14 = ["gtk/v4_14"] +v4_16 = ["gtk/v4_16"] [[bin]] name = "about_dialog" path = "about_dialog/main.rs" required-features = ["v4_6"] +[[bin]] +name = "accessible_text" +path = "accessible_text/main.rs" +required-features = ["v4_16"] + [[bin]] name = "basics" path = "basics/main.rs" diff --git a/examples/README.md b/examples/README.md index c4ba1b8c6058..57b3c32e74e6 100644 --- a/examples/README.md +++ b/examples/README.md @@ -16,6 +16,7 @@ cargo run --bin basics - [Basic example](./basics/) - [About Dialog](./about_dialog/) +- [Implementing the Accessible Text Interface](./accessible_text/) - [Using the Builder pattern](./builder_pattern/) - [Clipboard](./clipboard/) - [Clock example](./clock/) diff --git a/examples/accessible_text/README.md b/examples/accessible_text/README.md new file mode 100644 index 000000000000..846fb45ed10e --- /dev/null +++ b/examples/accessible_text/README.md @@ -0,0 +1,7 @@ +# Implementing the Accessible Text Interface + +This example creates a custom text editing widget that implements the Accessible Text Interface. This interface is used for providing screen reader accessibility to custom widgets. + +To test this implementation, enable the screen reader (Orca is enabled via Super+Alt+S) and edit / navigate the text. + +The widget mostly just delegates its implementation to the parent `TextView`. diff --git a/examples/accessible_text/main.rs b/examples/accessible_text/main.rs new file mode 100644 index 000000000000..05212ace0b5a --- /dev/null +++ b/examples/accessible_text/main.rs @@ -0,0 +1,24 @@ +mod text_view; + +use gtk::{glib, prelude::*}; +use text_view::AccessibleTextView; + +fn main() -> glib::ExitCode { + let application = gtk::Application::builder() + .application_id("com.github.gtk-rs.examples.accessible_text") + .build(); + application.connect_activate(build_ui); + application.run() +} + +fn build_ui(application: >k::Application) { + let window = gtk::ApplicationWindow::new(application); + + window.set_title(Some("Accessible Text Example")); + window.set_default_size(260, 140); + + let text_view = glib::Object::new::(); + window.set_child(Some(&text_view)); + + window.present(); +} diff --git a/examples/accessible_text/text_view.rs b/examples/accessible_text/text_view.rs new file mode 100644 index 000000000000..98ca5ed5a3a8 --- /dev/null +++ b/examples/accessible_text/text_view.rs @@ -0,0 +1,102 @@ +use gtk::glib; +use gtk::subclass::prelude::*; + +mod imp { + use gtk::{graphene, ACCESSIBLE_ATTRIBUTE_OVERLINE, ACCESSIBLE_ATTRIBUTE_OVERLINE_SINGLE}; + + use super::*; + + #[derive(Default)] + pub struct AccessibleTextView {} + + #[glib::object_subclass] + impl ObjectSubclass for AccessibleTextView { + const NAME: &'static str = "AccessibleTextView"; + type Type = super::AccessibleTextView; + type ParentType = gtk::TextView; + type Interfaces = (gtk::AccessibleText,); + } + + impl ObjectImpl for AccessibleTextView {} + impl WidgetImpl for AccessibleTextView {} + impl AccessibleTextImpl for AccessibleTextView { + fn attributes( + &self, + offset: u32, + ) -> Vec<(gtk::AccessibleTextRange, glib::GString, glib::GString)> { + let attributes = self.parent_attributes(offset); + println!("attributes({offset}) -> {attributes:?}"); + attributes + } + + fn caret_position(&self) -> u32 { + let pos = self.parent_caret_position(); + println!("caret_position() -> {pos}"); + pos + } + + fn contents(&self, start: u32, end: u32) -> Option { + let content = self.parent_contents(start, end); + println!( + "contents({start}, {end}) -> {:?}", + content + .as_ref() + .map(|c| std::str::from_utf8(c.as_ref()).unwrap()) + ); + content + } + + fn contents_at( + &self, + offset: u32, + granularity: gtk::AccessibleTextGranularity, + ) -> Option<(u32, u32, glib::Bytes)> { + let contents = self.parent_contents_at(offset, granularity); + println!( + "contents_at offset({offset}, {granularity:?}) -> {:?}", + contents + .as_ref() + .map(|(s, e, c)| (s, e, std::str::from_utf8(c.as_ref()).unwrap())) + ); + contents + } + + fn default_attributes(&self) -> Vec<(glib::GString, glib::GString)> { + let mut attrs = self.parent_default_attributes(); + + // Attributes can be added and removed + attrs.push(( + ACCESSIBLE_ATTRIBUTE_OVERLINE.to_owned(), + ACCESSIBLE_ATTRIBUTE_OVERLINE_SINGLE.to_owned(), + )); + println!("default_attributes() -> {attrs:?}"); + attrs + } + + fn selection(&self) -> Vec { + let selection = self.parent_selection(); + println!("selection() -> {selection:?}"); + selection + } + + fn extents(&self, start: u32, end: u32) -> Option { + let extents = self.parent_extents(start, end); + println!("extents({start}, {end}) -> {extents:?}"); + extents + } + + fn offset(&self, point: &graphene::Point) -> Option { + let offset = self.parent_offset(point); + println!("offset({:?}) -> {offset:?}", point); + offset + } + } + + impl TextViewImpl for AccessibleTextView {} +} + +glib::wrapper! { + pub struct AccessibleTextView(ObjectSubclass) + @extends gtk::Widget, gtk::TextView, + @implements gtk::Accessible, gtk::AccessibleText, gtk::Buildable, gtk::ConstraintTarget, gtk::Scrollable; +}