feat: implement most feature.
This commit is contained in:
@@ -6,27 +6,38 @@
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate TargetType="u:PopConfirm">
|
||||
<Panel>
|
||||
<ContentPresenter Name="PART_ContentPresenter" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}"/>
|
||||
<ContentPresenter
|
||||
Name="PART_ContentPresenter"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}" />
|
||||
<Popup
|
||||
IsLightDismissEnabled="True"
|
||||
IsOpen="{TemplateBinding IsDropdownOpen, Mode=TwoWay}"
|
||||
OverlayInputPassThroughElement="{Binding #PART_ContentPresenter}"
|
||||
Name="{x:Static u:PopConfirm.PART_Popup}"
|
||||
Placement="Bottom" >
|
||||
Name="{x:Static u:PopConfirm.PART_Popup}"
|
||||
Placement="{TemplateBinding Placement}" >
|
||||
<Border Theme="{DynamicResource CardBorder}">
|
||||
<StackPanel>
|
||||
<ContentPresenter Content="{TemplateBinding PopupHeader}" ContentTemplate="{TemplateBinding PopupHeaderTemplate}"/>
|
||||
<ContentPresenter Content="{TemplateBinding PopupContent}" ContentTemplate="{TemplateBinding PopupContentTemplate}"/>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button
|
||||
Name="{x:Static u:PopConfirm.PART_ConfirmButton}"
|
||||
Content="Confirm"
|
||||
Command="{TemplateBinding ConfirmCommand}"
|
||||
CommandParameter="{TemplateBinding ConfirmCommandParameter}"/>
|
||||
<ContentPresenter
|
||||
Content="{TemplateBinding PopupHeader}"
|
||||
ContentTemplate="{TemplateBinding PopupHeaderTemplate}"/>
|
||||
<ContentPresenter
|
||||
Content="{TemplateBinding PopupContent}"
|
||||
ContentTemplate="{TemplateBinding PopupContentTemplate}"/>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<Button
|
||||
Name="{x:Static u:PopConfirm.PART_CancelButton}"
|
||||
Content="Cancel"
|
||||
Margin="0 0 8 0"
|
||||
Content="{DynamicResource STRING_MENU_DIALOG_CANCEL}"
|
||||
Command="{TemplateBinding CancelCommand}"
|
||||
CommandParameter="{TemplateBinding CancelCommandParameter}"/>
|
||||
<Button
|
||||
Name="{x:Static u:PopConfirm.PART_ConfirmButton}"
|
||||
Theme="{DynamicResource SolidButton}"
|
||||
Content="{DynamicResource STRING_MENU_DIALOG_OK}"
|
||||
Command="{TemplateBinding ConfirmCommand}"
|
||||
CommandParameter="{TemplateBinding ConfirmCommandParameter}"/>
|
||||
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
@@ -1,142 +1,163 @@
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Windows.Input;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Metadata;
|
||||
using Avalonia.Controls.Presenters;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Controls.Templates;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.LogicalTree;
|
||||
using Irihi.Avalonia.Shared.Helpers;
|
||||
|
||||
namespace Ursa.Controls;
|
||||
|
||||
public class PopConfirm: ContentControl
|
||||
[PseudoClasses(PC_DropdownOpen)]
|
||||
public class PopConfirm : ContentControl
|
||||
{
|
||||
public const string PART_ConfirmButton = "PART_ConfirmButton";
|
||||
public const string PART_CancelButton = "PART_CancelButton";
|
||||
public const string PART_Popup = "PART_Popup";
|
||||
|
||||
private Button? _confirmButton;
|
||||
private Button? _cancelButton;
|
||||
private Popup? _popup;
|
||||
|
||||
public const string PC_DropdownOpen = ":dropdownopen";
|
||||
|
||||
public static readonly StyledProperty<object?> PopupHeaderProperty = AvaloniaProperty.Register<PopConfirm, object?>(
|
||||
nameof(PopupHeader));
|
||||
|
||||
public static readonly StyledProperty<IDataTemplate?> PopupHeaderTemplateProperty =
|
||||
AvaloniaProperty.Register<PopConfirm, IDataTemplate?>(
|
||||
nameof(PopupHeaderTemplate));
|
||||
|
||||
public static readonly StyledProperty<object?> PopupContentProperty =
|
||||
AvaloniaProperty.Register<PopConfirm, object?>(
|
||||
nameof(PopupContent));
|
||||
|
||||
public static readonly StyledProperty<IDataTemplate?> PopupContentTemplateProperty =
|
||||
AvaloniaProperty.Register<PopConfirm, IDataTemplate?>(
|
||||
nameof(PopupContentTemplate));
|
||||
|
||||
public static readonly StyledProperty<ICommand?> ConfirmCommandProperty =
|
||||
AvaloniaProperty.Register<PopConfirm, ICommand?>(
|
||||
nameof(ConfirmCommand));
|
||||
|
||||
public static readonly StyledProperty<ICommand?> CancelCommandProperty =
|
||||
AvaloniaProperty.Register<PopConfirm, ICommand?>(
|
||||
nameof(CancelCommand));
|
||||
|
||||
public static readonly StyledProperty<object?> ConfirmCommandParameterProperty =
|
||||
AvaloniaProperty.Register<PopConfirm, object?>(
|
||||
nameof(ConfirmCommandParameter));
|
||||
|
||||
public static readonly StyledProperty<object?> CancelCommandParameterProperty =
|
||||
AvaloniaProperty.Register<PopConfirm, object?>(
|
||||
nameof(CancelCommandParameter));
|
||||
|
||||
public static readonly StyledProperty<PopConfirmTriggerMode> TriggerModeProperty =
|
||||
AvaloniaProperty.Register<PopConfirm, PopConfirmTriggerMode>(
|
||||
nameof(TriggerMode));
|
||||
|
||||
public static readonly StyledProperty<bool> HandleAsyncCommandProperty =
|
||||
AvaloniaProperty.Register<PopConfirm, bool>(
|
||||
nameof(HandleAsyncCommand), true);
|
||||
|
||||
public static readonly StyledProperty<bool> IsDropdownOpenProperty = AvaloniaProperty.Register<PopConfirm, bool>(
|
||||
nameof(IsDropdownOpen));
|
||||
|
||||
public static readonly StyledProperty<PlacementMode> PlacementProperty =
|
||||
Popup.PlacementProperty.AddOwner<PopConfirm>();
|
||||
|
||||
private Button? _cancelButton;
|
||||
|
||||
|
||||
private IDisposable? _childChangeDisposable;
|
||||
|
||||
private Button? _confirmButton;
|
||||
private Popup? _popup;
|
||||
|
||||
static PopConfirm()
|
||||
{
|
||||
IsDropdownOpenProperty.AffectsPseudoClass<PopConfirm>(PC_DropdownOpen);
|
||||
TriggerModeProperty.Changed.AddClassHandler<PopConfirm, PopConfirmTriggerMode>((pop, args) =>
|
||||
pop.OnTriggerModeChanged(args));
|
||||
}
|
||||
|
||||
public object? PopupHeader
|
||||
{
|
||||
get => GetValue(PopupHeaderProperty);
|
||||
set => SetValue(PopupHeaderProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<IDataTemplate?> PopupHeaderTemplateProperty = AvaloniaProperty.Register<PopConfirm, IDataTemplate?>(
|
||||
nameof(PopupHeaderTemplate));
|
||||
|
||||
public IDataTemplate? PopupHeaderTemplate
|
||||
{
|
||||
get => GetValue(PopupHeaderTemplateProperty);
|
||||
set => SetValue(PopupHeaderTemplateProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<object?> PopupContentProperty = AvaloniaProperty.Register<PopConfirm, object?>(
|
||||
nameof(PopupContent));
|
||||
|
||||
public object? PopupContent
|
||||
{
|
||||
get => GetValue(PopupContentProperty);
|
||||
set => SetValue(PopupContentProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<IDataTemplate?> PopupContentTemplateProperty = AvaloniaProperty.Register<PopConfirm, IDataTemplate?>(
|
||||
nameof(PopupContentTemplate));
|
||||
|
||||
public IDataTemplate? PopupContentTemplate
|
||||
{
|
||||
get => GetValue(PopupContentTemplateProperty);
|
||||
set => SetValue(PopupContentTemplateProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<ICommand?> ConfirmCommandProperty = AvaloniaProperty.Register<PopConfirm, ICommand?>(
|
||||
nameof(ConfirmCommand));
|
||||
|
||||
public ICommand? ConfirmCommand
|
||||
{
|
||||
get => GetValue(ConfirmCommandProperty);
|
||||
set => SetValue(ConfirmCommandProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<ICommand?> CancelCommandProperty = AvaloniaProperty.Register<PopConfirm, ICommand?>(
|
||||
nameof(CancelCommand));
|
||||
|
||||
public ICommand? CancelCommand
|
||||
{
|
||||
get => GetValue(CancelCommandProperty);
|
||||
set => SetValue(CancelCommandProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<object?> ConfirmCommandParameterProperty = AvaloniaProperty.Register<PopConfirm, object?>(
|
||||
nameof(ConfirmCommandParameter));
|
||||
|
||||
public object? ConfirmCommandParameter
|
||||
{
|
||||
get => GetValue(ConfirmCommandParameterProperty);
|
||||
set => SetValue(ConfirmCommandParameterProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<object?> CancelCommandParameterProperty = AvaloniaProperty.Register<PopConfirm, object?>(
|
||||
nameof(CancelCommandParameter));
|
||||
|
||||
public object? CancelCommandParameter
|
||||
{
|
||||
get => GetValue(CancelCommandParameterProperty);
|
||||
set => SetValue(CancelCommandParameterProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<PopConfirmTriggerMode> TriggerModeProperty =
|
||||
AvaloniaProperty.Register<PopConfirm, PopConfirmTriggerMode>(
|
||||
nameof(TriggerMode));
|
||||
|
||||
public PopConfirmTriggerMode TriggerMode
|
||||
{
|
||||
get => GetValue(TriggerModeProperty);
|
||||
set => SetValue(TriggerModeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<bool> HandleAsyncCommandProperty = AvaloniaProperty.Register<PopConfirm, bool>(
|
||||
nameof(HandleAsyncCommand), true);
|
||||
|
||||
public bool HandleAsyncCommand
|
||||
{
|
||||
get => GetValue(HandleAsyncCommandProperty);
|
||||
set => SetValue(HandleAsyncCommandProperty, value);
|
||||
}
|
||||
|
||||
static PopConfirm()
|
||||
public bool IsDropdownOpen
|
||||
{
|
||||
ConfirmCommandProperty.Changed.AddClassHandler<PopConfirm, ICommand?>((popconfirm, args) => popconfirm.OnCommandChanged(args));
|
||||
get => GetValue(IsDropdownOpenProperty);
|
||||
set => SetValue(IsDropdownOpenProperty, value);
|
||||
}
|
||||
|
||||
private void OnCommandChanged(AvaloniaPropertyChangedEventArgs<ICommand?> args)
|
||||
public PlacementMode Placement
|
||||
{
|
||||
|
||||
get => GetValue(PlacementProperty);
|
||||
set => SetValue(PlacementProperty, value);
|
||||
}
|
||||
|
||||
public PopConfirm()
|
||||
private void OnTriggerModeChanged(AvaloniaPropertyChangedEventArgs<PopConfirmTriggerMode> args)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
base.OnPropertyChanged(change);
|
||||
|
||||
}
|
||||
|
||||
private void OnContentChanged()
|
||||
{
|
||||
|
||||
var child = Presenter?.Child;
|
||||
TeardownChildrenEventSubscriptions(child, args.GetOldValue<PopConfirmTriggerMode>());
|
||||
SetupChildrenEventSubscriptions(child, args.GetNewValue<PopConfirmTriggerMode>());
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
@@ -148,76 +169,102 @@ public class PopConfirm: ContentControl
|
||||
Button.ClickEvent.AddHandler(OnButtonClicked, _confirmButton, _cancelButton);
|
||||
}
|
||||
|
||||
private void OnButtonClicked(object sender, RoutedEventArgs e)
|
||||
private async void OnButtonClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!HandleAsyncCommand)
|
||||
{
|
||||
_popup?.SetValue(Popup.IsOpenProperty, false);
|
||||
}
|
||||
if (!HandleAsyncCommand) _popup?.SetValue(Popup.IsOpenProperty, false);
|
||||
// This is a hack for MVVM toolkit and Prism that uses INotifyPropertyChanged for async command. It counts the number of
|
||||
// IsRunning property changes to determine when the command is finished.
|
||||
if (sender is Button button && button.Command is { } command and INotifyPropertyChanged)
|
||||
if (sender is Button button && button.Command is { } command and (INotifyPropertyChanged or IDisposable))
|
||||
{
|
||||
var count = 0;
|
||||
|
||||
void OnCanExecuteChanged(object? _, System.EventArgs e)
|
||||
{
|
||||
count++;
|
||||
if (count != 2) return;
|
||||
var canExecute = command.CanExecute(button.CommandParameter);
|
||||
if (canExecute == true)
|
||||
if (canExecute)
|
||||
{
|
||||
command.CanExecuteChanged -= OnCanExecuteChanged;
|
||||
_popup?.SetValue(Popup.IsOpenProperty, false);
|
||||
}
|
||||
}
|
||||
|
||||
command.CanExecuteChanged += OnCanExecuteChanged;
|
||||
}
|
||||
// TODO: This is a hack for ReactiveUI that ReactiveCommand is an IDisposable.
|
||||
else if (sender is Button b2 && b2.Command is { } command2 and IDisposable)
|
||||
{
|
||||
var count = 0;
|
||||
void OnCanExecuteChanged2(object? _, System.EventArgs e)
|
||||
{
|
||||
count++;
|
||||
if (count != 2) return;
|
||||
var canExecute = command2.CanExecute(b2.CommandParameter);
|
||||
if (canExecute == true)
|
||||
{
|
||||
command2.CanExecuteChanged -= OnCanExecuteChanged2;
|
||||
_popup?.SetValue(Popup.IsOpenProperty, false);
|
||||
}
|
||||
}
|
||||
command2.CanExecuteChanged += OnCanExecuteChanged2;
|
||||
}
|
||||
else
|
||||
{
|
||||
_popup?.SetValue(Popup.IsOpenProperty, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private IDisposable? _childChangeDisposable;
|
||||
|
||||
protected override bool RegisterContentPresenter(ContentPresenter presenter)
|
||||
{
|
||||
var result = base.RegisterContentPresenter(presenter);
|
||||
_childChangeDisposable = presenter.GetPropertyChangedObservable(ContentPresenter.ChildProperty).Subscribe(OnChildChanged);
|
||||
_childChangeDisposable = presenter.GetPropertyChangedObservable(ContentPresenter.ChildProperty)
|
||||
.Subscribe(OnChildChanged);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void OnChildChanged(AvaloniaPropertyChangedEventArgs arg)
|
||||
{
|
||||
TeardownChildrenEventSubscriptions(arg.GetOldValue<Control?>(), TriggerMode);
|
||||
SetupChildrenEventSubscriptions(arg.GetNewValue<Control?>(), TriggerMode);
|
||||
if (arg.GetNewValue<Control?>() is null) return;
|
||||
if (arg.GetNewValue<Control?>() is Button button)
|
||||
button.Click += (o, e) => { SetCurrentValue(IsDropdownOpenProperty, true); };
|
||||
}
|
||||
|
||||
private void SetupChildrenEventSubscriptions(Control? child, PopConfirmTriggerMode mode)
|
||||
{
|
||||
if (child is null) return;
|
||||
if (mode == PopConfirmTriggerMode.Tap)
|
||||
{
|
||||
button.Click+= (o, e) =>
|
||||
{
|
||||
_popup?.SetValue(Popup.IsOpenProperty, !_popup.IsOpen);
|
||||
};
|
||||
if (child is Button button)
|
||||
Button.ClickEvent.AddHandler(OnMainButtonClicked, button);
|
||||
else
|
||||
PointerPressedEvent.AddHandler(OnMainElementPressed, child);
|
||||
}
|
||||
else if (mode == PopConfirmTriggerMode.Focus)
|
||||
{
|
||||
GotFocusEvent.AddHandler(OnMainElementGotFocus, child);
|
||||
LostFocusEvent.AddHandler(OnMainElementLostFocus, child);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMainElementLostFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var newFocus = TopLevel.GetTopLevel(this)?.FocusManager?.GetFocusedElement();
|
||||
if (newFocus is Visual v && _popup?.IsInsidePopup(v) == true) return;
|
||||
SetCurrentValue(IsDropdownOpenProperty, false);
|
||||
}
|
||||
|
||||
private void OnMainElementGotFocus(object sender, GotFocusEventArgs e)
|
||||
{
|
||||
SetCurrentValue(IsDropdownOpenProperty, true);
|
||||
}
|
||||
|
||||
private void TeardownChildrenEventSubscriptions(Control? child, PopConfirmTriggerMode mode)
|
||||
{
|
||||
if (child is null) return;
|
||||
PointerPressedEvent.RemoveHandler(OnMainElementPressed, child);
|
||||
Button.ClickEvent.RemoveHandler(OnMainButtonClicked, child);
|
||||
GotFocusEvent.RemoveHandler(OnMainElementGotFocus, child);
|
||||
LostFocusEvent.RemoveHandler(OnMainElementLostFocus, child);
|
||||
}
|
||||
|
||||
private void OnMainButtonClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SetCurrentValue(IsDropdownOpenProperty, !IsDropdownOpen);
|
||||
}
|
||||
|
||||
|
||||
private void OnMainElementPressed(object sender, PointerPressedEventArgs e)
|
||||
{
|
||||
SetCurrentValue(IsDropdownOpenProperty, !IsDropdownOpen);
|
||||
}
|
||||
|
||||
|
||||
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
|
||||
{
|
||||
_childChangeDisposable?.Dispose();
|
||||
|
||||
@@ -3,6 +3,5 @@ namespace Ursa.Controls;
|
||||
public enum PopConfirmTriggerMode
|
||||
{
|
||||
Tap,
|
||||
Hover,
|
||||
Focus,
|
||||
}
|
||||
Reference in New Issue
Block a user