using System.Collections; using System.Collections.Specialized; using Avalonia; using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Data.Converters; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.LogicalTree; using Irihi.Avalonia.Shared.Helpers; using Irihi.Avalonia.Shared.Contracts; namespace Ursa.Controls; [TemplatePart(PART_BackgroundBorder, typeof(Border))] [PseudoClasses(PC_DropDownOpen, PC_Empty)] public class MultiComboBox: SelectingItemsControl, IInnerContentControl { public const string PART_BackgroundBorder = "PART_BackgroundBorder"; public const string PC_DropDownOpen = ":dropdownopen"; public const string PC_Empty = ":selection-empty"; private Border? _rootBorder; private static ITemplate _defaultPanel = new FuncTemplate(() => new VirtualizingStackPanel()); public static readonly StyledProperty IsDropDownOpenProperty = ComboBox.IsDropDownOpenProperty.AddOwner(); public bool IsDropDownOpen { get => GetValue(IsDropDownOpenProperty); set => SetValue(IsDropDownOpenProperty, value); } public static readonly StyledProperty MaxDropdownHeightProperty = AvaloniaProperty.Register( nameof(MaxDropdownHeight)); public double MaxDropdownHeight { get => GetValue(MaxDropdownHeightProperty); set => SetValue(MaxDropdownHeightProperty, value); } public static readonly StyledProperty MaxSelectionBoxHeightProperty = AvaloniaProperty.Register( nameof(MaxSelectionBoxHeight)); public double MaxSelectionBoxHeight { get => GetValue(MaxSelectionBoxHeightProperty); set => SetValue(MaxSelectionBoxHeightProperty, value); } public new static readonly StyledProperty SelectedItemsProperty = AvaloniaProperty.Register( nameof(SelectedItems)); public new IList? SelectedItems { get => GetValue(SelectedItemsProperty); set => SetValue(SelectedItemsProperty, value); } public static readonly StyledProperty InnerLeftContentProperty = AvaloniaProperty.Register( nameof(InnerLeftContent)); public object? InnerLeftContent { get => GetValue(InnerLeftContentProperty); set => SetValue(InnerLeftContentProperty, value); } public static readonly StyledProperty InnerRightContentProperty = AvaloniaProperty.Register( nameof(InnerRightContent)); public object? InnerRightContent { get => GetValue(InnerRightContentProperty); set => SetValue(InnerRightContentProperty, value); } public static readonly StyledProperty SelectedItemTemplateProperty = AvaloniaProperty.Register( nameof(SelectedItemTemplate)); public IDataTemplate? SelectedItemTemplate { get => GetValue(SelectedItemTemplateProperty); set => SetValue(SelectedItemTemplateProperty, value); } static MultiComboBox() { FocusableProperty.OverrideDefaultValue(true); ItemsPanelProperty.OverrideDefaultValue(_defaultPanel); IsDropDownOpenProperty.AffectsPseudoClass(PC_DropDownOpen); SelectedItemsProperty.Changed.AddClassHandler((box, args) => box.OnSelectedItemsChanged(args)); } public MultiComboBox() { SelectedItems = new AvaloniaList(); if (SelectedItems is INotifyCollectionChanged c) { c.CollectionChanged+= OnSelectedItemsCollectionChanged; } } private void OnSelectedItemsChanged(AvaloniaPropertyChangedEventArgs args) { if (args.OldValue.Value is INotifyCollectionChanged old) { old.CollectionChanged-=OnSelectedItemsCollectionChanged; } if (args.NewValue.Value is INotifyCollectionChanged @new) { @new.CollectionChanged += OnSelectedItemsCollectionChanged; } } private void OnSelectedItemsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { PseudoClasses.Set(PC_Empty, SelectedItems?.Count == 0); } protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey) { recycleKey = item; return item is not MultiComboBoxItem; } protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey) { return new MultiComboBoxItem(); } protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); PointerPressedEvent.RemoveHandler(OnBackgroundPointerPressed, _rootBorder); _rootBorder = e.NameScope.Find(PART_BackgroundBorder); PointerPressedEvent.AddHandler(OnBackgroundPointerPressed, _rootBorder); PseudoClasses.Set(PC_Empty, SelectedItems?.Count == 0); } private void OnBackgroundPointerPressed(object sender, PointerPressedEventArgs e) { SetCurrentValue(IsDropDownOpenProperty, !IsDropDownOpen); } internal void ItemFocused(MultiComboBoxItem dropDownItem) { if (IsDropDownOpen && dropDownItem.IsFocused && dropDownItem.IsArrangeValid) { dropDownItem.BringIntoView(); } } public void Remove(object? o) { if (o is StyledElement s) { var data = s.DataContext; this.SelectedItems?.Remove(data); var item = this.Items.FirstOrDefault(a => ReferenceEquals(a, data)); if (item is not null) { var container = ContainerFromItem(item); if (container is MultiComboBoxItem t) { t.IsSelected = false; } } } } public void Clear() { // this.SelectedItems?.Clear(); var containers = Presenter?.Panel?.Children; if(containers is null) return; foreach (var container in containers) { if (container is MultiComboBoxItem t) { t.IsSelected = false; } } } protected override void OnUnloaded(RoutedEventArgs e) { base.OnUnloaded(e); if (SelectedItems is INotifyCollectionChanged c) { c.CollectionChanged-=OnSelectedItemsCollectionChanged; } } }