feat: basic position.

This commit is contained in:
rabbitism
2024-02-05 19:40:11 +08:00
parent 8e085bc264
commit fd85efd895
13 changed files with 222 additions and 46 deletions

View File

@@ -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>

View File

@@ -3,14 +3,19 @@ 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()
{
OpenDrawerCommand = new AsyncRelayCommand(OpenDrawer);
@@ -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 });
}
}

View File

@@ -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">

View File

@@ -1,4 +1,5 @@
using Avalonia.Controls;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
@@ -22,6 +23,33 @@ public class DefaultDrawerControl: DrawerControlBase
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)
{
base.OnApplyTemplate(e);

View File

@@ -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;
}
}
}

View File

@@ -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));

View 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;
}

View 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; }
}

View File

@@ -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;

View File

@@ -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();
}
}