Skip to main content

ferroui_controls/auto_complete_box/
auto_complete_box.rs

1// (c) Copyright Microsoft Corporation.
2// This source is subject to the Microsoft Public License (Ms-PL).
3// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details.
4// All other rights reserved.
5//
6// Ported from the Silverlight Toolkit sources as adapted by the upstream
7// project; the license text is in the `NOTICE.md` of this crate.
8//
9// This file holds both parts of the upstream partial class: the control and
10// its properties.
11
12use super::{AutoCompleteFilterMode, PopulatedEventArgs, PopulatingEventArgs};
13use crate::primitives::{
14    Popup, SelectingItemsControl, TemplateAppliedEventArgs, TemplatedControl, TemplatedControlImpl,
15    TemplatedControlImplExt,
16};
17use crate::templates::{FuncDataTemplate, IDataTemplate};
18use crate::utils::{BindingEvaluator, ISelectionAdapter, SelectingItemsControlSelectionAdapter};
19use crate::{
20    AssignedBinding, ContentControl, ControlImpl, ItemsChangedEventArgs, ItemsChangedHandler, ItemsSource,
21    SelectionChangedEventArgs, TextBlock, TextBox, TextChangedEventArgs,
22};
23use ferroui_base::animation::TimeSpan;
24use ferroui_base::collections::{FerroList, NotifyCollectionChangedAction};
25use ferroui_base::controls::NameScopeRef;
26use ferroui_base::data::core::ValueTypes;
27use ferroui_base::data::BindingMode;
28use ferroui_base::input::navigation::XYFocusHelpers;
29use ferroui_base::input::{
30    FocusChangedEventArgs, InputElement, InputElementImpl, InputElementImplExt, Key, KeyEventArgs,
31};
32use ferroui_base::interactivity::{
33    Interactive, InteractiveImpl, RoutedEvent, RoutedEventHandlerToken, RoutingStrategies,
34};
35use ferroui_base::layout::LayoutableImpl;
36use ferroui_base::media::IBrush;
37use ferroui_base::reactive::{Disposable, IDisposable, ObservableExt};
38use ferroui_base::threading::{
39    CancellationToken, CancellationTokenSource, Dispatcher, DispatcherPriority, DispatcherTimer,
40    FerroSynchronizationContext, OperationCanceledError,
41};
42use ferroui_base::utilities::{CancelEventArgs, HandlerList, StringComparison};
43use ferroui_base::{
44    ferro_class, ferro_class_info, ferro_impl_classes, ferro_properties, ferro_routed_event, instantiate, BoxedValue,
45    DirectProperty, FerroObject, FerroObjectExtensions, FerroObjectImpl, FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs,
46    Ref, StyledElementImpl, StyledProperty, StyledPropertyMetadata, StyledPropertyOptions, VisualImpl,
47};
48use std::cell::{Cell, RefCell};
49use std::future::Future;
50use std::pin::Pin;
51use std::rc::Rc;
52
53/// Represents the filter used by the [`AutoCompleteBox`] control to
54/// determine whether an item is a possible match for the specified text.
55///
56/// The function takes the string used as the basis for filtering and the
57/// item that is compared with it, and returns `true` to indicate the item
58/// is a possible match. `T` is the type used for filtering: the text form
59/// of an item (`Option<String>`) or the item itself
60/// (`Option<BoxedValue>`).
61///
62/// A filter is a reference: copies compare equal to what they were copied
63/// from, two filters made from the same function do not.
64pub struct AutoCompleteFilterPredicate<T: 'static>(Rc<dyn Fn(Option<&str>, &T) -> bool>);
65
66impl<T: 'static> AutoCompleteFilterPredicate<T> {
67    /// Wraps a filter function.
68    pub fn new(filter: impl Fn(Option<&str>, &T) -> bool + 'static) -> Self {
69        Self(Rc::new(filter))
70    }
71
72    /// Calls the filter.
73    pub fn invoke(&self, search: Option<&str>, item: &T) -> bool {
74        (self.0)(search, item)
75    }
76}
77
78impl<T: 'static> Clone for AutoCompleteFilterPredicate<T> {
79    fn clone(&self) -> Self {
80        Self(self.0.clone())
81    }
82}
83
84impl<T: 'static> PartialEq for AutoCompleteFilterPredicate<T> {
85    fn eq(&self, other: &Self) -> bool {
86        Rc::ptr_eq(&self.0, &other.0)
87    }
88}
89
90/// Represents the selector used by the [`AutoCompleteBox`] control to
91/// determine how the specified text should be modified with an item.
92///
93/// The function takes the string used as the basis for filtering and the
94/// selected item that should be combined with it, and returns the modified
95/// text that will be used by the control. `T` is the type used for
96/// selecting: the text form of an item (`Option<String>`) or the item
97/// itself (`BoxedValue`).
98///
99/// A selector is a reference: copies compare equal to what they were copied
100/// from, two selectors made from the same function do not.
101pub struct AutoCompleteSelector<T: 'static>(Rc<dyn Fn(Option<&str>, &T) -> String>);
102
103impl<T: 'static> AutoCompleteSelector<T> {
104    /// Wraps a selector function.
105    pub fn new(selector: impl Fn(Option<&str>, &T) -> String + 'static) -> Self {
106        Self(Rc::new(selector))
107    }
108
109    /// Calls the selector.
110    pub fn invoke(&self, search: Option<&str>, item: &T) -> String {
111        (self.0)(search, item)
112    }
113}
114
115impl<T: 'static> Clone for AutoCompleteSelector<T> {
116    fn clone(&self) -> Self {
117        Self(self.0.clone())
118    }
119}
120
121impl<T: 'static> PartialEq for AutoCompleteSelector<T> {
122    fn eq(&self, other: &Self) -> bool {
123        Rc::ptr_eq(&self.0, &other.0)
124    }
125}
126
127/// The pending result of an [`AutoCompleteAsyncPopulator`]: the items for
128/// the drop-down, or the notice that the population was cancelled.
129pub type AutoCompletePopulation = Pin<Box<dyn Future<Output = Result<Vec<Option<BoxedValue>>, OperationCanceledError>>>>;
130
131/// The asynchronous populator of an [`AutoCompleteBox`]: given the search
132/// text and a cancellation token it produces the items for the drop-down.
133/// The future is driven by the dispatcher of the control.
134///
135/// A populator is a reference: copies compare equal to what they were
136/// copied from.
137#[derive(Clone)]
138pub struct AutoCompleteAsyncPopulator(Rc<dyn Fn(Option<String>, CancellationToken) -> AutoCompletePopulation>);
139
140impl AutoCompleteAsyncPopulator {
141    /// Wraps a populator function.
142    pub fn new(populator: impl Fn(Option<String>, CancellationToken) -> AutoCompletePopulation + 'static) -> Self {
143        Self(Rc::new(populator))
144    }
145
146    /// Calls the populator.
147    pub fn invoke(&self, search_text: Option<String>, cancellation_token: CancellationToken) -> AutoCompletePopulation {
148        (self.0)(search_text, cancellation_token)
149    }
150}
151
152impl PartialEq for AutoCompleteAsyncPopulator {
153    fn eq(&self, other: &Self) -> bool {
154        Rc::ptr_eq(&self.0, &other.0)
155    }
156}
157
158/// Specifies the name of the selection adapter template part.
159const ELEMENT_SELECTION_ADAPTER: &str = "PART_SelectionAdapter";
160
161/// Specifies the name of the selector template part.
162const ELEMENT_SELECTOR: &str = "PART_SelectingItemsControl";
163
164/// Specifies the name of the popup template part.
165const ELEMENT_POPUP: &str = "PART_Popup";
166
167/// The name for the text box part.
168const ELEMENT_TEXT_BOX: &str = "PART_TextBox";
169
170const PC_DROPDOWN_OPEN: &str = ":dropdownopen";
171
172/// Resets a flag when dropped (the `finally` of the reference).
173struct Reset<'a>(&'a Cell<bool>);
174
175impl Drop for Reset<'_> {
176    fn drop(&mut self) {
177        self.0.set(false);
178    }
179}
180
181/// The length of a text in UTF-16 code units, the unit of the positions of
182/// a text box.
183fn utf16_length(text: Option<&str>) -> i32 {
184    text.map_or(0, |text| text.encode_utf16().count() as i32)
185}
186
187/// The first `length` UTF-16 code units of a text.
188fn utf16_prefix(text: &str, length: i32) -> String {
189    let units: Vec<u16> = text.encode_utf16().take(length.max(0) as usize).collect();
190    String::from_utf16_lossy(&units)
191}
192
193/// Represents a control that provides a text box for user input and a
194/// drop-down that contains possible matches based on the input in the text
195/// box.
196#[repr(C)]
197pub struct AutoCompleteBox {
198    base: TemplatedControl,
199
200    /// A local cached copy of the items data.
201    items: RefCell<Option<Rc<Vec<Option<BoxedValue>>>>>,
202
203    /// The observable collection that contains references to all of the
204    /// items in the generated view of data that is provided to the
205    /// selection-style control adapter.
206    view: Rc<FerroList<Option<BoxedValue>>>,
207
208    /// The view as the items source handed to the selection adapter.
209    view_source: ItemsSource,
210
211    /// A counter that tracks how many pending `Text` property change
212    /// notifications should be ignored. This is used when the `Text`
213    /// property is set programmatically and we want to suppress the
214    /// corresponding change handlers. The counter is decremented after each
215    /// ignored notification.
216    ///
217    /// The counter is important because the text changed event of the text
218    /// box does not fire immediately; using a counter allows nested
219    /// property changes to be ignored safely.
220    ignore_text_property_change: Cell<i32>,
221
222    /// A counter that tracks how many pending text changed notifications of
223    /// the text box should be ignored. This is distinct from
224    /// `ignore_text_property_change` and is used when the notification of
225    /// the text box needs to be suppressed (e.g., during programmatic
226    /// updates to the text box content). The counter is decremented after
227    /// each ignored event.
228    ignore_text_box_text_change: Cell<i32>,
229
230    /// A value indicating whether to ignore calling a pending change
231    /// handlers.
232    ignore_property_change: Cell<bool>,
233
234    /// A value indicating whether to ignore the selection changed event.
235    ignore_text_selection_change: Cell<bool>,
236
237    /// A value indicating whether to skip the text update processing when
238    /// the selected item is updated.
239    skip_selected_item_text_update: Cell<bool>,
240
241    /// The last observed text box selection start location.
242    text_selection_start: Cell<i32>,
243
244    /// A value indicating whether the user initiated the current populate
245    /// call.
246    user_called_populate: Cell<bool>,
247
248    /// A value indicating whether the popup has been opened at least once.
249    popup_has_opened: Cell<bool>,
250
251    /// The dispatcher timer used for the `MinimumPopulateDelay` condition
252    /// for auto completion, with the subscription to its tick.
253    delay_timer: RefCell<Option<(Rc<DispatcherTimer>, Rc<dyn IDisposable>)>>,
254
255    /// A value indicating whether a read-only property change handler
256    /// should allow the value to be set. This is used to ensure that
257    /// read-only properties cannot be changed via `set_value`, etc.
258    allow_write: Cell<bool>,
259
260    /// A boolean indicating if a cancellation was requested.
261    cancel_requested: Cell<bool>,
262
263    /// A boolean indicating if filtering is in action.
264    filter_in_action: Cell<bool>,
265
266    /// The text box template part.
267    text_box: RefCell<Option<Ref<TextBox>>>,
268    text_box_subscriptions: RefCell<Option<Rc<dyn IDisposable>>>,
269
270    /// The selection adapter, with the subscriptions to its events.
271    adapter: RefCell<Option<Rc<dyn ISelectionAdapter>>>,
272    adapter_subscriptions: RefCell<Vec<Rc<dyn IDisposable>>>,
273
274    /// A control that can provide updated string values from a binding.
275    value_member_binding_evaluator: RefCell<Option<Ref<BindingEvaluator>>>,
276
277    /// A weak subscription for the collection changed event: the collection
278    /// and the token of the handler.
279    collection_change_subscription: RefCell<Option<(ItemsSource, u64)>>,
280
281    population_cancellation_token_source: RefCell<Option<CancellationTokenSource>>,
282
283    item_template_is_from_value_member_binding: Cell<bool>,
284    setting_item_template_from_value_member_binding: Cell<bool>,
285
286    search_text: RefCell<Option<String>>,
287
288    /// The drop down popup control, with the subscription to its closed
289    /// event.
290    drop_down_popup: RefCell<Option<(Ref<Popup>, Rc<dyn IDisposable>)>>,
291
292    populating: HandlerList<dyn Fn(&PopulatingEventArgs)>,
293    populated: HandlerList<dyn Fn(&PopulatedEventArgs)>,
294    drop_down_opening: HandlerList<dyn Fn(&CancelEventArgs)>,
295    drop_down_opened: HandlerList<dyn Fn()>,
296    drop_down_closing: HandlerList<dyn Fn(&CancelEventArgs)>,
297    drop_down_closed: HandlerList<dyn Fn()>,
298}
299
300ferro_class! {
301    AutoCompleteBox: TemplatedControl, virtuals AutoCompleteBoxImpl: TemplatedControlImpl {
302        /// Returns the [`ISelectionAdapter`] part, if possible. Otherwise,
303        /// `None`.
304        fn get_selection_adapter_part(this, name_scope: &NameScopeRef) -> Option<Rc<dyn ISelectionAdapter>>;
305        /// Raises the `Populating` event.
306        fn on_populating(this, e: &PopulatingEventArgs);
307        /// Raises the `Populated` event.
308        fn on_populated(this, e: &PopulatedEventArgs);
309        /// Raises the `SelectionChanged` event.
310        fn on_selection_changed(this, e: &SelectionChangedEventArgs);
311        /// Raises the `DropDownOpening` event.
312        fn on_drop_down_opening(this, e: &CancelEventArgs);
313        /// Raises the `DropDownOpened` event.
314        fn on_drop_down_opened(this);
315        /// Raises the `DropDownClosing` event.
316        fn on_drop_down_closing(this, e: &CancelEventArgs);
317        /// Raises the `DropDownClosed` event.
318        fn on_drop_down_closed(this);
319        /// Raises the `TextChanged` event.
320        fn on_text_changed(this, e: &TextChangedEventArgs);
321        /// Converts the specified object to a string by using the binding
322        /// specified by the `ValueMemberBinding` property.
323        ///
324        /// Override this method to provide a custom string conversion.
325        fn format_value(this, value: &Option<BoxedValue>) -> Option<String>;
326    }
327}
328
329ferro_class_info!(AutoCompleteBox {
330    new: AutoCompleteBox::new,
331    markup: {
332        property_attributes: [ValueMemberBinding: [InheritDataTypeFromItems("ItemsSource")]],
333        attributes: [
334            TemplatePart("PART_Popup", type(Ref<Popup>)),
335            TemplatePart("PART_SelectingItemsControl", type(Ref<SelectingItemsControl>)),
336            TemplatePart("PART_SelectionAdapter", type(Rc<dyn ISelectionAdapter>)),
337            TemplatePart("PART_TextBox", type(Ref<TextBox>)),
338            PseudoClasses(":dropdownopen"),
339        ],
340    },
341});
342
343ferro_impl_classes!(AutoCompleteBox: StyledElementImpl, VisualImpl, LayoutableImpl, InteractiveImpl);
344
345impl ControlImpl for AutoCompleteBox {
346    fn on_create_automation_peer(this: &Self) -> Ref<crate::automation::peers::AutomationPeer> {
347        crate::automation::peers::AutoCompleteBoxAutomationPeer::new(this).upcast()
348    }
349}
350
351impl FerroObjectImpl for AutoCompleteBox {
352    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
353        Self::parent_on_property_changed(this, change);
354
355        if change.property() == Self::value_member_binding_property().as_property() {
356            this.on_value_member_binding_changed(change.get_new_value::<Option<AssignedBinding>>());
357        }
358    }
359}
360
361impl InputElementImpl for AutoCompleteBox {
362    /// Provides handling for the key down event.
363    fn on_key_down(this: &Self, e: &KeyEventArgs) {
364        Self::parent_on_key_down(this, e);
365
366        if e.handled() || !this.is_enabled() {
367            return;
368        }
369
370        // The drop down is open, pass along the key event arguments to the
371        // selection adapter. If it isn't handled by the adapter's logic,
372        // then we handle some simple navigation scenarios for controlling
373        // the drop down.
374        if this.is_drop_down_open() {
375            if let Some(adapter) = this.selection_adapter() {
376                adapter.handle_key_down(e);
377                if e.handled() {
378                    return;
379                }
380            }
381
382            if e.key == Key::Escape {
383                this.on_adapter_selection_canceled();
384                e.set_handled(true);
385            }
386        } else {
387            // The drop down is not open, the Down key will toggle it open.
388            // Ignore key buttons, if they are used for XY focus.
389            if e.key == Key::Down && !XYFocusHelpers::is_allowed_xy_navigation_mode(this, Some(e.key_device_type)) {
390                this.set_current_value(Self::is_drop_down_open_property(), true);
391                e.set_handled(true);
392            }
393        }
394
395        // Standard drop down navigation.
396        match e.key {
397            Key::F4 => {
398                this.set_current_value(Self::is_drop_down_open_property(), !this.is_drop_down_open());
399                e.set_handled(true);
400            }
401            Key::Enter => {
402                if this.is_drop_down_open() {
403                    this.on_adapter_selection_complete();
404                    e.set_handled(true);
405                }
406            }
407            _ => {}
408        }
409    }
410
411    fn on_got_focus(this: &Self, e: &FocusChangedEventArgs) {
412        if let Some(text_box) = this.text_box() {
413            if this.text_box_selection_length() <= 0 {
414                text_box.focus();
415                text_box.select_all();
416            }
417        }
418
419        Self::parent_on_got_focus(this, e);
420    }
421
422    fn on_lost_focus(this: &Self, e: &FocusChangedEventArgs) {
423        this.set_current_value(Self::is_drop_down_open_property(), false);
424
425        this.user_called_populate.set(false);
426
427        let text_box = this.text_box();
428        let text_box_context_menu_is_open = text_box.as_ref().is_some_and(|text_box| {
429            text_box.context_flyout().is_some_and(|flyout| flyout.is_open())
430                || text_box.context_menu().is_some_and(|menu| menu.is_open())
431        });
432        let context_menu_is_open = this.context_flyout().is_some_and(|flyout| flyout.is_open())
433            || this.context_menu().is_some_and(|menu| menu.is_open());
434
435        if !text_box_context_menu_is_open && !context_menu_is_open && this.clear_selection_on_lost_focus() {
436            this.clear_text_box_selection();
437        }
438
439        Self::parent_on_lost_focus(this, e);
440    }
441}
442
443impl TemplatedControlImpl for AutoCompleteBox {
444    /// Builds the visual tree for the [`AutoCompleteBox`] control when a
445    /// new template is applied.
446    fn on_apply_template(this: &Self, e: &TemplateAppliedEventArgs) {
447        if let Some((_, closed)) = this.drop_down_popup.take() {
448            closed.dispose();
449        }
450
451        // Set the template parts. Individual part setters remove and add
452        // any event handlers.
453        let popup = e.name_scope().find_as::<Popup>(ELEMENT_POPUP);
454        if let Some(popup) = &popup {
455            let weak = this.to_ref().downgrade();
456            let closed = popup.closed(move || {
457                if let Some(this) = weak.upgrade() {
458                    this.drop_down_popup_closed();
459                }
460            });
461            *this.drop_down_popup.borrow_mut() = Some((popup.clone(), closed));
462        }
463
464        this.set_selection_adapter(this.get_selection_adapter_part(e.name_scope()));
465        this.set_text_box(e.name_scope().find_as::<TextBox>(ELEMENT_TEXT_BOX));
466
467        // If the drop down property indicates that the popup is open, flip
468        // its value to invoke the changed handler.
469        if this.is_drop_down_open() && popup.is_some_and(|popup| !popup.is_open()) {
470            this.opening_drop_down(false);
471        }
472
473        Self::parent_on_apply_template(this, e);
474    }
475}
476
477impl AutoCompleteBoxImpl for AutoCompleteBox {
478    fn get_selection_adapter_part(_this: &Self, name_scope: &NameScopeRef) -> Option<Rc<dyn ISelectionAdapter>> {
479        let mut adapter: Option<Rc<dyn ISelectionAdapter>> = None;
480        if let Some(selector) = name_scope.find_as::<SelectingItemsControl>(ELEMENT_SELECTOR) {
481            // Check if it is already a selection adapter.
482            adapter = name_scope.find(ELEMENT_SELECTOR).and_then(|object| <dyn ISelectionAdapter>::from_object(&object));
483            if adapter.is_none() {
484                // Built in support for wrapping a selector control.
485                adapter = Some(SelectingItemsControlSelectionAdapter::with_selector(selector));
486            }
487        }
488        if adapter.is_none() {
489            adapter = name_scope.find(ELEMENT_SELECTION_ADAPTER).map(|object| {
490                <dyn ISelectionAdapter>::from_object(&object).unwrap_or_else(|| {
491                    panic!(
492                        "Expected control '{ELEMENT_SELECTION_ADAPTER}' to be 'ISelectionAdapter' but it was '{}'.",
493                        object.get_type().name()
494                    )
495                })
496            });
497        }
498        adapter
499    }
500
501    fn on_populating(this: &Self, e: &PopulatingEventArgs) {
502        for (_, handler) in this.populating.snapshot().iter() {
503            handler(e);
504        }
505    }
506
507    fn on_populated(this: &Self, e: &PopulatedEventArgs) {
508        for (_, handler) in this.populated.snapshot().iter() {
509            handler(e);
510        }
511    }
512
513    fn on_selection_changed(this: &Self, e: &SelectionChangedEventArgs) {
514        this.raise_event(e);
515    }
516
517    fn on_drop_down_opening(this: &Self, e: &CancelEventArgs) {
518        for (_, handler) in this.drop_down_opening.snapshot().iter() {
519            handler(e);
520        }
521    }
522
523    fn on_drop_down_opened(this: &Self) {
524        for (_, handler) in this.drop_down_opened.snapshot().iter() {
525            handler();
526        }
527    }
528
529    fn on_drop_down_closing(this: &Self, e: &CancelEventArgs) {
530        for (_, handler) in this.drop_down_closing.snapshot().iter() {
531            handler(e);
532        }
533    }
534
535    fn on_drop_down_closed(this: &Self) {
536        for (_, handler) in this.drop_down_closed.snapshot().iter() {
537            handler();
538        }
539    }
540
541    fn on_text_changed(this: &Self, e: &TextChangedEventArgs) {
542        this.raise_event(e);
543    }
544
545    fn format_value(this: &Self, value: &Option<BoxedValue>) -> Option<String> {
546        if let Some(value_member_binding) = this.value_member_binding() {
547            let evaluator = this.value_member_binding_evaluator.borrow().clone();
548            let evaluator = evaluator.unwrap_or_else(|| {
549                let evaluator = BindingEvaluator::new();
550                *this.value_member_binding_evaluator.borrow_mut() = Some(evaluator.clone());
551                evaluator
552            });
553            evaluator.update_binding(&value_member_binding);
554            let result = evaluator.evaluate_string(value).unwrap_or_default();
555            evaluator.clear_data_context();
556            return Some(result);
557        }
558
559        Some(ValueTypes::to_display_string(value.as_ref()))
560    }
561}
562
563ferro_properties! {
564    impl AutoCompleteBox {
565        /// Defines the `CaretIndex` property.
566        pub fn caret_index_property() -> StyledProperty<i32> {
567            TextBox::caret_index_property().add_owner_with::<AutoCompleteBox>(
568                StyledPropertyMetadata::new(Some(0)).with_default_binding_mode(BindingMode::TwoWay),
569            )
570        }
571
572        /// Defines the `PlaceholderText` property.
573        pub fn placeholder_text_property() -> StyledProperty<Option<String>> {
574            TextBox::placeholder_text_property().add_owner::<AutoCompleteBox>()
575        }
576
577        /// Defines the `PlaceholderForeground` property.
578        pub fn placeholder_foreground_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
579            TextBox::placeholder_foreground_property().add_owner::<AutoCompleteBox>()
580        }
581
582        /// Identifies the `MinimumPrefixLength` property.
583        pub fn minimum_prefix_length_property() -> StyledProperty<i32> {
584            FerroProperty::register_with::<AutoCompleteBox, _>(
585                "MinimumPrefixLength",
586                StyledPropertyOptions::new(1).validate(AutoCompleteBox::is_valid_minimum_prefix_length),
587            )
588        }
589
590        /// Identifies the `MinimumPopulateDelay` property.
591        pub fn minimum_populate_delay_property() -> StyledProperty<TimeSpan> {
592            FerroProperty::register_with::<AutoCompleteBox, _>(
593                "MinimumPopulateDelay",
594                StyledPropertyOptions::new(TimeSpan::ZERO).validate(AutoCompleteBox::is_valid_minimum_populate_delay),
595            )
596        }
597
598        /// Identifies the `MaxDropDownHeight` property.
599        pub fn max_drop_down_height_property() -> StyledProperty<f64> {
600            FerroProperty::register_with::<AutoCompleteBox, _>(
601                "MaxDropDownHeight",
602                StyledPropertyOptions::new(f64::INFINITY).validate(AutoCompleteBox::is_valid_max_drop_down_height),
603            )
604        }
605
606        /// Identifies the `IsTextCompletionEnabled` property.
607        pub fn is_text_completion_enabled_property() -> StyledProperty<bool> {
608            FerroProperty::register::<AutoCompleteBox, _>("IsTextCompletionEnabled", false)
609        }
610
611        /// Identifies the `ItemTemplate` property.
612        pub fn item_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
613            FerroProperty::register::<AutoCompleteBox, _>("ItemTemplate", None)
614        }
615
616        /// Defines the `ClearSelectionOnLostFocus` property.
617        pub fn clear_selection_on_lost_focus_property() -> StyledProperty<bool> {
618            TextBox::clear_selection_on_lost_focus_property().add_owner::<AutoCompleteBox>()
619        }
620
621        /// Identifies the `IsDropDownOpen` property.
622        pub fn is_drop_down_open_property() -> StyledProperty<bool> {
623            FerroProperty::register::<AutoCompleteBox, _>("IsDropDownOpen", false)
624        }
625
626        /// Identifies the `SelectedItem` property.
627        pub fn selected_item_property() -> StyledProperty<Option<BoxedValue>> {
628            FerroProperty::register_with::<AutoCompleteBox, _>(
629                "SelectedItem",
630                StyledPropertyOptions::new(None).default_binding_mode(BindingMode::TwoWay).enable_data_validation(true),
631            )
632        }
633
634        /// Identifies the `Text` property.
635        pub fn text_property() -> StyledProperty<Option<String>> {
636            TextBlock::text_property().add_owner_with::<AutoCompleteBox>(
637                StyledPropertyMetadata::new(Some(Some(String::new())))
638                    .with_default_binding_mode(BindingMode::TwoWay)
639                    .with_enable_data_validation(true),
640            )
641        }
642
643        /// Identifies the `SearchText` property.
644        pub fn search_text_property() -> DirectProperty<AutoCompleteBox, Option<String>> {
645            FerroProperty::register_direct::<AutoCompleteBox, _>(
646                "SearchText",
647                |o| o.search_text(),
648                None,
649                Some(String::new()),
650            )
651        }
652
653        /// Gets the identifier for the `FilterMode` property.
654        pub fn filter_mode_property() -> StyledProperty<AutoCompleteFilterMode> {
655            FerroProperty::register_with::<AutoCompleteBox, _>(
656                "FilterMode",
657                StyledPropertyOptions::new(AutoCompleteFilterMode::StartsWith)
658                    .validate(AutoCompleteBox::is_valid_filter_mode),
659            )
660        }
661
662        /// Identifies the `ItemFilter` property.
663        pub fn item_filter_property() -> StyledProperty<Option<AutoCompleteFilterPredicate<Option<BoxedValue>>>> {
664            FerroProperty::register::<AutoCompleteBox, _>("ItemFilter", None)
665        }
666
667        /// Identifies the `TextFilter` property.
668        pub fn text_filter_property() -> StyledProperty<Option<AutoCompleteFilterPredicate<Option<String>>>> {
669            FerroProperty::register::<AutoCompleteBox, _>(
670                "TextFilter",
671                AutoCompleteSearch::get_filter(AutoCompleteFilterMode::StartsWith),
672            )
673        }
674
675        /// Identifies the `ItemSelector` property.
676        pub fn item_selector_property() -> StyledProperty<Option<AutoCompleteSelector<BoxedValue>>> {
677            FerroProperty::register::<AutoCompleteBox, _>("ItemSelector", None)
678        }
679
680        /// Identifies the `TextSelector` property.
681        pub fn text_selector_property() -> StyledProperty<Option<AutoCompleteSelector<Option<String>>>> {
682            FerroProperty::register::<AutoCompleteBox, _>("TextSelector", None)
683        }
684
685        /// Identifies the `ItemsSource` property.
686        pub fn items_source_property() -> StyledProperty<Option<ItemsSource>> {
687            FerroProperty::register::<AutoCompleteBox, _>("ItemsSource", None)
688        }
689
690        /// Identifies the `AsyncPopulator` property.
691        pub fn async_populator_property() -> StyledProperty<Option<AutoCompleteAsyncPopulator>> {
692            FerroProperty::register::<AutoCompleteBox, _>("AsyncPopulator", None)
693        }
694
695        /// Defines the `MaxLength` property.
696        pub fn max_length_property() -> StyledProperty<i32> {
697            TextBox::max_length_property().add_owner::<AutoCompleteBox>()
698        }
699
700        /// Defines the `InnerLeftContent` property.
701        pub fn inner_left_content_property() -> StyledProperty<Option<BoxedValue>> {
702            TextBox::inner_left_content_property().add_owner::<AutoCompleteBox>()
703        }
704
705        /// Defines the `InnerRightContent` property.
706        pub fn inner_right_content_property() -> StyledProperty<Option<BoxedValue>> {
707            TextBox::inner_right_content_property().add_owner::<AutoCompleteBox>()
708        }
709
710        /// Defines the `ValueMemberBinding` property.
711        // XAML-SEAM: [InheritDataTypeFromItems(nameof(ItemsSource))] on the property (compiled bindings in markup).
712        pub fn value_member_binding_property() -> StyledProperty<Option<AssignedBinding>> {
713            FerroProperty::register_with::<AutoCompleteBox, _>(
714                "ValueMemberBinding",
715                StyledPropertyOptions::new(None).assign_binding(true),
716            )
717        }
718    }
719}
720
721impl AutoCompleteBox {
722    /// Defines the `Watermark` property.
723    #[deprecated(note = "Use placeholder_text_property instead.")]
724    pub fn watermark_property() -> &'static StyledProperty<Option<String>> {
725        Self::placeholder_text_property()
726    }
727
728    /// Defines the `WatermarkForeground` property.
729    #[deprecated(note = "Use placeholder_foreground_property instead.")]
730    pub fn watermark_foreground_property() -> &'static StyledProperty<Option<Rc<dyn IBrush>>> {
731        Self::placeholder_foreground_property()
732    }
733
734    ferro_routed_event!(
735        /// Defines the `SelectionChanged` event.
736        pub fn selection_changed_event() -> RoutedEvent<SelectionChangedEventArgs> {
737            RoutedEvent::register::<AutoCompleteBox, _>("SelectionChanged", RoutingStrategies::BUBBLE)
738        }
739    );
740
741    ferro_routed_event!(
742        /// Defines the `TextChanged` event.
743        pub fn text_changed_event() -> RoutedEvent<TextChangedEventArgs> {
744            RoutedEvent::register::<AutoCompleteBox, _>("TextChanged", RoutingStrategies::BUBBLE)
745        }
746    );
747
748    fn is_valid_minimum_prefix_length(value: &i32) -> bool {
749        *value >= -1
750    }
751
752    fn is_valid_minimum_populate_delay(value: &TimeSpan) -> bool {
753        value.total_milliseconds() >= 0.0
754    }
755
756    fn is_valid_max_drop_down_height(value: &f64) -> bool {
757        *value >= 0.0
758    }
759
760    fn is_valid_filter_mode(mode: &AutoCompleteFilterMode) -> bool {
761        match mode {
762            AutoCompleteFilterMode::None
763            | AutoCompleteFilterMode::StartsWith
764            | AutoCompleteFilterMode::StartsWithCaseSensitive
765            | AutoCompleteFilterMode::StartsWithOrdinal
766            | AutoCompleteFilterMode::StartsWithOrdinalCaseSensitive
767            | AutoCompleteFilterMode::Contains
768            | AutoCompleteFilterMode::ContainsCaseSensitive
769            | AutoCompleteFilterMode::ContainsOrdinal
770            | AutoCompleteFilterMode::ContainsOrdinalCaseSensitive
771            | AutoCompleteFilterMode::Equals
772            | AutoCompleteFilterMode::EqualsCaseSensitive
773            | AutoCompleteFilterMode::EqualsOrdinal
774            | AutoCompleteFilterMode::EqualsOrdinalCaseSensitive
775            | AutoCompleteFilterMode::Custom => true,
776        }
777    }
778
779    /// Handle the change of the `IsEnabled` property.
780    fn on_control_is_enabled_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
781        let is_enabled = e.get_new_value::<bool>();
782        if !is_enabled {
783            self.set_current_value(Self::is_drop_down_open_property(), false);
784        }
785    }
786
787    /// `MinimumPopulateDelay` property changed handler. Any current
788    /// dispatcher timer will be stopped. The timer will not be restarted
789    /// until the next text update call by the user.
790    fn on_minimum_populate_delay_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
791        let new_value = e.get_new_value::<TimeSpan>();
792
793        // Stop any existing timer.
794        let existing = self.delay_timer.borrow().as_ref().map(|(timer, _)| timer.clone());
795        if let Some(timer) = existing {
796            timer.stop();
797
798            if new_value == TimeSpan::ZERO {
799                if let Some((_, tick)) = self.delay_timer.take() {
800                    tick.dispose();
801                }
802            }
803        }
804
805        if new_value > TimeSpan::ZERO {
806            // Create or clear a dispatcher timer instance.
807            let timer = self.delay_timer.borrow().as_ref().map(|(timer, _)| timer.clone());
808            let timer = timer.unwrap_or_else(|| {
809                let timer = DispatcherTimer::new();
810                let weak = self.to_ref().downgrade();
811                let tick = timer.tick(move |_| {
812                    if let Some(this) = weak.upgrade() {
813                        this.populate_drop_down();
814                    }
815                });
816                *self.delay_timer.borrow_mut() = Some((timer.clone(), tick));
817                timer
818            });
819
820            // Set the new tick interval: the delay is positive here, so it is a duration.
821            timer.set_interval(new_value.to_duration().unwrap_or_default());
822        }
823    }
824
825    /// `IsDropDownOpen` property changed handler.
826    fn on_is_drop_down_open_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
827        // Ignore the change if requested.
828        if self.ignore_property_change.get() {
829            self.ignore_property_change.set(false);
830            return;
831        }
832
833        let (old_value, new_value) = e.get_old_and_new_value::<bool>();
834
835        if new_value {
836            self.text_updated(self.text(), true);
837        } else {
838            self.closing_drop_down(old_value);
839        }
840
841        self.update_pseudo_classes();
842    }
843
844    fn on_selected_item_property_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
845        if self.ignore_property_change.get() {
846            self.ignore_property_change.set(false);
847            return;
848        }
849
850        let (old_value, new_value) = e.get_old_and_new_value::<Option<BoxedValue>>();
851
852        // Update the text display.
853        if self.skip_selected_item_text_update.get() {
854            self.skip_selected_item_text_update.set(false);
855        } else {
856            self.on_selected_item_changed(&new_value);
857        }
858
859        // Fire the SelectionChanged event.
860        let mut removed = Vec::new();
861        if old_value.is_some() {
862            removed.push(old_value);
863        }
864
865        let mut added = Vec::new();
866        if new_value.is_some() {
867            added.push(new_value);
868        }
869
870        self.on_selection_changed(&SelectionChangedEventArgs::new(
871            Some(Self::selection_changed_event()),
872            removed,
873            added,
874        ));
875    }
876
877    /// `Text` property changed handler.
878    fn on_text_property_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
879        if self.ignore_text_property_change.get() > 0 {
880            self.ignore_text_property_change.set(self.ignore_text_property_change.get() - 1);
881            return;
882        }
883
884        self.text_updated(e.get_new_value::<Option<String>>(), false);
885    }
886
887    fn on_search_text_property_changed(&self, _e: &FerroPropertyChangedEventArgs<'_>) {
888        if self.ignore_property_change.get() {
889            self.ignore_property_change.set(false);
890            return;
891        }
892
893        // Ensure the property is only written when expected.
894        if !self.allow_write.get() {
895            // The reference resets the old value here before it reports the
896            // error; the property has no setter, so there is nothing that
897            // could have written it and nothing to write the old value
898            // with.
899            panic!("Cannot set read-only property SearchText.");
900        }
901    }
902
903    /// `FilterMode` property changed handler.
904    fn on_filter_mode_property_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
905        let mode = e.get_new_value::<AutoCompleteFilterMode>();
906
907        // Sets the filter predicate for the new value.
908        self.set_current_value(Self::text_filter_property(), AutoCompleteSearch::get_filter(mode));
909    }
910
911    /// `ItemFilter` property changed handler.
912    fn on_item_filter_property_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
913        let value = e.get_new_value::<Option<AutoCompleteFilterPredicate<Option<BoxedValue>>>>();
914
915        // If null, revert to the "None" predicate.
916        if value.is_none() {
917            self.set_current_value(Self::filter_mode_property(), AutoCompleteFilterMode::None);
918        } else {
919            self.set_current_value(Self::filter_mode_property(), AutoCompleteFilterMode::Custom);
920            self.set_current_value(Self::text_filter_property(), None);
921        }
922    }
923
924    /// `ItemsSource` property changed handler.
925    fn on_items_source_property_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
926        self.on_items_source_changed(e.get_new_value::<Option<ItemsSource>>());
927    }
928
929    fn on_item_template_property_changed(&self, _e: &FerroPropertyChangedEventArgs<'_>) {
930        if !self.setting_item_template_from_value_member_binding.get() {
931            self.item_template_is_from_value_member_binding.set(false);
932        }
933    }
934
935    fn on_value_member_binding_changed(&self, value: Option<AssignedBinding>) {
936        if self.item_template_is_from_value_member_binding.get() {
937            let template: Rc<dyn IDataTemplate> = FuncDataTemplate::new(
938                |data| data.is_some(),
939                move |_, _| {
940                    let control = ContentControl::new();
941                    if let Some(value) = &value {
942                        control.bind_binding(ContentControl::content_property().as_property(), &**value);
943                    }
944                    Some(control.upcast())
945                },
946                false,
947            );
948
949            self.setting_item_template_from_value_member_binding.set(true);
950            self.set_current_value(Self::item_template_property(), Some(template));
951            self.setting_item_template_from_value_member_binding.set(false);
952        }
953    }
954
955    fn static_constructor() {
956        InputElement::focusable_property().override_default_value::<AutoCompleteBox>(true);
957        InputElement::is_tab_stop_property().override_default_value::<AutoCompleteBox>(false);
958
959        Self::minimum_populate_delay_property()
960            .changed()
961            .add_class_handler::<AutoCompleteBox>(|x, e| x.on_minimum_populate_delay_changed(e));
962        Self::is_drop_down_open_property()
963            .changed()
964            .add_class_handler::<AutoCompleteBox>(|x, e| x.on_is_drop_down_open_changed(e));
965        Self::selected_item_property()
966            .changed()
967            .add_class_handler::<AutoCompleteBox>(|x, e| x.on_selected_item_property_changed(e));
968        Self::text_property().changed().add_class_handler::<AutoCompleteBox>(|x, e| x.on_text_property_changed(e));
969        Self::search_text_property()
970            .changed()
971            .add_class_handler::<AutoCompleteBox>(|x, e| x.on_search_text_property_changed(e));
972        Self::filter_mode_property()
973            .changed()
974            .add_class_handler::<AutoCompleteBox>(|x, e| x.on_filter_mode_property_changed(e));
975        Self::item_filter_property()
976            .changed()
977            .add_class_handler::<AutoCompleteBox>(|x, e| x.on_item_filter_property_changed(e));
978        Self::items_source_property()
979            .changed()
980            .add_class_handler::<AutoCompleteBox>(|x, e| x.on_items_source_property_changed(e));
981        Self::item_template_property()
982            .changed()
983            .add_class_handler::<AutoCompleteBox>(|x, e| x.on_item_template_property_changed(e));
984        InputElement::is_enabled_property()
985            .changed()
986            .add_class_handler::<AutoCompleteBox>(|x, e| x.on_control_is_enabled_changed(e));
987    }
988
989    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
990    pub fn construct() -> Self {
991        // The view object is always present: this is the first call of the
992        // view clearing helper of the reference.
993        let view = Rc::new(FerroList::new());
994        let view_source = ItemsSource::from(view.clone());
995
996        Self {
997            base: TemplatedControl::construct(),
998            items: RefCell::new(None),
999            view,
1000            view_source,
1001            ignore_text_property_change: Cell::new(0),
1002            ignore_text_box_text_change: Cell::new(0),
1003            ignore_property_change: Cell::new(false),
1004            ignore_text_selection_change: Cell::new(false),
1005            skip_selected_item_text_update: Cell::new(false),
1006            text_selection_start: Cell::new(0),
1007            user_called_populate: Cell::new(false),
1008            popup_has_opened: Cell::new(false),
1009            delay_timer: RefCell::new(None),
1010            allow_write: Cell::new(false),
1011            cancel_requested: Cell::new(false),
1012            filter_in_action: Cell::new(false),
1013            text_box: RefCell::new(None),
1014            text_box_subscriptions: RefCell::new(None),
1015            adapter: RefCell::new(None),
1016            adapter_subscriptions: RefCell::new(Vec::new()),
1017            value_member_binding_evaluator: RefCell::new(None),
1018            collection_change_subscription: RefCell::new(None),
1019            population_cancellation_token_source: RefCell::new(None),
1020            item_template_is_from_value_member_binding: Cell::new(true),
1021            setting_item_template_from_value_member_binding: Cell::new(false),
1022            search_text: RefCell::new(Some(String::new())),
1023            drop_down_popup: RefCell::new(None),
1024            populating: HandlerList::new(),
1025            populated: HandlerList::new(),
1026            drop_down_opening: HandlerList::new(),
1027            drop_down_opened: HandlerList::new(),
1028            drop_down_closing: HandlerList::new(),
1029            drop_down_closed: HandlerList::new(),
1030        }
1031    }
1032
1033    /// Initializes a new instance of the [`AutoCompleteBox`] class.
1034    pub fn new() -> Ref<Self> {
1035        instantiate(Self::construct())
1036    }
1037
1038    // --- properties ---------------------------------------------------------
1039
1040    /// Gets or sets the caret index.
1041    pub fn caret_index(&self) -> i32 {
1042        self.get_value(Self::caret_index_property())
1043    }
1044
1045    pub fn set_caret_index(&self, value: i32) {
1046        self.set_value(Self::caret_index_property(), value)
1047    }
1048
1049    /// Gets or sets the minimum number of characters required to be entered
1050    /// in the text box before the [`AutoCompleteBox`] displays possible
1051    /// matches. The default is 1.
1052    ///
1053    /// If you set `MinimumPrefixLength` to -1, the control will not provide
1054    /// possible matches. There is no maximum value, but setting it to a
1055    /// value that is too large will prevent the control from providing
1056    /// possible matches as well.
1057    pub fn minimum_prefix_length(&self) -> i32 {
1058        self.get_value(Self::minimum_prefix_length_property())
1059    }
1060
1061    pub fn set_minimum_prefix_length(&self, value: i32) {
1062        self.set_value(Self::minimum_prefix_length_property(), value)
1063    }
1064
1065    /// Gets or sets a value indicating whether the first possible match
1066    /// found during the filtering process will be displayed automatically
1067    /// in the text box. The default is `false`.
1068    pub fn is_text_completion_enabled(&self) -> bool {
1069        self.get_value(Self::is_text_completion_enabled_property())
1070    }
1071
1072    pub fn set_is_text_completion_enabled(&self, value: bool) {
1073        self.set_value(Self::is_text_completion_enabled_property(), value)
1074    }
1075
1076    /// Gets or sets the data template that is used to display the items in
1077    /// the drop-down portion of the [`AutoCompleteBox`] control.
1078    pub fn item_template(&self) -> Option<Rc<dyn IDataTemplate>> {
1079        self.get_value(Self::item_template_property())
1080    }
1081
1082    pub fn set_item_template(&self, value: Option<Rc<dyn IDataTemplate>>) {
1083        self.set_value(Self::item_template_property(), value)
1084    }
1085
1086    /// Gets or sets the minimum delay after text is typed in the text box
1087    /// before the control populates the list of possible matches in the
1088    /// drop-down. The default is zero.
1089    pub fn minimum_populate_delay(&self) -> TimeSpan {
1090        self.get_value(Self::minimum_populate_delay_property())
1091    }
1092
1093    pub fn set_minimum_populate_delay(&self, value: TimeSpan) {
1094        self.set_value(Self::minimum_populate_delay_property(), value)
1095    }
1096
1097    /// Gets or sets the maximum height of the drop-down portion of the
1098    /// [`AutoCompleteBox`] control. The default is infinity.
1099    pub fn max_drop_down_height(&self) -> f64 {
1100        self.get_value(Self::max_drop_down_height_property())
1101    }
1102
1103    pub fn set_max_drop_down_height(&self, value: f64) {
1104        self.set_value(Self::max_drop_down_height_property(), value)
1105    }
1106
1107    /// Gets or sets a value indicating whether the drop-down portion of the
1108    /// control is open.
1109    pub fn is_drop_down_open(&self) -> bool {
1110        self.get_value(Self::is_drop_down_open_property())
1111    }
1112
1113    pub fn set_is_drop_down_open(&self, value: bool) {
1114        self.set_value(Self::is_drop_down_open_property(), value)
1115    }
1116
1117    /// Gets or sets a value that determines whether the control clears its
1118    /// selection after it loses focus.
1119    pub fn clear_selection_on_lost_focus(&self) -> bool {
1120        self.get_value(Self::clear_selection_on_lost_focus_property())
1121    }
1122
1123    pub fn set_clear_selection_on_lost_focus(&self, value: bool) {
1124        self.set_value(Self::clear_selection_on_lost_focus_property(), value)
1125    }
1126
1127    /// Gets or sets the binding that is used to get the values for display
1128    /// in the text box portion of the [`AutoCompleteBox`] control, and to
1129    /// filter items for display in the drop-down.
1130    pub fn value_member_binding(&self) -> Option<AssignedBinding> {
1131        self.get_value(Self::value_member_binding_property())
1132    }
1133
1134    pub fn set_value_member_binding(&self, value: Option<AssignedBinding>) {
1135        self.set_value(Self::value_member_binding_property(), value)
1136    }
1137
1138    /// Gets or sets the selected item in the drop-down. The default is
1139    /// `None`.
1140    ///
1141    /// If the `IsTextCompletionEnabled` property is `true` and text typed
1142    /// by the user matches an item in the `ItemsSource` collection, which
1143    /// is then displayed in the text box, the `SelectedItem` property will
1144    /// be a null reference.
1145    pub fn selected_item(&self) -> Option<BoxedValue> {
1146        self.get_value(Self::selected_item_property())
1147    }
1148
1149    pub fn set_selected_item(&self, value: Option<BoxedValue>) {
1150        self.set_value(Self::selected_item_property(), value)
1151    }
1152
1153    /// Gets or sets the text in the text box portion of the
1154    /// [`AutoCompleteBox`] control.
1155    pub fn text(&self) -> Option<String> {
1156        self.get_value(Self::text_property())
1157    }
1158
1159    pub fn set_text(&self, value: Option<&str>) {
1160        self.set_value(Self::text_property(), value.map(str::to_owned))
1161    }
1162
1163    /// Gets the text that is used to filter items in the `ItemsSource` item
1164    /// collection.
1165    ///
1166    /// The search text value is typically the same as the `Text` property,
1167    /// but is set after the text changed event occurs and before the
1168    /// `Populating` event.
1169    pub fn search_text(&self) -> Option<String> {
1170        self.search_text.borrow().clone()
1171    }
1172
1173    fn set_search_text(&self, value: Option<String>) {
1174        self.allow_write.set(true);
1175        let _reset = Reset(&self.allow_write);
1176        self.set_and_raise(Self::search_text_property(), &self.search_text, value);
1177    }
1178
1179    /// Gets or sets how the text in the text box is used to filter items
1180    /// specified by the `ItemsSource` property for display in the
1181    /// drop-down. The default is [`AutoCompleteFilterMode::StartsWith`].
1182    ///
1183    /// Use the `FilterMode` property to specify how possible matches are
1184    /// filtered. For example, possible matches can be filtered in a
1185    /// predefined or custom way. `FilterMode` is automatically set to
1186    /// `Custom` if you set the `ItemFilter` property.
1187    pub fn filter_mode(&self) -> AutoCompleteFilterMode {
1188        self.get_value(Self::filter_mode_property())
1189    }
1190
1191    pub fn set_filter_mode(&self, value: AutoCompleteFilterMode) {
1192        self.set_value(Self::filter_mode_property(), value)
1193    }
1194
1195    /// Gets or sets the placeholder or descriptive text that is displayed
1196    /// even if the `Text` property is not yet set.
1197    pub fn placeholder_text(&self) -> Option<String> {
1198        self.get_value(Self::placeholder_text_property())
1199    }
1200
1201    pub fn set_placeholder_text(&self, value: Option<&str>) {
1202        self.set_value(Self::placeholder_text_property(), value.map(str::to_owned))
1203    }
1204
1205    /// Gets or sets the placeholder or descriptive text that is displayed
1206    /// even if the `Text` property is not yet set.
1207    #[deprecated(note = "Use placeholder_text instead.")]
1208    pub fn watermark(&self) -> Option<String> {
1209        self.placeholder_text()
1210    }
1211
1212    #[deprecated(note = "Use set_placeholder_text instead.")]
1213    pub fn set_watermark(&self, value: Option<&str>) {
1214        self.set_placeholder_text(value)
1215    }
1216
1217    /// Gets or sets the brush used for the foreground color of the
1218    /// placeholder text.
1219    pub fn placeholder_foreground(&self) -> Option<Rc<dyn IBrush>> {
1220        self.get_value(Self::placeholder_foreground_property())
1221    }
1222
1223    pub fn set_placeholder_foreground(&self, value: Option<Rc<dyn IBrush>>) {
1224        self.set_value(Self::placeholder_foreground_property(), value)
1225    }
1226
1227    /// Gets or sets the brush used for the foreground color of the
1228    /// placeholder text.
1229    #[deprecated(note = "Use placeholder_foreground instead.")]
1230    pub fn watermark_foreground(&self) -> Option<Rc<dyn IBrush>> {
1231        self.placeholder_foreground()
1232    }
1233
1234    #[deprecated(note = "Use set_placeholder_foreground instead.")]
1235    pub fn set_watermark_foreground(&self, value: Option<Rc<dyn IBrush>>) {
1236        self.set_placeholder_foreground(value)
1237    }
1238
1239    /// Gets or sets the custom method that uses user-entered text to filter
1240    /// the items specified by the `ItemsSource` property for display in the
1241    /// drop-down. The default is `None`.
1242    ///
1243    /// The filter mode is automatically set to `Custom` if you set the
1244    /// `ItemFilter` property.
1245    pub fn item_filter(&self) -> Option<AutoCompleteFilterPredicate<Option<BoxedValue>>> {
1246        self.get_value(Self::item_filter_property())
1247    }
1248
1249    pub fn set_item_filter(&self, value: Option<AutoCompleteFilterPredicate<Option<BoxedValue>>>) {
1250        self.set_value(Self::item_filter_property(), value)
1251    }
1252
1253    /// Gets or sets the custom method that uses the user-entered text to
1254    /// filter items specified by the `ItemsSource` property in a text-based
1255    /// way for display in the drop-down.
1256    ///
1257    /// The search mode is automatically set to `Custom` if you set the
1258    /// `TextFilter` property.
1259    pub fn text_filter(&self) -> Option<AutoCompleteFilterPredicate<Option<String>>> {
1260        self.get_value(Self::text_filter_property())
1261    }
1262
1263    pub fn set_text_filter(&self, value: Option<AutoCompleteFilterPredicate<Option<String>>>) {
1264        self.set_value(Self::text_filter_property(), value)
1265    }
1266
1267    /// Gets or sets the custom method that combines the user-entered text
1268    /// and one of the items specified by the `ItemsSource`.
1269    pub fn item_selector(&self) -> Option<AutoCompleteSelector<BoxedValue>> {
1270        self.get_value(Self::item_selector_property())
1271    }
1272
1273    pub fn set_item_selector(&self, value: Option<AutoCompleteSelector<BoxedValue>>) {
1274        self.set_value(Self::item_selector_property(), value)
1275    }
1276
1277    /// Gets or sets the custom method that combines the user-entered text
1278    /// and one of the items specified by the `ItemsSource` in a text-based
1279    /// way.
1280    pub fn text_selector(&self) -> Option<AutoCompleteSelector<Option<String>>> {
1281        self.get_value(Self::text_selector_property())
1282    }
1283
1284    pub fn set_text_selector(&self, value: Option<AutoCompleteSelector<Option<String>>>) {
1285        self.set_value(Self::text_selector_property(), value)
1286    }
1287
1288    /// Gets or sets the asynchronous populator of the drop-down.
1289    pub fn async_populator(&self) -> Option<AutoCompleteAsyncPopulator> {
1290        self.get_value(Self::async_populator_property())
1291    }
1292
1293    pub fn set_async_populator(&self, value: Option<AutoCompleteAsyncPopulator>) {
1294        self.set_value(Self::async_populator_property(), value)
1295    }
1296
1297    /// Gets or sets a collection that is used to generate the items for the
1298    /// drop-down portion of the [`AutoCompleteBox`] control.
1299    pub fn items_source(&self) -> Option<ItemsSource> {
1300        self.get_value(Self::items_source_property())
1301    }
1302
1303    pub fn set_items_source(&self, value: Option<ItemsSource>) {
1304        self.set_value(Self::items_source_property(), value)
1305    }
1306
1307    /// Gets or sets the maximum number of characters that the
1308    /// [`AutoCompleteBox`] can accept. This constraint only applies for
1309    /// manually entered (user-inputted) text.
1310    pub fn max_length(&self) -> i32 {
1311        self.get_value(Self::max_length_property())
1312    }
1313
1314    pub fn set_max_length(&self, value: i32) {
1315        self.set_value(Self::max_length_property(), value)
1316    }
1317
1318    /// Gets or sets custom content that is positioned on the left side of
1319    /// the text layout box.
1320    pub fn inner_left_content(&self) -> Option<BoxedValue> {
1321        self.get_value(Self::inner_left_content_property())
1322    }
1323
1324    pub fn set_inner_left_content(&self, value: Option<BoxedValue>) {
1325        self.set_value(Self::inner_left_content_property(), value)
1326    }
1327
1328    /// Gets or sets custom content that is positioned on the right side of
1329    /// the text layout box.
1330    pub fn inner_right_content(&self) -> Option<BoxedValue> {
1331        self.get_value(Self::inner_right_content_property())
1332    }
1333
1334    pub fn set_inner_right_content(&self, value: Option<BoxedValue>) {
1335        self.set_value(Self::inner_right_content_property(), value)
1336    }
1337
1338    // --- template parts -----------------------------------------------------
1339
1340    /// The drop down popup control.
1341    fn drop_down_popup(&self) -> Option<Ref<Popup>> {
1342        self.drop_down_popup.borrow().as_ref().map(|(popup, _)| popup.clone())
1343    }
1344
1345    /// The text box template part.
1346    fn text_box(&self) -> Option<Ref<TextBox>> {
1347        self.text_box.borrow().clone()
1348    }
1349
1350    fn set_text_box(&self, value: Option<Ref<TextBox>>) {
1351        if let Some(subscriptions) = self.text_box_subscriptions.take() {
1352            subscriptions.dispose();
1353        }
1354        *self.text_box.borrow_mut() = value.clone();
1355
1356        // Attach handlers.
1357        if let Some(text_box) = value {
1358            let weak = self.to_ref().downgrade();
1359            // The value at the time of subscribing is skipped.
1360            let skipped = Cell::new(false);
1361            let object: &FerroObject = &text_box;
1362            let subscription =
1363                FerroObjectExtensions::get_observable(object, TextBox::text_property()).subscribe_fn(move |_| {
1364                    if !skipped.replace(true) {
1365                        return;
1366                    }
1367                    if let Some(this) = weak.upgrade() {
1368                        this.on_text_box_text_changed();
1369                    }
1370                });
1371            *self.text_box_subscriptions.borrow_mut() = Some(subscription);
1372
1373            if let Some(text) = self.text() {
1374                self.update_text_value(Some(text), None);
1375            }
1376        }
1377    }
1378
1379    fn text_box_selection_start(&self) -> i32 {
1380        match self.text_box() {
1381            Some(text_box) => text_box.selection_start().min(text_box.selection_end()),
1382            None => 0,
1383        }
1384    }
1385
1386    fn text_box_selection_length(&self) -> i32 {
1387        match self.text_box() {
1388            Some(text_box) => (text_box.selection_end() - text_box.selection_start()).abs(),
1389            None => 0,
1390        }
1391    }
1392
1393    /// Gets the selection adapter used to populate the drop-down with a
1394    /// list of selectable items.
1395    ///
1396    /// You can use this property when you create an automation peer to use
1397    /// with the control or deriving from [`AutoCompleteBox`] to create a
1398    /// custom control.
1399    pub fn selection_adapter(&self) -> Option<Rc<dyn ISelectionAdapter>> {
1400        self.adapter.borrow().clone()
1401    }
1402
1403    /// Sets the selection adapter used to populate the drop-down with a
1404    /// list of selectable items.
1405    pub fn set_selection_adapter(&self, value: Option<Rc<dyn ISelectionAdapter>>) {
1406        let old = self.adapter.borrow().clone();
1407        if let Some(old) = old {
1408            for subscription in self.adapter_subscriptions.take() {
1409                subscription.dispose();
1410            }
1411            old.set_items_source(None);
1412        }
1413
1414        *self.adapter.borrow_mut() = value.clone();
1415
1416        if let Some(adapter) = value {
1417            let weak = self.to_ref().downgrade();
1418            let selection_changed = adapter.selection_changed(Rc::new({
1419                let weak = weak.clone();
1420                move |_| {
1421                    if let Some(this) = weak.upgrade() {
1422                        this.on_adapter_selection_changed();
1423                    }
1424                }
1425            }));
1426            let commit = adapter.commit(Rc::new({
1427                let weak = weak.clone();
1428                move |_| {
1429                    if let Some(this) = weak.upgrade() {
1430                        this.on_adapter_selection_complete();
1431                    }
1432                }
1433            }));
1434            let cancel = adapter.cancel(Rc::new({
1435                let weak = weak.clone();
1436                move |_| {
1437                    if let Some(this) = weak.upgrade() {
1438                        this.on_adapter_selection_canceled();
1439                    }
1440                }
1441            }));
1442            let cancel_complete = adapter.cancel(Rc::new(move |_| {
1443                if let Some(this) = weak.upgrade() {
1444                    this.on_adapter_selection_complete();
1445                }
1446            }));
1447            *self.adapter_subscriptions.borrow_mut() = vec![selection_changed, commit, cancel, cancel_complete];
1448            adapter.set_items_source(Some(self.view_source.clone()));
1449        }
1450    }
1451
1452    /// Determines whether the text box or drop-down portion of the
1453    /// [`AutoCompleteBox`] control has focus.
1454    #[deprecated(note = "Use is_keyboard_focus_within instead.")]
1455    pub fn has_focus(&self) -> bool {
1456        self.is_keyboard_focus_within()
1457    }
1458
1459    // --- events -------------------------------------------------------------
1460
1461    /// The handle removing a handler from one of the plain events.
1462    fn subscription<F: ?Sized + 'static>(
1463        &self,
1464        list: fn(&Self) -> &HandlerList<F>,
1465        handler: Rc<F>,
1466    ) -> Rc<dyn IDisposable> {
1467        let token = list(self).add(handler);
1468        let weak = self.to_ref().downgrade();
1469        Disposable::create(move || {
1470            if let Some(this) = weak.upgrade() {
1471                list(&this).remove(token);
1472            }
1473        })
1474    }
1475
1476    /// Occurs asynchronously when the text in the text box portion of the
1477    /// [`AutoCompleteBox`] changes.
1478    pub fn text_changed(
1479        &self,
1480        handler: impl Fn(&Interactive, &TextChangedEventArgs) + 'static,
1481    ) -> RoutedEventHandlerToken {
1482        self.add_handler(Self::text_changed_event(), handler)
1483    }
1484
1485    /// Occurs when the [`AutoCompleteBox`] is populating the drop-down with
1486    /// possible matches based on the `Text` property.
1487    ///
1488    /// If the event is cancelled, by setting the cancel flag of the args,
1489    /// the control will not automatically populate the selection adapter
1490    /// contained in the drop-down. In this case, if you want possible
1491    /// matches to appear, you must provide the logic for populating the
1492    /// selection adapter.
1493    pub fn populating(&self, handler: impl Fn(&PopulatingEventArgs) + 'static) -> Rc<dyn IDisposable> {
1494        self.subscription::<dyn Fn(&PopulatingEventArgs)>(|this| &this.populating, Rc::new(handler))
1495    }
1496
1497    /// Occurs when the [`AutoCompleteBox`] has populated the drop-down with
1498    /// possible matches based on the `Text` property.
1499    pub fn populated(&self, handler: impl Fn(&PopulatedEventArgs) + 'static) -> Rc<dyn IDisposable> {
1500        self.subscription::<dyn Fn(&PopulatedEventArgs)>(|this| &this.populated, Rc::new(handler))
1501    }
1502
1503    /// Occurs when the value of the `IsDropDownOpen` property is changing
1504    /// from false to true.
1505    pub fn drop_down_opening(&self, handler: impl Fn(&CancelEventArgs) + 'static) -> Rc<dyn IDisposable> {
1506        self.subscription::<dyn Fn(&CancelEventArgs)>(|this| &this.drop_down_opening, Rc::new(handler))
1507    }
1508
1509    /// Occurs when the value of the `IsDropDownOpen` property has changed
1510    /// from false to true and the drop-down is open.
1511    pub fn drop_down_opened(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
1512        self.subscription::<dyn Fn()>(|this| &this.drop_down_opened, Rc::new(handler))
1513    }
1514
1515    /// Occurs when the `IsDropDownOpen` property is changing from true to
1516    /// false.
1517    pub fn drop_down_closing(&self, handler: impl Fn(&CancelEventArgs) + 'static) -> Rc<dyn IDisposable> {
1518        self.subscription::<dyn Fn(&CancelEventArgs)>(|this| &this.drop_down_closing, Rc::new(handler))
1519    }
1520
1521    /// Occurs when the `IsDropDownOpen` property was changed from true to
1522    /// false and the drop-down is open.
1523    pub fn drop_down_closed(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
1524        self.subscription::<dyn Fn()>(|this| &this.drop_down_closed, Rc::new(handler))
1525    }
1526
1527    /// Occurs when the selected item in the drop-down portion of the
1528    /// [`AutoCompleteBox`] has changed.
1529    pub fn selection_changed(
1530        &self,
1531        handler: impl Fn(&Interactive, &SelectionChangedEventArgs) + 'static,
1532    ) -> RoutedEventHandlerToken {
1533        self.add_handler(Self::selection_changed_event(), handler)
1534    }
1535
1536    // --- drop-down ----------------------------------------------------------
1537
1538    /// Begin closing the drop-down.
1539    fn closing_drop_down(&self, old_value: bool) {
1540        let args = CancelEventArgs::new();
1541        self.on_drop_down_closing(&args);
1542
1543        if args.cancel() {
1544            self.ignore_property_change.set(true);
1545            self.set_current_value(Self::is_drop_down_open_property(), old_value);
1546        } else {
1547            self.close_drop_down();
1548        }
1549
1550        self.update_pseudo_classes();
1551    }
1552
1553    /// Begin opening the drop down by firing cancelable events, opening the
1554    /// drop-down or reverting, depending on the event argument values.
1555    fn opening_drop_down(&self, old_value: bool) {
1556        let args = CancelEventArgs::new();
1557
1558        // Opening.
1559        self.on_drop_down_opening(&args);
1560
1561        if args.cancel() {
1562            self.ignore_property_change.set(true);
1563            self.set_current_value(Self::is_drop_down_open_property(), old_value);
1564        } else {
1565            self.open_drop_down();
1566        }
1567
1568        self.update_pseudo_classes();
1569    }
1570
1571    /// Connects to the closed event of the drop down popup.
1572    fn drop_down_popup_closed(&self) {
1573        // Force the drop down property to be false.
1574        if self.is_drop_down_open() {
1575            self.set_current_value(Self::is_drop_down_open_property(), false);
1576        }
1577
1578        // Fire the DropDownClosed event.
1579        if self.popup_has_opened.get() {
1580            self.on_drop_down_closed();
1581        }
1582    }
1583
1584    /// Handles the timer tick when using a populate delay.
1585    fn populate_drop_down(&self) {
1586        let timer = self.delay_timer.borrow().as_ref().map(|(timer, _)| timer.clone());
1587        if let Some(timer) = timer {
1588            timer.stop();
1589        }
1590
1591        // Update the prefix/search text.
1592        self.set_search_text(self.text());
1593
1594        if self.try_populate_async(self.search_text()) {
1595            return;
1596        }
1597
1598        // The Populated event enables advanced, custom filtering. The
1599        // client needs to directly update the ItemsSource collection or
1600        // call the Populate method on the control to continue the display
1601        // process if Cancel is set to true.
1602        let populating = PopulatingEventArgs::new(self.search_text());
1603        self.on_populating(&populating);
1604        if !populating.cancel() {
1605            self.populate_complete();
1606        }
1607    }
1608
1609    fn try_populate_async(&self, search_text: Option<String>) -> bool {
1610        if let Some(source) = self.population_cancellation_token_source.take() {
1611            source.cancel();
1612        }
1613
1614        let Some(populator) = self.async_populator() else {
1615            return false;
1616        };
1617
1618        let source = CancellationTokenSource::new();
1619        let token = source.token();
1620        *self.population_cancellation_token_source.borrow_mut() = Some(source);
1621
1622        // Started the way an asynchronous method is called: it runs up to
1623        // its first pending await before this returns.
1624        let context = FerroSynchronizationContext::current().unwrap_or_else(|| {
1625            FerroSynchronizationContext::with_dispatcher(&Dispatcher::current_dispatcher(), DispatcherPriority::NORMAL)
1626        });
1627        drop(context.to_task_scheduler().start_local(Self::populate_async(self.to_ref(), populator, search_text, token)));
1628
1629        true
1630    }
1631
1632    async fn populate_async(
1633        this: Ref<Self>,
1634        populator: AutoCompleteAsyncPopulator,
1635        search_text: Option<String>,
1636        cancellation_token: CancellationToken,
1637    ) {
1638        // A cancelled population is dropped silently.
1639        if let Ok(result_list) = populator.invoke(search_text, cancellation_token.clone()).await {
1640            if !cancellation_token.is_cancellation_requested() {
1641                let target = this.clone();
1642                let token = cancellation_token.clone();
1643                let operation = Dispatcher::ui_thread().invoke_async_local(move || {
1644                    if !token.is_cancellation_requested() {
1645                        target.set_current_value(
1646                            Self::items_source_property(),
1647                            Some(ItemsSource::from_items(result_list)),
1648                        );
1649                        target.populate_complete();
1650                    }
1651                });
1652                // An aborted operation ends the population like a cancelled
1653                // one.
1654                let _ = operation.await;
1655            }
1656        }
1657
1658        this.population_cancellation_token_source.take();
1659    }
1660
1661    /// Private method that directly opens the popup, checks the expander
1662    /// button, and then fires the Opened event.
1663    fn open_drop_down(&self) {
1664        if let Some(popup) = self.drop_down_popup() {
1665            popup.set_is_open(true);
1666        }
1667        self.popup_has_opened.set(true);
1668        self.on_drop_down_opened();
1669    }
1670
1671    /// Private method that directly closes the popup, flips the Checked
1672    /// value, and then fires the Closed event.
1673    fn close_drop_down(&self) {
1674        if self.popup_has_opened.get() {
1675            if let Some(adapter) = self.selection_adapter() {
1676                adapter.set_selected_item(None);
1677            }
1678            if let Some(popup) = self.drop_down_popup() {
1679                popup.set_is_open(false);
1680            }
1681            self.on_drop_down_closed();
1682        }
1683    }
1684
1685    // --- text ---------------------------------------------------------------
1686
1687    /// Handle the text changed notification that is directly attached to
1688    /// the text box part. This ensures that only user initiated actions
1689    /// will result in an [`AutoCompleteBox`] suggestion and operation.
1690    fn on_text_box_text_changed(&self) {
1691        if self.ignore_text_box_text_change.get() > 0 {
1692            self.ignore_text_box_text_change.set(self.ignore_text_box_text_change.get() - 1);
1693            return;
1694        }
1695
1696        // Posted to allow the text box selection to update before
1697        // processing.
1698        let this = self.to_ref();
1699        Dispatcher::ui_thread().post_local(
1700            move || {
1701                // Call the central updated text method as a user-initiated
1702                // action.
1703                if let Some(text_box) = this.text_box() {
1704                    this.text_updated(text_box.text(), true);
1705                }
1706            },
1707            DispatcherPriority::DEFAULT,
1708        );
1709    }
1710
1711    /// Updates both the text box value and underlying text property value
1712    /// if and when they change. Automatically fires the text changed events
1713    /// when there is a change.
1714    ///
1715    /// `user_initiated` indicates whether the action was user initiated. In
1716    /// a user initiated mode, the underlying text property is updated. In a
1717    /// non-user interaction, the text box value is updated. When it is
1718    /// `None`, all values are updated.
1719    fn update_text_value(&self, value: Option<String>, user_initiated: Option<bool>) {
1720        let mut call_text_changed = false;
1721
1722        // Update the Text property.
1723        if user_initiated.unwrap_or(true) && self.text() != value {
1724            self.ignore_text_property_change.set(self.ignore_text_property_change.get() + 1);
1725            self.set_current_value(Self::text_property(), value.clone());
1726            call_text_changed = true;
1727        }
1728
1729        // Update the Text property of the text box.
1730        if user_initiated != Some(true) {
1731            if let Some(text_box) = self.text_box() {
1732                if text_box.text() != value {
1733                    self.ignore_text_box_text_change.set(self.ignore_text_box_text_change.get() + 1);
1734                    text_box.set_text(Some(value.as_deref().unwrap_or("")));
1735
1736                    // Text property value was set, fire event.
1737                    if !call_text_changed {
1738                        let text = self.text();
1739                        if text == value || text.is_none() {
1740                            call_text_changed = true;
1741                        }
1742                    }
1743                }
1744            }
1745        }
1746
1747        if call_text_changed {
1748            self.on_text_changed(&TextChangedEventArgs::with_event(Self::text_changed_event()));
1749        }
1750    }
1751
1752    /// Handle the update of the text for the control from any source,
1753    /// including the text box part and the `Text` property.
1754    ///
1755    /// `user_initiated` indicates whether the update is a user-initiated
1756    /// action. This should be `true` when the method is called from a text
1757    /// box event handler.
1758    fn text_updated(&self, new_text: Option<String>, user_initiated: bool) {
1759        let new_text = new_text.unwrap_or_default();
1760
1761        // The text changed event of the text box was not firing immediately
1762        // and was causing an immediate update, even with wrapping. If there
1763        // is a selection currently, no update should happen.
1764        if self.is_text_completion_enabled() {
1765            if let Some(text_box) = self.text_box() {
1766                if self.text_box_selection_length() > 0
1767                    && self.text_box_selection_start() != utf16_length(text_box.text().as_deref())
1768                {
1769                    return;
1770                }
1771            }
1772        }
1773
1774        // Evaluate the conditions needed for completion.
1775        // 1. Minimum prefix length
1776        // 2. If a delay timer is in use, use it
1777        let minimum_prefix_length = self.minimum_prefix_length();
1778        let minimum_length_reached =
1779            utf16_length(Some(&new_text)) >= minimum_prefix_length && minimum_prefix_length >= 0;
1780
1781        self.user_called_populate.set(minimum_length_reached && user_initiated);
1782
1783        // Update the interface and values only as necessary.
1784        self.update_text_value(Some(new_text), Some(user_initiated));
1785
1786        if minimum_length_reached {
1787            self.ignore_text_selection_change.set(true);
1788
1789            let timer = self.delay_timer.borrow().as_ref().map(|(timer, _)| timer.clone());
1790            match timer {
1791                Some(timer) => timer.start(),
1792                None => self.populate_drop_down(),
1793            }
1794        } else {
1795            self.set_search_text(Some(String::new()));
1796            if self.selected_item().is_some() {
1797                self.skip_selected_item_text_update.set(true);
1798            }
1799
1800            self.set_current_value(Self::selected_item_property(), None);
1801
1802            if self.is_drop_down_open() {
1803                self.set_current_value(Self::is_drop_down_open_property(), false);
1804            }
1805        }
1806    }
1807
1808    // --- view ---------------------------------------------------------------
1809
1810    /// A simple helper method to clear the view and ensure that a view
1811    /// object is always present and not null.
1812    fn clear_view(&self) {
1813        self.view.clear();
1814    }
1815
1816    /// Walks through the items enumeration. Performance is not going to be
1817    /// perfect with the current implementation.
1818    fn refresh_view(&self) {
1819        // If we have a running filter, trigger a request first.
1820        if self.filter_in_action.get() {
1821            self.cancel_requested.set(true);
1822        }
1823
1824        // Indicate that filtering is ongoing.
1825        self.filter_in_action.set(true);
1826
1827        // Indicate that filtering is not ongoing anymore, however this
1828        // method is left.
1829        let _filter_in_action = Reset(&self.filter_in_action);
1830        let _cancel_requested = Reset(&self.cancel_requested);
1831
1832        let items = self.items.borrow().clone();
1833        let Some(items) = items else {
1834            self.clear_view();
1835            return;
1836        };
1837
1838        // Cache the current text value.
1839        let text = self.text().unwrap_or_default();
1840
1841        // Cache the properties and determine if any filtering mode is on.
1842        let text_filter = self.text_filter();
1843        let item_filter = self.item_filter();
1844        let object_filtering = self.filter_mode() == AutoCompleteFilterMode::Custom && text_filter.is_none();
1845        let mut new_view_items = Vec::new();
1846
1847        // If the mode is object filtering and the item filter is null, we
1848        // report an error.
1849        if object_filtering && item_filter.is_none() {
1850            panic!("ItemFilter property can not be null when FilterMode has value AutoCompleteFilterMode.Custom");
1851        }
1852
1853        for item in items.iter() {
1854            // Exit the filter when requested if cancellation is requested.
1855            if self.cancel_requested.get() {
1856                return;
1857            }
1858
1859            let in_results = if let Some(text_filter) = &text_filter {
1860                text_filter.invoke(Some(&text), &self.format_value(item))
1861            } else if let (true, Some(item_filter)) = (object_filtering, &item_filter) {
1862                item_filter.invoke(Some(&text), item)
1863            } else {
1864                true
1865            };
1866
1867            if in_results {
1868                new_view_items.push(item.clone());
1869            }
1870        }
1871
1872        self.view.clear();
1873        self.view.add_range(new_view_items);
1874    }
1875
1876    /// Handle any change to the `ItemsSource` property, update the
1877    /// underlying observable collection view, and set the selection
1878    /// adapter's items source to the view if appropriate.
1879    fn on_items_source_changed(&self, new_value: Option<ItemsSource>) {
1880        // Remove handler for the collection changed event of the old value
1881        // (if present).
1882        if let Some((source, token)) = self.collection_change_subscription.take() {
1883            source.list().remove_collection_changed(token);
1884        }
1885
1886        // Add handler for the collection changed event of the new value (if
1887        // possible). The collection refers to this control weakly.
1888        if let Some(source) = &new_value {
1889            let weak = self.to_ref().downgrade();
1890            let handler: Rc<ItemsChangedHandler> = Rc::new(move |e: &ItemsChangedEventArgs<'_>| {
1891                if let Some(this) = weak.upgrade() {
1892                    this.items_collection_changed(e);
1893                }
1894            });
1895            if let Some(token) = source.list().add_collection_changed(handler) {
1896                *self.collection_change_subscription.borrow_mut() = Some((source.clone(), token));
1897            }
1898        }
1899
1900        // Store a local cached copy of the data.
1901        *self.items.borrow_mut() = new_value.map(|source| Rc::new(source.to_vec()));
1902
1903        // Clear and set the view on the selection adapter.
1904        self.clear_view();
1905        self.set_view_on_adapter();
1906        if self.is_drop_down_open() {
1907            self.refresh_view();
1908        }
1909    }
1910
1911    /// Hands the view to the selection adapter unless it has it.
1912    fn set_view_on_adapter(&self) {
1913        if let Some(adapter) = self.selection_adapter() {
1914            if adapter.items_source().as_ref() != Some(&self.view_source) {
1915                adapter.set_items_source(Some(self.view_source.clone()));
1916            }
1917        }
1918    }
1919
1920    /// Method that handles the collection changed event for the
1921    /// `ItemsSource` property.
1922    fn items_collection_changed(&self, e: &ItemsChangedEventArgs<'_>) {
1923        // Update the cache.
1924        {
1925            let mut cache = self.items.borrow_mut();
1926            let items = cache.as_mut().map(Rc::make_mut);
1927            if let Some(items) = items {
1928                if e.action == NotifyCollectionChangedAction::Remove {
1929                    for _ in 0..e.old_items.len() {
1930                        items.remove(e.old_starting_index as usize);
1931                    }
1932                }
1933                if e.action == NotifyCollectionChangedAction::Add && items.len() as i32 >= e.new_starting_index {
1934                    for index in 0..e.new_items.len() {
1935                        items.insert(e.new_starting_index as usize + index, e.new_items.get(index));
1936                    }
1937                }
1938                if e.action == NotifyCollectionChangedAction::Replace {
1939                    for index in 0..e.new_items.len() {
1940                        items[e.new_starting_index as usize] = e.new_items.get(index);
1941                    }
1942                }
1943            }
1944        }
1945
1946        // Update the view.
1947        if e.action == NotifyCollectionChangedAction::Remove || e.action == NotifyCollectionChangedAction::Replace {
1948            for index in 0..e.old_items.len() {
1949                self.view.remove(&e.old_items.get(index));
1950            }
1951        }
1952
1953        if e.action == NotifyCollectionChangedAction::Reset {
1954            // Significant changes to the underlying data.
1955            self.clear_view();
1956            if let Some(source) = self.items_source() {
1957                *self.items.borrow_mut() = Some(Rc::new(source.to_vec()));
1958            }
1959        }
1960
1961        // Refresh the observable collection used in the selection adapter.
1962        self.refresh_view();
1963    }
1964
1965    /// Notifies the [`AutoCompleteBox`] that the `ItemsSource` property has
1966    /// been set and the data can be filtered to provide possible matches in
1967    /// the drop-down.
1968    ///
1969    /// Call this method when you are providing custom population of the
1970    /// drop-down portion of the control, to signal the control that you are
1971    /// done with the population process. Typically, you use it when the
1972    /// population process is a long-running process and you want to cancel
1973    /// built-in filtering of the `ItemsSource` items. In this case, you can
1974    /// handle the `Populating` event and set the cancel flag of its args.
1975    /// When the long-running process has completed you call
1976    /// `populate_complete` to indicate the drop-down is populated.
1977    pub fn populate_complete(&self) {
1978        // Apply the search filter.
1979        self.refresh_view();
1980
1981        // Fire the Populated event containing the read-only view data.
1982        let populated = PopulatedEventArgs::new(self.view_source.clone());
1983        self.on_populated(&populated);
1984
1985        self.set_view_on_adapter();
1986
1987        let is_drop_down_open = self.user_called_populate.get() && self.view.count() > 0;
1988        if is_drop_down_open != self.is_drop_down_open() {
1989            self.ignore_property_change.set(true);
1990            self.set_current_value(Self::is_drop_down_open_property(), is_drop_down_open);
1991        }
1992        if self.is_drop_down_open() {
1993            self.opening_drop_down(false);
1994        } else {
1995            self.closing_drop_down(true);
1996        }
1997
1998        self.update_text_completion(self.user_called_populate.get());
1999    }
2000
2001    /// Performs text completion, if enabled, and a lookup on the underlying
2002    /// item values for an exact match. Will update the selected item value.
2003    ///
2004    /// `user_initiated` indicates whether the operation was user initiated.
2005    /// Text completion will not be performed when not directly initiated by
2006    /// the user.
2007    fn update_text_completion(&self, user_initiated: bool) {
2008        // By default this method will clear the selected value.
2009        let mut new_selected_item: Option<BoxedValue> = None;
2010        let text = self.text();
2011
2012        // Text search is StartsWith explicit and only when enabled, in line
2013        // with WPF's ComboBox lookup. When in use it will associate a Value
2014        // with the Text if it is found in ItemsSource. This is only valid
2015        // when there is data and the user initiated the action.
2016        if self.view.count() > 0 {
2017            let text_box = if self.is_text_completion_enabled() && user_initiated { self.text_box() } else { None };
2018            if let Some(text_box) = text_box {
2019                let current_length = utf16_length(text_box.text().as_deref());
2020                let selection_start = self.text_box_selection_start();
2021                if text.as_deref().is_some_and(|text| selection_start == utf16_length(Some(text)))
2022                    && selection_start > self.text_selection_start.get()
2023                {
2024                    // When the FilterMode property is set to either
2025                    // StartsWith or StartsWithCaseSensitive, the first item
2026                    // in the view is used. This will improve performance on
2027                    // the lookup. It assumes that the FilterMode the user
2028                    // has selected is an acceptable case sensitive matching
2029                    // function for their scenario.
2030                    let filter_mode = self.filter_mode();
2031                    let top = if filter_mode == AutoCompleteFilterMode::StartsWith
2032                        || filter_mode == AutoCompleteFilterMode::StartsWithCaseSensitive
2033                    {
2034                        self.view.get(0)
2035                    } else {
2036                        self.try_get_match(
2037                            text.as_deref(),
2038                            AutoCompleteSearch::get_filter(AutoCompleteFilterMode::StartsWith),
2039                        )
2040                    };
2041
2042                    // If the search was successful, update SelectedItem.
2043                    if top.is_some() {
2044                        let top_string = self.format_value(&top);
2045                        new_selected_item = top;
2046
2047                        // Only replace partially when the two words being
2048                        // the same.
2049                        let text = self.text();
2050                        let top_length = utf16_length(top_string.as_deref());
2051                        let min_length = top_length.min(utf16_length(text.as_deref()));
2052                        let text_start = text.as_deref().map(|text| utf16_prefix(text, min_length));
2053                        let top_start = top_string.as_deref().map(|top| utf16_prefix(top, min_length));
2054                        if AutoCompleteSearch::equals(text_start.as_deref(), &top_start) {
2055                            // Update the text.
2056                            self.update_text_value(top_string, None);
2057
2058                            // Select the text past the user's caret.
2059                            text_box.set_selection_start(current_length);
2060                            text_box.set_selection_end(top_length);
2061                        }
2062                    }
2063                }
2064            } else {
2065                // Perform an exact string lookup for the text. This is a
2066                // design change from the original Toolkit release when the
2067                // IsTextCompletionEnabled property behaved just like the
2068                // WPF ComboBox's IsTextSearchEnabled property.
2069                //
2070                // This change provides the behavior that most people expect
2071                // to find: a lookup for the value is always performed.
2072                new_selected_item = self.try_get_match(
2073                    text.as_deref(),
2074                    AutoCompleteSearch::get_filter(AutoCompleteFilterMode::EqualsCaseSensitive),
2075                );
2076            }
2077        }
2078
2079        // Update the selected item property.
2080        if !crate::reference_equals(&self.selected_item(), &new_selected_item) {
2081            self.skip_selected_item_text_update.set(true);
2082        }
2083        self.set_current_value(Self::selected_item_property(), new_selected_item);
2084
2085        // Restore updates for the text selection.
2086        if self.ignore_text_selection_change.get() {
2087            self.ignore_text_selection_change.set(false);
2088            if self.text_box().is_some() {
2089                self.text_selection_start.set(self.text_box_selection_start());
2090            }
2091        }
2092    }
2093
2094    /// Attempts to look through the view and locate the specific exact text
2095    /// match.
2096    ///
2097    /// `predicate` is the predicate to use for the partial or exact match.
2098    /// Returns the object or `None`.
2099    fn try_get_match(
2100        &self,
2101        search_text: Option<&str>,
2102        predicate: Option<AutoCompleteFilterPredicate<Option<String>>>,
2103    ) -> Option<BoxedValue> {
2104        let predicate = predicate?;
2105
2106        let view = self.view.snapshot();
2107        for item in view.iter() {
2108            if predicate.invoke(search_text, &self.format_value(item)) {
2109                return item.clone();
2110            }
2111        }
2112
2113        None
2114    }
2115
2116    fn update_pseudo_classes(&self) {
2117        self.pseudo_classes().set(PC_DROPDOWN_OPEN, self.is_drop_down_open());
2118    }
2119
2120    fn clear_text_box_selection(&self) {
2121        if let Some(text_box) = self.text_box() {
2122            let length = utf16_length(text_box.text().as_deref());
2123            text_box.set_selection_start(length);
2124            text_box.set_selection_end(length);
2125        }
2126    }
2127
2128    /// Called when the selected item is changed, updates the text value
2129    /// that is displayed in the text box part.
2130    fn on_selected_item_changed(&self, new_item: &Option<BoxedValue>) {
2131        let text = match new_item {
2132            None => self.search_text(),
2133            Some(item) => {
2134                if let Some(text_selector) = self.text_selector() {
2135                    Some(text_selector.invoke(self.search_text().as_deref(), &self.format_value(new_item)))
2136                } else if let Some(item_selector) = self.item_selector() {
2137                    Some(item_selector.invoke(self.search_text().as_deref(), item))
2138                } else {
2139                    self.format_value(new_item)
2140                }
2141            }
2142        };
2143
2144        // Update the Text property and the text box values.
2145        self.update_text_value(text, None);
2146
2147        // Move the caret to the end of the text box.
2148        self.clear_text_box_selection();
2149    }
2150
2151    /// Handles the selection changed event of the selection adapter.
2152    fn on_adapter_selection_changed(&self) {
2153        if let Some(adapter) = self.selection_adapter() {
2154            self.set_current_value(Self::selected_item_property(), adapter.selected_item());
2155        }
2156    }
2157
2158    /// Handles the commit event on the selection adapter.
2159    fn on_adapter_selection_complete(&self) {
2160        self.set_current_value(Self::is_drop_down_open_property(), false);
2161
2162        // Text should not be selected.
2163        self.clear_text_box_selection();
2164
2165        if let Some(text_box) = self.text_box() {
2166            text_box.focus();
2167        }
2168    }
2169
2170    /// Handles the cancel event on the selection adapter.
2171    fn on_adapter_selection_canceled(&self) {
2172        self.update_text_value(self.search_text(), None);
2173
2174        // Completion will update the selected value.
2175        self.update_text_completion(false);
2176    }
2177}
2178
2179/// A predefined set of filter functions for the known, built-in
2180/// [`AutoCompleteFilterMode`] enumeration values.
2181struct AutoCompleteSearch;
2182
2183impl AutoCompleteSearch {
2184    /// Index function that retrieves the filter for the provided
2185    /// [`AutoCompleteFilterMode`]. Returns the string-based comparison
2186    /// function.
2187    ///
2188    /// The filter of a mode is one object per thread, so that two requests
2189    /// for a mode give equal filters.
2190    fn get_filter(filter_mode: AutoCompleteFilterMode) -> Option<AutoCompleteFilterPredicate<Option<String>>> {
2191        type Filter = fn(Option<&str>, &Option<String>) -> bool;
2192
2193        ferroui_base::ferro_thread_local! {
2194            static FILTERS: [AutoCompleteFilterPredicate<Option<String>>; 12] = {
2195                let filters: [Filter; 12] = [
2196                    AutoCompleteSearch::starts_with,
2197                    AutoCompleteSearch::starts_with_case_sensitive,
2198                    AutoCompleteSearch::starts_with_ordinal,
2199                    AutoCompleteSearch::starts_with_ordinal_case_sensitive,
2200                    AutoCompleteSearch::contains,
2201                    AutoCompleteSearch::contains_case_sensitive,
2202                    AutoCompleteSearch::contains_ordinal,
2203                    AutoCompleteSearch::contains_ordinal_case_sensitive,
2204                    AutoCompleteSearch::equals,
2205                    AutoCompleteSearch::equals_case_sensitive,
2206                    AutoCompleteSearch::equals_ordinal,
2207                    AutoCompleteSearch::equals_ordinal_case_sensitive,
2208                ];
2209                filters.map(AutoCompleteFilterPredicate::new)
2210            };
2211        }
2212
2213        let index = match filter_mode {
2214            AutoCompleteFilterMode::StartsWith => 0,
2215            AutoCompleteFilterMode::StartsWithCaseSensitive => 1,
2216            AutoCompleteFilterMode::StartsWithOrdinal => 2,
2217            AutoCompleteFilterMode::StartsWithOrdinalCaseSensitive => 3,
2218            AutoCompleteFilterMode::Contains => 4,
2219            AutoCompleteFilterMode::ContainsCaseSensitive => 5,
2220            AutoCompleteFilterMode::ContainsOrdinal => 6,
2221            AutoCompleteFilterMode::ContainsOrdinalCaseSensitive => 7,
2222            AutoCompleteFilterMode::Equals => 8,
2223            AutoCompleteFilterMode::EqualsCaseSensitive => 9,
2224            AutoCompleteFilterMode::EqualsOrdinal => 10,
2225            AutoCompleteFilterMode::EqualsOrdinalCaseSensitive => 11,
2226            AutoCompleteFilterMode::None | AutoCompleteFilterMode::Custom => return None,
2227        };
2228
2229        Some(FILTERS.with(|filters| filters[index].clone()))
2230    }
2231
2232    /// An implementation of the contains member of a string that takes in
2233    /// a string comparison. The comparison itself is the one of the shared
2234    /// string comparison of the base library, which asks the current
2235    /// culture for the culture-sensitive modes.
2236    fn contains_with(s: &Option<String>, value: Option<&str>, comparison: StringComparison) -> bool {
2237        match (s, value) {
2238            (Some(s), Some(value)) => comparison.contains(s, value),
2239            _ => false,
2240        }
2241    }
2242
2243    fn starts_with_with(text: Option<&str>, value: &Option<String>, comparison: StringComparison) -> bool {
2244        match (value, text) {
2245            (Some(value), Some(text)) => comparison.starts_with(value, text),
2246            _ => false,
2247        }
2248    }
2249
2250    /// Check if the string value begins with the text.
2251    fn starts_with(text: Option<&str>, value: &Option<String>) -> bool {
2252        Self::starts_with_with(text, value, StringComparison::CurrentCultureIgnoreCase)
2253    }
2254
2255    /// Check if the string value begins with the text.
2256    fn starts_with_case_sensitive(text: Option<&str>, value: &Option<String>) -> bool {
2257        Self::starts_with_with(text, value, StringComparison::CurrentCulture)
2258    }
2259
2260    /// Check if the string value begins with the text.
2261    fn starts_with_ordinal(text: Option<&str>, value: &Option<String>) -> bool {
2262        Self::starts_with_with(text, value, StringComparison::OrdinalIgnoreCase)
2263    }
2264
2265    /// Check if the string value begins with the text.
2266    fn starts_with_ordinal_case_sensitive(text: Option<&str>, value: &Option<String>) -> bool {
2267        Self::starts_with_with(text, value, StringComparison::Ordinal)
2268    }
2269
2270    /// Check if the prefix is contained in the string value. The current
2271    /// culture's case insensitive string comparison operator is used.
2272    fn contains(text: Option<&str>, value: &Option<String>) -> bool {
2273        Self::contains_with(value, text, StringComparison::CurrentCultureIgnoreCase)
2274    }
2275
2276    /// Check if the prefix is contained in the string value.
2277    fn contains_case_sensitive(text: Option<&str>, value: &Option<String>) -> bool {
2278        Self::contains_with(value, text, StringComparison::CurrentCulture)
2279    }
2280
2281    /// Check if the prefix is contained in the string value.
2282    fn contains_ordinal(text: Option<&str>, value: &Option<String>) -> bool {
2283        Self::contains_with(value, text, StringComparison::OrdinalIgnoreCase)
2284    }
2285
2286    /// Check if the prefix is contained in the string value.
2287    fn contains_ordinal_case_sensitive(text: Option<&str>, value: &Option<String>) -> bool {
2288        Self::contains_with(value, text, StringComparison::Ordinal)
2289    }
2290
2291    /// Check if the string values are equal.
2292    fn equals(text: Option<&str>, value: &Option<String>) -> bool {
2293        StringComparison::CurrentCultureIgnoreCase.equals(value.as_deref(), text)
2294    }
2295
2296    /// Check if the string values are equal.
2297    fn equals_case_sensitive(text: Option<&str>, value: &Option<String>) -> bool {
2298        StringComparison::CurrentCulture.equals(value.as_deref(), text)
2299    }
2300
2301    /// Check if the string values are equal.
2302    fn equals_ordinal(text: Option<&str>, value: &Option<String>) -> bool {
2303        StringComparison::OrdinalIgnoreCase.equals(value.as_deref(), text)
2304    }
2305
2306    /// Check if the string values are equal.
2307    fn equals_ordinal_case_sensitive(text: Option<&str>, value: &Option<String>) -> bool {
2308        StringComparison::Ordinal.equals(value.as_deref(), text)
2309    }
2310}