Skip to main content

ferroui_controls_inking/
ink_toolbar_buttons.rs

1//! Toolbar controls adapted from the WinUI InkToolbar control family.
2use super::color_names::{
3    color_name, color_resource_key, contrast_ratio, default_palette, high_contrast_colors,
4};
5#[path = "ink_toolbar_strings.rs"]
6pub(crate) mod strings;
7use super::{
8    InkDrawingAttributes, InkError, InkHighContrastAdjustment, InkToolbar, InkToolbarFlyoutItem,
9    InkToolbarFlyoutItemKind, InkToolbarMenuKind, InkToolbarStencilKind, InkToolbarToggle,
10    InkToolbarTool, PenTipShape,
11};
12use ferroui_controls::automation::AutomationProperties;
13use ferroui_controls::primitives::{TemplatedControlImpl, ToggleButton, ToggleButtonImpl};
14use ferroui_controls::{
15    Button, ButtonImpl, ButtonImplExt, CheckBox, ContentControlImpl, Control, ControlImpl, Flyout,
16    RadioButton, StackPanel,
17};
18use ferroui_base::collections::FerroList;
19use ferroui_base::controls::ResourceKey;
20use ferroui_base::input::{
21    HoldingRoutedEventArgs, HoldingState, InputElement, InputElementImpl, InputElementImplExt,
22    TappedEventArgs,
23};
24use ferroui_base::interactivity::InteractiveImpl;
25use ferroui_base::layout::LayoutableImpl;
26use ferroui_base::media::immutable::ImmutableSolidColorBrush;
27use ferroui_base::media::{Color, IBrush};
28use ferroui_base::styling::ControlTheme;
29use ferroui_base::{
30    ferro_class, ferro_class_info, ferro_impl_classes, ferro_properties, instantiate,
31    DirectProperty, FerroObject, FerroObjectImpl, FerroObjectImplExt, FerroProperty,
32    FerroPropertyChangedEventArgs, Ref, Size, StyledElement, StyledElementImpl, StyledProperty,
33    StyledPropertyOptions, TypeInfo, VisualImpl,
34};
35use std::cell::{Cell, RefCell};
36use std::rc::Rc;
37
38type Palette = FerroList<Rc<dyn IBrush>>;
39
40/// A mutually exclusive toolbar tool; inherits keyboard and selection automation from RadioButton.
41#[repr(C)]
42pub struct InkToolbarToolButton {
43    base: RadioButton,
44    tool_kind: Cell<InkToolbarTool>,
45    permit_flyout: Cell<bool>,
46}
47ferro_class!(InkToolbarToolButton: RadioButton);
48ferro_class_info!(InkToolbarToolButton { new: InkToolbarToolButton::new, markup: {
49    properties: [ToolKind: InkToolbarTool { get: InkToolbarToolButton::tool_kind }],
50} });
51ferro_impl_classes!(InkToolbarToolButton: FerroObjectImpl, VisualImpl, LayoutableImpl, InteractiveImpl, TemplatedControlImpl, ContentControlImpl, ToggleButtonImpl);
52impl InputElementImpl for InkToolbarToolButton {
53    fn on_right_tapped(this: &Self, event: &TappedEventArgs) {
54        Self::parent_on_right_tapped(this, event);
55        if this.is_effectively_enabled() {
56            this.activate_secondary();
57            event.set_handled(true);
58        }
59    }
60    fn on_holding(this: &Self, event: &HoldingRoutedEventArgs) {
61        Self::parent_on_holding(this, event);
62        if this.is_effectively_enabled() && event.holding_state() == HoldingState::Started {
63            this.activate_secondary();
64            event.set_handled(true);
65        }
66    }
67}
68impl ButtonImpl for InkToolbarToolButton {
69    fn on_click(this: &Self) {
70        let was_checked = this.is_checked() == Some(true);
71        let previous = this.permit_flyout.replace(was_checked);
72        Self::parent_on_click(this);
73        this.permit_flyout.set(previous);
74    }
75    fn open_flyout(this: &Self) {
76        if this.permit_flyout.get() && this.can_open_configuration() {
77            Self::parent_open_flyout(this);
78        }
79    }
80}
81impl ControlImpl for InkToolbarToolButton {
82    fn on_create_automation_peer(this: &Self) -> Ref<ferroui_controls::automation::peers::AutomationPeer> {
83        super::InkToolbarToolButtonAutomationPeer::new(this).upcast()
84    }
85}
86
87impl StyledElementImpl for InkToolbarToolButton {
88    fn style_key_override(this: &Self) -> &'static TypeInfo {
89        toolbar_style_key(this, RadioButton::TYPE)
90    }
91}
92ferro_properties! { impl InkToolbarToolButton {
93    pub fn is_extension_glyph_shown_property() -> StyledProperty<bool> { FerroProperty::register::<InkToolbarToolButton,_>("IsExtensionGlyphShown", true) }
94} }
95impl InkToolbarToolButton {
96    fn static_constructor() {
97        InputElement::is_holding_enabled_property()
98            .override_default_value::<InkToolbarToolButton>(true);
99    }
100    fn activate_secondary(&self) {
101        self.set_current_value(ToggleButton::is_checked_property(), Some(true));
102        self.open_flyout();
103    }
104    pub(crate) fn can_open_configuration(&self) -> bool {
105        !toolbar_is_inking(self)
106            && self
107                .to_ref()
108                .cast::<InkToolbarCustomPenButton>()
109                .is_none_or(|pen| pen.configuration_content().is_some())
110            && self
111                .to_ref()
112                .cast::<InkToolbarCustomToolButton>()
113                .is_none_or(|tool| tool.configuration_content().is_some())
114            && self
115                .to_ref()
116                .cast::<InkToolbarEraserButton>()
117                .is_none_or(|eraser| eraser.is_clear_all_visible())
118    }
119    pub fn construct() -> Self {
120        Self {
121            base: RadioButton::construct(),
122            tool_kind: Cell::new(InkToolbarTool::CustomTool),
123            permit_flyout: Cell::new(true),
124        }
125    }
126    pub fn new() -> Ref<Self> {
127        instantiate(Self::construct())
128    }
129    pub fn tool_kind(&self) -> InkToolbarTool {
130        self.tool_kind.get()
131    }
132    pub(crate) fn set_tool_kind(&self, value: InkToolbarTool) {
133        self.tool_kind.set(value);
134    }
135    pub fn is_extension_glyph_shown(&self) -> bool {
136        self.get_value(Self::is_extension_glyph_shown_property())
137    }
138    pub fn set_is_extension_glyph_shown(&self, value: bool) {
139        self.set_value(Self::is_extension_glyph_shown_property(), value);
140    }
141}
142
143/// Base pen tool. Palettes are observable handles owned independently by each pen.
144#[repr(C)]
145pub struct InkToolbarPenButton {
146    base: InkToolbarToolButton,
147    palette_subscription: RefCell<Option<(Palette, u64)>>,
148    selected_brush: RefCell<Option<Rc<dyn IBrush>>>,
149    contrast_palette: RefCell<Option<Vec<Rc<dyn IBrush>>>>,
150    contrast_signature: Cell<Option<(InkHighContrastAdjustment, Color, Color)>>,
151    normal_index: Cell<i32>,
152    contrast_index: Cell<i32>,
153}
154ferro_class! { InkToolbarPenButton: InkToolbarToolButton, virtuals InkToolbarPenButtonImpl: ToggleButtonImpl {
155    fn create_ink_drawing_attributes(this) -> Result<InkDrawingAttributes, InkError>;
156} }
157ferro_class_info!(InkToolbarPenButton {
158    new: InkToolbarPenButton::new
159});
160ferro_impl_classes!(InkToolbarPenButton: StyledElementImpl, VisualImpl, LayoutableImpl, InteractiveImpl, InputElementImpl, ControlImpl, TemplatedControlImpl, ContentControlImpl, ButtonImpl, ToggleButtonImpl);
161impl FerroObjectImpl for InkToolbarPenButton {
162    fn constructed(this: &Self) {
163        Self::parent_constructed(this);
164        this.set_palette(default_palette(false));
165        let weak = this.to_ref().downgrade();
166        this.resources_changed(move |_| {
167            if let Some(pen) = weak.upgrade() {
168                pen.refresh_palette();
169            }
170        });
171        let weak = this.to_ref().downgrade();
172        this.actual_theme_variant_changed(move || {
173            if let Some(pen) = weak.upgrade() {
174                pen.refresh_palette();
175            }
176        });
177    }
178    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
179        Self::parent_on_property_changed(this, change);
180        if change.property() == Self::palette_property().as_property() {
181            if let Some((old, token)) = this.palette_subscription.borrow_mut().take() {
182                old.remove_collection_changed(token);
183            }
184            let palette = this.palette();
185            let weak = this.to_ref().downgrade();
186            let token = palette.add_collection_changed(Rc::new(move |_| {
187                if let Some(pen) = weak.upgrade() {
188                    pen.rebuild_contrast_palette(true);
189                }
190            }));
191            *this.palette_subscription.borrow_mut() = Some((palette, token));
192            this.rebuild_contrast_palette(true);
193        } else if change.property() == Self::selected_brush_index_property().as_property() {
194            this.update_selected_brush();
195        } else if change.property() == Self::min_stroke_width_property().as_property()
196            || change.property() == Self::max_stroke_width_property().as_property()
197        {
198            this.coerce_value(Self::selected_stroke_width_property().as_property());
199        }
200    }
201}
202impl InkToolbarPenButtonImpl for InkToolbarPenButton {
203    fn create_ink_drawing_attributes(this: &Self) -> Result<InkDrawingAttributes, InkError> {
204        let width = this.determine_stroke_width();
205        let attrs = InkDrawingAttributes {
206            color: this.selected_color()?,
207            size: Size::new(width, width),
208            ..InkDrawingAttributes::default()
209        };
210        attrs.validate()?;
211        Ok(attrs)
212    }
213}
214ferro_properties! { impl InkToolbarPenButton {
215    pub fn palette_property() -> StyledProperty<FerroList<Rc<dyn IBrush>>> { FerroProperty::register::<InkToolbarPenButton,_>("Palette", FerroList::new()) }
216    pub fn min_stroke_width_property() -> StyledProperty<f64> { FerroProperty::register_with::<InkToolbarPenButton,_>("MinStrokeWidth", StyledPropertyOptions::new(0.5_f64).coerce(|_,v| if v.is_finite() && v > 0.0 {v} else {0.5})) }
217    pub fn max_stroke_width_property() -> StyledProperty<f64> { FerroProperty::register_with::<InkToolbarPenButton,_>("MaxStrokeWidth", StyledPropertyOptions::new(32.0_f64).coerce(|_,v| if v.is_finite() && v > 0.0 {v} else {32.0})) }
218    pub fn selected_brush_property() -> DirectProperty<InkToolbarPenButton,Option<Rc<dyn IBrush>>> { FerroProperty::register_direct::<InkToolbarPenButton,_>("SelectedBrush",InkToolbarPenButton::selected_brush,None,None) }
219    pub fn selected_brush_index_property() -> StyledProperty<i32> { FerroProperty::register::<InkToolbarPenButton,_>("SelectedBrushIndex", 0) }
220    pub fn selected_stroke_width_property() -> StyledProperty<f64> {
221        FerroProperty::register_with::<InkToolbarPenButton,_>("SelectedStrokeWidth", StyledPropertyOptions::new(2.0_f64).coerce(|sender,value| {
222            let min = sender.get_value(Self::min_stroke_width_property());
223            let max = sender.get_value(Self::max_stroke_width_property()).max(min);
224            if value.is_finite() { value.clamp(min,max) } else { min }
225        }))
226    }
227} }
228impl InkToolbarPenButton {
229    pub fn construct() -> Self {
230        Self {
231            base: InkToolbarToolButton::construct(),
232            palette_subscription: RefCell::new(None),
233            selected_brush: RefCell::new(None),
234            contrast_palette: RefCell::new(None),
235            contrast_signature: Cell::new(None),
236            normal_index: Cell::new(0),
237            contrast_index: Cell::new(0),
238        }
239    }
240    pub fn new() -> Ref<Self> {
241        instantiate(Self::construct())
242    }
243    pub fn palette(&self) -> Palette {
244        self.get_value(Self::palette_property())
245    }
246    pub fn set_palette(&self, value: Palette) {
247        self.set_value(Self::palette_property(), value);
248    }
249    pub fn min_stroke_width(&self) -> f64 {
250        self.get_value(Self::min_stroke_width_property())
251    }
252    pub fn set_min_stroke_width(&self, value: f64) {
253        self.set_value(Self::min_stroke_width_property(), value);
254    }
255    pub fn max_stroke_width(&self) -> f64 {
256        self.get_value(Self::max_stroke_width_property())
257    }
258    pub fn set_max_stroke_width(&self, value: f64) {
259        self.set_value(Self::max_stroke_width_property(), value);
260    }
261    pub fn selected_brush(&self) -> Option<Rc<dyn IBrush>> {
262        self.selected_brush.borrow().clone()
263    }
264    pub fn selected_brush_index(&self) -> i32 {
265        self.get_value(Self::selected_brush_index_property())
266    }
267    pub fn set_selected_brush_index(&self, value: i32) {
268        self.set_value(Self::selected_brush_index_property(), value);
269    }
270    pub fn selected_stroke_width(&self) -> f64 {
271        self.get_value(Self::selected_stroke_width_property())
272    }
273    pub fn set_selected_stroke_width(&self, value: f64) {
274        self.set_value(Self::selected_stroke_width_property(), value);
275    }
276    pub fn determine_stroke_width(&self) -> f64 {
277        self.selected_stroke_width().clamp(
278            self.min_stroke_width(),
279            self.max_stroke_width().max(self.min_stroke_width()),
280        )
281    }
282    pub fn selected_color(&self) -> Result<Color, InkError> {
283        self.selected_brush()
284            .and_then(|b| b.as_solid_color_brush().map(|s| s.color()))
285            .ok_or(InkError::InvalidInput(
286                "Ink pens require a selected solid-color brush",
287            ))
288    }
289    pub(crate) fn effective_palette(&self) -> Vec<Rc<dyn IBrush>> {
290        self.contrast_palette
291            .borrow()
292            .clone()
293            .unwrap_or_else(|| self.palette().snapshot().as_ref().clone())
294    }
295    pub(crate) fn refresh_palette(&self) {
296        self.rebuild_contrast_palette(false);
297    }
298    fn rebuild_contrast_palette(&self, force: bool) {
299        let mut parent = self.parent();
300        let mut mode = InkHighContrastAdjustment::UseSystemColorsWhenNecessary;
301        while let Some(element) = parent {
302            if let Some(toolbar) = element.cast::<InkToolbar>() {
303                mode = toolbar
304                    .effective_ink_presenter()
305                    .map(|p| p.high_contrast_adjustment())
306                    .unwrap_or(mode);
307                break;
308            }
309            parent = element.parent();
310        }
311        let signature = high_contrast_colors(self, self.tool_kind() == InkToolbarTool::Highlighter)
312            .map(|(background, system)| (mode, background, system));
313        let previous = self.contrast_signature.replace(signature);
314        if force || previous != signature {
315            let palette = signature.map(|(mode, background, system)| {
316                let mut result = self
317                    .palette()
318                    .snapshot()
319                    .iter()
320                    .filter(|brush| {
321                        brush
322                            .as_solid_color_brush()
323                            .is_some_and(|brush| match mode {
324                                InkHighContrastAdjustment::UseOriginalColors => true,
325                                InkHighContrastAdjustment::UseSystemColors => false,
326                                InkHighContrastAdjustment::UseSystemColorsWhenNecessary => {
327                                    contrast_ratio(brush.color(), background) >= 7.0
328                                }
329                            })
330                    })
331                    .cloned()
332                    .collect::<Vec<_>>();
333                if !result.iter().any(|brush| {
334                    brush
335                        .as_solid_color_brush()
336                        .is_some_and(|brush| brush.color() == system)
337                }) {
338                    result.push(Rc::new(ImmutableSolidColorBrush::new(system)) as Rc<dyn IBrush>);
339                }
340                result
341            });
342            *self.contrast_palette.borrow_mut() = palette;
343            if previous.is_some() != signature.is_some() {
344                let index = if signature.is_some() {
345                    self.contrast_index.get()
346                } else {
347                    self.normal_index.get()
348                };
349                self.set_current_value(Self::selected_brush_index_property(), index);
350            }
351        }
352        self.update_selected_brush();
353    }
354    fn update_selected_brush(&self) {
355        let index = self.selected_brush_index();
356        let items = self.effective_palette();
357        // The source retains the previous brush for an invalid palette index.
358        let Some(selected) = (index >= 0)
359            .then(|| items.get(index as usize).cloned())
360            .flatten()
361        else {
362            return;
363        };
364        if self.contrast_signature.get().is_some() {
365            self.contrast_index.set(index);
366        } else {
367            self.normal_index.set(index);
368        }
369        let selected = Some(selected);
370        self.set_and_raise(
371            Self::selected_brush_property(),
372            &self.selected_brush,
373            selected.clone(),
374        );
375        let help = selected.and_then(|b| {
376            b.as_solid_color_brush().and_then(|brush| {
377                color_resource_key(brush.color()).map(|_| color_name(self, brush.color()))
378            })
379        });
380        AutomationProperties::set_help_text(self, help.as_deref());
381    }
382}
383
384#[repr(C)]
385pub struct InkToolbarBallpointPenButton {
386    base: InkToolbarPenButton,
387}
388ferro_class!(InkToolbarBallpointPenButton:InkToolbarPenButton);
389ferro_class_info!(InkToolbarBallpointPenButton {
390    new: InkToolbarBallpointPenButton::new
391});
392ferro_impl_classes!(InkToolbarBallpointPenButton:StyledElementImpl,VisualImpl,LayoutableImpl,InteractiveImpl,InputElementImpl,ControlImpl,TemplatedControlImpl,ContentControlImpl,ButtonImpl,ToggleButtonImpl);
393impl FerroObjectImpl for InkToolbarBallpointPenButton {
394    fn constructed(this: &Self) {
395        Self::parent_constructed(this);
396        this.set_tool_kind(InkToolbarTool::BallpointPen);
397        strings::bind_content(this, "InkToolbarBallpointPenButtonName");
398    }
399}
400impl InkToolbarPenButtonImpl for InkToolbarBallpointPenButton {
401    fn create_ink_drawing_attributes(this: &Self) -> Result<InkDrawingAttributes, InkError> {
402        let width = this.determine_stroke_width();
403        Ok(InkDrawingAttributes {
404            color: this.selected_color()?,
405            size: Size::new(width, width),
406            ..InkDrawingAttributes::default()
407        })
408    }
409}
410impl InkToolbarBallpointPenButton {
411    fn static_constructor() {
412        // The stroke widths of the style of WinUI's `InkToolbarBallpointPenButton` (`InkToolBar.xaml`), as
413        // defaults, so the button has them without the WinUI theme too.
414        override_stroke_widths::<InkToolbarBallpointPenButton>(1.0, 24.0, 2.0);
415    }
416    pub fn construct() -> Self {
417        Self {
418            base: InkToolbarPenButton::construct(),
419        }
420    }
421    pub fn new() -> Ref<Self> {
422        instantiate(Self::construct())
423    }
424}
425#[repr(C)]
426pub struct InkToolbarPencilButton {
427    base: InkToolbarPenButton,
428}
429ferro_class!(InkToolbarPencilButton:InkToolbarPenButton);
430ferro_class_info!(InkToolbarPencilButton {
431    new: InkToolbarPencilButton::new
432});
433ferro_impl_classes!(InkToolbarPencilButton:StyledElementImpl,VisualImpl,LayoutableImpl,InteractiveImpl,InputElementImpl,ControlImpl,TemplatedControlImpl,ContentControlImpl,ButtonImpl,ToggleButtonImpl);
434impl FerroObjectImpl for InkToolbarPencilButton {
435    fn constructed(this: &Self) {
436        Self::parent_constructed(this);
437        this.set_tool_kind(InkToolbarTool::Pencil);
438        strings::bind_content(this, "InkToolbarPencilButtonName");
439    }
440}
441impl InkToolbarPenButtonImpl for InkToolbarPencilButton {
442    fn create_ink_drawing_attributes(this: &Self) -> Result<InkDrawingAttributes, InkError> {
443        let width = this.determine_stroke_width();
444        Ok(InkDrawingAttributes {
445            color: this.selected_color()?,
446            size: Size::new(width, width),
447            ..InkDrawingAttributes::create_for_pencil()
448        })
449    }
450}
451impl InkToolbarPencilButton {
452    fn static_constructor() {
453        // The stroke widths of the style of WinUI's `InkToolbarPencilButton` (`InkToolBar.xaml`), as
454        // defaults, so the button has them without the WinUI theme too.
455        override_stroke_widths::<InkToolbarPencilButton>(1.0, 24.0, 4.0);
456    }
457    pub fn construct() -> Self {
458        Self {
459            base: InkToolbarPenButton::construct(),
460        }
461    }
462    pub fn new() -> Ref<Self> {
463        instantiate(Self::construct())
464    }
465}
466#[repr(C)]
467pub struct InkToolbarHighlighterButton {
468    base: InkToolbarPenButton,
469}
470ferro_class!(InkToolbarHighlighterButton:InkToolbarPenButton);
471ferro_class_info!(InkToolbarHighlighterButton {
472    new: InkToolbarHighlighterButton::new
473});
474ferro_impl_classes!(InkToolbarHighlighterButton:StyledElementImpl,VisualImpl,LayoutableImpl,InteractiveImpl,InputElementImpl,ControlImpl,TemplatedControlImpl,ContentControlImpl,ButtonImpl,ToggleButtonImpl);
475impl FerroObjectImpl for InkToolbarHighlighterButton {
476    fn constructed(this: &Self) {
477        Self::parent_constructed(this);
478        this.set_tool_kind(InkToolbarTool::Highlighter);
479        strings::bind_content(this, "InkToolbarHighlighterButtonName");
480        this.set_palette(default_palette(true));
481        // As WinUI (`InkToolBarHighlighterButton.h`): a local value, above the style's 36.
482        this.set_selected_stroke_width(16.0);
483    }
484}
485impl InkToolbarPenButtonImpl for InkToolbarHighlighterButton {
486    fn create_ink_drawing_attributes(this: &Self) -> Result<InkDrawingAttributes, InkError> {
487        let width = this.determine_stroke_width();
488        Ok(InkDrawingAttributes {
489            color: this.selected_color()?,
490            size: Size::new(width / 3.0, width),
491            pen_tip: PenTipShape::Rectangle,
492            draw_as_highlighter: true,
493            ..InkDrawingAttributes::default()
494        })
495    }
496}
497impl InkToolbarHighlighterButton {
498    fn static_constructor() {
499        // The style of WinUI's highlighter sets 12, 64 and 36; its constructor then sets 16
500        // as a local value, which wins (see `constructed`).
501        override_stroke_widths::<InkToolbarHighlighterButton>(12.0, 64.0, 16.0);
502    }
503    pub fn construct() -> Self {
504        Self {
505            base: InkToolbarPenButton::construct(),
506        }
507    }
508    pub fn new() -> Ref<Self> {
509        instantiate(Self::construct())
510    }
511}
512
513/// Custom pen construction uses a real virtual dispatch, so subclasses can define the nib.
514#[repr(C)]
515pub struct InkToolbarCustomPen {
516    base: FerroObject,
517}
518ferro_class! { InkToolbarCustomPen: FerroObject, virtuals InkToolbarCustomPenImpl: FerroObjectImpl {
519    fn create_ink_drawing_attributes_core(this, brush: Option<Rc<dyn IBrush>>, stroke_width: f64) -> Result<InkDrawingAttributes,InkError>;
520} }
521ferro_class_info!(InkToolbarCustomPen {
522    new: InkToolbarCustomPen::new
523});
524ferro_impl_classes!(InkToolbarCustomPen: FerroObjectImpl);
525impl InkToolbarCustomPenImpl for InkToolbarCustomPen {
526    fn create_ink_drawing_attributes_core(
527        _this: &Self,
528        brush: Option<Rc<dyn IBrush>>,
529        stroke_width: f64,
530    ) -> Result<InkDrawingAttributes, InkError> {
531        let color = brush
532            .as_ref()
533            .and_then(|brush| brush.as_solid_color_brush())
534            .map(|brush| brush.color())
535            .unwrap_or_else(|| InkDrawingAttributes::default().color);
536        let attrs = InkDrawingAttributes {
537            color,
538            size: Size::new(stroke_width, stroke_width),
539            ..InkDrawingAttributes::default()
540        };
541        attrs.validate()?;
542        Ok(attrs)
543    }
544}
545impl InkToolbarCustomPen {
546    pub fn construct() -> Self {
547        Self {
548            base: FerroObject::construct(),
549        }
550    }
551    pub fn new() -> Ref<Self> {
552        instantiate(Self::construct())
553    }
554    pub fn create_ink_drawing_attributes(
555        &self,
556        brush: Option<Rc<dyn IBrush>>,
557        stroke_width: f64,
558    ) -> Result<InkDrawingAttributes, InkError> {
559        self.create_ink_drawing_attributes_core(brush, stroke_width)
560    }
561}
562#[repr(C)]
563pub struct InkToolbarCustomPenButton {
564    base: InkToolbarPenButton,
565}
566ferro_class!(InkToolbarCustomPenButton:InkToolbarPenButton);
567ferro_class_info!(InkToolbarCustomPenButton {
568    new: InkToolbarCustomPenButton::new
569});
570ferro_impl_classes!(InkToolbarCustomPenButton:StyledElementImpl,VisualImpl,LayoutableImpl,InteractiveImpl,InputElementImpl,ControlImpl,TemplatedControlImpl,ContentControlImpl,ButtonImpl,ToggleButtonImpl);
571impl FerroObjectImpl for InkToolbarCustomPenButton {
572    fn constructed(this: &Self) {
573        Self::parent_constructed(this);
574        this.set_tool_kind(InkToolbarTool::CustomPen);
575    }
576}
577impl InkToolbarPenButtonImpl for InkToolbarCustomPenButton {
578    fn create_ink_drawing_attributes(this: &Self) -> Result<InkDrawingAttributes, InkError> {
579        match this.custom_pen() {
580            Some(pen) => pen.create_ink_drawing_attributes(
581                this.selected_brush(),
582                this.determine_stroke_width(),
583            ),
584            None => {
585                let width = this.determine_stroke_width();
586                Ok(InkDrawingAttributes {
587                    color: this.selected_color()?,
588                    size: Size::new(width, width),
589                    ..InkDrawingAttributes::default()
590                })
591            }
592        }
593    }
594}
595ferro_properties! {impl InkToolbarCustomPenButton{
596    pub fn custom_pen_property()->StyledProperty<Option<Ref<InkToolbarCustomPen>>>{FerroProperty::register::<InkToolbarCustomPenButton,_>("CustomPen",None)}
597    pub fn configuration_content_property()->StyledProperty<Option<Ref<Control>>>{FerroProperty::register::<InkToolbarCustomPenButton,_>("ConfigurationContent",None)}
598}}
599impl InkToolbarCustomPenButton {
600    fn static_constructor() {
601        // The stroke widths of the style of WinUI's `InkToolbarCustomPenButton` (`InkToolBar.xaml`), as
602        // defaults, so the button has them without the WinUI theme too.
603        override_stroke_widths::<InkToolbarCustomPenButton>(1.0, 10.0, 5.0);
604    }
605    pub fn construct() -> Self {
606        Self {
607            base: InkToolbarPenButton::construct(),
608        }
609    }
610    pub fn new() -> Ref<Self> {
611        instantiate(Self::construct())
612    }
613    pub fn custom_pen(&self) -> Option<Ref<InkToolbarCustomPen>> {
614        self.get_value(Self::custom_pen_property())
615    }
616    pub fn set_custom_pen(&self, value: Option<Ref<InkToolbarCustomPen>>) {
617        self.set_value(Self::custom_pen_property(), value);
618    }
619    pub fn configuration_content(&self) -> Option<Ref<Control>> {
620        self.get_value(Self::configuration_content_property())
621    }
622    pub fn set_configuration_content(&self, value: Option<Ref<Control>>) {
623        self.set_value(Self::configuration_content_property(), value);
624    }
625}
626
627#[repr(C)]
628pub struct InkToolbarEraserButton {
629    base: InkToolbarToolButton,
630}
631ferro_class!(InkToolbarEraserButton:InkToolbarToolButton);
632ferro_class_info!(InkToolbarEraserButton {
633    new: InkToolbarEraserButton::new
634});
635ferro_impl_classes!(InkToolbarEraserButton:StyledElementImpl,VisualImpl,LayoutableImpl,InteractiveImpl,InputElementImpl,ControlImpl,TemplatedControlImpl,ContentControlImpl,ButtonImpl,ToggleButtonImpl);
636impl FerroObjectImpl for InkToolbarEraserButton {
637    fn constructed(this: &Self) {
638        Self::parent_constructed(this);
639        this.set_tool_kind(InkToolbarTool::Eraser);
640        strings::bind_content(this, "InkToolbarEraserButtonName");
641    }
642    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
643        Self::parent_on_property_changed(this, change);
644        if change.property() == Self::is_clear_all_visible_property().as_property() {
645            this.set_is_extension_glyph_shown(this.is_clear_all_visible());
646        }
647    }
648}
649impl InkToolbarEraserButton {
650    pub fn construct() -> Self {
651        Self {
652            base: InkToolbarToolButton::construct(),
653        }
654    }
655    pub fn new() -> Ref<Self> {
656        instantiate(Self::construct())
657    }
658}
659#[repr(C)]
660pub struct InkToolbarCustomToolButton {
661    base: InkToolbarToolButton,
662}
663ferro_class!(InkToolbarCustomToolButton:InkToolbarToolButton);
664ferro_class_info!(InkToolbarCustomToolButton {
665    new: InkToolbarCustomToolButton::new
666});
667ferro_impl_classes!(InkToolbarCustomToolButton:StyledElementImpl,VisualImpl,LayoutableImpl,InteractiveImpl,InputElementImpl,ControlImpl,TemplatedControlImpl,ContentControlImpl,ButtonImpl,ToggleButtonImpl);
668impl FerroObjectImpl for InkToolbarCustomToolButton {
669    fn constructed(this: &Self) {
670        Self::parent_constructed(this);
671        this.set_tool_kind(InkToolbarTool::CustomTool);
672    }
673}
674impl InkToolbarCustomToolButton {
675    pub fn construct() -> Self {
676        Self {
677            base: InkToolbarToolButton::construct(),
678        }
679    }
680    pub fn new() -> Ref<Self> {
681        instantiate(Self::construct())
682    }
683}
684fn override_stroke_widths<T: ferroui_base::ObjectType>(minimum: f64, maximum: f64, selected: f64) {
685    InkToolbarPenButton::min_stroke_width_property().override_default_value::<T>(minimum);
686    InkToolbarPenButton::max_stroke_width_property().override_default_value::<T>(maximum);
687    InkToolbarPenButton::selected_stroke_width_property().override_default_value::<T>(selected);
688}
689
690/// Whether a change of `property` of a pen button can change the drawing attributes it
691/// creates: WinUI listens to `SelectedBrush` and `SelectedStrokeWidth` alone; the port also
692/// names what those two are computed from.
693pub(crate) fn affects_pen_attributes(property: &FerroProperty) -> bool {
694    property == InkToolbarPenButton::selected_brush_property().as_property()
695        || property == InkToolbarPenButton::selected_brush_index_property().as_property()
696        || property == InkToolbarPenButton::selected_stroke_width_property().as_property()
697        || property == InkToolbarPenButton::min_stroke_width_property().as_property()
698        || property == InkToolbarPenButton::max_stroke_width_property().as_property()
699        || property == InkToolbarPenButton::palette_property().as_property()
700        || property == InkToolbarCustomPenButton::custom_pen_property().as_property()
701}
702
703ferro_properties! {impl InkToolbarEraserButton{pub fn is_clear_all_visible_property()->StyledProperty<bool>{FerroProperty::register::<InkToolbarEraserButton,_>("IsClearAllVisible",true)}}}
704impl InkToolbarEraserButton {
705    pub fn is_clear_all_visible(&self) -> bool {
706        self.get_value(Self::is_clear_all_visible_property())
707    }
708    pub fn set_is_clear_all_visible(&self, value: bool) {
709        self.set_value(Self::is_clear_all_visible_property(), value);
710    }
711}
712ferro_properties! {impl InkToolbarCustomToolButton{pub fn configuration_content_property()->StyledProperty<Option<Ref<Control>>>{FerroProperty::register::<InkToolbarCustomToolButton,_>("ConfigurationContent",None)}}}
713impl InkToolbarCustomToolButton {
714    pub fn configuration_content(&self) -> Option<Ref<Control>> {
715        self.get_value(Self::configuration_content_property())
716    }
717    pub fn set_configuration_content(&self, value: Option<Ref<Control>>) {
718        self.set_value(Self::configuration_content_property(), value);
719    }
720}
721
722#[repr(C)]
723pub struct InkToolbarToggleButton {
724    base: CheckBox,
725    toggle_kind: Cell<InkToolbarToggle>,
726}
727ferro_class!(InkToolbarToggleButton:CheckBox);
728ferro_class_info!(InkToolbarToggleButton{new:InkToolbarToggleButton::new,markup:{properties:[ToggleKind:InkToolbarToggle{get:InkToolbarToggleButton::toggle_kind}],}});
729ferro_impl_classes!(InkToolbarToggleButton:FerroObjectImpl,VisualImpl,LayoutableImpl,InteractiveImpl,InputElementImpl,ControlImpl,TemplatedControlImpl,ContentControlImpl,ButtonImpl,ToggleButtonImpl);
730impl StyledElementImpl for InkToolbarToggleButton {
731    fn style_key_override(this: &Self) -> &'static TypeInfo {
732        toolbar_style_key(this, CheckBox::TYPE)
733    }
734}
735impl InkToolbarToggleButton {
736    pub fn construct() -> Self {
737        Self {
738            base: CheckBox::construct(),
739            toggle_kind: Cell::new(InkToolbarToggle::Custom),
740        }
741    }
742    pub fn new() -> Ref<Self> {
743        instantiate(Self::construct())
744    }
745    pub fn toggle_kind(&self) -> InkToolbarToggle {
746        self.toggle_kind.get()
747    }
748}
749#[repr(C)]
750pub struct InkToolbarCustomToggleButton {
751    base: InkToolbarToggleButton,
752}
753ferro_class!(InkToolbarCustomToggleButton:InkToolbarToggleButton);
754ferro_class_info!(InkToolbarCustomToggleButton {
755    new: InkToolbarCustomToggleButton::new
756});
757ferro_impl_classes!(InkToolbarCustomToggleButton:StyledElementImpl,VisualImpl,LayoutableImpl,InteractiveImpl,InputElementImpl,ControlImpl,TemplatedControlImpl,ContentControlImpl,ButtonImpl,ToggleButtonImpl);
758impl FerroObjectImpl for InkToolbarCustomToggleButton {
759    fn constructed(this: &Self) {
760        Self::parent_constructed(this);
761        this.toggle_kind.set(InkToolbarToggle::Custom);
762    }
763}
764impl InkToolbarCustomToggleButton {
765    pub fn construct() -> Self {
766        Self {
767            base: InkToolbarToggleButton::construct(),
768        }
769    }
770    pub fn new() -> Ref<Self> {
771        instantiate(Self::construct())
772    }
773}
774#[repr(C)]
775pub struct InkToolbarRulerButton {
776    base: InkToolbarToggleButton,
777}
778ferro_class!(InkToolbarRulerButton:InkToolbarToggleButton);
779ferro_class_info!(InkToolbarRulerButton {
780    new: InkToolbarRulerButton::new
781});
782ferro_impl_classes!(InkToolbarRulerButton:StyledElementImpl,VisualImpl,LayoutableImpl,InteractiveImpl,InputElementImpl,ControlImpl,TemplatedControlImpl,ContentControlImpl,ButtonImpl,ToggleButtonImpl);
783impl FerroObjectImpl for InkToolbarRulerButton {
784    fn constructed(this: &Self) {
785        Self::parent_constructed(this);
786        this.toggle_kind.set(InkToolbarToggle::Ruler);
787        strings::bind_content(this, "InkToolbarStencilRulerName");
788    }
789}
790impl InkToolbarRulerButton {
791    pub fn construct() -> Self {
792        Self {
793            base: InkToolbarToggleButton::construct(),
794        }
795    }
796    pub fn new() -> Ref<Self> {
797        instantiate(Self::construct())
798    }
799}
800#[repr(C)]
801pub struct InkToolbarMenuButton {
802    base: ToggleButton,
803}
804ferro_class!(InkToolbarMenuButton:ToggleButton);
805ferro_class_info!(InkToolbarMenuButton{new:InkToolbarMenuButton::new,markup:{properties:[MenuKind:InkToolbarMenuKind{get:InkToolbarMenuButton::menu_kind}],}});
806ferro_impl_classes!(InkToolbarMenuButton: FerroObjectImpl, VisualImpl, LayoutableImpl, InteractiveImpl, InputElementImpl, TemplatedControlImpl, ContentControlImpl, ButtonImpl, ToggleButtonImpl);
807impl ControlImpl for InkToolbarMenuButton {
808    fn on_create_automation_peer(this: &Self) -> Ref<ferroui_controls::automation::peers::AutomationPeer> {
809        super::InkToolbarMenuButtonAutomationPeer::new(this).upcast()
810    }
811}
812
813impl StyledElementImpl for InkToolbarMenuButton {
814    fn style_key_override(this: &Self) -> &'static TypeInfo {
815        toolbar_style_key(this, ToggleButton::TYPE)
816    }
817}
818ferro_properties! {impl InkToolbarMenuButton{pub fn is_extension_glyph_shown_property()->StyledProperty<bool>{FerroProperty::register::<InkToolbarMenuButton,_>("IsExtensionGlyphShown",true)}}}
819impl InkToolbarMenuButton {
820    pub fn construct() -> Self {
821        Self {
822            base: ToggleButton::construct(),
823        }
824    }
825    pub fn new() -> Ref<Self> {
826        instantiate(Self::construct())
827    }
828    pub fn menu_kind(&self) -> InkToolbarMenuKind {
829        InkToolbarMenuKind::Stencil
830    }
831    pub fn is_extension_glyph_shown(&self) -> bool {
832        self.get_value(Self::is_extension_glyph_shown_property())
833    }
834    pub fn set_is_extension_glyph_shown(&self, value: bool) {
835        self.set_value(Self::is_extension_glyph_shown_property(), value);
836    }
837}
838
839#[repr(C)]
840pub struct InkToolbarStencilButton {
841    base: InkToolbarMenuButton,
842    presenter: RefCell<Option<Rc<super::InkPresenter>>>,
843    ruler: RefCell<Option<Rc<super::InkPresenterRuler>>>,
844    protractor: RefCell<Option<Rc<super::InkPresenterProtractor>>>,
845    menu_items: RefCell<Vec<(InkToolbarStencilKind, Ref<InkToolbarFlyoutItem>)>>,
846    syncing_menu: Cell<bool>,
847    permit_flyout: Cell<bool>,
848}
849ferro_class!(InkToolbarStencilButton:InkToolbarMenuButton);
850ferro_class_info!(InkToolbarStencilButton {
851    new: InkToolbarStencilButton::new
852});
853ferro_impl_classes!(InkToolbarStencilButton:StyledElementImpl,VisualImpl,LayoutableImpl,InteractiveImpl,ControlImpl,TemplatedControlImpl,ContentControlImpl);
854impl InputElementImpl for InkToolbarStencilButton {
855    fn on_right_tapped(this: &Self, event: &TappedEventArgs) {
856        Self::parent_on_right_tapped(this, event);
857        if this.is_effectively_enabled() {
858            this.activate_secondary();
859            event.set_handled(true);
860        }
861    }
862    fn on_holding(this: &Self, event: &HoldingRoutedEventArgs) {
863        Self::parent_on_holding(this, event);
864        if this.is_effectively_enabled() && event.holding_state() == HoldingState::Started {
865            this.activate_secondary();
866            event.set_handled(true);
867        }
868    }
869}
870impl ToggleButtonImpl for InkToolbarStencilButton {
871    fn toggle(this: &Self) {
872        // Two choices retain the active stencil when opening configuration. A single choice
873        // behaves as a normal toggle, just as the source's one-stencil mode.
874        let checked = if this.is_ruler_item_visible() && this.is_protractor_item_visible() {
875            true
876        } else {
877            this.is_checked() != Some(true)
878        };
879        this.set_current_value(ToggleButton::is_checked_property(), Some(checked));
880    }
881}
882impl ButtonImpl for InkToolbarStencilButton {
883    fn on_click(this: &Self) {
884        let previous = this.permit_flyout.replace(this.is_checked() == Some(true));
885        Self::parent_on_click(this);
886        this.permit_flyout.set(previous);
887    }
888    fn open_flyout(this: &Self) {
889        if this.is_ruler_item_visible()
890            && this.is_protractor_item_visible()
891            && this.permit_flyout.get()
892            && !toolbar_is_inking(this)
893        {
894            Self::parent_open_flyout(this);
895        }
896    }
897}
898impl FerroObjectImpl for InkToolbarStencilButton {
899    fn constructed(this: &Self) {
900        Self::parent_constructed(this);
901        strings::bind_content(this, "InkToolbarStencilButtonName");
902        this.create_stencil_flyout();
903    }
904    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
905        Self::parent_on_property_changed(this, change);
906        let property = change.property();
907        if property == Self::selected_stencil_property().as_property()
908            && !this.syncing_menu.get()
909        {
910            // As WinUI (`OnSelectedStencilChanged`): the item of the newly selected stencil is
911            // shown, rather than the selection being forced back to the one visible item.
912            match this.selected_stencil() {
913                InkToolbarStencilKind::Ruler => this.set_is_ruler_item_visible(true),
914                InkToolbarStencilKind::Protractor => this.set_is_protractor_item_visible(true),
915            }
916        }
917        if property == Self::selected_stencil_property().as_property()
918            || property == ToggleButton::is_checked_property().as_property()
919            || property == Self::is_ruler_item_visible_property().as_property()
920            || property == Self::is_protractor_item_visible_property().as_property()
921        {
922            this.synchronize_stencil_menu();
923        }
924    }
925}
926ferro_properties! {impl InkToolbarStencilButton{
927    pub fn ruler_property()->DirectProperty<InkToolbarStencilButton,Option<Rc<super::InkPresenterRuler>>>{FerroProperty::register_direct::<InkToolbarStencilButton,_>("Ruler",InkToolbarStencilButton::ruler,None,None)}
928    pub fn protractor_property()->DirectProperty<InkToolbarStencilButton,Option<Rc<super::InkPresenterProtractor>>>{FerroProperty::register_direct::<InkToolbarStencilButton,_>("Protractor",InkToolbarStencilButton::protractor,None,None)}
929    pub fn selected_stencil_property()->StyledProperty<InkToolbarStencilKind>{FerroProperty::register::<InkToolbarStencilButton,_>("SelectedStencil",InkToolbarStencilKind::Ruler)}
930    pub fn is_ruler_item_visible_property()->StyledProperty<bool>{FerroProperty::register::<InkToolbarStencilButton,_>("IsRulerItemVisible",true)}
931    pub fn is_protractor_item_visible_property()->StyledProperty<bool>{FerroProperty::register::<InkToolbarStencilButton,_>("IsProtractorItemVisible",true)}
932}}
933impl InkToolbarStencilButton {
934    fn static_constructor() {
935        InputElement::is_holding_enabled_property()
936            .override_default_value::<InkToolbarStencilButton>(true);
937    }
938    fn activate_secondary(&self) {
939        if self.is_ruler_item_visible() && self.is_protractor_item_visible() {
940            self.set_current_value(ToggleButton::is_checked_property(), Some(true));
941            self.open_flyout();
942        } else {
943            self.on_click();
944        }
945    }
946    pub fn construct() -> Self {
947        Self {
948            base: InkToolbarMenuButton::construct(),
949            presenter: RefCell::new(None),
950            ruler: RefCell::new(None),
951            protractor: RefCell::new(None),
952            menu_items: RefCell::new(Vec::new()),
953            syncing_menu: Cell::new(false),
954            permit_flyout: Cell::new(true),
955        }
956    }
957    pub fn new() -> Ref<Self> {
958        instantiate(Self::construct())
959    }
960    fn create_stencil_flyout(&self) {
961        let panel = StackPanel::new();
962        let mut items = Vec::new();
963        for (kind, label) in [
964            (InkToolbarStencilKind::Ruler, "InkToolbarStencilRulerName"),
965            (
966                InkToolbarStencilKind::Protractor,
967                "InkToolbarStencilProtractorName",
968            ),
969        ] {
970            let item = InkToolbarFlyoutItem::new();
971            item.set_kind(InkToolbarFlyoutItemKind::RadioCheck);
972            item.set_name(Some(if kind == InkToolbarStencilKind::Ruler {
973                "StencilRuler".to_owned()
974            } else {
975                "StencilProtractor".to_owned()
976            }));
977            strings::bind_content(&item, label);
978            let weak = self.to_ref().downgrade();
979            item.checked(move |_| {
980                if let Some(button) = weak.upgrade() {
981                    if button.syncing_menu.replace(true) {
982                        return;
983                    }
984                    button.set_selected_stencil(kind);
985                    button.set_is_checked(Some(true));
986                    button.syncing_menu.set(false);
987                    button.synchronize_stencil_menu();
988                }
989            });
990            let weak = self.to_ref().downgrade();
991            item.unchecked(move |_| {
992                if let Some(button) = weak.upgrade() {
993                    // An item unchecked because the other item of the radio group was checked:
994                    // the checked item selects its stencil (WinUI selects through the click of
995                    // an item, after which the group is unchecked).
996                    let other_checked = button
997                        .menu_items
998                        .borrow()
999                        .iter()
1000                        .any(|(other, item)| *other != kind && item.is_checked() == Some(true));
1001                    if other_checked {
1002                        return;
1003                    }
1004                    if button.syncing_menu.replace(true) {
1005                        return;
1006                    }
1007                    if button.selected_stencil() == kind {
1008                        button.set_is_checked(Some(false));
1009                    }
1010                    button.syncing_menu.set(false);
1011                    button.synchronize_stencil_menu();
1012                }
1013            });
1014            let weak = self.to_ref().downgrade();
1015            item.add_handler(Button::click_event(), move |_, _| {
1016                if let Some(button) = weak.upgrade() {
1017                    if let Some(flyout) = button.flyout() {
1018                        flyout.hide();
1019                    }
1020                }
1021            });
1022            panel.children().add(&item);
1023            items.push((kind, item));
1024        }
1025        *self.menu_items.borrow_mut() = items;
1026        let flyout = toolbar_flyout(self);
1027        flyout.set_content(Some(Rc::new(panel.upcast::<Control>())));
1028        self.set_flyout(flyout);
1029        self.synchronize_stencil_menu();
1030    }
1031    fn synchronize_stencil_menu(&self) {
1032        if self.syncing_menu.replace(true) {
1033            return;
1034        }
1035        let items = self.menu_items.borrow().clone();
1036        let ruler = self.is_ruler_item_visible();
1037        let protractor = self.is_protractor_item_visible();
1038        if ruler != protractor {
1039            self.set_current_value(
1040                Self::selected_stencil_property(),
1041                if ruler {
1042                    InkToolbarStencilKind::Ruler
1043                } else {
1044                    InkToolbarStencilKind::Protractor
1045                },
1046            );
1047        }
1048        self.set_current_value(
1049            InkToolbarMenuButton::is_extension_glyph_shown_property(),
1050            ruler && protractor,
1051        );
1052        let size = i32::from(ruler) + i32::from(protractor);
1053        let mut position = 0;
1054        for (kind, item) in items {
1055            let visible = if kind == InkToolbarStencilKind::Ruler {
1056                ruler
1057            } else {
1058                protractor
1059            };
1060            item.set_is_visible(visible);
1061            item.set_is_checked(Some(
1062                self.selected_stencil() == kind && self.is_checked() == Some(true),
1063            ));
1064            if visible {
1065                position += 1;
1066            }
1067            AutomationProperties::set_position_in_set(&item, if visible { position } else { 0 });
1068            AutomationProperties::set_size_of_set(&item, size);
1069        }
1070        self.syncing_menu.set(false);
1071    }
1072    pub(crate) fn set_presenter(&self, value: Option<Rc<super::InkPresenter>>) {
1073        let ruler = value.as_ref().map(|p| p.ruler());
1074        let protractor = value.as_ref().map(|p| p.protractor());
1075        *self.presenter.borrow_mut() = value;
1076        self.set_and_raise(Self::ruler_property(), &self.ruler, ruler);
1077        self.set_and_raise(Self::protractor_property(), &self.protractor, protractor);
1078    }
1079    pub fn ruler(&self) -> Option<Rc<super::InkPresenterRuler>> {
1080        self.ruler.borrow().clone()
1081    }
1082    pub fn protractor(&self) -> Option<Rc<super::InkPresenterProtractor>> {
1083        self.protractor.borrow().clone()
1084    }
1085    pub fn selected_stencil(&self) -> InkToolbarStencilKind {
1086        self.get_value(Self::selected_stencil_property())
1087    }
1088    pub fn set_selected_stencil(&self, value: InkToolbarStencilKind) {
1089        self.set_value(Self::selected_stencil_property(), value);
1090    }
1091    pub fn is_ruler_item_visible(&self) -> bool {
1092        self.get_value(Self::is_ruler_item_visible_property())
1093    }
1094    pub fn set_is_ruler_item_visible(&self, value: bool) {
1095        self.set_value(Self::is_ruler_item_visible_property(), value);
1096    }
1097    pub fn is_protractor_item_visible(&self) -> bool {
1098        self.get_value(Self::is_protractor_item_visible_property())
1099    }
1100    pub fn set_is_protractor_item_visible(&self, value: bool) {
1101        self.set_value(Self::is_protractor_item_visible_property(), value);
1102    }
1103}
1104
1105/// A flyout of a toolbar button. As WinUI (`InkToolbarFlyoutStyle`, applied by the tool and menu
1106/// buttons), its presenter takes the control theme `InkToolbarFlyoutStyle` of the button's
1107/// resources when it opens, if the styles of the toolbar define one.
1108pub(crate) fn toolbar_flyout(owner: &Control) -> Ref<Flyout> {
1109    let flyout = Flyout::new();
1110    let weak_owner = owner.to_ref().downgrade();
1111    let weak_flyout = flyout.downgrade();
1112    let _ = flyout.opening(move |_| {
1113        let (Some(owner), Some(flyout)) = (weak_owner.upgrade(), weak_flyout.upgrade()) else {
1114            return;
1115        };
1116        let theme = owner
1117            .try_find_resource(&ResourceKey::from("InkToolbarFlyoutStyle"), None)
1118            .flatten();
1119        if let Some(theme) = ferroui_base::metadata::from_markup_value::<Ref<ControlTheme>>(&theme) {
1120            flyout.set_flyout_presenter_theme(theme);
1121        }
1122    });
1123    flyout
1124}
1125
1126pub(crate) fn toolbar_is_inking(control: &Control) -> bool {
1127    let mut parent = control.parent();
1128    while let Some(element) = parent {
1129        if let Some(toolbar) = element.cast::<InkToolbar>() {
1130            return toolbar
1131                .effective_ink_presenter()
1132                .is_some_and(|presenter| presenter.is_inking());
1133        }
1134        parent = element.parent();
1135    }
1136    false
1137}
1138
1139/// Use a dedicated theme when one is installed, retaining the framework's native control
1140/// theme otherwise. Resource changes invalidate the implicit theme through StyledElement.
1141pub(crate) fn toolbar_style_key(
1142    this: &StyledElement,
1143    fallback: &'static TypeInfo,
1144) -> &'static TypeInfo {
1145    let actual = this.get_type();
1146    if this.theme().is_some() {
1147        return actual;
1148    }
1149    let mut candidate = Some(actual);
1150    while let Some(kind) = candidate {
1151        if kind == fallback {
1152            break;
1153        }
1154        if this
1155            .try_find_resource(&ResourceKey::Type(kind), None)
1156            .flatten()
1157            .is_some_and(|resource| resource.downcast_ref::<Ref<ControlTheme>>().is_some())
1158        {
1159            return kind;
1160        }
1161        candidate = kind.base_type();
1162    }
1163    fallback
1164}