diff --git a/demo/Ursa.Demo/Models/MenuKeys.cs b/demo/Ursa.Demo/Models/MenuKeys.cs
index 2b30912..f97a060 100644
--- a/demo/Ursa.Demo/Models/MenuKeys.cs
+++ b/demo/Ursa.Demo/Models/MenuKeys.cs
@@ -8,6 +8,7 @@ public static class MenuKeys
public const string MenuKeyButtonGroup = "ButtonGroup";
public const string MenuKeyDialog = "Dialog";
public const string MenuKeyDivider = "Divider";
+ public const string MenuKeyDrawer = "Drawer";
public const string MenuKeyDualBadge = "DualBadge";
public const string MenuKeyEnumSelector = "EnumSelector";
public const string MenuKeyImageViewer = "ImageViewer";
diff --git a/demo/Ursa.Demo/Pages/DrawerDemo.axaml b/demo/Ursa.Demo/Pages/DrawerDemo.axaml
new file mode 100644
index 0000000..10d780a
--- /dev/null
+++ b/demo/Ursa.Demo/Pages/DrawerDemo.axaml
@@ -0,0 +1,14 @@
+
+
+
+
+
diff --git a/demo/Ursa.Demo/Pages/DrawerDemo.axaml.cs b/demo/Ursa.Demo/Pages/DrawerDemo.axaml.cs
new file mode 100644
index 0000000..3aa8916
--- /dev/null
+++ b/demo/Ursa.Demo/Pages/DrawerDemo.axaml.cs
@@ -0,0 +1,13 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace Ursa.Demo.Pages;
+
+public partial class DrawerDemo : UserControl
+{
+ public DrawerDemo()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/demo/Ursa.Demo/ViewModels/DrawerDemoViewModel.cs b/demo/Ursa.Demo/ViewModels/DrawerDemoViewModel.cs
new file mode 100644
index 0000000..ff9fed2
--- /dev/null
+++ b/demo/Ursa.Demo/ViewModels/DrawerDemoViewModel.cs
@@ -0,0 +1,23 @@
+using System.Threading.Tasks;
+using System.Windows.Input;
+using Avalonia.Controls;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using Ursa.Controls;
+
+namespace Ursa.Demo.ViewModels;
+
+public class DrawerDemoViewModel: ObservableObject
+{
+ public ICommand OpenDrawerCommand { get; set; }
+
+ public DrawerDemoViewModel()
+ {
+ OpenDrawerCommand = new AsyncRelayCommand(OpenDrawer);
+ }
+
+ private async Task OpenDrawer()
+ {
+ await Drawer.Show("Hello World");
+ }
+}
\ No newline at end of file
diff --git a/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs b/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs
index 50ab100..6161fde 100644
--- a/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs
+++ b/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs
@@ -30,6 +30,7 @@ public class MainViewViewModel : ViewModelBase
MenuKeys.MenuKeyButtonGroup => new ButtonGroupDemoViewModel(),
MenuKeys.MenuKeyDialog => new DialogDemoViewModel(),
MenuKeys.MenuKeyDivider => new DividerDemoViewModel(),
+ MenuKeys.MenuKeyDrawer => new DrawerDemoViewModel(),
MenuKeys.MenuKeyDualBadge => new DualBadgeDemoViewModel(),
MenuKeys.MenuKeyEnumSelector => new EnumSelectorDemoViewModel(),
MenuKeys.MenuKeyImageViewer => new ImageViewerDemoViewModel(),
diff --git a/demo/Ursa.Demo/ViewModels/MenuViewModel.cs b/demo/Ursa.Demo/ViewModels/MenuViewModel.cs
index 672c5b4..45fa3b4 100644
--- a/demo/Ursa.Demo/ViewModels/MenuViewModel.cs
+++ b/demo/Ursa.Demo/ViewModels/MenuViewModel.cs
@@ -17,6 +17,7 @@ public class MenuViewModel: ViewModelBase
new() { MenuHeader = "Button Group", Key = MenuKeys.MenuKeyButtonGroup, Status = "Updated"},
new() { MenuHeader = "Dialog", Key = MenuKeys.MenuKeyDialog },
new() { MenuHeader = "Divider", Key = MenuKeys.MenuKeyDivider },
+ new() { MenuHeader = "Drawer", Key = MenuKeys.MenuKeyDrawer },
new() { MenuHeader = "DualBadge", Key = MenuKeys.MenuKeyDualBadge },
new() { MenuHeader = "Enum Selector", Key = MenuKeys.MenuKeyEnumSelector },
new() { MenuHeader = "Icon Button", Key = MenuKeys.MenuKeyIconButton },
diff --git a/src/Ursa.Themes.Semi/Controls/Drawer.axaml b/src/Ursa.Themes.Semi/Controls/Drawer.axaml
new file mode 100644
index 0000000..31b3a54
--- /dev/null
+++ b/src/Ursa.Themes.Semi/Controls/Drawer.axaml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Ursa.Themes.Semi/Controls/_index.axaml b/src/Ursa.Themes.Semi/Controls/_index.axaml
index f21a847..1821ec6 100644
--- a/src/Ursa.Themes.Semi/Controls/_index.axaml
+++ b/src/Ursa.Themes.Semi/Controls/_index.axaml
@@ -7,6 +7,7 @@
+
diff --git a/src/Ursa/Controls/Dialog/DefaultDialogControl.cs b/src/Ursa/Controls/Dialog/DefaultDialogControl.cs
index a5d7935..2d0b25f 100644
--- a/src/Ursa/Controls/Dialog/DefaultDialogControl.cs
+++ b/src/Ursa/Controls/Dialog/DefaultDialogControl.cs
@@ -9,7 +9,6 @@ using Ursa.EventArgs;
namespace Ursa.Controls;
-
[TemplatePart(PART_OKButton, typeof(Button))]
[TemplatePart(PART_CancelButton, typeof(Button))]
[TemplatePart(PART_YesButton, typeof(Button))]
diff --git a/src/Ursa/Controls/Dialog/OverlayDialogHost.cs b/src/Ursa/Controls/Dialog/OverlayDialogHost.cs
index cfb43e7..5a586fd 100644
--- a/src/Ursa/Controls/Dialog/OverlayDialogHost.cs
+++ b/src/Ursa/Controls/Dialog/OverlayDialogHost.cs
@@ -1,19 +1,22 @@
using Avalonia;
using Avalonia.Animation;
+using Avalonia.Animation.Easings;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Media;
+using Avalonia.Styling;
using Avalonia.Utilities;
+using Ursa.EventArgs;
namespace Ursa.Controls;
public class OverlayDialogHost : Canvas
{
private readonly List _dialogs = new();
- private readonly List _modalDialogs = new();
+ private readonly List _modalDialogs = new();
private readonly List _masks = new();
public string? HostId { get; set; }
@@ -57,9 +60,16 @@ public class OverlayDialogHost : Canvas
if (sender is Border border)
{
int i = _masks.IndexOf(border);
- DialogControl dialog = _modalDialogs[i];
- dialog.CloseDialog();
- border.RemoveHandler(PointerReleasedEvent, ClickBorderToCloseDialog);
+ if (_modalDialogs[i] is DialogControl dialog)
+ {
+ dialog?.CloseDialog();
+ border.RemoveHandler(PointerReleasedEvent, ClickBorderToCloseDialog);
+ }
+ else if(_modalDialogs[i] is DrawerControlBase drawer)
+ {
+ drawer.CloseDrawer();
+ border.RemoveHandler(PointerReleasedEvent, ClickBorderToCloseDialog);
+ }
}
}
@@ -87,7 +97,11 @@ public class OverlayDialogHost : Canvas
foreach (var modalDialog in _modalDialogs)
{
- ResetDialogPosition(modalDialog, oldSize, newSize);
+ if (modalDialog is DialogControl c)
+ {
+ ResetDialogPosition(c, oldSize, newSize);
+ }
+
}
}
@@ -168,10 +182,7 @@ public class OverlayDialogHost : Canvas
ResetZIndices();
}
- internal void AddDrawer(DrawerControlBase control)
- {
-
- }
+
private void OnDialogControlClosing(object sender, object? e)
{
@@ -225,6 +236,84 @@ public class OverlayDialogHost : Canvas
control.AddHandler(DialogControl.ClosedEvent, OnDialogControlClosing);
control.AddHandler(DialogControl.LayerChangedEvent, OnDialogLayerChanged);
}
+
+ internal async void AddDrawer(DrawerControlBase control)
+ {
+ var mask = CreateOverlayMask(false);
+ mask.Opacity = 0;
+ _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));
+ control.Height = this.Bounds.Height;
+ control.AddHandler(DrawerControlBase.ClosedEvent, OnDrawerControlClosing);
+ // SetLeft(control, this.Bounds.Width - control.Bounds.Width);
+ var animation = CreateAnimation(control.Bounds.Width);
+ var animation2 = CreateOpacityAnimation();
+ await Task.WhenAll(animation.RunAsync(control), animation2.RunAsync(mask));
+ }
+
+ private Animation CreateAnimation(double width)
+ {
+ var animation = new Animation();
+ animation.Easing = new CubicEaseOut();
+ animation.FillMode = FillMode.Forward;
+ var keyFrame1 = new KeyFrame(){ Cue = new Cue(0.0) };
+ keyFrame1.Setters.Add(new Setter() { Property = Canvas.LeftProperty, Value = Bounds.Width });
+ var keyFrame2 = new KeyFrame() { Cue = new Cue(1.0) };
+ keyFrame2.Setters.Add(new Setter() { Property = Canvas.LeftProperty, Value = Bounds.Width - width });
+ animation.Children.Add(keyFrame1);
+ animation.Children.Add(keyFrame2);
+ animation.Duration = TimeSpan.FromSeconds(0.3);
+ return animation;
+ }
+
+ private Animation CreateOpacityAnimation()
+ {
+ var animation = new Animation();
+ animation.FillMode = FillMode.Forward;
+ var keyFrame1 = new KeyFrame(){ Cue = new Cue(0.0) };
+ keyFrame1.Setters.Add(new Setter(){ Property = OpacityProperty, Value = 0.0});
+ var keyFrame2 = new KeyFrame() { Cue = new Cue(1.0) };
+ keyFrame2.Setters.Add(new Setter() { Property = OpacityProperty, Value = 1.0 });
+ animation.Children.Add(keyFrame1);
+ animation.Children.Add(keyFrame2);
+ animation.Duration = TimeSpan.FromSeconds(0.3);
+ return animation;
+ }
+
+ private void OnDrawerControlClosing(object sender, ResultEventArgs e)
+ {
+ if (sender is DrawerControlBase control)
+ {
+ Children.Remove(control);
+ control.RemoveHandler(DialogControl.ClosedEvent, OnDialogControlClosing);
+ control.RemoveHandler(DialogControl.LayerChangedEvent, OnDialogLayerChanged);
+ 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();
+ }
+ }
// Handle dialog layer change event
private void OnDialogLayerChanged(object sender, DialogLayerChangeEventArgs e)
diff --git a/src/Ursa/Controls/Drawer/CustomDrawerControl.cs b/src/Ursa/Controls/Drawer/CustomDrawerControl.cs
new file mode 100644
index 0000000..0e08d37
--- /dev/null
+++ b/src/Ursa/Controls/Drawer/CustomDrawerControl.cs
@@ -0,0 +1,6 @@
+namespace Ursa.Controls;
+
+public class CustomDrawerControl: DrawerControlBase
+{
+
+}
\ No newline at end of file
diff --git a/src/Ursa/Controls/Drawer/DefaultDrawerControl.cs b/src/Ursa/Controls/Drawer/DefaultDrawerControl.cs
index c3a937f..4ca0811 100644
--- a/src/Ursa/Controls/Drawer/DefaultDrawerControl.cs
+++ b/src/Ursa/Controls/Drawer/DefaultDrawerControl.cs
@@ -1,6 +1,39 @@
-namespace Ursa.Controls;
+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 DefaultDrawerControl: DrawerControlBase
{
+ 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;
+
+ protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+ {
+ base.OnApplyTemplate(e);
+ EventHelper.UnregisterClickEvent(OnDefaultButtonClick, _yesButton, _noButton, _okButton, _cancelButton);
+ _yesButton = e.NameScope.Find