Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 50 additions & 3 deletions SecRandom.Core/Controls/MultiComboBox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@
using Avalonia;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Controls.Utils;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.VisualTree;

namespace SecRandom.Core.Controls;

Expand All @@ -18,6 +22,7 @@ public class MultiComboBox : ItemsControl
public const string PART_SelectionBox = "PART_SelectionBox";
public const string PC_DropDownOpen = ":dropdownopen";
public const string PC_Empty = ":selection-empty";
public const string PC_Pressed = ":pressed";

private static readonly ITemplate<Panel?> _defaultPanel =
new FuncTemplate<Panel?>(() => new VirtualizingStackPanel());
Expand All @@ -43,6 +48,7 @@ public class MultiComboBox : ItemsControl
private INotifyCollectionChanged? _subscribedSelection;
private bool _selectionViewDirty;
private bool _syncingSelectionView;
private Popup? _popup;

public static readonly StyledProperty<bool> IsDropDownOpenProperty =
ComboBox.IsDropDownOpenProperty.AddOwner<MultiComboBox>();
Expand All @@ -69,6 +75,7 @@ static MultiComboBox()
{
FocusableProperty.OverrideDefaultValue<MultiComboBox>(true);
ItemsPanelProperty.OverrideDefaultValue<MultiComboBox>(_defaultPanel);
PlatformFeedback.FeedbackTypeProperty.OverrideDefaultValue<MultiComboBox>(FeedbackType.Auto);
SelectedItemsProperty.Changed.AddClassHandler<MultiComboBox, IList?>((box, args) =>
box.OnSelectedItemsChanged(args));
}
Expand All @@ -77,7 +84,6 @@ public MultiComboBox()
{
SetCurrentValue(SelectedItemsProperty, new AvaloniaList<object>());
RemoveCommand = new MultiComboBoxRemoveCommand(this);
AddHandler(PointerPressedEvent, OnBackgroundPointerPressed);
}

public bool IsDropDownOpen
Expand Down Expand Up @@ -174,6 +180,7 @@ protected override void PrepareContainerForItemOverride(Control container, objec
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_popup = e.NameScope.Find<Popup>("PART_Popup");

var selectionBox = e.NameScope.Find<ItemsControl>(PART_SelectionBox);
if (selectionBox is null)
Expand All @@ -190,9 +197,49 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang
PseudoClasses.Set(PC_DropDownOpen, IsDropDownOpen);
}

private void OnBackgroundPointerPressed(object? sender, PointerPressedEventArgs e)
/// <summary>
/// 与 Avalonia <see cref="ComboBox" /> 相同的指针节奏:按下只进入按压态(:pressed),
/// 抬起才切换下拉。按下瞬间展开下拉时,同一次触摸的抬起仍会落在刚出现的下拉层上,
/// 轻触关闭把这次手势当作下拉框之外的按压,下拉框随即收起,用户点不到里面的复选框;
/// 改为抬起展开后,打开手势已经结束,这条收起路径不存在。
/// </summary>
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
SetCurrentValue(IsDropDownOpenProperty, !IsDropDownOpen);
base.OnPointerPressed(e);
if (!e.Handled && e.Source is Visual source)
{
if (_popup?.IsInsidePopup(source) == true)
{
e.Handled = true;
return;
}
}

if (IsDropDownOpen)
{
// 下拉展开时按下控件视为关闭,而不是再切回打开
SetCurrentValue(IsDropDownOpenProperty, false);
e.Handled = true;
}
else
{
PseudoClasses.Set(PC_Pressed, true);
}
}

protected override void OnPointerReleased(PointerReleasedEventArgs e)
{
if (!e.Handled && e.Source is Visual source
&& _popup?.IsInsidePopup(source) != true
&& PseudoClasses.Contains(PC_Pressed))
{
SetCurrentValue(IsDropDownOpenProperty, !IsDropDownOpen);
this.PerformFeedback(FeedbackAction.Click);
e.Handled = true;
}

PseudoClasses.Set(PC_Pressed, false);
base.OnPointerReleased(e);
}

protected override void OnKeyDown(KeyEventArgs e)
Expand Down
Loading