feat: basic position.
This commit is contained in:
@@ -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">
|
||||
<Canvas HorizontalAlignment="Left" Width="500">
|
||||
<StackPanel HorizontalAlignment="Left" >
|
||||
<u:EnumSelector EnumType="{x:Type common:Position}" Value="{Binding SelectedPosition}"></u:EnumSelector>
|
||||
<Button Content="Call Drawer" HorizontalAlignment="Stretch" Command="{Binding OpenDrawerCommand}"></Button>
|
||||
</Canvas>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
|
||||
@@ -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<Calendar, string, bool>("Hello World");
|
||||
await Drawer.ShowCustom<Calendar, string, bool>("Hello World", new CustomDrawerOptions() { Position = SelectedPosition, MinWidth = 400 });
|
||||
}
|
||||
}
|
||||
@@ -3,16 +3,17 @@
|
||||
xmlns:u="https://irihi.tech/ursa">
|
||||
<ControlTheme TargetType="u:CustomDrawerControl" x:Key="{x:Type u:CustomDrawerControl}">
|
||||
<Setter Property="VerticalAlignment" Value="Stretch"></Setter>
|
||||
<Setter Property="MinWidth" Value="400"></Setter>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"></Setter>
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate TargetType="u:CustomDrawerControl">
|
||||
<Border Margin="8 0 0 0"
|
||||
<Border Margin="8 -1 -1 -1"
|
||||
Padding="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Classes="Shadow"
|
||||
ClipToBounds="False"
|
||||
CornerRadius="12 0 0 12"
|
||||
BorderThickness="1 0 0 0"
|
||||
IsHitTestVisible="True"
|
||||
Theme="{DynamicResource CardBorder}">
|
||||
<Border ClipToBounds="True" CornerRadius="12 0 0 12">
|
||||
|
||||
@@ -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<DialogButton> ButtonsProperty = AvaloniaProperty.Register<DefaultDrawerControl, DialogButton>(
|
||||
nameof(Buttons), DialogButton.OKCancel);
|
||||
|
||||
public DialogButton Buttons
|
||||
{
|
||||
get => GetValue(ButtonsProperty);
|
||||
set => SetValue(ButtonsProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<DialogMode> ModeProperty = AvaloniaProperty.Register<DefaultDrawerControl, DialogMode>(
|
||||
nameof(Mode), DialogMode.None);
|
||||
|
||||
public DialogMode Mode
|
||||
{
|
||||
get => GetValue(ModeProperty);
|
||||
set => SetValue(ModeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<string?> TitleProperty = AvaloniaProperty.Register<DefaultDrawerControl, string?>(
|
||||
nameof(Title));
|
||||
|
||||
public string? Title
|
||||
{
|
||||
get => GetValue(TitleProperty);
|
||||
set => SetValue(TitleProperty, value);
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
|
||||
@@ -1,11 +1,27 @@
|
||||
using Avalonia.Controls;
|
||||
using Ursa.Common;
|
||||
using Ursa.Controls.Options;
|
||||
|
||||
namespace Ursa.Controls;
|
||||
|
||||
public static class Drawer
|
||||
{
|
||||
public static Task<TResult?> Show<TView, TViewModel, TResult>(TViewModel vm, Position position = Position.Right)
|
||||
public static Task<TResult?> Show<TView, TViewModel, TResult>(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<TResult>();
|
||||
}
|
||||
|
||||
public static Task<TResult?> ShowCustom<TView, TViewModel, TResult>(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<TResult>();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Position> PositionProperty = AvaloniaProperty.Register<DrawerControlBase, Position>(
|
||||
nameof(Position));
|
||||
|
||||
public static readonly StyledProperty<Position> PositionProperty =
|
||||
AvaloniaProperty.Register<DrawerControlBase, Position>(
|
||||
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<DrawerControlBase, object?>((o, e) => o.OnDataContextChange(e));
|
||||
|
||||
17
src/Ursa/Controls/Drawer/Options/CustomDrawerOptions.cs
Normal file
17
src/Ursa/Controls/Drawer/Options/CustomDrawerOptions.cs
Normal file
@@ -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;
|
||||
}
|
||||
19
src/Ursa/Controls/Drawer/Options/DefaultDrawerOptions.cs
Normal file
19
src/Ursa/Controls/Drawer/Options/DefaultDrawerOptions.cs
Normal file
@@ -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; }
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user