diff --git a/demo/Ursa.Demo/Pages/DrawerDemo.axaml b/demo/Ursa.Demo/Pages/DrawerDemo.axaml index 0c968a2..4dfb13d 100644 --- a/demo/Ursa.Demo/Pages/DrawerDemo.axaml +++ b/demo/Ursa.Demo/Pages/DrawerDemo.axaml @@ -5,11 +5,13 @@ mc:Ignorable="d" d:DesignWidth="800" xmlns:vm="clr-namespace:Ursa.Demo.ViewModels;assembly=Ursa.Demo" xmlns:u="https://irihi.tech/ursa" + xmlns:common="clr-namespace:Ursa.Common;assembly=Ursa" x:DataType="vm:DrawerDemoViewModel" x:CompileBindings="True" d:DesignHeight="450" x:Class="Ursa.Demo.Pages.DrawerDemo"> - + + - + diff --git a/demo/Ursa.Demo/ViewModels/DrawerDemoViewModel.cs b/demo/Ursa.Demo/ViewModels/DrawerDemoViewModel.cs index ff9fed2..7786715 100644 --- a/demo/Ursa.Demo/ViewModels/DrawerDemoViewModel.cs +++ b/demo/Ursa.Demo/ViewModels/DrawerDemoViewModel.cs @@ -3,13 +3,18 @@ using System.Windows.Input; using Avalonia.Controls; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using Ursa.Common; using Ursa.Controls; +using Ursa.Controls.Options; namespace Ursa.Demo.ViewModels; -public class DrawerDemoViewModel: ObservableObject +public partial class DrawerDemoViewModel: ObservableObject { public ICommand OpenDrawerCommand { get; set; } + + [ObservableProperty] private Position _selectedPosition; + public DrawerDemoViewModel() { @@ -18,6 +23,6 @@ public class DrawerDemoViewModel: ObservableObject private async Task OpenDrawer() { - await Drawer.Show("Hello World"); + await Drawer.ShowCustom("Hello World", new CustomDrawerOptions() { Position = SelectedPosition, MinWidth = 400 }); } } \ No newline at end of file diff --git a/src/Ursa.Themes.Semi/Controls/Drawer.axaml b/src/Ursa.Themes.Semi/Controls/Drawer.axaml index aac9838..ad17115 100644 --- a/src/Ursa.Themes.Semi/Controls/Drawer.axaml +++ b/src/Ursa.Themes.Semi/Controls/Drawer.axaml @@ -3,16 +3,17 @@ xmlns:u="https://irihi.tech/ursa"> - + - diff --git a/src/Ursa/Controls/Drawer/DefaultDrawerControl.cs b/src/Ursa/Controls/Drawer/DefaultDrawerControl.cs index 4ca0811..f457b29 100644 --- a/src/Ursa/Controls/Drawer/DefaultDrawerControl.cs +++ b/src/Ursa/Controls/Drawer/DefaultDrawerControl.cs @@ -1,4 +1,5 @@ -using Avalonia.Controls; +using Avalonia; +using Avalonia.Controls; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; using Avalonia.Interactivity; @@ -21,6 +22,33 @@ public class DefaultDrawerControl: DrawerControlBase private Button? _noButton; private Button? _okButton; private Button? _cancelButton; + + public static readonly StyledProperty ButtonsProperty = AvaloniaProperty.Register( + nameof(Buttons), DialogButton.OKCancel); + + public DialogButton Buttons + { + get => GetValue(ButtonsProperty); + set => SetValue(ButtonsProperty, value); + } + + public static readonly StyledProperty ModeProperty = AvaloniaProperty.Register( + nameof(Mode), DialogMode.None); + + public DialogMode Mode + { + get => GetValue(ModeProperty); + set => SetValue(ModeProperty, value); + } + + public static readonly StyledProperty TitleProperty = AvaloniaProperty.Register( + nameof(Title)); + + public string? Title + { + get => GetValue(TitleProperty); + set => SetValue(TitleProperty, value); + } protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { diff --git a/src/Ursa/Controls/Drawer/Drawer.cs b/src/Ursa/Controls/Drawer/Drawer.cs index dbf9a8d..d8e2b4e 100644 --- a/src/Ursa/Controls/Drawer/Drawer.cs +++ b/src/Ursa/Controls/Drawer/Drawer.cs @@ -1,11 +1,27 @@ using Avalonia.Controls; using Ursa.Common; +using Ursa.Controls.Options; namespace Ursa.Controls; public static class Drawer { - public static Task Show(TViewModel vm, Position position = Position.Right) + public static Task Show(TViewModel vm, DefaultDrawerOptions? options = null) + where TView: Control, new() + { + var host = OverlayDialogManager.GetHost(null); + if (host is null) return Task.FromResult(default(TResult)); + var drawer = new DefaultDrawerControl() + { + Content = new TView(), + DataContext = vm, + }; + ConfigureDefaultDrawer(drawer, options); + host.AddDrawer(drawer); + return drawer.ShowAsync(); + } + + public static Task ShowCustom(TViewModel vm, CustomDrawerOptions? options = null) where TView: Control, new() { var host = OverlayDialogManager.GetHost(null); @@ -14,9 +30,51 @@ public static class Drawer { Content = new TView(), DataContext = vm, - Position = position, }; + ConfigureCustomDrawer(dialog, options); host.AddDrawer(dialog); return dialog.ShowAsync(); } + + private static void ConfigureCustomDrawer(CustomDrawerControl drawer, CustomDrawerOptions? options) + { + options ??= CustomDrawerOptions.Default; + drawer.Position = options.Position; + drawer.CanClickOnMaskToClose = options.CanClickOnMaskToClose; + drawer.IsCloseButtonVisible = options.IsCloseButtonVisible; + drawer.ShowMask = options.ShowMask; + drawer.CanLightDismiss = options.CanLightDismiss; + if (options.Position == Position.Left || options.Position == Position.Right) + { + drawer.MinWidth = options.MinWidth ?? 0.0; + drawer.MaxWidth = options.MaxWidth ?? double.PositiveInfinity; + } + if (options.Position is Position.Top or Position.Bottom) + { + drawer.MinHeight = options.MinHeight ?? 0.0; + drawer.MaxHeight = options.MaxHeight ?? double.PositiveInfinity; + } + } + + private static void ConfigureDefaultDrawer(DefaultDrawerControl drawer, DefaultDrawerOptions? options) + { + options ??= DefaultDrawerOptions.Default; + drawer.Position = options.Position; + drawer.CanClickOnMaskToClose = options.CanClickOnMaskToClose; + drawer.IsCloseButtonVisible = options.IsCloseButtonVisible; + drawer.Buttons = options.Buttons; + drawer.Title = options.Title; + drawer.ShowMask = options.ShowMask; + drawer.CanLightDismiss = options.CanLightDismiss; + if (options.Position == Position.Left || options.Position == Position.Right) + { + drawer.MinWidth = options.MinWidth ?? 0.0; + drawer.MaxWidth = options.MaxWidth ?? double.PositiveInfinity; + } + if (options.Position is Position.Top or Position.Bottom) + { + drawer.MinHeight = options.MinHeight ?? 0.0; + drawer.MaxHeight = options.MaxHeight ?? double.PositiveInfinity; + } + } } \ No newline at end of file diff --git a/src/Ursa/Controls/Drawer/DrawerControlBase.cs b/src/Ursa/Controls/Drawer/DrawerControlBase.cs index 07e168c..294e8ce 100644 --- a/src/Ursa/Controls/Drawer/DrawerControlBase.cs +++ b/src/Ursa/Controls/Drawer/DrawerControlBase.cs @@ -16,12 +16,12 @@ public abstract class DrawerControlBase: OverlayFeedbackElement public const string PART_CloseButton = "PART_CloseButton"; internal bool CanClickOnMaskToClose { get; set; } - internal bool ShowCloseButton { get; set; } protected internal Button? _closeButton; - - public static readonly StyledProperty PositionProperty = AvaloniaProperty.Register( - nameof(Position)); + + public static readonly StyledProperty PositionProperty = + AvaloniaProperty.Register( + nameof(Position), defaultValue: Position.Right); public Position Position { @@ -48,6 +48,9 @@ public const string PART_CloseButton = "PART_CloseButton"; set => SetValue(IsCloseButtonVisibleProperty, value); } + protected internal bool ShowMask { get; set; } + protected internal bool CanLightDismiss { get; set; } + static DrawerControlBase() { DataContextProperty.Changed.AddClassHandler((o, e) => o.OnDataContextChange(e)); diff --git a/src/Ursa/Controls/Drawer/Options/CustomDrawerOptions.cs b/src/Ursa/Controls/Drawer/Options/CustomDrawerOptions.cs new file mode 100644 index 0000000..9969b18 --- /dev/null +++ b/src/Ursa/Controls/Drawer/Options/CustomDrawerOptions.cs @@ -0,0 +1,17 @@ +using Ursa.Common; + +namespace Ursa.Controls.Options; + +public class CustomDrawerOptions +{ + internal static CustomDrawerOptions Default => new (); + public Position Position { get; set; } = Position.Right; + public bool CanClickOnMaskToClose { get; set; } = true; + public bool CanLightDismiss { get; set; } = false; + public bool ShowMask { get; set; } = true; + public bool IsCloseButtonVisible { get; set; } = true; + public double? MinWidth { get; set; } = null; + public double? MinHeight { get; set; } = null; + public double? MaxWidth { get; set; } = null; + public double? MaxHeight { get; set; } = null; +} \ No newline at end of file diff --git a/src/Ursa/Controls/Drawer/Options/DefaultDrawerOptions.cs b/src/Ursa/Controls/Drawer/Options/DefaultDrawerOptions.cs new file mode 100644 index 0000000..0504240 --- /dev/null +++ b/src/Ursa/Controls/Drawer/Options/DefaultDrawerOptions.cs @@ -0,0 +1,19 @@ +using Ursa.Common; + +namespace Ursa.Controls.Options; + +public class DefaultDrawerOptions +{ + internal static DefaultDrawerOptions Default => new (); + public Position Position { get; set; } = Position.Right; + public bool CanClickOnMaskToClose { get; set; } = true; + public bool CanLightDismiss { get; set; } = false; + public bool ShowMask { get; set; } = true; + public bool IsCloseButtonVisible { get; set; } = true; + public double? MinWidth { get; set; } = null; + public double? MinHeight { get; set; } = null; + public double? MaxWidth { get; set; } = null; + public double? MaxHeight { get; set; } = null; + public DialogButton Buttons { get; set; } = DialogButton.OKCancel; + public string? Title { get; set; } +} \ No newline at end of file diff --git a/src/Ursa/Controls/Dialog/Options/DialogButton.cs b/src/Ursa/Controls/OverlayShared/DialogButton.cs similarity index 100% rename from src/Ursa/Controls/Dialog/Options/DialogButton.cs rename to src/Ursa/Controls/OverlayShared/DialogButton.cs diff --git a/src/Ursa/Controls/Dialog/Options/DialogMode.cs b/src/Ursa/Controls/OverlayShared/DialogMode.cs similarity index 100% rename from src/Ursa/Controls/Dialog/Options/DialogMode.cs rename to src/Ursa/Controls/OverlayShared/DialogMode.cs diff --git a/src/Ursa/Controls/Dialog/Options/DialogResult.cs b/src/Ursa/Controls/OverlayShared/DialogResult.cs similarity index 100% rename from src/Ursa/Controls/Dialog/Options/DialogResult.cs rename to src/Ursa/Controls/OverlayShared/DialogResult.cs diff --git a/src/Ursa/Controls/OverlayShared/OverlayDialogHost.Dialog.cs b/src/Ursa/Controls/OverlayShared/OverlayDialogHost.Dialog.cs index 5c810d6..f1aa68f 100644 --- a/src/Ursa/Controls/OverlayShared/OverlayDialogHost.Dialog.cs +++ b/src/Ursa/Controls/OverlayShared/OverlayDialogHost.Dialog.cs @@ -21,8 +21,7 @@ public partial class OverlayDialogHost public Thickness SnapThickness { get; set; } = new Thickness(0); - - private void ResetDialogPosition(DialogControlBase control, Size newSize) + private static void ResetDialogPosition(DialogControlBase control, Size newSize) { var width = newSize.Width - control.Bounds.Width; var height = newSize.Height - control.Bounds.Height; @@ -77,7 +76,7 @@ public partial class OverlayDialogHost { if (e.Source is DialogControlBase item) { - AnchorDialog(item); + AnchorAndUpdatePositionInfo(item); } } @@ -183,10 +182,10 @@ public partial class OverlayDialogHost double top = GetTopPosition(control); SetLeft(control, left); SetTop(control, top); - AnchorDialog(control); + AnchorAndUpdatePositionInfo(control); } - private void AnchorDialog(DialogControlBase control) + private void AnchorAndUpdatePositionInfo(DialogControlBase control) { control.ActualHorizontalAnchor = HorizontalPosition.Center; control.ActualVerticalAnchor = VerticalPosition.Center; diff --git a/src/Ursa/Controls/OverlayShared/OverlayDialogHost.Drawer.cs b/src/Ursa/Controls/OverlayShared/OverlayDialogHost.Drawer.cs index edd93be..f0252b4 100644 --- a/src/Ursa/Controls/OverlayShared/OverlayDialogHost.Drawer.cs +++ b/src/Ursa/Controls/OverlayShared/OverlayDialogHost.Drawer.cs @@ -3,7 +3,9 @@ using Avalonia.Animation; using Avalonia.Animation.Easings; using Avalonia.Controls; using Avalonia.Styling; +using Ursa.Common; using Ursa.Controls.OverlayShared; +using Ursa.Controls.Shapes; using Ursa.EventArgs; namespace Ursa.Controls; @@ -12,64 +14,106 @@ public partial class OverlayDialogHost { internal async void AddDrawer(DrawerControlBase control) { - var mask = CreateOverlayMask(true, false); - mask.Opacity = 0; + PureRectangle? mask = null; + if (control.ShowMask == false && control.CanLightDismiss) + { + mask = CreateOverlayMask(false, true); + } + else if (control.ShowMask) + { + mask = CreateOverlayMask(control.ShowMask, control.CanClickOnMaskToClose); + } _layers.Add(new DialogPair(mask, control)); ResetZIndices(); - this.Children.Add(mask); + if(mask is not null)this.Children.Add(mask); this.Children.Add(control); control.Measure(this.Bounds.Size); - control.Arrange(new Rect(control.DesiredSize).WithHeight(this.Bounds.Height)); - // control.Height = this.Bounds.Height; + control.Arrange(new Rect(control.DesiredSize)); + SetDrawerPosition(control); control.AddHandler(OverlayFeedbackElement.ClosedEvent, OnDrawerControlClosing); - var animation = CreateAnimation(control.Bounds.Width); - var animation2 = CreateOpacityAnimation(); - await Task.WhenAll(animation.RunAsync(control), animation2.RunAsync(mask)); + var animation = CreateAnimation(control.Bounds.Size, control.Position, true); + await Task.WhenAll(animation.RunAsync(control), _maskAppearAnimation.RunAsync(mask)); } - private Animation CreateAnimation(double width) + private void SetDrawerPosition(DrawerControlBase control) { + if(control.Position is Position.Left or Position.Right) + { + control.Height = this.Bounds.Height; + } + if(control.Position is Position.Top or Position.Bottom) + { + control.Width = this.Bounds.Width; + } + } + + private Animation CreateAnimation(Size elementBounds, Position position, bool appear = true) + { + // left or top. + double source = 0; + double target = 0; + if (position == Position.Left) + { + source = appear ? -elementBounds.Width : 0; + target = appear ? 0 : -elementBounds.Width; + } + + if (position == Position.Right) + { + source = appear ? Bounds.Width : Bounds.Width - elementBounds.Width; + target = appear ? Bounds.Width - elementBounds.Width : Bounds.Width; + } + + if (position == Position.Top) + { + source = appear ? -elementBounds.Height : 0; + target = appear ? 0 : -elementBounds.Height; + } + + if (position == Position.Bottom) + { + source = appear ? Bounds.Height : Bounds.Height - elementBounds.Height; + target = appear ? Bounds.Height - elementBounds.Height : Bounds.Height; + } + + var targetProperty = position==Position.Left || position==Position.Right ? Canvas.LeftProperty : Canvas.TopProperty; 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 }); + keyFrame1.Setters.Add(new Setter() + { Property = targetProperty, Value = source }); var keyFrame2 = new KeyFrame() { Cue = new Cue(1.0) }; - keyFrame2.Setters.Add(new Setter() { Property = Canvas.LeftProperty, Value = Bounds.Width - width }); + keyFrame2.Setters.Add(new Setter() + { Property = targetProperty, Value = target }); 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) + private async void OnDrawerControlClosing(object sender, ResultEventArgs e) { if (sender is DrawerControlBase control) { var layer = _layers.FirstOrDefault(a => a.Element == control); if(layer is null) return; _layers.Remove(layer); + control.RemoveHandler(OverlayFeedbackElement.ClosedEvent, OnDialogControlClosing); + control.RemoveHandler(DialogControlBase.LayerChangedEvent, OnDialogLayerChanged); if (layer.Mask is not null) { layer.Mask.RemoveHandler(PointerPressedEvent, ClickMaskToCloseDialog); + var disappearAnimation = CreateAnimation(control.Bounds.Size, control.Position, false); + await Task.WhenAll(disappearAnimation.RunAsync(control), _maskDisappearAnimation.RunAsync(layer.Mask)); + Children.Remove(layer.Mask); + } + else + { + var disappearAnimation = CreateAnimation(control.Bounds.Size, control.Position, false); + await disappearAnimation.RunAsync(control); } Children.Remove(control); - control.RemoveHandler(OverlayFeedbackElement.ClosedEvent, OnDialogControlClosing); - control.RemoveHandler(DialogControlBase.LayerChangedEvent, OnDialogLayerChanged); ResetZIndices(); } }