1use super::{
2 CommandBarButton, CommandBarDefaultLabelPosition, CommandBarOverflowButtonVisibility, CommandBarSeparator,
3 CommandBarToggleButton, ICommandBarElement,
4};
5use crate::items_source::{IItemsList, ItemsChangedEventArgs, ItemsChangedHandler, ItemsSource, ItemsView};
6use crate::primitives::{
7 Popup, TemplateAppliedEventArgs, TemplatedControl, TemplatedControlImpl, TemplatedControlImplExt,
8};
9use crate::{Button, ContentControl, Control, ControlImpl, ItemsControl};
10use ferroui_base::collections::{
11 CollectionChangedHandler, FerroList, NotifyCollectionChangedAction, NotifyCollectionChangedEventArgs,
12};
13use ferroui_base::input::{
14 FocusChangedEventArgs, InputElement, InputElementImpl, Key, KeyEventArgs, KeyModifiers, NavigationMethod,
15};
16use ferroui_base::interactivity::{
17 Interactive, InteractiveImpl, RoutedEvent, RoutedEventArgs, RoutedEventHandlerToken, RoutingStrategies,
18};
19use ferroui_base::layout::{LayoutableImpl, LayoutableImplExt};
20use ferroui_base::reactive::{CompositeDisposable, IDisposable, ObservableExt};
21use ferroui_base::threading::{Dispatcher, DispatcherPriority};
22use ferroui_base::utilities::HandlerList;
23use ferroui_base::{
24 ferro_class, ferro_class_info, ferro_impl_classes, ferro_properties, ferro_routed_event, instantiate, BoxedValue,
25 DirectProperty, FerroObjectExtensions, FerroObjectImpl, FerroObjectImplExt, FerroProperty,
26 FerroPropertyChangedEventArgs, Ref, Size, StyledElementImpl, StyledProperty, Visual, VisualImpl, VisualImplExt,
27 VisualTreeAttachmentEventArgs,
28};
29use std::any::Any;
30use std::cell::{Cell, RefCell};
31use std::rc::Rc;
32
33pub type CommandBarElementList = FerroList<Rc<dyn ICommandBarElement>>;
36
37type ElementChangedHandler = CollectionChangedHandler<Rc<dyn ICommandBarElement>>;
38
39#[derive(Clone)]
49pub struct CommandBarElementCollection(Rc<CommandBarElementCollectionData>);
50
51struct CommandBarElementCollectionData {
52 items: RefCell<Rc<Vec<Rc<dyn ICommandBarElement>>>>,
53 collection_changed: HandlerList<ElementChangedHandler>,
54}
55
56impl PartialEq for CommandBarElementCollection {
57 fn eq(&self, other: &Self) -> bool {
58 Rc::ptr_eq(&self.0, &other.0)
59 }
60}
61
62impl std::fmt::Debug for CommandBarElementCollection {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 write!(f, "CommandBarElementCollection(count = {})", self.count())
65 }
66}
67
68impl CommandBarElementCollection {
69 fn new() -> Self {
70 Self(Rc::new(CommandBarElementCollectionData {
71 items: RefCell::new(Rc::new(Vec::new())),
72 collection_changed: HandlerList::new(),
73 }))
74 }
75
76 pub fn count(&self) -> usize {
78 self.0.items.borrow().len()
79 }
80
81 pub fn is_empty(&self) -> bool {
83 self.count() == 0
84 }
85
86 pub fn get(&self, index: usize) -> Rc<dyn ICommandBarElement> {
88 self.0.items.borrow()[index].clone()
89 }
90
91 pub fn snapshot(&self) -> Rc<Vec<Rc<dyn ICommandBarElement>>> {
94 self.0.items.borrow().clone()
95 }
96
97 pub fn to_vec(&self) -> Vec<Rc<dyn ICommandBarElement>> {
99 self.snapshot().to_vec()
100 }
101
102 pub fn contains(&self, element: &Rc<dyn ICommandBarElement>) -> bool {
104 self.index_of(element).is_some()
105 }
106
107 pub fn index_of(&self, element: &Rc<dyn ICommandBarElement>) -> Option<usize> {
109 self.0.items.borrow().iter().position(|item| item == element)
110 }
111
112 pub fn add_collection_changed(&self, handler: Rc<ElementChangedHandler>) -> u64 {
115 self.0.collection_changed.add(handler)
116 }
117
118 pub fn remove_collection_changed(&self, token: u64) -> bool {
120 self.0.collection_changed.remove(token)
121 }
122
123 pub fn as_items_source(&self) -> ItemsSource {
125 let list: Rc<dyn IItemsList> = self.0.clone();
126 ItemsSource::new(list)
127 }
128
129 fn clear(&self) {
132 *self.0.items.borrow_mut() = Rc::new(Vec::new());
133 self.0.notify(&NotifyCollectionChangedEventArgs {
134 action: NotifyCollectionChangedAction::Reset,
135 new_items: &[],
136 old_items: &[],
137 new_starting_index: -1,
138 old_starting_index: -1,
139 });
140 }
141
142 fn add(&self, element: Rc<dyn ICommandBarElement>) {
143 let index = {
144 let mut items = self.0.items.borrow_mut();
145 let items = Rc::make_mut(&mut items);
146 items.push(element.clone());
147 items.len() - 1
148 };
149 self.0.notify(&NotifyCollectionChangedEventArgs {
150 action: NotifyCollectionChangedAction::Add,
151 new_items: std::slice::from_ref(&element),
152 old_items: &[],
153 new_starting_index: index as i32,
154 old_starting_index: -1,
155 });
156 }
157}
158
159impl CommandBarElementCollectionData {
160 fn notify(&self, e: &NotifyCollectionChangedEventArgs<'_, Rc<dyn ICommandBarElement>>) {
161 if self.collection_changed.is_empty() {
162 return;
163 }
164 for (_, handler) in self.collection_changed.snapshot().iter() {
165 handler(e);
166 }
167 }
168}
169
170fn element_item(element: &Rc<dyn ICommandBarElement>) -> Option<BoxedValue> {
173 match element.as_object().and_then(|object| object.downcast_ref::<Control>()) {
174 Some(control) => Some(Control::boxed(control.to_ref())),
175 None => Some(Rc::new(element.clone())),
176 }
177}
178
179impl IItemsList for CommandBarElementCollectionData {
180 fn count(&self) -> usize {
181 self.items.borrow().len()
182 }
183
184 fn get_at(&self, index: usize) -> Option<BoxedValue> {
185 let element = self.items.borrow()[index].clone();
186 element_item(&element)
187 }
188
189 fn is_notifying(&self) -> bool {
190 true
191 }
192
193 fn add_collection_changed(&self, handler: Rc<ItemsChangedHandler>) -> Option<u64> {
194 Some(self.collection_changed.add(Rc::new(
195 move |e: &NotifyCollectionChangedEventArgs<'_, Rc<dyn ICommandBarElement>>| {
196 let new_items: Vec<Option<BoxedValue>> = e.new_items.iter().map(element_item).collect();
197 let old_items: Vec<Option<BoxedValue>> = e.old_items.iter().map(element_item).collect();
198 handler(&ItemsChangedEventArgs {
199 action: e.action,
200 new_items: ItemsView::from_slice(&new_items),
201 old_items: ItemsView::from_slice(&old_items),
202 new_starting_index: e.new_starting_index,
203 old_starting_index: e.old_starting_index,
204 });
205 },
206 )))
207 }
208
209 fn remove_collection_changed(&self, token: u64) {
210 self.collection_changed.remove(token);
211 }
212
213 fn as_any(&self) -> &dyn Any {
214 self
215 }
216}
217
218impl From<CommandBarElementCollection> for ItemsSource {
219 fn from(value: CommandBarElementCollection) -> Self {
220 value.as_items_source()
221 }
222}
223
224struct Reset<'a>(&'a Cell<bool>);
226
227impl Drop for Reset<'_> {
228 fn drop(&mut self) {
229 self.0.set(false);
230 }
231}
232
233fn element_as<T: ferroui_base::ObjectType>(element: &dyn ICommandBarElement) -> Option<&T> {
235 element.as_object().and_then(|object| object.downcast_ref::<T>())
236}
237
238fn is_separator(element: &dyn ICommandBarElement) -> bool {
239 element_as::<CommandBarSeparator>(element).is_some()
240}
241
242#[repr(C)]
245pub struct CommandBar {
246 base: TemplatedControl,
247 has_secondary_commands: Cell<bool>,
248 is_overflow_button_visible: Cell<bool>,
249
250 overflow_button: RefCell<Option<Ref<Button>>>,
251 overflow_popup: RefCell<Option<Ref<Popup>>>,
252 overflow_presenter: RefCell<Option<Ref<ItemsControl>>>,
253 content_presenter: RefCell<Option<Ref<Control>>>,
254 overflow_button_handlers: Cell<
258 Option<(RoutedEventHandlerToken, RoutedEventHandlerToken, RoutedEventHandlerToken, RoutedEventHandlerToken)>,
259 >,
260 overflow_presenter_handler: Cell<Option<RoutedEventHandlerToken>>,
261 overflow_popup_opened: RefCell<Option<Rc<dyn IDisposable>>>,
262 primary_commands_changed: Cell<Option<u64>>,
264 secondary_commands_changed: Cell<Option<u64>>,
265
266 visible_primary_commands: CommandBarElementCollection,
267 overflow_items: CommandBarElementCollection,
268 overflow_primary_secondary_separator: Rc<dyn ICommandBarElement>,
269 secondary_command_visibility_subscriptions: CompositeDisposable,
270 is_dynamic_update_in_progress: Cell<bool>,
271 is_opening_overflow_popup: Cell<bool>,
272 has_deferred_dynamic_overflow_update: Cell<bool>,
273 constraint_width: Cell<f64>,
274 opened_via_keyboard: Cell<bool>,
275}
276
277ferro_class!(CommandBar: TemplatedControl);
278
279ferro_class_info!(CommandBar {
280 new: CommandBar::new,
281 markup: {
282 content: PrimaryCommands,
283 properties: [
284 PrimaryCommands: CommandBarElementList {
287 get: CommandBar::primary_commands,
288 set: |bar: &Ref<CommandBar>, value: CommandBarElementList| bar.set_primary_commands(Some(value))
289 },
290 SecondaryCommands: CommandBarElementList {
291 get: CommandBar::secondary_commands,
292 set: |bar: &Ref<CommandBar>, value: CommandBarElementList| bar.set_secondary_commands(Some(value))
293 },
294 VisiblePrimaryCommands: CommandBarElementCollection { get: CommandBar::visible_primary_commands },
295 OverflowItems: CommandBarElementCollection { get: CommandBar::overflow_items },
296 ],
297 attributes: [
298 TemplatePart("PART_OverflowButton", type(Ref<Button>)),
299 TemplatePart("PART_OverflowPopup", type(Ref<Popup>)),
300 TemplatePart("PART_OverflowPresenter", type(Ref<ItemsControl>)),
301 TemplatePart("PART_ContentPresenter", type(Ref<Control>)),
302 ],
303 },
304});
305
306ferro_impl_classes!(CommandBar: StyledElementImpl, InteractiveImpl, InputElementImpl, ControlImpl);
307
308impl FerroObjectImpl for CommandBar {
309 fn constructed(this: &Self) {
310 Self::parent_constructed(this);
311
312 let primary_commands = CommandBarElementList::new();
313 this.set_current_value(Self::primary_commands_property(), Some(primary_commands));
314
315 let secondary_commands = CommandBarElementList::new();
316 this.set_current_value(Self::secondary_commands_property(), Some(secondary_commands));
317
318 this.rebuild_secondary_command_visibility_subscriptions();
319
320 let weak = this.to_ref().downgrade();
321 this.size_changed(move |_, _| {
322 if let Some(this) = weak.upgrade() {
323 this.command_bar_size_changed();
324 }
325 });
326 }
327
328 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
329 Self::parent_on_property_changed(this, change);
330
331 let property = change.property();
332
333 if property == Self::is_open_property().as_property() {
334 let is_open = change.get_new_value::<bool>();
335 if is_open {
336 this.raise_event(&RoutedEventArgs::with_event(Self::opening_event()));
337 let overflow_popup = this.overflow_popup.borrow().clone();
338 if let Some(overflow_popup) = overflow_popup {
339 {
340 this.is_opening_overflow_popup.set(true);
341 let _reset = Reset(&this.is_opening_overflow_popup);
342 overflow_popup.set_is_open(true);
343 }
344
345 this.apply_deferred_dynamic_overflow_update();
346 }
347 this.raise_event(&RoutedEventArgs::with_event(Self::opened_event()));
348 } else {
349 this.raise_event(&RoutedEventArgs::with_event(Self::closing_event()));
350 let overflow_popup = this.overflow_popup.borrow().clone();
351 if let Some(overflow_popup) = overflow_popup {
352 overflow_popup.set_is_open(false);
353 }
354 this.raise_event(&RoutedEventArgs::with_event(Self::closed_event()));
355 }
356 } else if property == Self::default_label_position_property().as_property() {
357 this.apply_label_position_to_children();
358 if this.is_dynamic_overflow_enabled() {
359 this.request_dynamic_overflow_update();
360 }
361 } else if property == Self::overflow_button_visibility_property().as_property() {
362 this.update_overflow_button_visibility();
363 } else if property == Self::is_sticky_property().as_property() {
364 this.update_sticky_behavior();
365 } else if property == Self::is_dynamic_overflow_enabled_property().as_property() {
366 this.request_dynamic_overflow_update();
367 } else if property == Self::primary_commands_property().as_property() {
368 let (old_primary, new_primary) = change.get_old_and_new_value::<Option<CommandBarElementList>>();
369 if let (Some(old_primary), Some(token)) = (old_primary, this.primary_commands_changed.take()) {
370 old_primary.remove_collection_changed(token);
371 }
372 if let Some(new_primary) = new_primary {
373 let weak = this.to_ref().downgrade();
374 let token = new_primary.add_collection_changed(Rc::new(
375 move |e: &NotifyCollectionChangedEventArgs<'_, Rc<dyn ICommandBarElement>>| {
376 if let Some(this) = weak.upgrade() {
377 this.on_primary_commands_changed(e);
378 }
379 },
380 ));
381 this.primary_commands_changed.set(Some(token));
382 }
383 this.apply_label_position_to_children();
384 this.request_dynamic_overflow_update();
385 } else if property == Self::secondary_commands_property().as_property() {
386 let (old_secondary, new_secondary) = change.get_old_and_new_value::<Option<CommandBarElementList>>();
387 if let (Some(old_secondary), Some(token)) = (old_secondary, this.secondary_commands_changed.take()) {
388 old_secondary.remove_collection_changed(token);
389 }
390 if let Some(new_secondary) = new_secondary {
391 let weak = this.to_ref().downgrade();
392 let token = new_secondary.add_collection_changed(Rc::new(
393 move |_: &NotifyCollectionChangedEventArgs<'_, Rc<dyn ICommandBarElement>>| {
394 if let Some(this) = weak.upgrade() {
395 this.on_secondary_commands_changed();
396 }
397 },
398 ));
399 this.secondary_commands_changed.set(Some(token));
400 }
401 this.rebuild_secondary_command_visibility_subscriptions();
402 this.request_dynamic_overflow_update();
403 }
404 }
405}
406
407impl VisualImpl for CommandBar {
408 fn on_attached_to_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
409 Self::parent_on_attached_to_visual_tree(this, e);
410 this.rebuild_secondary_command_visibility_subscriptions();
411 }
412
413 fn on_detached_from_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
414 this.secondary_command_visibility_subscriptions.clear();
415 Self::parent_on_detached_from_visual_tree(this, e);
416 }
417}
418
419impl LayoutableImpl for CommandBar {
420 fn measure_override(this: &Self, available_size: Size) -> Size {
421 let new_constraint = if available_size.width.is_finite() { available_size.width } else { f64::INFINITY };
422
423 if new_constraint != this.constraint_width.get() {
424 this.constraint_width.set(new_constraint);
425 this.request_dynamic_overflow_update();
426 }
427
428 Self::parent_measure_override(this, available_size)
429 }
430}
431
432impl TemplatedControlImpl for CommandBar {
433 fn on_apply_template(this: &Self, e: &TemplateAppliedEventArgs) {
434 Self::parent_on_apply_template(this, e);
435
436 let overflow_button = this.overflow_button.borrow().clone();
437 if let (Some(overflow_button), Some((click, got_focus, key_down, pointer_pressed))) =
438 (overflow_button, this.overflow_button_handlers.take())
439 {
440 overflow_button.remove_handler(Button::click_event(), click);
441 overflow_button.remove_handler(InputElement::got_focus_event(), got_focus);
442 overflow_button.remove_handler(InputElement::key_down_event(), key_down);
443 overflow_button.remove_handler(InputElement::pointer_pressed_event(), pointer_pressed);
444 }
445 let overflow_presenter = this.overflow_presenter.borrow().clone();
446 if let (Some(overflow_presenter), Some(key_down)) =
447 (overflow_presenter, this.overflow_presenter_handler.take())
448 {
449 overflow_presenter.remove_handler(InputElement::key_down_event(), key_down);
450 }
451 let overflow_popup_opened = this.overflow_popup_opened.take();
452 if let Some(overflow_popup_opened) = overflow_popup_opened {
453 overflow_popup_opened.dispose();
454 }
455
456 let overflow_button = e.name_scope().find_as::<Button>("PART_OverflowButton");
457 let overflow_popup = e.name_scope().find_as::<Popup>("PART_OverflowPopup");
458 let overflow_presenter = e.name_scope().find_as::<ItemsControl>("PART_OverflowPresenter");
459 let content_presenter = e.name_scope().find_as::<Control>("PART_ContentPresenter");
460 *this.overflow_button.borrow_mut() = overflow_button.clone();
461 *this.overflow_popup.borrow_mut() = overflow_popup.clone();
462 *this.overflow_presenter.borrow_mut() = overflow_presenter.clone();
463 *this.content_presenter.borrow_mut() = content_presenter;
464
465 let weak = this.to_ref().downgrade();
466
467 if let Some(overflow_button) = overflow_button {
468 let click = overflow_button.click({
469 let weak = weak.clone();
470 move |_, _| {
471 if let Some(this) = weak.upgrade() {
472 this.on_overflow_button_click();
473 }
474 }
475 });
476 let got_focus = overflow_button.add_handler(InputElement::got_focus_event(), {
477 let weak = weak.clone();
478 move |_: &Interactive, e: &FocusChangedEventArgs| {
479 if let Some(this) = weak.upgrade() {
480 this.on_overflow_button_got_focus(e);
481 }
482 }
483 });
484 let key_down = overflow_button.add_handler_with(
485 InputElement::key_down_event(),
486 {
487 let weak = weak.clone();
488 move |_: &Interactive, e: &KeyEventArgs| {
489 if let Some(this) = weak.upgrade() {
490 this.on_overflow_button_key_down(e);
491 }
492 }
493 },
494 Interactive::DEFAULT_ROUTES,
495 true,
496 );
497 let pointer_pressed = overflow_button.add_handler_with(
498 InputElement::pointer_pressed_event(),
499 {
500 let weak = weak.clone();
501 move |_: &Interactive, _| {
502 if let Some(this) = weak.upgrade() {
503 this.on_overflow_button_pointer_pressed();
504 }
505 }
506 },
507 Interactive::DEFAULT_ROUTES,
508 true,
509 );
510 this.overflow_button_handlers.set(Some((click, got_focus, key_down, pointer_pressed)));
511 }
512 if let Some(overflow_presenter) = overflow_presenter {
513 let key_down = overflow_presenter.add_handler(InputElement::key_down_event(), {
514 let weak = weak.clone();
515 move |_: &Interactive, e: &KeyEventArgs| {
516 if let Some(this) = weak.upgrade() {
517 this.on_overflow_presenter_key_down(e);
518 }
519 }
520 });
521 this.overflow_presenter_handler.set(Some(key_down));
522 }
523 if let Some(overflow_popup) = overflow_popup {
524 let opened = overflow_popup.opened(move || {
525 if let Some(this) = weak.upgrade() {
526 this.on_overflow_popup_opened();
527 }
528 });
529 *this.overflow_popup_opened.borrow_mut() = Some(opened);
530 }
531
532 this.apply_label_position_to_children();
533 this.update_overflow_button_visibility();
534 this.update_sticky_behavior();
535 this.request_dynamic_overflow_update();
536 }
537}
538
539ferro_properties! {
540 impl CommandBar {
541 pub fn primary_commands_property() -> StyledProperty<Option<CommandBarElementList>> {
543 FerroProperty::register::<CommandBar, _>("PrimaryCommands", None)
544 }
545
546 pub fn secondary_commands_property() -> StyledProperty<Option<CommandBarElementList>> {
548 FerroProperty::register::<CommandBar, _>("SecondaryCommands", None)
549 }
550
551 pub fn content_property() -> StyledProperty<Option<BoxedValue>> {
553 ContentControl::content_property().add_owner::<CommandBar>()
554 }
555
556 pub fn default_label_position_property() -> StyledProperty<CommandBarDefaultLabelPosition> {
558 FerroProperty::register::<CommandBar, _>("DefaultLabelPosition", CommandBarDefaultLabelPosition::Bottom)
559 }
560
561 pub fn is_dynamic_overflow_enabled_property() -> StyledProperty<bool> {
563 FerroProperty::register::<CommandBar, _>("IsDynamicOverflowEnabled", false)
564 }
565
566 pub fn overflow_button_visibility_property() -> StyledProperty<CommandBarOverflowButtonVisibility> {
568 FerroProperty::register::<CommandBar, _>(
569 "OverflowButtonVisibility",
570 CommandBarOverflowButtonVisibility::Auto,
571 )
572 }
573
574 pub fn is_open_property() -> StyledProperty<bool> {
576 FerroProperty::register::<CommandBar, _>("IsOpen", false)
577 }
578
579 pub fn is_sticky_property() -> StyledProperty<bool> {
581 FerroProperty::register::<CommandBar, _>("IsSticky", false)
582 }
583
584 pub fn item_width_bottom_property() -> StyledProperty<f64> {
586 FerroProperty::register::<CommandBar, _>("ItemWidthBottom", 70.0)
587 }
588
589 pub fn item_width_right_property() -> StyledProperty<f64> {
591 FerroProperty::register::<CommandBar, _>("ItemWidthRight", 102.0)
592 }
593
594 pub fn item_width_collapsed_property() -> StyledProperty<f64> {
596 FerroProperty::register::<CommandBar, _>("ItemWidthCollapsed", 42.0)
597 }
598
599 pub fn has_secondary_commands_property() -> DirectProperty<CommandBar, bool> {
601 FerroProperty::register_direct::<CommandBar, _>(
602 "HasSecondaryCommands",
603 |o| o.has_secondary_commands(),
604 None,
605 false,
606 )
607 }
608
609 pub fn is_overflow_button_visible_property() -> DirectProperty<CommandBar, bool> {
611 FerroProperty::register_direct::<CommandBar, _>(
612 "IsOverflowButtonVisible",
613 |o| o.is_overflow_button_visible(),
614 None,
615 false,
616 )
617 }
618 }
619}
620
621impl CommandBar {
622 ferro_routed_event!(
623 pub fn opening_event() -> RoutedEvent<RoutedEventArgs> {
625 RoutedEvent::register::<CommandBar, _>("Opening", RoutingStrategies::BUBBLE)
626 }
627 );
628
629 ferro_routed_event!(
630 pub fn opened_event() -> RoutedEvent<RoutedEventArgs> {
632 RoutedEvent::register::<CommandBar, _>("Opened", RoutingStrategies::BUBBLE)
633 }
634 );
635
636 ferro_routed_event!(
637 pub fn closing_event() -> RoutedEvent<RoutedEventArgs> {
639 RoutedEvent::register::<CommandBar, _>("Closing", RoutingStrategies::BUBBLE)
640 }
641 );
642
643 ferro_routed_event!(
644 pub fn closed_event() -> RoutedEvent<RoutedEventArgs> {
646 RoutedEvent::register::<CommandBar, _>("Closed", RoutingStrategies::BUBBLE)
647 }
648 );
649
650 fn static_constructor() {
651 ItemsSource::register_binding_conversion::<CommandBarElementCollection>();
654 }
655
656 pub fn construct() -> Self {
658 Self {
659 base: TemplatedControl::construct(),
660 has_secondary_commands: Cell::new(false),
661 is_overflow_button_visible: Cell::new(false),
662 overflow_button: RefCell::new(None),
663 overflow_popup: RefCell::new(None),
664 overflow_presenter: RefCell::new(None),
665 content_presenter: RefCell::new(None),
666 overflow_button_handlers: Cell::new(None),
667 overflow_presenter_handler: Cell::new(None),
668 overflow_popup_opened: RefCell::new(None),
669 primary_commands_changed: Cell::new(None),
670 secondary_commands_changed: Cell::new(None),
671 visible_primary_commands: CommandBarElementCollection::new(),
672 overflow_items: CommandBarElementCollection::new(),
673 overflow_primary_secondary_separator: CommandBarSeparator::new().as_command_bar_element(),
674 secondary_command_visibility_subscriptions: CompositeDisposable::new(),
675 is_dynamic_update_in_progress: Cell::new(false),
676 is_opening_overflow_popup: Cell::new(false),
677 has_deferred_dynamic_overflow_update: Cell::new(false),
678 constraint_width: Cell::new(f64::INFINITY),
679 opened_via_keyboard: Cell::new(false),
680 }
681 }
682
683 pub fn new() -> Ref<Self> {
685 instantiate(Self::construct())
686 }
687
688 pub fn visible_primary_commands(&self) -> CommandBarElementCollection {
691 self.visible_primary_commands.clone()
692 }
693
694 pub fn overflow_items(&self) -> CommandBarElementCollection {
698 self.overflow_items.clone()
699 }
700
701 pub fn primary_commands(&self) -> CommandBarElementList {
704 match self.get_value(Self::primary_commands_property()) {
705 Some(value) => value,
706 None => {
707 let list = CommandBarElementList::new();
708 self.set_current_value(Self::primary_commands_property(), Some(list.clone()));
709 list
710 }
711 }
712 }
713
714 pub fn set_primary_commands(&self, value: Option<CommandBarElementList>) {
716 self.set_value(Self::primary_commands_property(), value)
717 }
718
719 pub fn secondary_commands(&self) -> CommandBarElementList {
722 match self.get_value(Self::secondary_commands_property()) {
723 Some(value) => value,
724 None => {
725 let list = CommandBarElementList::new();
726 self.set_current_value(Self::secondary_commands_property(), Some(list.clone()));
727 list
728 }
729 }
730 }
731
732 pub fn set_secondary_commands(&self, value: Option<CommandBarElementList>) {
735 self.set_value(Self::secondary_commands_property(), value)
736 }
737
738 pub fn content(&self) -> Option<BoxedValue> {
740 self.get_value(Self::content_property())
741 }
742
743 pub fn set_content(&self, value: Option<BoxedValue>) {
745 self.set_value(Self::content_property(), value)
746 }
747
748 pub fn default_label_position(&self) -> CommandBarDefaultLabelPosition {
750 self.get_value(Self::default_label_position_property())
751 }
752
753 pub fn set_default_label_position(&self, value: CommandBarDefaultLabelPosition) {
755 self.set_value(Self::default_label_position_property(), value)
756 }
757
758 pub fn is_dynamic_overflow_enabled(&self) -> bool {
761 self.get_value(Self::is_dynamic_overflow_enabled_property())
762 }
763
764 pub fn set_is_dynamic_overflow_enabled(&self, value: bool) {
767 self.set_value(Self::is_dynamic_overflow_enabled_property(), value)
768 }
769
770 pub fn overflow_button_visibility(&self) -> CommandBarOverflowButtonVisibility {
772 self.get_value(Self::overflow_button_visibility_property())
773 }
774
775 pub fn set_overflow_button_visibility(&self, value: CommandBarOverflowButtonVisibility) {
777 self.set_value(Self::overflow_button_visibility_property(), value)
778 }
779
780 pub fn is_open(&self) -> bool {
782 self.get_value(Self::is_open_property())
783 }
784
785 pub fn set_is_open(&self, value: bool) {
787 self.set_value(Self::is_open_property(), value)
788 }
789
790 pub fn is_sticky(&self) -> bool {
793 self.get_value(Self::is_sticky_property())
794 }
795
796 pub fn set_is_sticky(&self, value: bool) {
799 self.set_value(Self::is_sticky_property(), value)
800 }
801
802 pub fn item_width_bottom(&self) -> f64 {
806 self.get_value(Self::item_width_bottom_property())
807 }
808
809 pub fn set_item_width_bottom(&self, value: f64) {
812 self.set_value(Self::item_width_bottom_property(), value)
813 }
814
815 pub fn item_width_right(&self) -> f64 {
819 self.get_value(Self::item_width_right_property())
820 }
821
822 pub fn set_item_width_right(&self, value: f64) {
824 self.set_value(Self::item_width_right_property(), value)
825 }
826
827 pub fn item_width_collapsed(&self) -> f64 {
831 self.get_value(Self::item_width_collapsed_property())
832 }
833
834 pub fn set_item_width_collapsed(&self, value: f64) {
836 self.set_value(Self::item_width_collapsed_property(), value)
837 }
838
839 pub fn has_secondary_commands(&self) -> bool {
842 self.has_secondary_commands.get()
843 }
844
845 fn set_has_secondary_commands(&self, value: bool) {
846 self.set_and_raise_cell(Self::has_secondary_commands_property(), &self.has_secondary_commands, value);
847 }
848
849 pub fn is_overflow_button_visible(&self) -> bool {
851 self.is_overflow_button_visible.get()
852 }
853
854 fn set_is_overflow_button_visible(&self, value: bool) {
855 self.set_and_raise_cell(Self::is_overflow_button_visible_property(), &self.is_overflow_button_visible, value);
856 }
857
858 pub fn opening(&self, handler: impl Fn(&Interactive, &RoutedEventArgs) + 'static) -> RoutedEventHandlerToken {
860 self.add_handler(Self::opening_event(), handler)
861 }
862
863 pub fn opened(&self, handler: impl Fn(&Interactive, &RoutedEventArgs) + 'static) -> RoutedEventHandlerToken {
865 self.add_handler(Self::opened_event(), handler)
866 }
867
868 pub fn closing(&self, handler: impl Fn(&Interactive, &RoutedEventArgs) + 'static) -> RoutedEventHandlerToken {
870 self.add_handler(Self::closing_event(), handler)
871 }
872
873 pub fn closed(&self, handler: impl Fn(&Interactive, &RoutedEventArgs) + 'static) -> RoutedEventHandlerToken {
875 self.add_handler(Self::closed_event(), handler)
876 }
877
878 fn command_bar_size_changed(&self) {
879 self.request_dynamic_overflow_update();
880 }
881
882 fn on_overflow_button_click(&self) {
883 self.set_current_value(Self::is_open_property(), !self.is_open());
884 }
885
886 fn on_overflow_button_got_focus(&self, e: &FocusChangedEventArgs) {
887 self.opened_via_keyboard
888 .set(matches!(e.navigation_method, NavigationMethod::Directional | NavigationMethod::Tab));
889 }
890
891 fn on_overflow_button_key_down(&self, e: &KeyEventArgs) {
892 if matches!(e.key, Key::Enter | Key::Space) {
893 self.opened_via_keyboard.set(true);
894 }
895 }
896
897 fn on_overflow_button_pointer_pressed(&self) {
898 self.opened_via_keyboard.set(false);
899 }
900
901 fn on_overflow_presenter_key_down(&self, e: &KeyEventArgs) {
902 match e.key {
903 Key::Up => {
904 self.navigate_overflow(false);
905 e.set_handled(true);
906 }
907 Key::Down => {
908 self.navigate_overflow(true);
909 e.set_handled(true);
910 }
911 Key::Home => {
912 self.focus_overflow_item(true, NavigationMethod::Directional);
913 e.set_handled(true);
914 }
915 Key::End => {
916 self.focus_overflow_item(false, NavigationMethod::Directional);
917 e.set_handled(true);
918 }
919 Key::Escape => {
920 self.set_current_value(Self::is_open_property(), false);
921 let overflow_button = self.overflow_button.borrow().clone();
922 if let Some(overflow_button) = overflow_button {
923 overflow_button.focus_with(NavigationMethod::Unspecified, KeyModifiers::NONE);
924 }
925 e.set_handled(true);
926 }
927 _ => {}
928 }
929 }
930
931 fn on_overflow_popup_opened(&self) {
932 let method =
933 if self.opened_via_keyboard.get() { NavigationMethod::Directional } else { NavigationMethod::Pointer };
934
935 let weak = self.to_ref().downgrade();
936 Dispatcher::ui_thread().post_local(
937 move || {
938 if let Some(this) = weak.upgrade() {
939 if this.is_open() {
940 this.focus_overflow_item(true, method);
941 }
942 }
943 },
944 DispatcherPriority::LOADED,
945 );
946 }
947
948 fn navigate_overflow(&self, forward: bool) {
949 let items = self.get_focusable_overflow_items();
950 if items.is_empty() {
951 return;
952 }
953
954 let current = items.iter().position(|item| item.is_focused() || item.is_keyboard_focus_within());
955
956 let next = match current {
957 None => {
958 if forward {
959 0
960 } else {
961 items.len() - 1
962 }
963 }
964 Some(current) => {
965 if forward {
966 (current + 1) % items.len()
967 } else {
968 (current + items.len() - 1) % items.len()
969 }
970 }
971 };
972
973 items[next].focus_with(NavigationMethod::Directional, KeyModifiers::NONE);
974 }
975
976 fn focus_overflow_item(&self, first: bool, method: NavigationMethod) {
977 let items = self.get_focusable_overflow_items();
978 if !items.is_empty() {
979 items[if first { 0 } else { items.len() - 1 }].focus_with(method, KeyModifiers::NONE);
980 }
981 }
982
983 fn get_focusable_overflow_items(&self) -> Vec<Ref<Control>> {
984 let mut result = Vec::new();
985 for item in self.overflow_items.snapshot().iter() {
986 if let Some(control) = element_as::<Control>(&**item) {
987 if control.is_enabled() && control.is_visible() && control.focusable() && !is_separator(&**item) {
988 result.push(control.to_ref());
989 }
990 }
991 }
992 result
993 }
994
995 fn on_primary_commands_changed(&self, e: &NotifyCollectionChangedEventArgs<'_, Rc<dyn ICommandBarElement>>) {
996 for element in e.new_items {
997 self.apply_label_position_to_element(&**element);
998 }
999 self.request_dynamic_overflow_update();
1000 }
1001
1002 fn on_secondary_commands_changed(&self) {
1003 self.rebuild_secondary_command_visibility_subscriptions();
1004 self.request_dynamic_overflow_update();
1005 }
1006
1007 fn request_dynamic_overflow_update(&self) {
1008 if self.is_opening_overflow_popup.get() {
1009 self.has_deferred_dynamic_overflow_update.set(true);
1010 return;
1011 }
1012
1013 self.update_dynamic_overflow();
1014 }
1015
1016 fn apply_deferred_dynamic_overflow_update(&self) {
1017 if !self.has_deferred_dynamic_overflow_update.get() {
1018 return;
1019 }
1020
1021 self.has_deferred_dynamic_overflow_update.set(false);
1022 self.update_dynamic_overflow();
1023 }
1024
1025 fn update_dynamic_overflow(&self) {
1026 let primary_commands = self.primary_commands().snapshot();
1030 let secondary_commands = self.secondary_commands().snapshot();
1031 if self.is_dynamic_update_in_progress.get() {
1032 return;
1033 }
1034 self.is_dynamic_update_in_progress.set(true);
1035 let _reset = Reset(&self.is_dynamic_update_in_progress);
1036
1037 self.visible_primary_commands.clear();
1038 self.overflow_items.clear();
1039 self.set_overflow_mode(&*self.overflow_primary_secondary_separator, false);
1040
1041 let mut overflowed_primary_commands: Vec<Rc<dyn ICommandBarElement>> = Vec::new();
1042
1043 let constraint_width = self.constraint_width.get();
1044 let available_width = if constraint_width.is_finite() { constraint_width } else { self.bounds().width };
1045
1046 if !self.is_dynamic_overflow_enabled() || available_width <= 0.0 {
1047 for item in primary_commands.iter() {
1048 self.set_overflow_mode(&**item, false);
1049 self.visible_primary_commands.add(item.clone());
1050 }
1051 } else {
1052 let content_presenter = self.content_presenter.borrow().clone();
1053 let content_width = content_presenter.map_or(0.0, |presenter| presenter.desired_size().width);
1054
1055 if available_width < 50.0 {
1056 for item in primary_commands.iter() {
1057 self.set_overflow_mode(&**item, false);
1058 self.visible_primary_commands.add(item.clone());
1059 }
1060 } else {
1061 let item_width = match self.default_label_position() {
1062 CommandBarDefaultLabelPosition::Right => self.item_width_right(),
1063 CommandBarDefaultLabelPosition::Collapsed => self.item_width_collapsed(),
1064 CommandBarDefaultLabelPosition::Bottom => self.item_width_bottom(),
1065 };
1066
1067 let primary_non_sep_count = primary_commands.iter().filter(|item| !is_separator(&***item)).count();
1068
1069 const OVERFLOW_BUTTON_WIDTH: f64 = 48.0;
1070 let padding = self.padding();
1071 let padding_h = padding.left + padding.right;
1072 let base_available = available_width - content_width - padding_h;
1073 let all_fit_without_button = secondary_commands.is_empty()
1074 && base_available + 2.0 >= primary_non_sep_count as f64 * item_width;
1075
1076 let max_items = if all_fit_without_button {
1077 primary_non_sep_count
1078 } else {
1079 let available_for_items = base_available - OVERFLOW_BUTTON_WIDTH;
1080 let mut max_items = ((available_for_items / item_width) as i32).max(0) as usize;
1081 if max_items == 0 && !primary_commands.is_empty() && available_for_items > 32.0 {
1082 max_items = 1;
1083 }
1084 max_items
1085 };
1086
1087 let mut prioritized: Vec<(usize, i32)> = primary_commands
1089 .iter()
1090 .enumerate()
1091 .map(|(index, item)| (index, Self::get_dynamic_overflow_order(&**item)))
1092 .collect();
1093
1094 prioritized.sort_by(|a, b| a.1.cmp(&b.1).then(a.0.cmp(&b.0)));
1095
1096 let mut visible_indices = vec![false; primary_commands.len()];
1098 let mut non_separator_count = 0;
1099 for &(index, _) in &prioritized {
1100 if is_separator(&*primary_commands[index]) {
1101 visible_indices[index] = true;
1102 } else if non_separator_count < max_items {
1103 visible_indices[index] = true;
1104 non_separator_count += 1;
1105 }
1106 }
1107
1108 if non_separator_count == 0 {
1109 visible_indices.fill(false);
1110 }
1111
1112 Self::trim_orphaned_separators_from_visible_commands(&primary_commands, &mut visible_indices);
1113
1114 for (index, item) in primary_commands.iter().enumerate() {
1115 if visible_indices[index] {
1116 self.set_overflow_mode(&**item, false);
1117 self.visible_primary_commands.add(item.clone());
1118 } else if is_separator(&**item) {
1119 self.set_overflow_mode(&**item, false);
1120 } else {
1121 self.set_overflow_mode(&**item, true);
1122 overflowed_primary_commands.push(item.clone());
1123 }
1124 }
1125 }
1126 }
1127
1128 self.add_overflow_items(&overflowed_primary_commands, &secondary_commands);
1129 self.set_has_secondary_commands(!self.overflow_items.is_empty());
1130 self.update_overflow_button_visibility();
1131 }
1132
1133 fn set_overflow_mode(&self, element: &dyn ICommandBarElement, in_overflow: bool) {
1134 element.set_is_in_overflow(in_overflow);
1135 self.apply_label_position_to_element(element);
1136 }
1137
1138 fn get_dynamic_overflow_order(element: &dyn ICommandBarElement) -> i32 {
1139 if let Some(button) = element_as::<CommandBarButton>(element) {
1140 button.dynamic_overflow_order()
1141 } else if let Some(toggle_button) = element_as::<CommandBarToggleButton>(element) {
1142 toggle_button.dynamic_overflow_order()
1143 } else {
1144 0
1145 }
1146 }
1147
1148 fn apply_label_position_to_children(&self) {
1149 for command in self.primary_commands().snapshot().iter() {
1150 self.apply_label_position_to_element(&**command);
1151 }
1152 for command in self.secondary_commands().snapshot().iter() {
1153 self.apply_label_position_to_element(&**command);
1154 }
1155
1156 self.apply_label_position_to_element(&*self.overflow_primary_secondary_separator);
1157 }
1158
1159 fn apply_label_position_to_element(&self, element: &dyn ICommandBarElement) {
1160 let label_position = if element.is_in_overflow() {
1161 CommandBarDefaultLabelPosition::Right
1162 } else {
1163 self.default_label_position()
1164 };
1165
1166 element.set_is_compact(label_position == CommandBarDefaultLabelPosition::Collapsed);
1167
1168 if let Some(button) = element_as::<CommandBarButton>(element) {
1169 button.set_label_position(label_position);
1170 } else if let Some(toggle_button) = element_as::<CommandBarToggleButton>(element) {
1171 toggle_button.set_label_position(label_position);
1172 }
1173 }
1174
1175 fn update_sticky_behavior(&self) {
1176 let overflow_popup = self.overflow_popup.borrow().clone();
1177 if let Some(overflow_popup) = overflow_popup {
1178 overflow_popup.set_is_light_dismiss_enabled(!self.is_sticky());
1179 }
1180 }
1181
1182 fn update_overflow_button_visibility(&self) {
1183 self.set_is_overflow_button_visible(match self.overflow_button_visibility() {
1184 CommandBarOverflowButtonVisibility::Visible => true,
1185 CommandBarOverflowButtonVisibility::Collapsed => false,
1186 CommandBarOverflowButtonVisibility::Auto => self.has_secondary_commands(),
1187 });
1188 }
1189
1190 fn add_overflow_items(
1191 &self,
1192 overflowed_primary_commands: &[Rc<dyn ICommandBarElement>],
1193 secondary_commands: &[Rc<dyn ICommandBarElement>],
1194 ) {
1195 for item in overflowed_primary_commands {
1196 self.overflow_items.add(item.clone());
1197 }
1198
1199 if !overflowed_primary_commands.is_empty() && Self::has_visible_elements(secondary_commands) {
1200 self.set_overflow_mode(&*self.overflow_primary_secondary_separator, true);
1201 self.overflow_items.add(self.overflow_primary_secondary_separator.clone());
1202 }
1203
1204 for item in secondary_commands {
1205 self.set_overflow_mode(&**item, true);
1206 self.overflow_items.add(item.clone());
1207 }
1208 }
1209
1210 fn rebuild_secondary_command_visibility_subscriptions(&self) {
1211 self.secondary_command_visibility_subscriptions.clear();
1212
1213 for command in self.secondary_commands().snapshot().iter() {
1214 if let Some(visual) = command.as_object().filter(|object| object.is::<Visual>()) {
1215 let is_initial_value = Cell::new(true);
1216 let weak = self.to_ref().downgrade();
1217 let subscription = FerroObjectExtensions::get_observable(visual, Visual::is_visible_property())
1218 .subscribe_fn(move |_| {
1219 if is_initial_value.replace(false) {
1220 return;
1221 }
1222
1223 if let Some(this) = weak.upgrade() {
1224 this.request_dynamic_overflow_update();
1225 }
1226 });
1227 self.secondary_command_visibility_subscriptions.add(subscription);
1228 }
1229 }
1230 }
1231
1232 fn has_visible_elements(commands: &[Rc<dyn ICommandBarElement>]) -> bool {
1233 commands.iter().any(|command| element_as::<Visual>(&**command).is_some_and(|visual| visual.is_visible()))
1234 }
1235
1236 fn trim_orphaned_separators_from_visible_commands(
1237 commands: &[Rc<dyn ICommandBarElement>],
1238 visible_indices: &mut [bool],
1239 ) {
1240 let mut to_remove = Vec::new();
1241 for i in 0..commands.len() {
1242 if !visible_indices[i] || !is_separator(&*commands[i]) {
1243 continue;
1244 }
1245
1246 let has_non_separator_before =
1247 Self::find_non_separator_in_visible_commands(commands, visible_indices, false, i as isize - 1)
1248 .is_some();
1249 let has_non_separator_after =
1250 Self::find_non_separator_in_visible_commands(commands, visible_indices, true, i as isize + 1)
1251 .is_some();
1252
1253 if !has_non_separator_before || !has_non_separator_after {
1254 to_remove.push(i);
1255 }
1256 }
1257
1258 for index in to_remove {
1259 visible_indices[index] = false;
1260 }
1261
1262 let mut previous_was_separator = false;
1263 for i in 0..commands.len() {
1264 if !visible_indices[i] {
1265 continue;
1266 }
1267
1268 if is_separator(&*commands[i]) {
1269 if previous_was_separator {
1270 visible_indices[i] = false;
1271 } else {
1272 previous_was_separator = true;
1273 }
1274 } else {
1275 previous_was_separator = false;
1276 }
1277 }
1278 }
1279
1280 fn find_non_separator_in_visible_commands(
1283 commands: &[Rc<dyn ICommandBarElement>],
1284 visible_indices: &[bool],
1285 forward: bool,
1286 start_index: isize,
1287 ) -> Option<usize> {
1288 let mut i = start_index;
1289 while if forward { i < commands.len() as isize } else { i >= 0 } {
1290 let index = i as usize;
1291 if visible_indices[index] && !is_separator(&*commands[index]) {
1292 return Some(index);
1293 }
1294 i += if forward { 1 } else { -1 };
1295 }
1296 None
1297 }
1298}