From 03d0e5df7465d46b9cec67215fad8fdf446f5f70 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Wed, 14 Jan 2026 16:50:05 -0600 Subject: [PATCH 1/2] feat: More text attributes on Windows --- platforms/windows/src/text.rs | 48 ++++++++++++++++++++++++++++++++++- platforms/windows/src/util.rs | 35 ++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/platforms/windows/src/text.rs b/platforms/windows/src/text.rs index 50d19ebc..3c50187f 100644 --- a/platforms/windows/src/text.rs +++ b/platforms/windows/src/text.rs @@ -5,7 +5,7 @@ #![allow(non_upper_case_globals)] -use accesskit::{Action, ActionData, ActionRequest, ScrollHint}; +use accesskit::{Action, ActionData, ActionRequest, ScrollHint, VerticalOffset}; use accesskit_consumer::{ Node, TextPosition as Position, TextRange as Range, Tree, TreeState, WeakTextRange as WeakRange, }; @@ -458,6 +458,52 @@ impl ITextRangeProvider_Impl for PlatformRange_Impl { UIA_FontWeightAttributeId => self.read(|range| { Ok(Variant::from(range.font_weight().map(|value| value as i32)).into()) }), + UIA_IsItalicAttributeId => { + self.read(|range| Ok(Variant::from(range.is_italic()).into())) + } + UIA_BackgroundColorAttributeId => { + self.read(|range| Ok(Variant::from(range.background_color()).into())) + } + UIA_ForegroundColorAttributeId => { + self.read(|range| Ok(Variant::from(range.foreground_color()).into())) + } + UIA_OverlineStyleAttributeId => { + self.read(|range| Ok(Variant::from(range.overline().map(|d| d.style)).into())) + } + UIA_OverlineColorAttributeId => { + self.read(|range| Ok(Variant::from(range.overline().map(|d| d.color)).into())) + } + UIA_StrikethroughStyleAttributeId => { + self.read(|range| Ok(Variant::from(range.strikethrough().map(|d| d.style)).into())) + } + UIA_StrikethroughColorAttributeId => { + self.read(|range| Ok(Variant::from(range.strikethrough().map(|d| d.color)).into())) + } + UIA_UnderlineStyleAttributeId => { + self.read(|range| Ok(Variant::from(range.underline().map(|d| d.style)).into())) + } + UIA_UnderlineColorAttributeId => { + self.read(|range| Ok(Variant::from(range.underline().map(|d| d.color)).into())) + } + UIA_HorizontalTextAlignmentAttributeId => { + self.read(|range| Ok(Variant::from(range.text_align()).into())) + } + UIA_IsSubscriptAttributeId => self.read(|range| { + Ok(Variant::from( + range + .vertical_offset() + .map(|o| o == VerticalOffset::Subscript), + ) + .into()) + }), + UIA_IsSuperscriptAttributeId => self.read(|range| { + Ok(Variant::from( + range + .vertical_offset() + .map(|o| o == VerticalOffset::Superscript), + ) + .into()) + }), // TODO: implement more attributes _ => { let value = unsafe { UiaGetReservedNotSupportedValue() }.unwrap(); diff --git a/platforms/windows/src/util.rs b/platforms/windows/src/util.rs index 94cac694..44e21efa 100644 --- a/platforms/windows/src/util.rs +++ b/platforms/windows/src/util.rs @@ -3,7 +3,7 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use accesskit::Point; +use accesskit::{Color, Point, TextAlign, TextDecorationStyle}; use accesskit_consumer::{TextRangePropertyValue, TreeState}; use std::{ fmt::{self, Write}, @@ -146,6 +146,39 @@ impl From for Variant { } } +impl From for Variant { + fn from(value: Color) -> Self { + let rgb: i32 = + (value.red as i32) | ((value.green as i32) << 8) | ((value.blue as i32) << 16); + Self(rgb.into()) + } +} + +impl From for Variant { + fn from(value: TextDecorationStyle) -> Self { + let value = match value { + TextDecorationStyle::Solid => TextDecorationLineStyle_Single, + TextDecorationStyle::Dotted => TextDecorationLineStyle_Dot, + TextDecorationStyle::Dashed => TextDecorationLineStyle_Dash, + TextDecorationStyle::Double => TextDecorationLineStyle_Double, + TextDecorationStyle::Wavy => TextDecorationLineStyle_Wavy, + }; + Self::from(value.0) + } +} + +impl From for Variant { + fn from(value: TextAlign) -> Self { + let value = match value { + TextAlign::Left => HorizontalTextAlignment_Left, + TextAlign::Right => HorizontalTextAlignment_Right, + TextAlign::Center => HorizontalTextAlignment_Centered, + TextAlign::Justify => HorizontalTextAlignment_Justified, + }; + Self::from(value.0) + } +} + impl From for Variant { fn from(value: bool) -> Self { Self(value.into()) From c98dfdd24eaec3b5e7f4744c941018da0a8f4c57 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Thu, 15 Jan 2026 13:11:55 -0600 Subject: [PATCH 2/2] Factor out `read` call in `GetAttributeValue` --- platforms/windows/src/text.rs | 82 ++++++++++++++--------------------- 1 file changed, 32 insertions(+), 50 deletions(-) diff --git a/platforms/windows/src/text.rs b/platforms/windows/src/text.rs index 3c50187f..f3406284 100644 --- a/platforms/windows/src/text.rs +++ b/platforms/windows/src/text.rs @@ -426,15 +426,13 @@ impl ITextRangeProvider_Impl for PlatformRange_Impl { } fn GetAttributeValue(&self, id: UIA_TEXTATTRIBUTE_ID) -> Result { - match id { + self.read(|range| match id { UIA_IsReadOnlyAttributeId => { // TBD: do we ever want to support mixed read-only/editable text? - self.with_node(|node| { - let value = node.is_read_only(); - Ok(value.into()) - }) + let value = range.node().is_read_only(); + Ok(value.into()) } - UIA_CaretPositionAttributeId => self.read(|range| { + UIA_CaretPositionAttributeId => { let mut value = CaretPosition_Unknown; if range.is_degenerate() { let pos = range.start(); @@ -445,71 +443,55 @@ impl ITextRangeProvider_Impl for PlatformRange_Impl { } } Ok(value.0.into()) - }), - UIA_CultureAttributeId => { - self.read(|range| Ok(Variant::from(range.language().map(LocaleName)).into())) - } - UIA_FontNameAttributeId => { - self.read(|range| Ok(Variant::from(range.font_family()).into())) } - UIA_FontSizeAttributeId => self.read(|range| { + UIA_CultureAttributeId => Ok(Variant::from(range.language().map(LocaleName)).into()), + UIA_FontNameAttributeId => Ok(Variant::from(range.font_family()).into()), + UIA_FontSizeAttributeId => { Ok(Variant::from(range.font_size().map(|value| value as f64)).into()) - }), - UIA_FontWeightAttributeId => self.read(|range| { - Ok(Variant::from(range.font_weight().map(|value| value as i32)).into()) - }), - UIA_IsItalicAttributeId => { - self.read(|range| Ok(Variant::from(range.is_italic()).into())) - } - UIA_BackgroundColorAttributeId => { - self.read(|range| Ok(Variant::from(range.background_color()).into())) } - UIA_ForegroundColorAttributeId => { - self.read(|range| Ok(Variant::from(range.foreground_color()).into())) + UIA_FontWeightAttributeId => { + Ok(Variant::from(range.font_weight().map(|value| value as i32)).into()) } + UIA_IsItalicAttributeId => Ok(Variant::from(range.is_italic()).into()), + UIA_BackgroundColorAttributeId => Ok(Variant::from(range.background_color()).into()), + UIA_ForegroundColorAttributeId => Ok(Variant::from(range.foreground_color()).into()), UIA_OverlineStyleAttributeId => { - self.read(|range| Ok(Variant::from(range.overline().map(|d| d.style)).into())) + Ok(Variant::from(range.overline().map(|d| d.style)).into()) } UIA_OverlineColorAttributeId => { - self.read(|range| Ok(Variant::from(range.overline().map(|d| d.color)).into())) + Ok(Variant::from(range.overline().map(|d| d.color)).into()) } UIA_StrikethroughStyleAttributeId => { - self.read(|range| Ok(Variant::from(range.strikethrough().map(|d| d.style)).into())) + Ok(Variant::from(range.strikethrough().map(|d| d.style)).into()) } UIA_StrikethroughColorAttributeId => { - self.read(|range| Ok(Variant::from(range.strikethrough().map(|d| d.color)).into())) + Ok(Variant::from(range.strikethrough().map(|d| d.color)).into()) } UIA_UnderlineStyleAttributeId => { - self.read(|range| Ok(Variant::from(range.underline().map(|d| d.style)).into())) + Ok(Variant::from(range.underline().map(|d| d.style)).into()) } UIA_UnderlineColorAttributeId => { - self.read(|range| Ok(Variant::from(range.underline().map(|d| d.color)).into())) - } - UIA_HorizontalTextAlignmentAttributeId => { - self.read(|range| Ok(Variant::from(range.text_align()).into())) + Ok(Variant::from(range.underline().map(|d| d.color)).into()) } - UIA_IsSubscriptAttributeId => self.read(|range| { - Ok(Variant::from( - range - .vertical_offset() - .map(|o| o == VerticalOffset::Subscript), - ) - .into()) - }), - UIA_IsSuperscriptAttributeId => self.read(|range| { - Ok(Variant::from( - range - .vertical_offset() - .map(|o| o == VerticalOffset::Superscript), - ) - .into()) - }), + UIA_HorizontalTextAlignmentAttributeId => Ok(Variant::from(range.text_align()).into()), + UIA_IsSubscriptAttributeId => Ok(Variant::from( + range + .vertical_offset() + .map(|o| o == VerticalOffset::Subscript), + ) + .into()), + UIA_IsSuperscriptAttributeId => Ok(Variant::from( + range + .vertical_offset() + .map(|o| o == VerticalOffset::Superscript), + ) + .into()), // TODO: implement more attributes _ => { let value = unsafe { UiaGetReservedNotSupportedValue() }.unwrap(); Ok(value.into()) } - } + }) } fn GetBoundingRectangles(&self) -> Result<*mut SAFEARRAY> {