Merge pull request #71 from irihitech/dialog
Dialog System, and MessageBox remake
This commit is contained in:
133
src/Ursa/Controls/Dialog/DefaultDialogControl.cs
Normal file
133
src/Ursa/Controls/Dialog/DefaultDialogControl.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Metadata;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Ursa.Common;
|
||||
|
||||
namespace Ursa.Controls;
|
||||
|
||||
|
||||
[TemplatePart(PART_OKButton, typeof(Button))]
|
||||
[TemplatePart(PART_CancelButton, typeof(Button))]
|
||||
[TemplatePart(PART_YesButton, typeof(Button))]
|
||||
[TemplatePart(PART_NoButton, typeof(Button))]
|
||||
[TemplatePart(PART_TitleArea, typeof(Panel))]
|
||||
public class DefaultDialogControl: DialogControl
|
||||
{
|
||||
public const string PART_OKButton = "PART_OKButton";
|
||||
public const string PART_CancelButton = "PART_CancelButton";
|
||||
public const string PART_YesButton = "PART_YesButton";
|
||||
public const string PART_NoButton = "PART_NoButton";
|
||||
|
||||
private Button? _okButton;
|
||||
private Button? _cancelButton;
|
||||
private Button? _yesButton;
|
||||
private Button? _noButton;
|
||||
|
||||
public static readonly StyledProperty<string?> TitleProperty = AvaloniaProperty.Register<DefaultDialogControl, string?>(
|
||||
nameof(Title));
|
||||
|
||||
public string? Title
|
||||
{
|
||||
get => GetValue(TitleProperty);
|
||||
set => SetValue(TitleProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<DialogButton> ButtonsProperty = AvaloniaProperty.Register<DefaultDialogControl, DialogButton>(
|
||||
nameof(Buttons));
|
||||
|
||||
public DialogButton Buttons
|
||||
{
|
||||
get => GetValue(ButtonsProperty);
|
||||
set => SetValue(ButtonsProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<DialogMode> ModeProperty = AvaloniaProperty.Register<DefaultDialogControl, DialogMode>(
|
||||
nameof(Mode));
|
||||
|
||||
public DialogMode Mode
|
||||
{
|
||||
get => GetValue(ModeProperty);
|
||||
set => SetValue(ModeProperty, value);
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
EventHelper.UnregisterClickEvent(DefaultButtonsClose, _okButton, _cancelButton, _yesButton, _noButton);
|
||||
_okButton = e.NameScope.Find<Button>(PART_OKButton);
|
||||
_cancelButton = e.NameScope.Find<Button>(PART_CancelButton);
|
||||
_yesButton = e.NameScope.Find<Button>(PART_YesButton);
|
||||
_noButton = e.NameScope.Find<Button>(PART_NoButton);
|
||||
EventHelper.RegisterClickEvent(DefaultButtonsClose, _yesButton, _noButton, _okButton, _cancelButton);
|
||||
SetButtonVisibility();
|
||||
}
|
||||
|
||||
|
||||
private void SetButtonVisibility()
|
||||
{
|
||||
switch (Buttons)
|
||||
{
|
||||
case DialogButton.None:
|
||||
SetVisibility(_okButton, false);
|
||||
SetVisibility(_cancelButton, false);
|
||||
SetVisibility(_yesButton, false);
|
||||
SetVisibility(_noButton, false);
|
||||
break;
|
||||
case DialogButton.OK:
|
||||
SetVisibility(_okButton, true);
|
||||
SetVisibility(_cancelButton, false);
|
||||
SetVisibility(_yesButton, false);
|
||||
SetVisibility(_noButton, false);
|
||||
break;
|
||||
case DialogButton.OKCancel:
|
||||
SetVisibility(_okButton, true);
|
||||
SetVisibility(_cancelButton, true);
|
||||
SetVisibility(_yesButton, false);
|
||||
SetVisibility(_noButton, false);
|
||||
break;
|
||||
case DialogButton.YesNo:
|
||||
SetVisibility(_okButton, false);
|
||||
SetVisibility(_cancelButton, false);
|
||||
SetVisibility(_yesButton, true);
|
||||
SetVisibility(_noButton, true);
|
||||
break;
|
||||
case DialogButton.YesNoCancel:
|
||||
SetVisibility(_okButton, false);
|
||||
SetVisibility(_cancelButton, true);
|
||||
SetVisibility(_yesButton, true);
|
||||
SetVisibility(_noButton, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetVisibility(Button? button, bool visible)
|
||||
{
|
||||
if (button is not null) button.IsVisible = visible;
|
||||
}
|
||||
|
||||
private void DefaultButtonsClose(object sender, RoutedEventArgs args)
|
||||
{
|
||||
if (sender is Button button)
|
||||
{
|
||||
if (button == _okButton)
|
||||
{
|
||||
OnDialogControlClosing(this, DialogResult.OK);
|
||||
}
|
||||
else if (button == _cancelButton)
|
||||
{
|
||||
OnDialogControlClosing(this, DialogResult.Cancel);
|
||||
}
|
||||
else if (button == _yesButton)
|
||||
{
|
||||
OnDialogControlClosing(this, DialogResult.Yes);
|
||||
}
|
||||
else if (button == _noButton)
|
||||
{
|
||||
OnDialogControlClosing(this, DialogResult.No);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
125
src/Ursa/Controls/Dialog/DefaultDialogWindow.cs
Normal file
125
src/Ursa/Controls/Dialog/DefaultDialogWindow.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Metadata;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Interactivity;
|
||||
using Ursa.Common;
|
||||
|
||||
namespace Ursa.Controls;
|
||||
|
||||
[TemplatePart(PART_YesButton, typeof(Button))]
|
||||
[TemplatePart(PART_NoButton, typeof(Button))]
|
||||
[TemplatePart(PART_OKButton, typeof(Button))]
|
||||
[TemplatePart(PART_CancelButton, typeof(Button))]
|
||||
public class DefaultDialogWindow: DialogWindow
|
||||
{
|
||||
protected override Type StyleKeyOverride { get; } = typeof(DefaultDialogWindow);
|
||||
|
||||
public const string PART_YesButton = "PART_YesButton";
|
||||
public const string PART_NoButton = "PART_NoButton";
|
||||
public const string PART_OKButton = "PART_OKButton";
|
||||
public const string PART_CancelButton = "PART_CancelButton";
|
||||
|
||||
private Button? _yesButton;
|
||||
private Button? _noButton;
|
||||
private Button? _okButton;
|
||||
private Button? _cancelButton;
|
||||
|
||||
public static readonly StyledProperty<DialogButton> ButtonsProperty = AvaloniaProperty.Register<DefaultDialogWindow, DialogButton>(
|
||||
nameof(Buttons));
|
||||
|
||||
public DialogButton Buttons
|
||||
{
|
||||
get => GetValue(ButtonsProperty);
|
||||
set => SetValue(ButtonsProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<DialogMode> ModeProperty = AvaloniaProperty.Register<DefaultDialogWindow, DialogMode>(
|
||||
nameof(Mode));
|
||||
|
||||
public DialogMode Mode
|
||||
{
|
||||
get => GetValue(ModeProperty);
|
||||
set => SetValue(ModeProperty, value);
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
EventHelper.UnregisterClickEvent(OnDefaultClose, _okButton, _cancelButton, _yesButton, _noButton);
|
||||
|
||||
_okButton = e.NameScope.Find<Button>(PART_OKButton);
|
||||
_cancelButton = e.NameScope.Find<Button>(PART_CancelButton);
|
||||
_yesButton = e.NameScope.Find<Button>(PART_YesButton);
|
||||
_noButton = e.NameScope.Find<Button>(PART_NoButton);
|
||||
|
||||
EventHelper.RegisterClickEvent(OnDefaultClose, _yesButton, _noButton, _okButton, _cancelButton);
|
||||
SetButtonVisibility();
|
||||
}
|
||||
|
||||
private void OnDefaultClose(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender == _yesButton)
|
||||
{
|
||||
Close(DialogResult.Yes);
|
||||
return;
|
||||
}
|
||||
if(sender == _noButton)
|
||||
{
|
||||
Close(DialogResult.No);
|
||||
return;
|
||||
}
|
||||
if(sender == _okButton)
|
||||
{
|
||||
Close(DialogResult.OK);
|
||||
return;
|
||||
}
|
||||
if(sender == _cancelButton)
|
||||
{
|
||||
Close(DialogResult.Cancel);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetButtonVisibility()
|
||||
{
|
||||
switch (Buttons)
|
||||
{
|
||||
case DialogButton.None:
|
||||
SetVisibility(_okButton, false);
|
||||
SetVisibility(_cancelButton, false);
|
||||
SetVisibility(_yesButton, false);
|
||||
SetVisibility(_noButton, false);
|
||||
break;
|
||||
case DialogButton.OK:
|
||||
SetVisibility(_okButton, true);
|
||||
SetVisibility(_cancelButton, false);
|
||||
SetVisibility(_yesButton, false);
|
||||
SetVisibility(_noButton, false);
|
||||
break;
|
||||
case DialogButton.OKCancel:
|
||||
SetVisibility(_okButton, true);
|
||||
SetVisibility(_cancelButton, true);
|
||||
SetVisibility(_yesButton, false);
|
||||
SetVisibility(_noButton, false);
|
||||
break;
|
||||
case DialogButton.YesNo:
|
||||
SetVisibility(_okButton, false);
|
||||
SetVisibility(_cancelButton, false);
|
||||
SetVisibility(_yesButton, true);
|
||||
SetVisibility(_noButton, true);
|
||||
break;
|
||||
case DialogButton.YesNoCancel:
|
||||
SetVisibility(_okButton, false);
|
||||
SetVisibility(_cancelButton, true);
|
||||
SetVisibility(_yesButton, true);
|
||||
SetVisibility(_noButton, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetVisibility(Button? button, bool visible)
|
||||
{
|
||||
if (button is not null) button.IsVisible = visible;
|
||||
}
|
||||
}
|
||||
173
src/Ursa/Controls/Dialog/Dialog.cs
Normal file
173
src/Ursa/Controls/Dialog/Dialog.cs
Normal file
@@ -0,0 +1,173 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Controls.Shapes;
|
||||
using Avalonia.Media;
|
||||
using Ursa.Common;
|
||||
|
||||
namespace Ursa.Controls;
|
||||
|
||||
public static class Dialog
|
||||
{
|
||||
/// <summary>
|
||||
/// Show a Window Modal Dialog that with all content fully customized.
|
||||
/// </summary>
|
||||
/// <param name="vm"></param>
|
||||
/// <typeparam name="TView"></typeparam>
|
||||
/// <typeparam name="TViewModel"></typeparam>
|
||||
/// <typeparam name="TResult"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static async Task<TResult?> ShowCustomModalAsync<TView, TViewModel, TResult>(TViewModel vm)
|
||||
where TView : Control, new()
|
||||
{
|
||||
var mainWindow = GetMainWindow();
|
||||
return await ShowCustomModalAsync<TView, TViewModel, TResult>(mainWindow, vm);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Show a Window Modal Dialog that with all content fully customized. And the owner of the dialog is specified.
|
||||
/// </summary>
|
||||
/// <param name="owner"></param>
|
||||
/// <param name="vm"></param>
|
||||
/// <typeparam name="TView"></typeparam>
|
||||
/// <typeparam name="TViewModel"></typeparam>
|
||||
/// <typeparam name="TResult"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static async Task<TResult> ShowCustomModalAsync<TView, TViewModel, TResult>(Window? owner, TViewModel? vm)
|
||||
where TView: Control, new()
|
||||
{
|
||||
var window = new DialogWindow
|
||||
{
|
||||
Content = new TView { DataContext = vm },
|
||||
DataContext = vm,
|
||||
};
|
||||
if (owner is null)
|
||||
{
|
||||
window.Show();
|
||||
return default;
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = await window.ShowDialog<TResult>(owner);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<DialogResult> ShowModalAsync<TView, TViewModel>(
|
||||
Window? owner,
|
||||
TViewModel vm,
|
||||
string? title = null,
|
||||
DialogMode mode = DialogMode.None,
|
||||
DialogButton buttons = DialogButton.OKCancel)
|
||||
where TView : Control, new()
|
||||
{
|
||||
var window = new DefaultDialogWindow()
|
||||
{
|
||||
Content = new TView() { DataContext = vm },
|
||||
DataContext = vm,
|
||||
Buttons = buttons,
|
||||
Title = title,
|
||||
Mode = mode,
|
||||
};
|
||||
if (owner is null)
|
||||
{
|
||||
window.Show();
|
||||
return DialogResult.None;
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = await window.ShowDialog<DialogResult>(owner);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<DialogResult> ShowModalAsync<TView, TViewModel>(
|
||||
TViewModel vm,
|
||||
string? title = null,
|
||||
DialogMode mode = DialogMode.None,
|
||||
DialogButton buttons = DialogButton.OKCancel)
|
||||
where TView: Control, new()
|
||||
{
|
||||
var mainWindow = GetMainWindow();
|
||||
return await ShowModalAsync<TView, TViewModel>(mainWindow, vm, title, mode, buttons);
|
||||
}
|
||||
|
||||
private static Window? GetMainWindow()
|
||||
{
|
||||
var lifetime = Application.Current?.ApplicationLifetime;
|
||||
return lifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } w } ? w : null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class OverlayDialog
|
||||
{
|
||||
public static Task<DialogResult> ShowModalAsync<TView, TViewModel>(
|
||||
TViewModel vm,
|
||||
string? hostId = null,
|
||||
string? title = null,
|
||||
DialogMode mode = DialogMode.None,
|
||||
DialogButton buttons = DialogButton.OKCancel)
|
||||
where TView : Control, new()
|
||||
{
|
||||
var t = new DefaultDialogControl()
|
||||
{
|
||||
Content = new TView(){ DataContext = vm },
|
||||
DataContext = vm,
|
||||
Buttons = buttons,
|
||||
Title = title,
|
||||
Mode = mode,
|
||||
};
|
||||
var host = OverlayDialogManager.GetHost(hostId);
|
||||
host?.AddModalDialog(t);
|
||||
return t.ShowAsync<DialogResult>();
|
||||
}
|
||||
|
||||
public static Task<TResult> ShowCustomModalAsync<TView, TViewModel, TResult>(
|
||||
TViewModel vm,
|
||||
string? hostId = null)
|
||||
where TView: Control, new()
|
||||
{
|
||||
var t = new DialogControl()
|
||||
{
|
||||
Content = new TView() { DataContext = vm },
|
||||
DataContext = vm,
|
||||
};
|
||||
var host = OverlayDialogManager.GetHost(hostId);
|
||||
host?.AddModalDialog(t);
|
||||
return t.ShowAsync<TResult>();
|
||||
}
|
||||
|
||||
public static void Show<TView, TViewModel>(
|
||||
TViewModel vm,
|
||||
string? hostId = null,
|
||||
string? title = null,
|
||||
DialogMode mode = DialogMode.None,
|
||||
DialogButton buttons = DialogButton.OKCancel)
|
||||
where TView: Control, new()
|
||||
{
|
||||
var t = new DefaultDialogControl()
|
||||
{
|
||||
Content = new TView() { DataContext = vm },
|
||||
DataContext = vm,
|
||||
Buttons = buttons,
|
||||
Title = title,
|
||||
Mode = mode,
|
||||
};
|
||||
var host = OverlayDialogManager.GetHost(hostId);
|
||||
host?.AddDialog(t);
|
||||
}
|
||||
|
||||
public static void ShowCustom<TView, TViewModel>(TViewModel vm, string? hostId = null)
|
||||
where TView: Control, new()
|
||||
{
|
||||
var t = new DialogControl()
|
||||
{
|
||||
Content = new TView() { DataContext = vm },
|
||||
DataContext = vm,
|
||||
};
|
||||
var host = OverlayDialogManager.GetHost(hostId);
|
||||
host?.AddDialog(t);
|
||||
}
|
||||
|
||||
}
|
||||
131
src/Ursa/Controls/Dialog/DialogControl.cs
Normal file
131
src/Ursa/Controls/Dialog/DialogControl.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Metadata;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Ursa.Common;
|
||||
|
||||
namespace Ursa.Controls;
|
||||
|
||||
[TemplatePart(PART_CloseButton, typeof(Button))]
|
||||
[TemplatePart(PART_TitleArea, typeof(Panel))]
|
||||
[PseudoClasses(PC_Modal)]
|
||||
public class DialogControl: ContentControl
|
||||
{
|
||||
public const string PART_CloseButton = "PART_CloseButton";
|
||||
public const string PART_TitleArea = "PART_TitleArea";
|
||||
public const string PC_Modal = ":modal";
|
||||
|
||||
protected internal Button? _closeButton;
|
||||
private Panel? _titleArea;
|
||||
public event EventHandler<DialogLayerChangeEventArgs>? LayerChanged;
|
||||
public event EventHandler<object?>? DialogControlClosing;
|
||||
|
||||
static DialogControl()
|
||||
{
|
||||
DataContextProperty.Changed.AddClassHandler<DialogControl, object?>((o, e) => o.OnDataContextChange(e));
|
||||
}
|
||||
|
||||
private void OnDataContextChange(AvaloniaPropertyChangedEventArgs<object?> args)
|
||||
{
|
||||
if (args.OldValue.Value is IDialogContext oldContext)
|
||||
{
|
||||
oldContext.Closed-= CloseFromContext;
|
||||
}
|
||||
|
||||
if (args.NewValue.Value is IDialogContext newContext)
|
||||
{
|
||||
newContext.Closed += CloseFromContext;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
EventHelper.UnregisterClickEvent(Close, _closeButton);
|
||||
|
||||
_titleArea?.RemoveHandler(PointerMovedEvent, OnTitlePointerMove);
|
||||
_titleArea?.RemoveHandler(PointerPressedEvent, OnTitlePointerPressed);
|
||||
|
||||
_closeButton = e.NameScope.Find<Button>(PART_CloseButton);
|
||||
_titleArea = e.NameScope.Find<Panel>(PART_TitleArea);
|
||||
|
||||
|
||||
_titleArea?.AddHandler(PointerMovedEvent, OnTitlePointerMove, RoutingStrategies.Bubble);
|
||||
_titleArea?.AddHandler(PointerPressedEvent, OnTitlePointerPressed, RoutingStrategies.Bubble);
|
||||
EventHelper.RegisterClickEvent(Close, _closeButton);
|
||||
}
|
||||
|
||||
private void OnTitlePointerPressed(object sender, PointerPressedEventArgs e)
|
||||
{
|
||||
e.Source = this;
|
||||
}
|
||||
|
||||
private void OnTitlePointerMove(object sender, PointerEventArgs e)
|
||||
{
|
||||
e.Source = this;
|
||||
}
|
||||
|
||||
|
||||
public Task<T> ShowAsync<T>()
|
||||
{
|
||||
var tcs = new TaskCompletionSource<T>();
|
||||
void OnCloseHandler(object sender, object? args)
|
||||
{
|
||||
if (args is T result)
|
||||
{
|
||||
tcs.SetResult(result);
|
||||
DialogControlClosing-= OnCloseHandler;
|
||||
}
|
||||
else
|
||||
{
|
||||
tcs.SetResult(default(T));
|
||||
DialogControlClosing-= OnCloseHandler;
|
||||
}
|
||||
}
|
||||
|
||||
this.DialogControlClosing += OnCloseHandler;
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
private void Close(object sender, RoutedEventArgs args)
|
||||
{
|
||||
if (this.DataContext is IDialogContext context)
|
||||
{
|
||||
context.Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
DialogControlClosing?.Invoke(this, DialogResult.None);
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseFromContext(object sender, object? args)
|
||||
{
|
||||
if (this.DataContext is IDialogContext context)
|
||||
{
|
||||
DialogControlClosing?.Invoke(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void UpdateLayer(object? o)
|
||||
{
|
||||
if (o is DialogLayerChangeType t)
|
||||
{
|
||||
LayerChanged?.Invoke(this, new DialogLayerChangeEventArgs(t));
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnDialogControlClosing(object sender, object? args)
|
||||
{
|
||||
DialogControlClosing?.Invoke(this, args);
|
||||
}
|
||||
|
||||
internal void SetAsModal(bool modal)
|
||||
{
|
||||
PseudoClasses.Set(PC_Modal, modal);
|
||||
}
|
||||
}
|
||||
18
src/Ursa/Controls/Dialog/DialogLayerChangeEventArgs.cs
Normal file
18
src/Ursa/Controls/Dialog/DialogLayerChangeEventArgs.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace Ursa.Controls;
|
||||
|
||||
public class DialogLayerChangeEventArgs
|
||||
{
|
||||
public DialogLayerChangeType ChangeType { get; }
|
||||
public DialogLayerChangeEventArgs(DialogLayerChangeType type)
|
||||
{
|
||||
ChangeType = type;
|
||||
}
|
||||
}
|
||||
|
||||
public enum DialogLayerChangeType
|
||||
{
|
||||
BringForward,
|
||||
SendBackward,
|
||||
BringToFront,
|
||||
SendToBack
|
||||
}
|
||||
11
src/Ursa/Controls/Dialog/DialogMode.cs
Normal file
11
src/Ursa/Controls/Dialog/DialogMode.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Ursa.Controls;
|
||||
|
||||
public enum DialogMode
|
||||
{
|
||||
Info,
|
||||
Warning,
|
||||
Error,
|
||||
Question,
|
||||
None,
|
||||
Success,
|
||||
}
|
||||
67
src/Ursa/Controls/Dialog/DialogWindow.cs
Normal file
67
src/Ursa/Controls/Dialog/DialogWindow.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Metadata;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Ursa.Common;
|
||||
|
||||
namespace Ursa.Controls;
|
||||
|
||||
[TemplatePart(PART_CloseButton, typeof(Button))]
|
||||
public class DialogWindow: Window
|
||||
{
|
||||
public const string PART_CloseButton = "PART_CloseButton";
|
||||
|
||||
protected override Type StyleKeyOverride { get; } = typeof(DialogWindow);
|
||||
|
||||
private Button? _closeButton;
|
||||
|
||||
static DialogWindow()
|
||||
{
|
||||
DataContextProperty.Changed.AddClassHandler<DialogWindow, object?>((o, e) => o.OnDataContextChange(e));
|
||||
}
|
||||
|
||||
private void OnDataContextChange(AvaloniaPropertyChangedEventArgs<object?> args)
|
||||
{
|
||||
if (args.OldValue.Value is IDialogContext oldContext)
|
||||
{
|
||||
oldContext.Closed-= OnDialogContextClose;
|
||||
}
|
||||
|
||||
if (args.NewValue.Value is IDialogContext newContext)
|
||||
{
|
||||
newContext.Closed += OnDialogContextClose;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
EventHelper.UnregisterClickEvent(OnDefaultClose, _closeButton);
|
||||
_closeButton = e.NameScope.Find<Button>(PART_CloseButton);
|
||||
EventHelper.RegisterClickEvent(OnDefaultClose, _closeButton);
|
||||
}
|
||||
|
||||
private void OnDialogContextClose(object? sender, object? args)
|
||||
{
|
||||
Close(args);
|
||||
}
|
||||
|
||||
private void OnDefaultClose(object sender, RoutedEventArgs args)
|
||||
{
|
||||
if (DataContext is IDialogContext context)
|
||||
{
|
||||
context.Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
Close(null);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPointerPressed(PointerPressedEventArgs e)
|
||||
{
|
||||
this.BeginMoveDrag(e);
|
||||
}
|
||||
}
|
||||
7
src/Ursa/Controls/Dialog/IDialogContext.cs
Normal file
7
src/Ursa/Controls/Dialog/IDialogContext.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Ursa.Controls;
|
||||
|
||||
public interface IDialogContext
|
||||
{
|
||||
void Close();
|
||||
event EventHandler<object?> Closed;
|
||||
}
|
||||
223
src/Ursa/Controls/Dialog/OverlayDialogHost.cs
Normal file
223
src/Ursa/Controls/Dialog/OverlayDialogHost.cs
Normal file
@@ -0,0 +1,223 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Utilities;
|
||||
|
||||
namespace Ursa.Controls;
|
||||
|
||||
public class OverlayDialogHost : Canvas
|
||||
{
|
||||
private readonly List<DialogControl> _dialogs = new();
|
||||
private readonly List<DialogControl> _modalDialogs = new();
|
||||
private readonly List<Border> _masks = new();
|
||||
|
||||
public string? HostId { get; set; }
|
||||
|
||||
private Point _lastPoint;
|
||||
|
||||
public static readonly StyledProperty<IBrush?> OverlayMaskBrushProperty =
|
||||
AvaloniaProperty.Register<OverlayDialogHost, IBrush?>(
|
||||
nameof(OverlayMaskBrush));
|
||||
|
||||
public IBrush? OverlayMaskBrush
|
||||
{
|
||||
get => GetValue(OverlayMaskBrushProperty);
|
||||
set => SetValue(OverlayMaskBrushProperty, value);
|
||||
}
|
||||
|
||||
private Border CreateOverlayMask() => new()
|
||||
{
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
VerticalAlignment = VerticalAlignment.Stretch,
|
||||
Width = this.Bounds.Width,
|
||||
Height = this.Bounds.Height,
|
||||
[!BackgroundProperty] = this[!OverlayMaskBrushProperty],
|
||||
IsVisible = true,
|
||||
};
|
||||
|
||||
protected sealed override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnAttachedToVisualTree(e);
|
||||
OverlayDialogManager.RegisterHost(this, HostId);
|
||||
}
|
||||
|
||||
protected sealed override void OnSizeChanged(SizeChangedEventArgs e)
|
||||
{
|
||||
base.OnSizeChanged(e);
|
||||
for (int i = 0; i < _masks.Count; i++)
|
||||
{
|
||||
_masks[i].Width = this.Bounds.Width;
|
||||
_masks[i].Height = this.Bounds.Height;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
OverlayDialogManager.UnregisterHost(HostId);
|
||||
base.OnDetachedFromVisualTree(e);
|
||||
}
|
||||
|
||||
protected override void OnPointerMoved(PointerEventArgs e)
|
||||
{
|
||||
base.OnPointerMoved(e);
|
||||
if (e.Source is DialogControl item)
|
||||
{
|
||||
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
||||
{
|
||||
var p = e.GetPosition(this);
|
||||
var left = p.X - _lastPoint.X;
|
||||
var top = p.Y - _lastPoint.Y;
|
||||
left = MathUtilities.Clamp(left, 0, Bounds.Width - item.Bounds.Width);
|
||||
top = MathUtilities.Clamp(top, 0, Bounds.Height - item.Bounds.Height);
|
||||
Canvas.SetLeft(item, left);
|
||||
Canvas.SetTop(item, top);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPointerPressed(PointerPressedEventArgs e)
|
||||
{
|
||||
// base.OnPointerPressed(e);
|
||||
if (e.Source is DialogControl item)
|
||||
{
|
||||
_lastPoint = e.GetPosition(item);
|
||||
}
|
||||
}
|
||||
|
||||
internal void AddDialog(DialogControl control)
|
||||
{
|
||||
this.Children.Add(control);
|
||||
_dialogs.Add(control);
|
||||
control.Measure(this.Bounds.Size);
|
||||
control.Arrange(new Rect(control.DesiredSize));
|
||||
SetToCenter(control);
|
||||
control.DialogControlClosing += OnDialogControlClosing;
|
||||
control.LayerChanged += OnDialogLayerChanged;
|
||||
ResetZIndices();
|
||||
}
|
||||
|
||||
private void OnDialogControlClosing(object sender, object? e)
|
||||
{
|
||||
if (sender is DialogControl control)
|
||||
{
|
||||
this.Children.Remove(control);
|
||||
control.DialogControlClosing -= OnDialogControlClosing;
|
||||
control.LayerChanged -= OnDialogLayerChanged;
|
||||
if (_dialogs.Contains(control))
|
||||
{
|
||||
_dialogs.Remove(control);
|
||||
}
|
||||
else if (_modalDialogs.Contains(control))
|
||||
{
|
||||
_modalDialogs.Remove(control);
|
||||
if (_masks.Count > 0)
|
||||
{
|
||||
var last = _masks.Last();
|
||||
this.Children.Remove(last);
|
||||
_masks.Remove(last);
|
||||
if (_masks.Count > 0)
|
||||
{
|
||||
_masks.Last().IsVisible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
ResetZIndices();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a dialog as a modal dialog to the host
|
||||
/// </summary>
|
||||
/// <param name="control"></param>
|
||||
internal void AddModalDialog(DialogControl control)
|
||||
{
|
||||
var mask = CreateOverlayMask();
|
||||
_masks.Add(mask);
|
||||
_modalDialogs.Add(control);
|
||||
control.SetAsModal(true);
|
||||
for (int i = 0; i < _masks.Count-1; i++)
|
||||
{
|
||||
_masks[i].Opacity = 0.5;
|
||||
}
|
||||
ResetZIndices();
|
||||
this.Children.Add(mask);
|
||||
this.Children.Add(control);
|
||||
control.Measure(this.Bounds.Size);
|
||||
control.Arrange(new Rect(control.DesiredSize));
|
||||
SetToCenter(control);
|
||||
control.DialogControlClosing += OnDialogControlClosing;
|
||||
control.LayerChanged += OnDialogLayerChanged;
|
||||
}
|
||||
|
||||
// Handle dialog layer change event
|
||||
private void OnDialogLayerChanged(object sender, DialogLayerChangeEventArgs e)
|
||||
{
|
||||
if (sender is not DialogControl control)
|
||||
return;
|
||||
if (!_dialogs.Contains(control))
|
||||
return;
|
||||
int index = _dialogs.IndexOf(control);
|
||||
_dialogs.Remove(control);
|
||||
int newIndex = index;
|
||||
switch (e.ChangeType)
|
||||
{
|
||||
case DialogLayerChangeType.BringForward:
|
||||
newIndex = MathUtilities.Clamp(index + 1, 0, _dialogs.Count);
|
||||
break;
|
||||
case DialogLayerChangeType.SendBackward:
|
||||
newIndex = MathUtilities.Clamp(index - 1, 0, _dialogs.Count);
|
||||
break;
|
||||
case DialogLayerChangeType.BringToFront:
|
||||
newIndex = _dialogs.Count;
|
||||
break;
|
||||
case DialogLayerChangeType.SendToBack:
|
||||
newIndex = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
_dialogs.Insert(newIndex, control);
|
||||
for (int i = 0; i < _dialogs.Count; i++)
|
||||
{
|
||||
_dialogs[i].ZIndex = i;
|
||||
}
|
||||
|
||||
for (int i = 0; i < _masks.Count * 2; i += 2)
|
||||
{
|
||||
_masks[i].ZIndex = _dialogs.Count + i;
|
||||
_modalDialogs[i].ZIndex = _dialogs.Count + i + 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void ResetZIndices()
|
||||
{
|
||||
int index = 0;
|
||||
for ( int i = 0; i< _dialogs.Count; i++)
|
||||
{
|
||||
_dialogs[i].ZIndex = index;
|
||||
index++;
|
||||
}
|
||||
for(int i = 0; i< _masks.Count; i++)
|
||||
{
|
||||
_masks[i].ZIndex = index;
|
||||
index++;
|
||||
_modalDialogs[i].ZIndex = index;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetToCenter(DialogControl? control)
|
||||
{
|
||||
// return;
|
||||
if (control is null) return;
|
||||
double left = (this.Bounds.Width - control.Bounds.Width) / 2;
|
||||
double top = (this.Bounds.Height - control.Bounds.Height) / 2;
|
||||
left = MathUtilities.Clamp(left, 0, Bounds.Width);
|
||||
top = MathUtilities.Clamp(top, 0, Bounds.Height);
|
||||
Canvas.SetLeft(control, left);
|
||||
Canvas.SetTop(control, top);
|
||||
}
|
||||
}
|
||||
42
src/Ursa/Controls/Dialog/OverlayDialogManager.cs
Normal file
42
src/Ursa/Controls/Dialog/OverlayDialogManager.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Ursa.Controls;
|
||||
|
||||
internal static class OverlayDialogManager
|
||||
{
|
||||
private static OverlayDialogHost? _defaultHost;
|
||||
private static readonly ConcurrentDictionary<string, OverlayDialogHost> Hosts = new();
|
||||
|
||||
public static void RegisterHost(OverlayDialogHost host, string? id)
|
||||
{
|
||||
if (id == null)
|
||||
{
|
||||
if (_defaultHost != null)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot register multiple OverlayDialogHost with empty HostId");
|
||||
}
|
||||
_defaultHost = host;
|
||||
return;
|
||||
}
|
||||
Hosts.TryAdd(id, host);
|
||||
}
|
||||
|
||||
public static void UnregisterHost(string? id)
|
||||
{
|
||||
if (id is null)
|
||||
{
|
||||
_defaultHost = null;
|
||||
return;
|
||||
}
|
||||
Hosts.TryRemove(id, out _);
|
||||
}
|
||||
|
||||
public static OverlayDialogHost? GetHost(string? id)
|
||||
{
|
||||
if (id is null)
|
||||
{
|
||||
return _defaultHost;
|
||||
}
|
||||
return Hosts.TryGetValue(id, out var host) ? host : null;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Controls.Notifications;
|
||||
using Avalonia.Styling;
|
||||
|
||||
namespace Ursa.Controls;
|
||||
|
||||
@@ -56,4 +57,25 @@ public static class MessageBox
|
||||
var result = await messageWindow.ShowDialog<MessageBoxResult>(owner);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static async Task<MessageBoxResult> ShowOverlayAsync(
|
||||
string message,
|
||||
string? title = null,
|
||||
string? hostId = null,
|
||||
MessageBoxIcon icon = MessageBoxIcon.None,
|
||||
MessageBoxButton button = MessageBoxButton.OKCancel)
|
||||
{
|
||||
var host = OverlayDialogManager.GetHost(hostId);
|
||||
if (host is null) return MessageBoxResult.None;
|
||||
var messageControl = new MessageBoxControl()
|
||||
{
|
||||
Content = message,
|
||||
Title = title,
|
||||
Buttons = button,
|
||||
MessageIcon = icon,
|
||||
};
|
||||
host.AddModalDialog(messageControl);
|
||||
var result = await messageControl.ShowAsync<MessageBoxResult>();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
135
src/Ursa/Controls/MessageBox/MessageBoxControl.cs
Normal file
135
src/Ursa/Controls/MessageBox/MessageBoxControl.cs
Normal file
@@ -0,0 +1,135 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Metadata;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Styling;
|
||||
using Ursa.Common;
|
||||
|
||||
namespace Ursa.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// The messageBox used to display in OverlayDialogHost.
|
||||
/// </summary>
|
||||
[TemplatePart(PART_CloseButton, typeof(Button))]
|
||||
[TemplatePart(PART_NoButton, typeof(Button))]
|
||||
[TemplatePart(PART_OKButton, typeof(Button))]
|
||||
[TemplatePart(PART_CancelButton, typeof(Button))]
|
||||
[TemplatePart(PART_YesButton, typeof(Button))]
|
||||
public class MessageBoxControl: DialogControl
|
||||
{
|
||||
public const string PART_YesButton = "PART_YesButton";
|
||||
public const string PART_NoButton = "PART_NoButton";
|
||||
public const string PART_OKButton = "PART_OKButton";
|
||||
public const string PART_CancelButton = "PART_CancelButton";
|
||||
|
||||
private Button? _yesButton;
|
||||
private Button? _noButton;
|
||||
private Button? _okButton;
|
||||
private Button? _cancelButton;
|
||||
|
||||
public static readonly StyledProperty<MessageBoxIcon> MessageIconProperty =
|
||||
AvaloniaProperty.Register<MessageBoxWindow, MessageBoxIcon>(
|
||||
nameof(MessageIcon));
|
||||
|
||||
public MessageBoxIcon MessageIcon
|
||||
{
|
||||
get => GetValue(MessageIconProperty);
|
||||
set => SetValue(MessageIconProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<MessageBoxButton> ButtonsProperty = AvaloniaProperty.Register<MessageBoxControl, MessageBoxButton>(
|
||||
nameof(Buttons), MessageBoxButton.OK);
|
||||
|
||||
public MessageBoxButton Buttons
|
||||
{
|
||||
get => GetValue(ButtonsProperty);
|
||||
set => SetValue(ButtonsProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<string?> TitleProperty = AvaloniaProperty.Register<MessageBoxControl, string?>(
|
||||
nameof(Title));
|
||||
|
||||
public string? Title
|
||||
{
|
||||
get => GetValue(TitleProperty);
|
||||
set => SetValue(TitleProperty, value);
|
||||
}
|
||||
|
||||
static MessageBoxControl()
|
||||
{
|
||||
ButtonsProperty.Changed.AddClassHandler<MessageBoxControl>((o, e) => { o.SetButtonVisibility(); });
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
EventHelper.UnregisterClickEvent(DefaultButtonsClose, _okButton, _cancelButton, _yesButton, _noButton);
|
||||
_okButton = e.NameScope.Find<Button>(PART_OKButton);
|
||||
_cancelButton = e.NameScope.Find<Button>(PART_CancelButton);
|
||||
_yesButton = e.NameScope.Find<Button>(PART_YesButton);
|
||||
_noButton = e.NameScope.Find<Button>(PART_NoButton);
|
||||
EventHelper.RegisterClickEvent(DefaultButtonsClose, _okButton, _cancelButton, _yesButton, _noButton);
|
||||
SetButtonVisibility();
|
||||
}
|
||||
|
||||
private void DefaultButtonsClose(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is Button button)
|
||||
{
|
||||
if (button == _okButton)
|
||||
{
|
||||
OnDialogControlClosing(this, MessageBoxResult.OK);
|
||||
}
|
||||
else if (button == _cancelButton)
|
||||
{
|
||||
OnDialogControlClosing(this, MessageBoxResult.Cancel);
|
||||
}
|
||||
else if (button == _yesButton)
|
||||
{
|
||||
OnDialogControlClosing(this, MessageBoxResult.Yes);
|
||||
}
|
||||
else if (button == _noButton)
|
||||
{
|
||||
OnDialogControlClosing(this, MessageBoxResult.No);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetButtonVisibility()
|
||||
{
|
||||
switch (Buttons)
|
||||
{
|
||||
case MessageBoxButton.OK:
|
||||
SetVisibility(_okButton, true);
|
||||
SetVisibility(_cancelButton, false);
|
||||
SetVisibility(_yesButton, false);
|
||||
SetVisibility(_noButton, false);
|
||||
break;
|
||||
case MessageBoxButton.OKCancel:
|
||||
SetVisibility(_okButton, true);
|
||||
SetVisibility(_cancelButton, true);
|
||||
SetVisibility(_yesButton, false);
|
||||
SetVisibility(_noButton, false);
|
||||
break;
|
||||
case MessageBoxButton.YesNo:
|
||||
SetVisibility(_okButton, false);
|
||||
SetVisibility(_cancelButton, false);
|
||||
SetVisibility(_yesButton, true);
|
||||
SetVisibility(_noButton, true);
|
||||
break;
|
||||
case MessageBoxButton.YesNoCancel:
|
||||
SetVisibility(_okButton, false);
|
||||
SetVisibility(_cancelButton, true);
|
||||
SetVisibility(_yesButton, true);
|
||||
SetVisibility(_noButton, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetVisibility(Button? button, bool visible)
|
||||
{
|
||||
if (button is not null) button.IsVisible = visible;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Platform;
|
||||
using Ursa.Common;
|
||||
|
||||
namespace Ursa.Controls;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user