Skip to content

feat: Expose the orientation property #421

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 26, 2024
Merged

feat: Expose the orientation property #421

merged 2 commits into from
May 26, 2024

Conversation

DataTriny
Copy link
Member

These changes were tested on all platforms with the example below:

platforms/winit/examples/orientation.rs

use accesskit::{
    Action, DefaultActionVerb, Node, NodeBuilder, NodeId, Orientation, Rect, Role, Tree, TreeUpdate,
};
use accesskit_winit::{Adapter, Event as AccessKitEvent, WindowEvent as AccessKitWindowEvent};
use std::error::Error;
use winit::{
    application::ApplicationHandler,
    event::WindowEvent,
    event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy},
    window::{Window, WindowId},
};

const WINDOW_TITLE: &str = "Orientation test";

const WINDOW_ID: NodeId = NodeId(0);
const HORIZONTAL_SCROLLBAR_ID: NodeId = NodeId(1);
const VERTICAL_SCROLLBAR_ID: NodeId = NodeId(2);
const INITIAL_FOCUS: NodeId = WINDOW_ID;

const HORIZONTAL_SCROLLBAR_RECT: Rect = Rect {
    x0: 0.0,
    y0: 380.0,
    x1: 380.0,
    y1: 400.0,
};

const VERTICAL_SCROLLBAR_RECT: Rect = Rect {
    x0: 380.0,
    y0: 0.0,
    x1: 400.0,
    y1: 380.0,
};

fn build_scrollbar(bounds: Rect, orientation: Orientation, value: f64) -> Node {
    let mut builder = NodeBuilder::new(Role::ScrollBar);
    builder.set_bounds(bounds);
    builder.set_orientation(orientation);
    builder.set_min_numeric_value(0.0);
    builder.set_max_numeric_value(100.0);
    builder.set_numeric_value(value);
    builder.add_action(Action::Focus);
    builder.set_default_action_verb(DefaultActionVerb::Click);
    builder.build()
}

struct UiState;

impl UiState {
    fn new() -> Self {
        Self {}
    }

    fn build_root(&mut self) -> Node {
        let mut builder = NodeBuilder::new(Role::Window);
        builder.set_children(&[HORIZONTAL_SCROLLBAR_ID, VERTICAL_SCROLLBAR_ID]);
        builder.set_name(WINDOW_TITLE);
        builder.build()
    }

    fn build_initial_tree(&mut self) -> TreeUpdate {
        let root = self.build_root();
        let hbar = build_scrollbar(HORIZONTAL_SCROLLBAR_RECT, Orientation::Horizontal, 0.0);
        let vbar = build_scrollbar(VERTICAL_SCROLLBAR_RECT, Orientation::Vertical, 50.0);
        let mut tree = Tree::new(WINDOW_ID);
        tree.app_name = Some("orientation_example".to_string());
        TreeUpdate {
            nodes: vec![
                (WINDOW_ID, root),
                (HORIZONTAL_SCROLLBAR_ID, hbar),
                (VERTICAL_SCROLLBAR_ID, vbar),
            ],
            tree: Some(tree),
            focus: INITIAL_FOCUS,
        }
    }
}

struct WindowState {
    window: Window,
    adapter: Adapter,
    ui: UiState,
}

impl WindowState {
    fn new(window: Window, adapter: Adapter, ui: UiState) -> Self {
        Self {
            window,
            adapter,
            ui,
        }
    }
}

struct Application {
    event_loop_proxy: EventLoopProxy<AccessKitEvent>,
    window: Option<WindowState>,
}

impl Application {
    fn new(event_loop_proxy: EventLoopProxy<AccessKitEvent>) -> Self {
        Self {
            event_loop_proxy,
            window: None,
        }
    }

    fn create_window(&mut self, event_loop: &ActiveEventLoop) -> Result<(), Box<dyn Error>> {
        let window_attributes = Window::default_attributes()
            .with_title(WINDOW_TITLE)
            .with_visible(false);

        let window = event_loop.create_window(window_attributes)?;
        let adapter = Adapter::with_event_loop_proxy(&window, self.event_loop_proxy.clone());
        window.set_visible(true);

        self.window = Some(WindowState::new(window, adapter, UiState::new()));
        Ok(())
    }
}

impl ApplicationHandler<AccessKitEvent> for Application {
    fn window_event(&mut self, _: &ActiveEventLoop, _: WindowId, event: WindowEvent) {
        let window = match &mut self.window {
            Some(window) => window,
            None => return,
        };
        let adapter = &mut window.adapter;

        adapter.process_event(&window.window, &event);
        match event {
            WindowEvent::CloseRequested => {
                self.window = None;
            }
            _ => (),
        }
    }

    fn user_event(&mut self, _: &ActiveEventLoop, user_event: AccessKitEvent) {
        let window = match &mut self.window {
            Some(window) => window,
            None => return,
        };
        let adapter = &mut window.adapter;
        let state = &mut window.ui;

        match user_event.window_event {
            AccessKitWindowEvent::InitialTreeRequested => {
                adapter.update_if_active(|| state.build_initial_tree());
            }
            _ => (),
        }
    }

    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
        self.create_window(event_loop)
            .expect("failed to create initial window");
    }

    fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
        if self.window.is_none() {
            event_loop.exit();
        }
    }
}

fn main() -> Result<(), Box<dyn Error>> {
    let event_loop = EventLoop::with_user_event().build()?;
    let mut state = Application::new(event_loop.create_proxy());
    event_loop.run_app(&mut state).map_err(Into::into)
}

  • On Windows: neither NVDA nor Narrator report this apparently, I checked that it was indeed exposed via Accessibility Insights.
  • On Linux: Orca announces the orientation of both scrollbars when navigating in flat review mode.
  • On macOS: VoiceOver announces the orientation of both scrollbars. Note that Chromium doesn't implement this so I hope I'm doing it right.

@mwcampbell mwcampbell merged commit 590aada into main May 26, 2024
10 checks passed
@mwcampbell mwcampbell deleted the orientation branch May 26, 2024 19:04
@mwcampbell mwcampbell mentioned this pull request May 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants