Skip to main content

ferroui_controls_inking/
ink_toolbar_automation_peer.rs

1//! Accessibility adapters: reuse the framework providers and add inking-specific patterns.
2use super::ink_toolbar_buttons::{strings, toolbar_is_inking};
3use super::{
4    InkToolbar, InkToolbarFlyoutItem, InkToolbarFlyoutItemKind, InkToolbarMenuButton,
5    InkToolbarStencilButton, InkToolbarStencilKind, InkToolbarStrokeWidthSlider, InkToolbarTool,
6    InkToolbarToolButton,
7};
8use ferroui_controls::automation::peers::{
9    AutomationControlType, AutomationPeer, AutomationPeerImpl, AutomationPeerImplExt,
10    ControlAutomationPeer, ControlAutomationPeerImpl, RadioButtonAutomationPeer,
11    RadioButtonAutomationPeerImpl, RangeBaseAutomationPeerImpl, RangeBaseAutomationPeerImplExt,
12    SliderAutomationPeer, ToggleButtonAutomationPeer,
13};
14use ferroui_controls::automation::provider::{
15    IExpandCollapseProvider, IInvokeProvider, ISelectionItemProvider, ISelectionProvider,
16    IToggleProvider, IValueProvider, ProviderError,
17};
18use ferroui_controls::automation::{
19    AutomationProperties, ElementNotEnabledException, ExpandCollapsePatternIdentifiers,
20    ExpandCollapseState, ValuePatternIdentifiers,
21};
22use ferroui_controls::primitives::{RangeBase, ToggleButton};
23use ferroui_controls::{Button, Panel};
24use ferroui_base::data::core::ValueType;
25use ferroui_base::{
26    ferro_class, ferro_class_info, ferro_impl_classes, instantiate, BoxedValue, FerroObjectImpl,
27    FerroObjectImplExt, FerroPropertyChangedEventArgs, ObjectType, Ref,
28};
29use std::rc::Rc;
30
31/// The handle through which a peer of this crate implements a provider contract, as
32/// `ferroui_controls::automation::provider::Provider` does in the controls crate (whose
33/// implementations cannot be written outside it): the peer class lists the conversion of this
34/// type in the `interfaces` of its class information.
35struct Provider<T: ObjectType>(Ref<T>);
36
37macro_rules! conversions {
38    ($($name:ident: $provider:ident),* $(,)?) => {
39        impl<T: ObjectType> Provider<T> {
40            $(
41                fn $name(peer: Ref<T>) -> Rc<dyn $provider>
42                where
43                    Self: $provider,
44                {
45                    Rc::new(Self(peer))
46                }
47            )*
48        }
49    };
50}
51
52conversions! {
53    as_expand_collapse_provider: IExpandCollapseProvider,
54    as_invoke_provider: IInvokeProvider,
55    as_selection_item_provider: ISelectionItemProvider,
56    as_value_provider: IValueProvider,
57}
58
59fn button_state(owner: &Button) -> ExpandCollapseState {
60    match owner.flyout() {
61        None => ExpandCollapseState::LeafNode,
62        Some(f) if f.is_open() => ExpandCollapseState::Expanded,
63        _ => ExpandCollapseState::Collapsed,
64    }
65}
66fn append_text(base: Option<String>, suffix: String) -> Option<String> {
67    Some(match base.filter(|value| !value.is_empty()) {
68        Some(base) if !suffix.is_empty() => format!("{base}, {suffix}"),
69        Some(base) => base,
70        None => suffix,
71    })
72}
73fn is_builtin_pen(owner: &Button) -> bool {
74    owner
75        .to_ref()
76        .cast::<InkToolbarToolButton>()
77        .is_some_and(|owner| {
78            matches!(
79                owner.tool_kind(),
80                InkToolbarTool::BallpointPen | InkToolbarTool::Pencil | InkToolbarTool::Highlighter
81            )
82        })
83}
84fn menu_has_dropdown(owner: &Button) -> bool {
85    owner.flyout().is_some()
86        && owner
87            .to_ref()
88            .cast::<InkToolbarStencilButton>()
89            .is_none_or(|owner| owner.is_ruler_item_visible() && owner.is_protractor_item_visible())
90}
91fn is_radio_item(owner: &InkToolbarFlyoutItem) -> bool {
92    matches!(
93        owner.kind(),
94        InkToolbarFlyoutItemKind::Radio | InkToolbarFlyoutItemKind::RadioCheck
95    )
96}
97// The existing framework selection-provider contract has a single error type.
98// Preserve rejection without panicking; native UIA_E_INVALIDOPERATION remains
99// distinguishable by the message, not by a separate HRESULT category.
100fn invalid_selection() -> ElementNotEnabledException {
101    ElementNotEnabledException::with_message("The requested selection operation is not supported.")
102}
103fn expand(owner: &Button) -> Result<(), ElementNotEnabledException> {
104    if !owner.is_effectively_enabled() {
105        return Err(ElementNotEnabledException::new());
106    }
107    // Native peers route through OpenL3/ExecuteToolAction(false), which selects
108    // the owner before considering configuration availability. Reuse the same
109    // guards as pointer activation, including FerroUI's real inking state (the
110    // pinned WinUI lift leaves that state unwired).
111    if let Some(tool) = owner.to_ref().cast::<InkToolbarToolButton>() {
112        tool.set_current_value(ToggleButton::is_checked_property(), Some(true));
113        if !tool.can_open_configuration() {
114            return Ok(());
115        }
116    } else if let Some(menu) = owner.to_ref().cast::<InkToolbarMenuButton>() {
117        menu.set_current_value(ToggleButton::is_checked_property(), Some(true));
118        if toolbar_is_inking(&menu) {
119            return Ok(());
120        }
121    }
122    if let Some(flyout) = owner.flyout() {
123        flyout.show_at(owner);
124    }
125    Ok(())
126}
127fn collapse(owner: &Button) -> Result<(), ElementNotEnabledException> {
128    if !owner.is_effectively_enabled() {
129        return Err(ElementNotEnabledException::new());
130    }
131    if let Some(flyout) = owner.flyout() {
132        flyout.hide();
133    }
134    Ok(())
135}
136fn observe_flyout(peer: &Ref<AutomationPeer>, owner: &Button) {
137    if let Some(flyout) = owner.flyout() {
138        let weak_peer = peer.downgrade();
139        let weak_owner = owner.to_ref().downgrade();
140        let weak_flyout = flyout.downgrade();
141        flyout.opened(move || {
142            if let (Some(peer), Some(owner), Some(flyout)) = (
143                weak_peer.upgrade(),
144                weak_owner.upgrade(),
145                weak_flyout.upgrade(),
146            ) {
147                if owner.flyout().as_ref() == Some(&flyout) {
148                    peer.raise_property_changed_event(
149                        ExpandCollapsePatternIdentifiers::expand_collapse_state_property(),
150                        Some(Rc::new(ExpandCollapseState::Collapsed)),
151                        Some(Rc::new(ExpandCollapseState::Expanded)),
152                    );
153                }
154            }
155        });
156        let weak_peer = peer.downgrade();
157        let weak_owner = owner.to_ref().downgrade();
158        let weak_flyout = flyout.downgrade();
159        flyout.closed(move || {
160            if let (Some(peer), Some(owner), Some(flyout)) = (
161                weak_peer.upgrade(),
162                weak_owner.upgrade(),
163                weak_flyout.upgrade(),
164            ) {
165                if owner.flyout().as_ref() == Some(&flyout) {
166                    peer.raise_property_changed_event(
167                        ExpandCollapsePatternIdentifiers::expand_collapse_state_property(),
168                        Some(Rc::new(ExpandCollapseState::Expanded)),
169                        Some(Rc::new(ExpandCollapseState::Collapsed)),
170                    );
171                }
172            }
173        });
174    }
175}
176fn subscribe_flyout(peer: &Ref<AutomationPeer>, owner: &Button) {
177    observe_flyout(peer, owner);
178    let weak_peer = peer.downgrade();
179    let weak_owner = owner.to_ref().downgrade();
180    owner.property_changed(move |change| {
181        if change.property() == Button::flyout_property().as_property() {
182            if let (Some(peer), Some(owner)) = (weak_peer.upgrade(), weak_owner.upgrade()) {
183                observe_flyout(&peer, &owner);
184            }
185        }
186    });
187}
188
189#[repr(C)]
190pub struct InkToolbarAutomationPeer {
191    base: ControlAutomationPeer,
192}
193ferro_class!(InkToolbarAutomationPeer:ControlAutomationPeer);
194ferro_class_info!(InkToolbarAutomationPeer {});
195ferro_impl_classes!(InkToolbarAutomationPeer:FerroObjectImpl,ControlAutomationPeerImpl);
196impl AutomationPeerImpl for InkToolbarAutomationPeer {
197    fn get_class_name_core(_this: &Self) -> String {
198        "InkToolbar".to_owned()
199    }
200    fn get_automation_control_type_core(_this: &Self) -> AutomationControlType {
201        // As WinUI (`InkToolBarAutomationPeer.cpp`, which matches the peer of WinUI 2).
202        AutomationControlType::Pane
203    }
204}
205impl InkToolbarAutomationPeer {
206    pub fn construct(owner: &InkToolbar) -> Self {
207        Self {
208            base: ControlAutomationPeer::construct(owner),
209        }
210    }
211    pub fn new(owner: &InkToolbar) -> Ref<Self> {
212        instantiate(Self::construct(owner))
213    }
214}
215
216#[repr(C)]
217pub struct InkToolbarToolButtonAutomationPeer {
218    base: RadioButtonAutomationPeer,
219}
220ferro_class!(InkToolbarToolButtonAutomationPeer:RadioButtonAutomationPeer);
221ferro_class_info!(InkToolbarToolButtonAutomationPeer{interfaces:[Rc<dyn IExpandCollapseProvider> => Provider::as_expand_collapse_provider,Rc<dyn ISelectionItemProvider> => Provider::as_selection_item_provider]});
222ferro_impl_classes!(InkToolbarToolButtonAutomationPeer:ControlAutomationPeerImpl,RadioButtonAutomationPeerImpl);
223impl FerroObjectImpl for InkToolbarToolButtonAutomationPeer {
224    fn constructed(this: &Self) {
225        Self::parent_constructed(this);
226        subscribe_flyout(&this.to_ref().upcast(), &this.owner_button());
227    }
228}
229impl AutomationPeerImpl for InkToolbarToolButtonAutomationPeer {
230    fn get_class_name_core(_this: &Self) -> String {
231        "InkToolbarToolButton".to_owned()
232    }
233    fn get_automation_control_type_core(this: &Self) -> AutomationControlType {
234        if is_builtin_pen(&this.owner_button()) {
235            AutomationControlType::Button
236        } else {
237            AutomationControlType::Custom
238        }
239    }
240    fn get_localized_control_type_core(this: &Self) -> String {
241        let owner = this.owner_button();
242        let key = if is_builtin_pen(&owner) && owner.flyout().is_some() {
243            "InkToolbarDropDownButtonControlTypeName"
244        } else {
245            "InkToolbarToolButtonControlTypeName"
246        };
247        strings::text(&owner, key)
248    }
249    fn get_help_text_core(this: &Self) -> Option<String> {
250        let owner = this.owner_button();
251        if is_builtin_pen(&owner) {
252            // Tooltip fallback must not replace an explicitly cleared color
253            // description: the native peer then reports only the palette hint.
254            append_text(
255                AutomationProperties::get_help_text(&owner),
256                strings::text(&owner, "InkToolbarColorPaletteHelpText"),
257            )
258        } else {
259            Self::parent_get_help_text_core(this)
260        }
261    }
262    fn get_name_core(this: &Self) -> Option<String> {
263        let owner = this.owner_button();
264        let name = Self::parent_get_name_core(this);
265        if owner
266            .cast::<InkToolbarToolButton>()
267            .is_some_and(|owner| owner.is_checked() == Some(true))
268        {
269            append_text(
270                name,
271                strings::text(&owner, "InkToolbarToolButtonSelectedStateName"),
272            )
273        } else {
274            name
275        }
276    }
277}
278impl InkToolbarToolButtonAutomationPeer {
279    pub fn construct(owner: &InkToolbarToolButton) -> Self {
280        Self {
281            base: RadioButtonAutomationPeer::construct(owner),
282        }
283    }
284    pub fn new(owner: &InkToolbarToolButton) -> Ref<Self> {
285        instantiate(Self::construct(owner))
286    }
287    fn owner_button(&self) -> Ref<Button> {
288        ControlAutomationPeer::owner(self)
289            .cast()
290            .expect("tool button peer owner")
291    }
292}
293impl IExpandCollapseProvider for Provider<InkToolbarToolButtonAutomationPeer> {
294    fn peer(&self) -> Ref<AutomationPeer> {
295        self.0.clone().upcast()
296    }
297    fn expand_collapse_state(&self) -> ExpandCollapseState {
298        // The upstream tool provider retains Collapsed even without L3 content.
299        match button_state(&self.0.owner_button()) {
300            ExpandCollapseState::LeafNode => ExpandCollapseState::Collapsed,
301            state => state,
302        }
303    }
304    fn shows_menu(&self) -> bool {
305        true
306    }
307    fn expand(&self) -> Result<(), ElementNotEnabledException> {
308        expand(&self.0.owner_button())
309    }
310    fn collapse(&self) -> Result<(), ElementNotEnabledException> {
311        collapse(&self.0.owner_button())
312    }
313}
314impl ISelectionItemProvider for Provider<InkToolbarToolButtonAutomationPeer> {
315    fn peer(&self) -> Ref<AutomationPeer> {
316        self.0.clone().upcast()
317    }
318    fn is_selected(&self) -> bool {
319        self.0.is_selected()
320    }
321    fn selection_container(&self) -> Option<Rc<dyn ISelectionProvider>> {
322        self.0.selection_container()
323    }
324    fn add_to_selection(&self) -> Result<(), ElementNotEnabledException> {
325        let owner = self.0.owner_button();
326        if !owner.is_effectively_enabled() {
327            return Err(ElementNotEnabledException::new());
328        }
329        if self.is_selected() {
330            Ok(())
331        } else {
332            Err(invalid_selection())
333        }
334    }
335    fn remove_from_selection(&self) -> Result<(), ElementNotEnabledException> {
336        let owner = self.0.owner_button();
337        if !owner.is_effectively_enabled() {
338            return Err(ElementNotEnabledException::new());
339        }
340        if self.is_selected() {
341            Err(invalid_selection())
342        } else {
343            Ok(())
344        }
345    }
346    fn select(&self) -> Result<(), ElementNotEnabledException> {
347        let owner = self.0.owner_button();
348        if !owner.is_effectively_enabled() {
349            return Err(ElementNotEnabledException::new());
350        }
351        owner
352            .cast::<InkToolbarToolButton>()
353            .expect("tool peer owner")
354            .set_is_checked(Some(true));
355        Ok(())
356    }
357}
358
359#[repr(C)]
360pub struct InkToolbarMenuButtonAutomationPeer {
361    base: ToggleButtonAutomationPeer,
362}
363ferro_class!(InkToolbarMenuButtonAutomationPeer:ToggleButtonAutomationPeer);
364ferro_class_info!(InkToolbarMenuButtonAutomationPeer{interfaces:[Rc<dyn IExpandCollapseProvider> => Provider::as_expand_collapse_provider]});
365ferro_impl_classes!(InkToolbarMenuButtonAutomationPeer:ControlAutomationPeerImpl);
366impl FerroObjectImpl for InkToolbarMenuButtonAutomationPeer {
367    fn constructed(this: &Self) {
368        Self::parent_constructed(this);
369        subscribe_flyout(&this.to_ref().upcast(), &this.owner_button());
370    }
371}
372impl AutomationPeerImpl for InkToolbarMenuButtonAutomationPeer {
373    fn get_class_name_core(_this: &Self) -> String {
374        "InkToolbarMenuButton".to_owned()
375    }
376    fn get_automation_control_type_core(_this: &Self) -> AutomationControlType {
377        AutomationControlType::Button
378    }
379    fn get_localized_control_type_core(this: &Self) -> String {
380        let owner = this.owner_button();
381        strings::text(
382            &owner,
383            if menu_has_dropdown(&owner) {
384                "InkToolbarStencilDropDownControlTypeName"
385            } else {
386                "InkToolbarToolButtonControlTypeName"
387            },
388        )
389    }
390    fn get_name_core(this: &Self) -> Option<String> {
391        let owner = this.owner_button();
392        let persistent = strings::text(&owner, "InkToolbarStencilButtonName");
393        let mut name = Self::parent_get_name_core(this);
394        if let Some(stencil) = owner.cast::<InkToolbarStencilButton>() {
395            if name
396                .as_deref()
397                .is_none_or(|value| value.is_empty() || value == persistent)
398            {
399                name = Some(strings::text(
400                    &owner,
401                    match stencil.selected_stencil() {
402                        InkToolbarStencilKind::Ruler => "InkToolbarStencilRulerName",
403                        InkToolbarStencilKind::Protractor => "InkToolbarStencilProtractorName",
404                    },
405                ));
406            }
407        }
408        append_text(Some(persistent), name.unwrap_or_default())
409    }
410    fn get_provider_core(this: &Self, provider_type: ValueType) -> Option<BoxedValue> {
411        let dropdown = menu_has_dropdown(&this.owner_button());
412        if (provider_type == ValueType::of::<Rc<dyn IExpandCollapseProvider>>() && !dropdown)
413            || (provider_type == ValueType::of::<Rc<dyn IToggleProvider>>() && dropdown)
414        {
415            return None;
416        }
417        Self::parent_get_provider_core(this, provider_type)
418    }
419}
420impl InkToolbarMenuButtonAutomationPeer {
421    pub fn construct(owner: &InkToolbarMenuButton) -> Self {
422        Self {
423            base: ToggleButtonAutomationPeer::construct(owner),
424        }
425    }
426    pub fn new(owner: &InkToolbarMenuButton) -> Ref<Self> {
427        instantiate(Self::construct(owner))
428    }
429    fn owner_button(&self) -> Ref<Button> {
430        ControlAutomationPeer::owner(self)
431            .cast()
432            .expect("menu button peer owner")
433    }
434}
435impl IExpandCollapseProvider for Provider<InkToolbarMenuButtonAutomationPeer> {
436    fn peer(&self) -> Ref<AutomationPeer> {
437        self.0.clone().upcast()
438    }
439    fn expand_collapse_state(&self) -> ExpandCollapseState {
440        let owner = self.0.owner_button();
441        if menu_has_dropdown(&owner) {
442            button_state(&owner)
443        } else {
444            ExpandCollapseState::LeafNode
445        }
446    }
447    fn shows_menu(&self) -> bool {
448        true
449    }
450    fn expand(&self) -> Result<(), ElementNotEnabledException> {
451        let owner = self.0.owner_button();
452        if menu_has_dropdown(&owner) {
453            expand(&owner)
454        } else {
455            Ok(())
456        }
457    }
458    fn collapse(&self) -> Result<(), ElementNotEnabledException> {
459        collapse(&self.0.owner_button())
460    }
461}
462
463#[repr(C)]
464pub struct InkToolbarFlyoutItemAutomationPeer {
465    base: ToggleButtonAutomationPeer,
466}
467ferro_class!(InkToolbarFlyoutItemAutomationPeer:ToggleButtonAutomationPeer);
468ferro_class_info!(InkToolbarFlyoutItemAutomationPeer{interfaces:[Rc<dyn ISelectionItemProvider> => Provider::as_selection_item_provider,Rc<dyn IInvokeProvider> => Provider::as_invoke_provider]});
469ferro_impl_classes!(InkToolbarFlyoutItemAutomationPeer:FerroObjectImpl,ControlAutomationPeerImpl);
470impl AutomationPeerImpl for InkToolbarFlyoutItemAutomationPeer {
471    fn get_class_name_core(_this: &Self) -> String {
472        "InkToolbarFlyoutItem".to_owned()
473    }
474    fn get_automation_control_type_core(_this: &Self) -> AutomationControlType {
475        AutomationControlType::MenuItem
476    }
477    fn get_provider_core(this: &Self, provider_type: ValueType) -> Option<BoxedValue> {
478        if provider_type == ValueType::of::<Rc<dyn IToggleProvider>>() {
479            // The WinUI item derives from ButtonBase, exposing invocation and
480            // (for radio kinds) selection rather than an additional Toggle.
481            return None;
482        }
483        if provider_type == ValueType::of::<Rc<dyn ISelectionItemProvider>>()
484            && !is_radio_item(&this.owner_item())
485        {
486            return None;
487        }
488        Self::parent_get_provider_core(this, provider_type)
489    }
490}
491impl InkToolbarFlyoutItemAutomationPeer {
492    pub fn construct(owner: &InkToolbarFlyoutItem) -> Self {
493        Self {
494            base: ToggleButtonAutomationPeer::construct(owner),
495        }
496    }
497    pub fn new(owner: &InkToolbarFlyoutItem) -> Ref<Self> {
498        instantiate(Self::construct(owner))
499    }
500    fn owner_item(&self) -> Ref<InkToolbarFlyoutItem> {
501        ControlAutomationPeer::owner(self)
502            .cast()
503            .expect("flyout item peer owner")
504    }
505    fn ensure_selection_enabled(
506        &self,
507    ) -> Result<Ref<InkToolbarFlyoutItem>, ElementNotEnabledException> {
508        let owner = self.owner_item();
509        if !owner.is_effectively_enabled() {
510            return Err(ElementNotEnabledException::new());
511        }
512        if !is_radio_item(&owner) {
513            return Err(invalid_selection());
514        }
515        Ok(owner)
516    }
517}
518impl IInvokeProvider for Provider<InkToolbarFlyoutItemAutomationPeer> {
519    fn peer(&self) -> Ref<AutomationPeer> {
520        self.0.clone().upcast()
521    }
522    fn invoke(&self) -> Result<(), ElementNotEnabledException> {
523        let owner = self.0.owner_item();
524        if !owner.is_effectively_enabled() {
525            return Err(ElementNotEnabledException::new());
526        }
527        owner.on_click();
528        Ok(())
529    }
530}
531impl ISelectionItemProvider for Provider<InkToolbarFlyoutItemAutomationPeer> {
532    fn peer(&self) -> Ref<AutomationPeer> {
533        self.0.clone().upcast()
534    }
535    fn is_selected(&self) -> bool {
536        let owner = self.0.owner_item();
537        is_radio_item(&owner) && owner.is_checked() == Some(true)
538    }
539    fn selection_container(&self) -> Option<Rc<dyn ISelectionProvider>> {
540        None
541    }
542    fn add_to_selection(&self) -> Result<(), ElementNotEnabledException> {
543        let owner = self.0.ensure_selection_enabled()?;
544        if owner.is_checked() == Some(true) {
545            return Ok(());
546        }
547        if owner
548            .visual_parent()
549            .and_then(|parent| parent.cast::<Panel>())
550            .is_some_and(|panel| {
551                panel
552                    .children()
553                    .snapshot()
554                    .iter()
555                    .filter_map(|child| child.cast::<InkToolbarFlyoutItem>())
556                    .any(|other| {
557                        other != owner && is_radio_item(&other) && other.is_checked() == Some(true)
558                    })
559            })
560        {
561            return Err(invalid_selection());
562        }
563        owner.on_click();
564        Ok(())
565    }
566    fn remove_from_selection(&self) -> Result<(), ElementNotEnabledException> {
567        let owner = self.0.ensure_selection_enabled()?;
568        if owner.is_checked() == Some(true) {
569            if owner.kind() == InkToolbarFlyoutItemKind::Radio {
570                return Err(invalid_selection());
571            }
572            owner.on_click();
573        }
574        Ok(())
575    }
576    fn select(&self) -> Result<(), ElementNotEnabledException> {
577        let owner = self.0.ensure_selection_enabled()?;
578        if owner.is_checked() != Some(true) {
579            owner.on_click();
580        }
581        Ok(())
582    }
583}
584
585#[repr(C)]
586pub struct InkToolbarStrokeWidthSliderAutomationPeer {
587    base: SliderAutomationPeer,
588}
589ferro_class!(InkToolbarStrokeWidthSliderAutomationPeer:SliderAutomationPeer);
590ferro_class_info!(InkToolbarStrokeWidthSliderAutomationPeer{interfaces:[Rc<dyn IValueProvider> => Provider::as_value_provider]});
591ferro_impl_classes!(InkToolbarStrokeWidthSliderAutomationPeer:FerroObjectImpl,ControlAutomationPeerImpl);
592impl RangeBaseAutomationPeerImpl for InkToolbarStrokeWidthSliderAutomationPeer {
593    fn owner_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
594        Self::parent_owner_property_changed(this, change);
595        if change.property() == RangeBase::value_property().as_property() {
596            this.raise_property_changed_event(
597                ValuePatternIdentifiers::value_property(),
598                change
599                    .get_old_value::<f64>()
600                    .map(|value| Rc::new(value.to_string()) as BoxedValue),
601                Some(Rc::new(change.get_new_value::<f64>().to_string()) as BoxedValue),
602            );
603        }
604    }
605}
606impl AutomationPeerImpl for InkToolbarStrokeWidthSliderAutomationPeer {
607    fn get_class_name_core(_this: &Self) -> String {
608        "InkToolbarStrokeWidthSlider".to_owned()
609    }
610    fn get_name_core(this: &Self) -> Option<String> {
611        let owner = this.owner_slider();
612        let range = strings::text(&owner, "InkToolbarStrokeWidthSliderRangeFormat")
613            .replace("%1!s!", &owner.minimum().to_string())
614            .replace("%2!s!", &owner.maximum().to_string());
615        append_text(Self::parent_get_name_core(this), range)
616    }
617}
618impl InkToolbarStrokeWidthSliderAutomationPeer {
619    pub fn construct(owner: &InkToolbarStrokeWidthSlider) -> Self {
620        Self {
621            base: SliderAutomationPeer::construct(owner),
622        }
623    }
624    pub fn new(owner: &InkToolbarStrokeWidthSlider) -> Ref<Self> {
625        instantiate(Self::construct(owner))
626    }
627    fn owner_slider(&self) -> Ref<InkToolbarStrokeWidthSlider> {
628        ControlAutomationPeer::owner(self)
629            .cast()
630            .expect("stroke width peer owner")
631    }
632}
633impl IValueProvider for Provider<InkToolbarStrokeWidthSliderAutomationPeer> {
634    fn peer(&self) -> Ref<AutomationPeer> {
635        self.0.clone().upcast()
636    }
637    fn is_read_only(&self) -> bool {
638        !self.0.owner_slider().is_effectively_enabled()
639    }
640    fn value(&self) -> Option<String> {
641        Some(self.0.owner_slider().value().to_string())
642    }
643    fn set_value(&self, value: Option<&str>) -> Result<(), ProviderError> {
644        let owner = self.0.owner_slider();
645        if !owner.is_effectively_enabled() {
646            return Err(Box::new(ElementNotEnabledException::new()));
647        }
648        let value = value.unwrap_or("").parse::<f64>()?;
649        if !value.is_finite() {
650            return Err(Box::new(super::InkError::InvalidInput(
651                "Stroke width must be finite",
652            )));
653        }
654        owner.set_range_value(value.clamp(owner.minimum(), owner.maximum()));
655        Ok(())
656    }
657}