1use super::color_names::{bind_color_name, high_contrast_colors};
3use super::ink_toolbar::round_panel_thickness;
4use super::ink_toolbar_buttons::{strings, toolbar_style_key};
5use super::{InkToolbarFlyoutItemKind, InkToolbarPenButton};
6use ferroui_controls::automation::AutomationProperties;
7use ferroui_controls::primitives::{RangeBase, TemplatedControlImpl, ToggleButton, ToggleButtonImpl};
8use ferroui_controls::shapes::{Ellipse, Path};
9use ferroui_controls::utils::BorderRenderHelper;
10use ferroui_controls::{
11 Border, ButtonImpl, Canvas, ContentControlImpl, Control, ControlImpl, Decorator, PanelImpl,
12 Slider, SliderImpl, StackPanel, StackPanelImpl, TextBlock, WrapPanel,
13};
14use ferroui_base::animation::easings::CubicEaseOut;
15use ferroui_base::animation::{Animation, Cue, FillMode, IClock, IGlobalClock, KeyFrame, TimeSpan};
16use ferroui_base::collections::FerroList;
17use ferroui_base::data::BindingPriority;
18use ferroui_base::input::{
19 InputElementImpl, InputElementImplExt, Key, KeyEventArgs, KeyboardNavigation,
20 KeyboardNavigationMode, NavigationMethod,
21};
22use ferroui_base::interactivity::InteractiveImpl;
23use ferroui_base::layout::{Layoutable, LayoutableImpl, LayoutableImplExt, Orientation};
24use ferroui_base::media::immutable::ImmutableSolidColorBrush;
25use ferroui_base::media::{
26 BackgroundSizing, BoxShadows, DrawingContext, FontWeight, IBrush, ITransform, MatrixTransform,
27 PenLineCap, PenLineJoin, PolylineGeometry, TransformGroup, TranslateTransform,
28};
29use ferroui_base::reactive::{CompositeDisposable, Disposable, IDisposable, Observable};
30use ferroui_base::styling::Setter;
31use ferroui_base::utilities::HandlerList;
32use ferroui_base::{
33 ferro_class, ferro_class_info, ferro_impl_classes, ferro_properties, instantiate, CornerRadius,
34 DirectProperty, FerroLocator, FerroObjectImpl, FerroObjectImplExt, FerroProperty,
35 FerroPropertyChangedEventArgs, LocatorExtensions, Point, Rect, Ref, Size, StyledElementImpl,
36 StyledProperty, Thickness, TypeInfo, Visual, VisualImpl, VisualImplExt,
37 VisualTreeAttachmentEventArgs, WeakRef,
38};
39use std::cell::{Cell, RefCell};
40use std::rc::Rc;
41
42#[repr(C)]
43pub struct InkToolbarStrokeWidthSlider {
44 base: Slider,
45}
46ferro_class!(InkToolbarStrokeWidthSlider:Slider);
47ferro_class_info!(InkToolbarStrokeWidthSlider {
48 new: InkToolbarStrokeWidthSlider::new
49});
50ferro_impl_classes!(InkToolbarStrokeWidthSlider: VisualImpl, LayoutableImpl, InteractiveImpl, InputElementImpl, TemplatedControlImpl, SliderImpl);
51impl ControlImpl for InkToolbarStrokeWidthSlider {
52 fn on_create_automation_peer(this: &Self) -> Ref<ferroui_controls::automation::peers::AutomationPeer> {
53 super::InkToolbarStrokeWidthSliderAutomationPeer::new(this).upcast()
54 }
55}
56
57impl StyledElementImpl for InkToolbarStrokeWidthSlider {
58 fn style_key_override(this: &Self) -> &'static TypeInfo {
59 toolbar_style_key(this, Slider::TYPE)
60 }
61}
62impl FerroObjectImpl for InkToolbarStrokeWidthSlider {
63 fn constructed(this: &Self) {
64 Self::parent_constructed(this);
65 this.set_small_change(0.5);
66 this.set_large_change(2.0);
67 strings::bind_name(this, "InkToolbarPenConfigurationSizeSliderName");
68 }
69}
70impl InkToolbarStrokeWidthSlider {
71 pub fn construct() -> Self {
72 Self {
73 base: Slider::construct(),
74 }
75 }
76 pub fn new() -> Ref<Self> {
77 instantiate(Self::construct())
78 }
79}
80
81#[repr(C)]
82pub struct InkToolbarPenConfigurationControl {
83 base: StackPanel,
84 pen: RefCell<Option<WeakRef<InkToolbarPenButton>>>,
86 pen_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
87 palette_subscription: RefCell<Option<(FerroList<Rc<dyn IBrush>>, u64)>>,
88 palette_panel: RefCell<Option<Ref<WrapPanel>>>,
89 slider: RefCell<Option<Ref<InkToolbarStrokeWidthSlider>>>,
90 syncing: Cell<bool>,
91 displayed_palette: RefCell<Vec<Rc<dyn IBrush>>>,
92 preview: RefCell<Option<(Ref<Canvas>, Ref<Path>)>>,
93 border_renderer: RefCell<BorderRenderHelper>,
94 entrance: RefCell<Option<Rc<CompositeDisposable>>>,
95 entrance_epoch: Cell<u64>,
96}
97ferro_class!(InkToolbarPenConfigurationControl:StackPanel);
98ferro_class_info!(InkToolbarPenConfigurationControl {
99 new: InkToolbarPenConfigurationControl::new
100});
101ferro_impl_classes!(InkToolbarPenConfigurationControl:StyledElementImpl,InteractiveImpl,ControlImpl,PanelImpl);
102impl LayoutableImpl for InkToolbarPenConfigurationControl {
103 fn measure_override(this: &Self, available_size: Size) -> Size {
104 let insets = this.layout_insets();
105 Self::parent_measure_override(this, available_size.deflate(insets)).inflate(insets)
106 }
107 fn arrange_override(this: &Self, final_size: Size) -> Size {
108 Self::parent_arrange_override(this, final_size.deflate(this.layout_insets()));
109 final_size
110 }
111}
112impl StackPanelImpl for InkToolbarPenConfigurationControl {
113 fn arrange_child(
114 this: &Self,
115 child: &Ref<Control>,
116 rect: Rect,
117 _panel_size: Size,
118 _orientation: Orientation,
119 ) {
120 let insets = this.layout_insets();
121 child.arrange(
122 rect.with_x(rect.x + insets.left)
123 .with_y(rect.y + insets.top),
124 );
125 }
126}
127impl VisualImpl for InkToolbarPenConfigurationControl {
128 fn on_attached_to_visual_tree(this: &Self, event: &VisualTreeAttachmentEventArgs) {
129 Self::parent_on_attached_to_visual_tree(this, event);
130 this.start_entrance();
131 }
132 fn on_detached_from_visual_tree(this: &Self, event: &VisualTreeAttachmentEventArgs) {
133 this.stop_entrance();
134 Self::parent_on_detached_from_visual_tree(this, event);
135 }
136 fn render(this: &Self, context: &mut DrawingContext) {
137 this.border_renderer.borrow_mut().render(
138 context,
139 this.bounds().size(),
140 round_panel_thickness(this, this.border_thickness()),
141 this.corner_radius(),
142 this.background_sizing(),
143 this.background().as_ref(),
144 this.border_brush().as_ref(),
145 &BoxShadows::default(),
146 );
147 }
148 fn clip_to_bounds_radius(this: &Self) -> Option<CornerRadius> {
149 Some(this.corner_radius())
150 }
151}
152impl InputElementImpl for InkToolbarPenConfigurationControl {
153 fn on_key_down(this: &Self, event: &KeyEventArgs) {
154 Self::parent_on_key_down(this, event);
155 if event.handled() {
156 return;
157 }
158 let panel = this.palette_panel.borrow().clone();
159 let Some(panel) = panel else {
160 return;
161 };
162 let children = panel.children().snapshot();
163 let Some(index) = children
164 .iter()
165 .position(|child| child.is_keyboard_focus_within())
166 else {
167 return;
168 };
169 let destination = match event.key {
170 Key::Left => index.checked_sub(1),
171 Key::Right => index.checked_add(1),
172 Key::Up => index.checked_sub(6),
173 Key::Down => index.checked_add(6),
174 Key::Home => Some(0),
175 Key::End => children.len().checked_sub(1),
176 _ => return,
177 };
178 if let Some(destination) = destination.filter(|&index| index < children.len()) {
179 let child = &children[destination];
180 if child.focus_with(NavigationMethod::Directional, event.key_modifiers) {
181 if let Some(pen) = this.pen_button() {
182 pen.set_selected_brush_index(destination as i32);
183 }
184 }
185 }
186 event.set_handled(true);
187 }
188}
189impl FerroObjectImpl for InkToolbarPenConfigurationControl {
190 fn constructed(this: &Self) {
191 Self::parent_constructed(this);
192 this.set_orientation(Orientation::Vertical);
193 this.set_spacing(8.0);
194 let palette = WrapPanel::new();
195 palette.set_width(312.0);
196 palette.set_margin(Thickness::new(4.0, 0.0, 4.0, 2.0));
197 KeyboardNavigation::set_tab_navigation(&palette, KeyboardNavigationMode::Once);
198 strings::bind_name(&palette, "InkToolbarPenConfigurationColorsLabel");
199 let colors_title = TextBlock::new();
200 colors_title.set_font_weight(FontWeight::SemiBold);
201 colors_title.set_margin(Thickness::new(12.0, 0.0, 12.0, 0.0));
202 strings::bind_label(&colors_title, "InkToolbarPenConfigurationColorsLabel");
203 AutomationProperties::set_heading_level(&colors_title, 2);
204 let size_title = TextBlock::new();
205 size_title.set_font_weight(FontWeight::SemiBold);
206 size_title.set_margin(Thickness::new(12.0, 0.0, 12.0, 0.0));
207 strings::bind_label(&size_title, "InkToolbarPenConfigurationSizeLabel");
208 AutomationProperties::set_heading_level(&size_title, 2);
209 let preview_canvas = Canvas::new();
210 preview_canvas.set_width(296.0);
211 preview_canvas.set_margin(Thickness::new(12.0, 0.0, 12.0, 4.0));
212 preview_canvas.set_is_hit_test_visible(false);
213 let preview = Path::new();
214 preview.set_data(PolylineGeometry::with_points(PREVIEW_POINTS, false));
215 preview.set_stroke_line_cap(PenLineCap::Round);
216 preview.set_stroke_join(PenLineJoin::Round);
217 preview_canvas.children().add(preview.clone());
218 let slider = InkToolbarStrokeWidthSlider::new();
219 slider.set_width(296.0);
220 slider.set_height(44.0);
221 slider.set_margin(Thickness::new(12.0, 0.0, 12.0, 0.0));
222 let weak = this.to_ref().downgrade();
223 slider.property_changed(move |change| {
224 if change.property() != RangeBase::value_property().as_property() {
225 return;
226 }
227 if let Some(config) = weak.upgrade() {
228 if !config.syncing.get() {
229 if let Some(pen) = config.pen_button() {
230 pen.set_selected_stroke_width(change.get_new_value::<f64>());
231 }
232 }
233 }
234 });
235 this.children().add(colors_title);
236 this.children().add(palette.clone());
237 this.children().add(size_title);
238 this.children().add(preview_canvas.clone());
239 this.children().add(slider.clone());
240 *this.palette_panel.borrow_mut() = Some(palette);
241 *this.slider.borrow_mut() = Some(slider);
242 *this.preview.borrow_mut() = Some((preview_canvas, preview));
243 let weak = this.to_ref().downgrade();
244 this.resources_changed(move |_| {
245 if let Some(config) = weak.upgrade() {
246 config.rebuild_palette();
247 }
248 });
249 this.bind_pen();
250 }
251 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
252 Self::parent_on_property_changed(this, change);
253 if change.property() == Self::pen_button_property().as_property() {
254 this.bind_pen();
255 }
256 }
257}
258ferro_properties! {impl InkToolbarPenConfigurationControl{
259 pub fn pen_button_property()->DirectProperty<InkToolbarPenConfigurationControl,Option<Ref<InkToolbarPenButton>>>{FerroProperty::register_direct::<InkToolbarPenConfigurationControl,_>("PenButton",InkToolbarPenConfigurationControl::pen_button,None,None)}
260 pub fn border_brush_property()->StyledProperty<Option<Rc<dyn IBrush>>>{Border::border_brush_property().add_owner::<InkToolbarPenConfigurationControl>()}
261 pub fn border_thickness_property()->StyledProperty<Thickness>{Border::border_thickness_property().add_owner::<InkToolbarPenConfigurationControl>()}
262 pub fn corner_radius_property()->StyledProperty<CornerRadius>{Border::corner_radius_property().add_owner::<InkToolbarPenConfigurationControl>()}
263 pub fn background_sizing_property()->StyledProperty<BackgroundSizing>{Border::background_sizing_property().add_owner::<InkToolbarPenConfigurationControl>()}
264 pub fn padding_property()->StyledProperty<Thickness>{Decorator::padding_property().add_owner::<InkToolbarPenConfigurationControl>()}
265}}
266impl InkToolbarPenConfigurationControl {
267 fn static_constructor() {
268 Visual::affects_render::<InkToolbarPenConfigurationControl>(&[
269 Self::border_brush_property().as_property(),
270 Self::border_thickness_property().as_property(),
271 Self::corner_radius_property().as_property(),
272 Self::background_sizing_property().as_property(),
273 ]);
274 Layoutable::affects_measure::<InkToolbarPenConfigurationControl>(&[
275 Self::padding_property().as_property(),
276 Self::border_thickness_property().as_property(),
277 ]);
278 }
279 pub fn construct() -> Self {
280 Self {
281 base: StackPanel::construct(),
282 pen: RefCell::new(None),
283 pen_subscription: RefCell::new(None),
284 palette_subscription: RefCell::new(None),
285 palette_panel: RefCell::new(None),
286 slider: RefCell::new(None),
287 syncing: Cell::new(false),
288 displayed_palette: RefCell::new(Vec::new()),
289 preview: RefCell::new(None),
290 border_renderer: RefCell::new(BorderRenderHelper::new()),
291 entrance: RefCell::new(None),
292 entrance_epoch: Cell::new(0),
293 }
294 }
295 pub fn new() -> Ref<Self> {
296 instantiate(Self::construct())
297 }
298 pub fn for_pen(pen: &InkToolbarPenButton) -> Ref<Self> {
299 let result = Self::new();
300 *result.pen.borrow_mut() = Some(pen.to_ref().downgrade());
301 result.raise_direct_property_changed(
302 Self::pen_button_property(),
303 &None,
304 &Some(pen.to_ref()),
305 );
306 result
307 }
308 pub fn pen_button(&self) -> Option<Ref<InkToolbarPenButton>> {
309 self.pen.borrow().as_ref().and_then(WeakRef::upgrade)
310 }
311 pub fn border_brush(&self) -> Option<Rc<dyn IBrush>> {
312 self.get_value(Self::border_brush_property())
313 }
314 pub fn set_border_brush(&self, value: Option<Rc<dyn IBrush>>) {
315 self.set_value(Self::border_brush_property(), value);
316 }
317 pub fn border_thickness(&self) -> Thickness {
318 self.get_value(Self::border_thickness_property())
319 }
320 pub fn set_border_thickness(&self, value: Thickness) {
321 self.set_value(Self::border_thickness_property(), value);
322 }
323 pub fn corner_radius(&self) -> CornerRadius {
324 self.get_value(Self::corner_radius_property())
325 }
326 pub fn set_corner_radius(&self, value: CornerRadius) {
327 self.set_value(Self::corner_radius_property(), value);
328 }
329 pub fn background_sizing(&self) -> BackgroundSizing {
330 self.get_value(Self::background_sizing_property())
331 }
332 pub fn set_background_sizing(&self, value: BackgroundSizing) {
333 self.set_value(Self::background_sizing_property(), value);
334 }
335 pub fn padding(&self) -> Thickness {
336 self.get_value(Self::padding_property())
337 }
338 pub fn set_padding(&self, value: Thickness) {
339 self.set_value(Self::padding_property(), value);
340 }
341 fn layout_insets(&self) -> Thickness {
342 round_panel_thickness(self, self.padding())
343 + round_panel_thickness(self, self.border_thickness())
344 }
345 pub fn stroke_width_slider(&self) -> Option<Ref<InkToolbarStrokeWidthSlider>> {
346 self.slider.borrow().clone()
347 }
348 fn start_entrance(&self) {
353 self.stop_entrance();
354 let clock = self.clock().or_else(|| {
355 FerroLocator::current()
356 .get_service::<dyn IGlobalClock>()
357 .map(|clock| clock as Rc<dyn IClock>)
358 });
359 let Some(clock) = clock else {
360 return;
362 };
363 let children: Vec<_> = self
364 .children()
365 .snapshot()
366 .iter()
367 .filter(|child| child.is_visible())
368 .cloned()
369 .collect();
370 if children.is_empty() {
371 return;
372 }
373 let subscriptions = Rc::new(CompositeDisposable::new());
374 *self.entrance.borrow_mut() = Some(subscriptions.clone());
375 let epoch = self.entrance_epoch.get();
376 let remaining = Rc::new(Cell::new(children.len()));
377 for (index, child) in children.iter().enumerate() {
378 let transform = TransformGroup::new();
379 if let Some(original) = child.render_transform() {
380 transform
381 .children()
382 .add(MatrixTransform::with_matrix(original.value()).upcast());
383 }
384 transform.children().add(TranslateTransform::new().upcast());
385 let transform: Rc<dyn ITransform> = transform.into();
386 if let Some(overlay) = child.set_value_with_priority(
387 Visual::render_transform_property(),
388 Some(transform),
389 BindingPriority::Animation,
390 ) {
391 subscriptions.add(overlay);
392 }
393 let animation = Animation::new();
394 animation.set_duration(TimeSpan::from_milliseconds(200.0));
395 animation.set_delay(TimeSpan::from_milliseconds(index as f64 * 30.0));
396 animation.set_fill_mode(FillMode::Backward);
397 animation.set_easing(CubicEaseOut::new());
398 animation.children().add(KeyFrame::with_cue(
399 Cue::new(0.0),
400 [
401 Setter::new(Visual::opacity_property(), 0.0) as _,
402 Setter::new(TranslateTransform::x_property(), 40.0) as _,
403 ],
404 ));
405 let final_opacity = Setter::empty();
408 final_opacity.set_property(Some(Visual::opacity_property().as_property()));
409 animation.children().add(KeyFrame::with_cue(
410 Cue::new(1.0),
411 [
412 final_opacity as _,
413 Setter::new(TranslateTransform::x_property(), 0.0) as _,
414 ],
415 ));
416 let weak = self.to_ref().downgrade();
417 let remaining = remaining.clone();
418 let completed = Rc::new(move || {
419 remaining.set(remaining.get().saturating_sub(1));
420 if remaining.get() == 0 {
421 if let Some(configuration) = weak.upgrade() {
422 if configuration.entrance_epoch.get() == epoch {
423 configuration.stop_entrance();
424 }
425 }
426 }
427 });
428 subscriptions.add(animation.apply(
429 child,
430 Some(clock.clone()),
431 Observable::return_(true),
432 Some(completed),
433 true,
434 ));
435 }
436 }
437 fn stop_entrance(&self) {
438 self.entrance_epoch
439 .set(self.entrance_epoch.get().wrapping_add(1));
440 let subscriptions = self.entrance.borrow_mut().take();
441 if let Some(subscriptions) = subscriptions {
442 subscriptions.dispose();
443 }
444 }
445 fn bind_pen(&self) {
446 if let Some(subscription) = self.pen_subscription.borrow_mut().take() {
447 subscription.dispose();
448 }
449 if let Some(pen) = self.pen_button() {
450 let weak = self.to_ref().downgrade();
451 let subscription = pen.property_changed(move |change| {
452 if let Some(config) = weak.upgrade() {
453 if !super::ink_toolbar_buttons::affects_pen_attributes(change.property()) {
454 return;
455 }
456 if change.property() == InkToolbarPenButton::palette_property().as_property() {
457 config.bind_palette();
458 }
459 let palette_changed = config.displayed_palette.borrow().as_slice()
460 != pen_palette(&config).as_slice();
461 if palette_changed {
462 config.rebuild_palette();
463 }
464 config.synchronize();
465 }
466 });
467 *self.pen_subscription.borrow_mut() = Some(subscription);
468 }
469 self.bind_palette();
470 self.synchronize();
471 }
472 fn bind_palette(&self) {
473 if let Some((palette, token)) = self.palette_subscription.borrow_mut().take() {
474 palette.remove_collection_changed(token);
475 }
476 if let Some(pen) = self.pen_button() {
477 let palette = pen.palette();
478 let weak = self.to_ref().downgrade();
479 let token = palette.add_collection_changed(Rc::new(move |_| {
480 if let Some(config) = weak.upgrade() {
481 config.rebuild_palette();
482 }
483 }));
484 *self.palette_subscription.borrow_mut() = Some((palette, token));
485 }
486 self.rebuild_palette();
487 }
488 fn rebuild_palette(&self) {
489 let panel = self.palette_panel.borrow().clone();
490 let Some(panel) = panel else {
491 return;
492 };
493 panel.children().clear();
494 let Some(pen) = self.pen_button() else {
495 return;
496 };
497 let effective_palette = pen.effective_palette();
498 *self.displayed_palette.borrow_mut() = effective_palette.clone();
499 for (index, brush) in effective_palette.iter().enumerate() {
500 let item = InkToolbarFlyoutItem::new();
501 item.set_kind(InkToolbarFlyoutItemKind::Radio);
502 item.set_width(52.0);
503 item.set_height(52.0);
504 item.classes().add("ink-palette-item");
505 let swatch = Ellipse::new();
506 swatch.set_width(32.0);
507 swatch.set_height(32.0);
508 swatch.set_fill(Some(brush.clone()));
509 if let Some((_, system)) = high_contrast_colors(self, false) {
510 swatch.set_stroke(Some(
511 Rc::new(ImmutableSolidColorBrush::new(system)) as Rc<dyn IBrush>
512 ));
513 swatch.set_stroke_thickness(1.0);
514 }
515 item.set_content(Some(Rc::new(swatch.upcast::<Control>())));
516 bind_color_name(
517 &item,
518 brush.as_solid_color_brush().map(|brush| brush.color()),
519 );
520 let weak = pen.downgrade();
521 item.checked(move |_| {
522 if let Some(pen) = weak.upgrade() {
523 pen.set_selected_brush_index(index as i32);
524 }
525 });
526 panel.children().add(item);
527 }
528 self.synchronize();
529 }
530 fn synchronize(&self) {
531 if self.syncing.replace(true) {
532 return;
533 }
534 let pen = self.pen_button();
535 let slider = self.slider.borrow().clone();
536 let panel = self.palette_panel.borrow().clone();
537 if let (Some(pen), Some(slider)) = (pen.as_ref(), slider) {
538 slider.set_minimum(pen.min_stroke_width());
539 slider.set_maximum(pen.max_stroke_width().max(pen.min_stroke_width()));
540 slider.set_range_value(pen.selected_stroke_width());
541 }
542 if let (Some(pen), Some(panel)) = (pen, panel) {
543 for (index, item) in panel.children().snapshot().iter().enumerate() {
544 if let Some(item) = item.cast::<InkToolbarFlyoutItem>() {
545 let selected = index as i32 == pen.selected_brush_index();
546 item.set_is_checked(Some(selected));
547 if selected {
548 KeyboardNavigation::set_tab_once_active_element(
549 &panel,
550 Some(item.upcast()),
551 );
552 }
553 }
554 }
555 }
556 self.syncing.set(false);
557 self.update_preview();
558 }
559 pub fn stroke_preview(&self) -> Option<Ref<Path>> {
561 self.preview.borrow().as_ref().map(|(_, path)| path.clone())
562 }
563 fn update_preview(&self) {
564 let pen = self.pen_button();
565 let preview = self.preview.borrow().clone();
566 let (Some(pen), Some((canvas, path))) = (pen, preview) else {
567 return;
568 };
569 let attrs = pen.create_ink_drawing_attributes().ok();
570 let brush = attrs
571 .map(|attrs| Rc::new(ImmutableSolidColorBrush::new(attrs.color)) as Rc<dyn IBrush>)
572 .or_else(|| pen.selected_brush());
573 path.set_stroke(brush);
574 path.set_stroke_thickness(
575 attrs
576 .map(|attrs| attrs.size.width)
577 .unwrap_or_else(|| pen.selected_stroke_width()),
578 );
579 path.set_opacity(if attrs.is_some_and(|attrs| attrs.draw_as_highlighter) {
580 0.5
581 } else {
582 1.0
583 });
584 let min_y = PREVIEW_POINTS
585 .iter()
586 .map(|point| point.y)
587 .fold(f64::INFINITY, f64::min);
588 let max_y = PREVIEW_POINTS
589 .iter()
590 .map(|point| point.y)
591 .fold(f64::NEG_INFINITY, f64::max);
592 let height = max_y - min_y + pen.max_stroke_width().max(path.stroke_thickness()) + 10.0;
593 canvas.set_height(height);
594 Canvas::set_top(&path, height / 2.0 - (min_y + max_y) / 2.0);
595 }
596}
597impl Drop for InkToolbarPenConfigurationControl {
598 fn drop(&mut self) {
599 self.stop_entrance();
600 if let Some(subscription) = self.pen_subscription.get_mut().take() {
601 subscription.dispose();
602 }
603 if let Some((palette, token)) = self.palette_subscription.get_mut().take() {
604 palette.remove_collection_changed(token);
605 }
606 }
607}
608fn pen_palette(config: &InkToolbarPenConfigurationControl) -> Vec<Rc<dyn IBrush>> {
609 config
610 .pen_button()
611 .map(|pen| pen.effective_palette())
612 .unwrap_or_default()
613}
614
615type ItemHandlers = Rc<HandlerList<dyn Fn(&InkToolbarFlyoutItem)>>;
616#[repr(C)]
617pub struct InkToolbarFlyoutItem {
618 base: ToggleButton,
619 checked: ItemHandlers,
620 unchecked: ItemHandlers,
621}
622ferro_class!(InkToolbarFlyoutItem:ToggleButton);
623ferro_class_info!(InkToolbarFlyoutItem {
624 new: InkToolbarFlyoutItem::new
625});
626ferro_impl_classes!(InkToolbarFlyoutItem: VisualImpl, LayoutableImpl, InteractiveImpl, InputElementImpl, TemplatedControlImpl, ContentControlImpl, ButtonImpl);
627impl ControlImpl for InkToolbarFlyoutItem {
628 fn on_create_automation_peer(this: &Self) -> Ref<ferroui_controls::automation::peers::AutomationPeer> {
629 super::InkToolbarFlyoutItemAutomationPeer::new(this).upcast()
630 }
631}
632
633impl StyledElementImpl for InkToolbarFlyoutItem {
634 fn style_key_override(this: &Self) -> &'static TypeInfo {
635 toolbar_style_key(this, ToggleButton::TYPE)
636 }
637}
638impl FerroObjectImpl for InkToolbarFlyoutItem {
639 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
640 Self::parent_on_property_changed(this, change);
641 if change.property() == ToggleButton::is_checked_property().as_property() {
642 if this.is_checked() == Some(true) {
643 this.uncheck_others_in_group();
644 }
645 let handlers = if this.is_checked() == Some(true) {
646 &this.checked
647 } else {
648 &this.unchecked
649 };
650 for (_, handler) in handlers.snapshot().iter() {
651 handler(this);
652 }
653 }
654 }
655}
656impl ToggleButtonImpl for InkToolbarFlyoutItem {
657 fn toggle(this: &Self) {
658 let value = match this.kind() {
659 InkToolbarFlyoutItemKind::Simple => return,
660 InkToolbarFlyoutItemKind::Radio => true,
661 InkToolbarFlyoutItemKind::Check | InkToolbarFlyoutItemKind::RadioCheck => {
662 this.is_checked() != Some(true)
663 }
664 };
665 this.set_current_value(ToggleButton::is_checked_property(), Some(value));
666 }
667}
668ferro_properties! {impl InkToolbarFlyoutItem{pub fn kind_property()->StyledProperty<InkToolbarFlyoutItemKind>{FerroProperty::register::<InkToolbarFlyoutItem,_>("Kind",InkToolbarFlyoutItemKind::Simple)}}}
669impl InkToolbarFlyoutItem {
670 pub fn construct() -> Self {
671 Self {
672 base: ToggleButton::construct(),
673 checked: Rc::new(HandlerList::new()),
674 unchecked: Rc::new(HandlerList::new()),
675 }
676 }
677 pub fn new() -> Ref<Self> {
678 instantiate(Self::construct())
679 }
680 fn uncheck_others_in_group(&self) {
683 let kind = self.kind();
684 if !matches!(
685 kind,
686 InkToolbarFlyoutItemKind::Radio | InkToolbarFlyoutItemKind::RadioCheck
687 ) {
688 return;
689 }
690 let Some(panel) = self.parent().and_then(|parent| parent.cast::<ferroui_controls::Panel>()) else {
691 return;
692 };
693 let this: Ref<Control> = self.to_ref().upcast();
694 for item in panel
695 .children()
696 .snapshot()
697 .iter()
698 .filter(|child| **child != this)
699 .filter_map(|child| child.cast::<InkToolbarFlyoutItem>())
700 .filter(|item| item.kind() == kind && item.is_checked() == Some(true))
701 {
702 item.set_is_checked(Some(false));
703 }
704 }
705 pub fn kind(&self) -> InkToolbarFlyoutItemKind {
706 self.get_value(Self::kind_property())
707 }
708 pub fn set_kind(&self, value: InkToolbarFlyoutItemKind) {
709 self.set_value(Self::kind_property(), value);
710 }
711 pub fn checked(
712 &self,
713 handler: impl Fn(&InkToolbarFlyoutItem) + 'static,
714 ) -> Rc<dyn IDisposable> {
715 subscribe(&self.checked, handler)
716 }
717 pub fn unchecked(
718 &self,
719 handler: impl Fn(&InkToolbarFlyoutItem) + 'static,
720 ) -> Rc<dyn IDisposable> {
721 subscribe(&self.unchecked, handler)
722 }
723}
724fn subscribe(
725 list: &ItemHandlers,
726 handler: impl Fn(&InkToolbarFlyoutItem) + 'static,
727) -> Rc<dyn IDisposable> {
728 let token = list.add(Rc::new(handler));
729 let weak = Rc::downgrade(list);
730 Disposable::create(move || {
731 if let Some(list) = weak.upgrade() {
732 list.remove(token);
733 }
734 })
735}
736
737const PREVIEW_POINTS: [Point; 103] = [
739 Point::new(24.0, 28.5),
740 Point::new(26.175271, 26.452467),
741 Point::new(28.468156, 24.57762),
742 Point::new(30.860594, 22.862812),
743 Point::new(33.253032, 21.148004),
744 Point::new(35.745021, 19.593234),
745 Point::new(38.3185, 18.185855),
746 Point::new(40.891979, 16.778476),
747 Point::new(43.546946, 15.518487),
748 Point::new(46.26534, 14.39324),
749 Point::new(48.983733, 13.267993),
750 Point::new(51.765552, 12.277489),
751 Point::new(54.592735, 11.409078),
752 Point::new(57.419917, 10.540668),
753 Point::new(60.292463, 9.794352),
754 Point::new(63.192308, 9.1574822),
755 Point::new(66.092153, 8.5206123),
756 Point::new(69.019299, 7.9931887),
757 Point::new(71.955681, 7.5625631),
758 Point::new(74.892063, 7.1319376),
759 Point::new(77.837682, 6.7981102),
760 Point::new(80.774476, 6.5484328),
761 Point::new(83.711269, 6.2987555),
762 Point::new(86.639236, 6.1332282),
763 Point::new(89.540314, 6.0392029),
764 Point::new(92.12168, 5.9271775),
765 Point::new(94.691318, 5.9313799),
766 Point::new(97.248453, 6.040089),
767 Point::new(99.805587, 6.1487981),
768 Point::new(102.35022, 6.3620141),
769 Point::new(104.88157, 6.668016),
770 Point::new(107.41293, 6.974018),
771 Point::new(109.931, 7.3728059),
772 Point::new(112.43502, 7.852659),
773 Point::new(114.93904, 8.332512),
774 Point::new(117.42901, 8.8934302),
775 Point::new(119.90414, 9.5236926),
776 Point::new(122.37928, 10.153955),
777 Point::new(124.83959, 10.853562),
778 Point::new(127.28429, 11.610792),
779 Point::new(129.72899, 12.368022),
780 Point::new(132.15809, 13.182876),
781 Point::new(134.57081, 14.043632),
782 Point::new(136.98352, 14.904388),
783 Point::new(139.37986, 15.811047),
784 Point::new(141.75904, 16.751888),
785 Point::new(144.13822, 17.692728),
786 Point::new(146.50025, 18.667751),
787 Point::new(148.84434, 19.665234),
788 Point::new(150.92055, 20.558561),
789 Point::new(153.02009, 21.393996),
790 Point::new(155.13997, 22.171709),
791 Point::new(157.25986, 22.949423),
792 Point::new(159.40009, 23.669414),
793 Point::new(161.55771, 24.331854),
794 Point::new(163.71533, 24.994294),
795 Point::new(165.89033, 25.599182),
796 Point::new(168.07973, 26.146689),
797 Point::new(170.26914, 26.694195),
798 Point::new(172.47295, 27.184321),
799 Point::new(174.6882, 27.617235),
800 Point::new(176.90346, 28.050149),
801 Point::new(179.13015, 28.425851),
802 Point::new(181.3653, 28.744513),
803 Point::new(183.60046, 29.063175),
804 Point::new(185.84408, 29.324795),
805 Point::new(188.0932, 29.529545),
806 Point::new(190.34231, 29.734295),
807 Point::new(192.59692, 29.882174),
808 Point::new(194.85406, 29.973352),
809 Point::new(197.11119, 30.06453),
810 Point::new(199.37085, 30.099008),
811 Point::new(201.63006, 30.076955),
812 Point::new(203.88927, 30.054901),
813 Point::new(206.14803, 29.976318),
814 Point::new(208.40338, 29.841374),
815 Point::new(210.65872, 29.70643),
816 Point::new(212.91064, 29.515126),
817 Point::new(215.15617, 29.267631),
818 Point::new(219.64724, 28.772643),
819 Point::new(224.11274, 28.052893),
820 Point::new(228.52891, 27.109745),
821 Point::new(230.73699, 26.638171),
822 Point::new(232.93274, 26.110747),
823 Point::new(235.11319, 25.527643),
824 Point::new(237.29363, 24.94454),
825 Point::new(239.45877, 24.305756),
826 Point::new(241.60564, 23.611464),
827 Point::new(243.7525, 22.917171),
828 Point::new(245.88109, 22.167369),
829 Point::new(247.98843, 21.362228),
830 Point::new(250.09577, 20.557087),
831 Point::new(252.18187, 19.696606),
832 Point::new(254.24374, 18.780957),
833 Point::new(257.44071, 17.278777),
834 Point::new(260.5682, 15.598548),
835 Point::new(263.55223, 13.698859),
836 Point::new(265.04425, 12.749015),
837 Point::new(266.50041, 11.744305),
838 Point::new(267.91145, 10.679554),
839 Point::new(269.32249, 9.6148034),
840 Point::new(270.68842, 8.4900108),
841 Point::new(272.0, 7.3),
842];
843
844#[cfg(test)]
845#[path = "ink_toolbar_configuration_tests.rs"]
846mod tests;