use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Copy, Clone, Debug, Default, PartialEq)]
pub struct Point {
pub x: f32,
pub y: f32,
}
impl Point {
pub const ZERO: Self = Self { x: 0.0, y: 0.0 };
pub const fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq)]
pub struct Size {
pub width: f32,
pub height: f32,
}
impl Size {
pub const ZERO: Self = Self {
width: 0.0,
height: 0.0,
};
pub const fn new(width: f32, height: f32) -> Self {
Self { width, height }
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq)]
pub struct Bounds {
pub origin: Point,
pub size: Size,
}
impl Bounds {
pub const ZERO: Self = Self {
origin: Point::ZERO,
size: Size::ZERO,
};
pub const fn new(origin: Point, size: Size) -> Self {
Self { origin, size }
}
pub fn from_origin_size(x: f32, y: f32, width: f32, height: f32) -> Self {
Self {
origin: Point::new(x, y),
size: Size::new(width, height),
}
}
#[inline]
pub fn contains(&self, point: Point) -> bool {
point.x >= self.origin.x
&& point.x <= self.origin.x + self.size.width
&& point.y >= self.origin.y
&& point.y <= self.origin.y + self.size.height
}
#[inline]
pub fn intersects(&self, other: &Bounds) -> bool {
self.origin.x < other.origin.x + other.size.width
&& self.origin.x + self.size.width > other.origin.x
&& self.origin.y < other.origin.y + other.size.height
&& self.origin.y + self.size.height > other.origin.y
}
pub fn intersection(&self, other: &Bounds) -> Option<Self> {
if !self.intersects(other) {
return None;
}
let x = self.origin.x.max(other.origin.x);
let y = self.origin.y.max(other.origin.y);
let right = (self.origin.x + self.size.width).min(other.origin.x + other.size.width);
let bottom = (self.origin.y + self.size.height).min(other.origin.y + other.size.height);
Some(Self::from_origin_size(x, y, right - x, bottom - y))
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq)]
pub struct Edges<T: Copy + Default> {
pub top: T,
pub right: T,
pub bottom: T,
pub left: T,
}
impl<T: Copy + Default> Edges<T> {
pub const fn new(top: T, right: T, bottom: T, left: T) -> Self {
Self {
top,
right,
bottom,
left,
}
}
pub const fn all(value: T) -> Self {
Self {
top: value,
right: value,
bottom: value,
left: value,
}
}
pub const fn symmetric(vertical: T, horizontal: T) -> Self {
Self {
top: vertical,
right: horizontal,
bottom: vertical,
left: horizontal,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct LayoutId(
pub taffy::NodeId,
);
impl From<taffy::NodeId> for LayoutId {
fn from(id: taffy::NodeId) -> Self {
Self(id)
}
}
impl From<LayoutId> for taffy::NodeId {
fn from(id: LayoutId) -> Self {
id.0
}
}
static ELEMENT_ID_COUNTER: AtomicU64 = AtomicU64::new(1);
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct ElementId(
pub u64,
);
impl ElementId {
pub fn next() -> Self {
Self(ELEMENT_ID_COUNTER.fetch_add(1, Ordering::Relaxed))
}
pub(crate) const fn from_hash(hash: u64) -> Self {
Self(hash)
}
pub(crate) const fn root() -> Self {
Self(0)
}
pub const fn from_raw(id: u64) -> Self {
Self(id)
}
pub const fn as_u64(self) -> u64 {
self.0
}
}
#[derive(Clone, Debug, Default)]
pub enum NodeContext {
Text {
width_lpx: f32,
height_lpx: f32,
},
Image {
width_lpx: f32,
height_lpx: f32,
},
#[default]
None,
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
#[allow(missing_docs)] pub enum AccessibilityRole {
#[default]
Unknown,
Button,
Checkbox,
Dialog,
Group,
Heading {
level: u8,
},
Image,
Label,
Link,
List,
ListItem,
Menu,
MenuItem,
ProgressBar,
RadioButton,
ScrollView,
Slider,
Switch,
Tab,
TabPanel,
TextInput,
Tooltip,
Window,
None,
}
impl From<AccessibilityRole> for accesskit::Role {
fn from(role: AccessibilityRole) -> Self {
match role {
AccessibilityRole::Unknown => accesskit::Role::Unknown,
AccessibilityRole::Button => accesskit::Role::Button,
AccessibilityRole::Checkbox => accesskit::Role::CheckBox,
AccessibilityRole::Dialog => accesskit::Role::Dialog,
AccessibilityRole::Group => accesskit::Role::Group,
AccessibilityRole::Heading { .. } => accesskit::Role::Heading,
AccessibilityRole::Image => accesskit::Role::Image,
AccessibilityRole::Label => accesskit::Role::Label,
AccessibilityRole::Link => accesskit::Role::Link,
AccessibilityRole::List => accesskit::Role::List,
AccessibilityRole::ListItem => accesskit::Role::ListItem,
AccessibilityRole::Menu => accesskit::Role::Menu,
AccessibilityRole::MenuItem => accesskit::Role::MenuItem,
AccessibilityRole::ProgressBar => accesskit::Role::ProgressIndicator,
AccessibilityRole::RadioButton => accesskit::Role::RadioButton,
AccessibilityRole::ScrollView => accesskit::Role::ScrollView,
AccessibilityRole::Slider => accesskit::Role::Slider,
AccessibilityRole::Switch => accesskit::Role::Switch,
AccessibilityRole::Tab => accesskit::Role::Tab,
AccessibilityRole::TabPanel => accesskit::Role::TabPanel,
AccessibilityRole::TextInput => accesskit::Role::TextInput,
AccessibilityRole::Tooltip => accesskit::Role::Tooltip,
AccessibilityRole::Window => accesskit::Role::Window,
AccessibilityRole::None => accesskit::Role::Unknown,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum LiveRegion {
Polite,
Assertive,
}
#[derive(Default, Clone, Debug, PartialEq, Eq)]
pub struct AccessibilityRelationships {
pub labelled_by: Vec<ElementId>,
pub described_by: Vec<ElementId>,
pub controls: Vec<ElementId>,
pub owns: Vec<ElementId>,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[allow(missing_docs)] pub enum AccessibilityAction {
Click,
Focus,
Blur,
Increment,
Decrement,
Expand,
Collapse,
ScrollIntoView,
}
impl From<AccessibilityAction> for accesskit::Action {
fn from(action: AccessibilityAction) -> Self {
match action {
AccessibilityAction::Click => accesskit::Action::Click,
AccessibilityAction::Focus => accesskit::Action::Focus,
AccessibilityAction::Blur => accesskit::Action::Blur,
AccessibilityAction::Increment => accesskit::Action::Increment,
AccessibilityAction::Decrement => accesskit::Action::Decrement,
AccessibilityAction::Expand => accesskit::Action::Expand,
AccessibilityAction::Collapse => accesskit::Action::Collapse,
AccessibilityAction::ScrollIntoView => accesskit::Action::ScrollIntoView,
}
}
}
#[derive(Clone, Debug, Default)]
pub struct AccessibilityInfo {
pub role: AccessibilityRole,
pub label: Option<String>,
pub description: Option<String>,
pub value: Option<String>,
pub is_disabled: bool,
pub is_focused: bool,
pub is_expanded: Option<bool>,
pub is_selected: Option<bool>,
pub live_region: Option<LiveRegion>,
pub relationships: AccessibilityRelationships,
pub tab_index: Option<i32>,
}
#[derive(Clone, Debug)]
pub struct AccessibilityNode {
pub id: ElementId,
pub bounds: Bounds,
pub info: AccessibilityInfo,
pub children: Vec<AccessibilityNode>,
pub actions: Vec<AccessibilityAction>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bounds_contains() {
let b = Bounds::from_origin_size(10.0, 10.0, 100.0, 50.0);
assert!(b.contains(Point::new(50.0, 30.0)));
assert!(b.contains(Point::new(10.0, 10.0))); assert!(b.contains(Point::new(110.0, 60.0))); assert!(!b.contains(Point::new(5.0, 30.0))); assert!(!b.contains(Point::new(115.0, 30.0))); }
#[test]
fn bounds_intersects() {
let a = Bounds::from_origin_size(0.0, 0.0, 100.0, 100.0);
let b = Bounds::from_origin_size(50.0, 50.0, 100.0, 100.0);
let c = Bounds::from_origin_size(200.0, 200.0, 50.0, 50.0);
assert!(a.intersects(&b));
assert!(b.intersects(&a));
assert!(!a.intersects(&c));
assert!(!c.intersects(&a));
}
#[test]
fn element_id_uniqueness() {
let id1 = ElementId::next();
let id2 = ElementId::next();
let id3 = ElementId::next();
assert_ne!(id1, id2);
assert_ne!(id2, id3);
assert!(id2.0 > id1.0);
}
#[test]
fn element_id_from_hash_stability() {
let id1 = ElementId::from_hash(12345);
let id2 = ElementId::from_hash(12345);
assert_eq!(id1, id2);
let id3 = ElementId::from_hash(67890);
assert_ne!(id1, id3);
}
#[test]
fn element_id_root_sentinel() {
let root = ElementId::root();
assert_eq!(root.as_u64(), 0);
let next_id = ElementId::next();
assert_ne!(root, next_id);
}
}