WIP: animation.
This commit is contained in:
@@ -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";
|
||||
|
||||
14
demo/Ursa.Demo/Pages/DrawerDemo.axaml
Normal file
14
demo/Ursa.Demo/Pages/DrawerDemo.axaml
Normal file
@@ -0,0 +1,14 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800"
|
||||
xmlns:vm="clr-namespace:Ursa.Demo.ViewModels;assembly=Ursa.Demo"
|
||||
x:DataType="vm:DrawerDemoViewModel"
|
||||
x:CompileBindings="True"
|
||||
d:DesignHeight="450"
|
||||
x:Class="Ursa.Demo.Pages.DrawerDemo">
|
||||
<StackPanel HorizontalAlignment="Left">
|
||||
<Button Content="Call Drawer" Command="{Binding OpenDrawerCommand}"></Button>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
13
demo/Ursa.Demo/Pages/DrawerDemo.axaml.cs
Normal file
13
demo/Ursa.Demo/Pages/DrawerDemo.axaml.cs
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
23
demo/Ursa.Demo/ViewModels/DrawerDemoViewModel.cs
Normal file
23
demo/Ursa.Demo/ViewModels/DrawerDemoViewModel.cs
Normal file
@@ -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<Calendar, string, bool>("Hello World");
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
|
||||
@@ -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 },
|
||||
|
||||
44
src/Ursa.Themes.Semi/Controls/Drawer.axaml
Normal file
44
src/Ursa.Themes.Semi/Controls/Drawer.axaml
Normal file
@@ -0,0 +1,44 @@
|
||||
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
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="Template">
|
||||
<ControlTemplate TargetType="u:CustomDrawerControl">
|
||||
<Border Margin="8 0 0 0"
|
||||
Padding="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Classes="Shadow"
|
||||
ClipToBounds="False"
|
||||
CornerRadius="12 0 0 12"
|
||||
IsHitTestVisible="True"
|
||||
Theme="{DynamicResource CardBorder}">
|
||||
<Border ClipToBounds="True" CornerRadius="12 0 0 12">
|
||||
<Grid RowDefinitions="Auto, *">
|
||||
<ContentPresenter
|
||||
Name="PART_ContentPresenter"
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Content="{TemplateBinding Content}" />
|
||||
<Grid Grid.Row="0" ColumnDefinitions="*, Auto">
|
||||
<Panel
|
||||
Name="{x:Static u:DialogControl.PART_TitleArea}"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Background="Transparent" />
|
||||
<Button
|
||||
Name="{x:Static u:MessageBoxWindow.PART_CloseButton}"
|
||||
Grid.Column="1"
|
||||
Margin="0,24,24,0"
|
||||
DockPanel.Dock="Right"
|
||||
Theme="{DynamicResource CloseButton}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</ControlTheme>
|
||||
</ResourceDictionary>
|
||||
@@ -7,6 +7,7 @@
|
||||
<ResourceInclude Source="Dialog.axaml" />
|
||||
<ResourceInclude Source="DialogShared.axaml" />
|
||||
<ResourceInclude Source="Divider.axaml" />
|
||||
<ResourceInclude Source="Drawer.axaml" />
|
||||
<ResourceInclude Source="DualBadge.axaml" />
|
||||
<ResourceInclude Source="EnumSelector.axaml" />
|
||||
<ResourceInclude Source="IconButton.axaml" />
|
||||
|
||||
@@ -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))]
|
||||
|
||||
@@ -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<DialogControl> _dialogs = new();
|
||||
private readonly List<DialogControl> _modalDialogs = new();
|
||||
private readonly List<Control> _modalDialogs = new();
|
||||
private readonly List<Border> _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)
|
||||
|
||||
6
src/Ursa/Controls/Drawer/CustomDrawerControl.cs
Normal file
6
src/Ursa/Controls/Drawer/CustomDrawerControl.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Ursa.Controls;
|
||||
|
||||
public class CustomDrawerControl: DrawerControlBase
|
||||
{
|
||||
|
||||
}
|
||||
@@ -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<Button>(PART_YesButton);
|
||||
_noButton = e.NameScope.Find<Button>(PART_NoButton);
|
||||
_okButton = e.NameScope.Find<Button>(PART_OKButton);
|
||||
_cancelButton = e.NameScope.Find<Button>(PART_CancelButton);
|
||||
EventHelper.RegisterClickEvent(OnDefaultButtonClick, _yesButton, _noButton, _okButton, _cancelButton);
|
||||
}
|
||||
|
||||
private void OnDefaultButtonClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,21 @@
|
||||
namespace Ursa.Controls;
|
||||
using Avalonia.Controls;
|
||||
using Ursa.Common;
|
||||
|
||||
namespace Ursa.Controls;
|
||||
|
||||
public static class Drawer
|
||||
{
|
||||
public static Task<TResult?> ShowDialogAsync<TView, TViewModel, TResult>(TViewModel viewModel)
|
||||
public static Task<TResult?> Show<TView, TViewModel, TResult>(TViewModel vm, Position position = Position.Right)
|
||||
where TView: Control, new()
|
||||
{
|
||||
var host = OverlayDialogManager.GetHost(null);
|
||||
if (host is null) return Task.FromResult(default(TResult));
|
||||
var dialog = new DefaultDrawerControl();
|
||||
var dialog = new CustomDrawerControl()
|
||||
{
|
||||
Content = new TView(),
|
||||
DataContext = vm,
|
||||
Position = position,
|
||||
};
|
||||
host.AddDrawer(dialog);
|
||||
return dialog.ShowAsync<TResult>();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Metadata;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Threading;
|
||||
using Ursa.Common;
|
||||
@@ -7,10 +9,15 @@ using Ursa.EventArgs;
|
||||
|
||||
namespace Ursa.Controls;
|
||||
|
||||
[TemplatePart(PART_CloseButton, typeof(Button))]
|
||||
public abstract class DrawerControlBase: ContentControl
|
||||
{
|
||||
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));
|
||||
@@ -43,7 +50,7 @@ public abstract class DrawerControlBase: ContentControl
|
||||
public static readonly RoutedEvent<ResultEventArgs> ClosedEvent = RoutedEvent.Register<DrawerControlBase, ResultEventArgs>(
|
||||
nameof(Closed), RoutingStrategies.Bubble);
|
||||
|
||||
public event EventHandler<ResultEventArgs> Closed
|
||||
public event EventHandler<ResultEventArgs>? Closed
|
||||
{
|
||||
add => AddHandler(ClosedEvent, value);
|
||||
remove => RemoveHandler(ClosedEvent, value);
|
||||
@@ -54,6 +61,14 @@ public abstract class DrawerControlBase: ContentControl
|
||||
DataContextProperty.Changed.AddClassHandler<DrawerControlBase, object?>((o, e) => o.OnDataContextChange(e));
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
EventHelper.UnregisterClickEvent(OnCloseButtonClick, _closeButton);
|
||||
_closeButton = e.NameScope.Find<Button>(PART_CloseButton);
|
||||
EventHelper.RegisterClickEvent(OnCloseButtonClick, _closeButton);
|
||||
}
|
||||
|
||||
private void OnDataContextChange(AvaloniaPropertyChangedEventArgs<object?> args)
|
||||
{
|
||||
if(args.OldValue.Value is IDialogContext oldContext)
|
||||
@@ -71,6 +86,8 @@ public abstract class DrawerControlBase: ContentControl
|
||||
RaiseEvent(new ResultEventArgs(ClosedEvent, e));
|
||||
}
|
||||
|
||||
private void OnCloseButtonClick(object sender, RoutedEventArgs e) => CloseDrawer();
|
||||
|
||||
public Task<T?> ShowAsync<T>(CancellationToken? token = default)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<T?>();
|
||||
@@ -97,6 +114,13 @@ public abstract class DrawerControlBase: ContentControl
|
||||
|
||||
internal virtual void CloseDrawer()
|
||||
{
|
||||
RaiseEvent(new ResultEventArgs(ClosedEvent, null));
|
||||
if (DataContext is IDialogContext context)
|
||||
{
|
||||
context.Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
RaiseEvent(new ResultEventArgs(ClosedEvent, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
8
src/Ursa/Controls/Layout/DefaultDialogLayout.cs
Normal file
8
src/Ursa/Controls/Layout/DefaultDialogLayout.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Avalonia.Controls.Primitives;
|
||||
|
||||
namespace Ursa.Controls.Layout;
|
||||
|
||||
public class DefaultDialogLayout: TemplatedControl
|
||||
{
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user