Browse Source

Merge branch 'master' into xamlil

pull/2322/head
danwalmsley 7 years ago
committed by GitHub
parent
commit
dff34db5ab
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      nukebuild/Numerge
  2. 1
      samples/ControlCatalog/MainView.xaml
  3. 11
      samples/ControlCatalog/MainWindow.xaml
  4. 15
      samples/ControlCatalog/MainWindow.xaml.cs
  5. 10
      samples/ControlCatalog/Pages/NotificationsPage.xaml
  6. 18
      samples/ControlCatalog/Pages/NotificationsPage.xaml.cs
  7. 44
      samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
  8. 30
      samples/ControlCatalog/ViewModels/NotificationViewModel.cs
  9. 19
      samples/ControlCatalog/Views/CustomNotificationView.xaml
  10. 18
      samples/ControlCatalog/Views/CustomNotificationView.xaml.cs
  11. 50
      src/Avalonia.Controls/LayoutTransformControl.cs
  12. 23
      src/Avalonia.Controls/Notifications/IManagedNotificationManager.cs
  13. 44
      src/Avalonia.Controls/Notifications/INotification.cs
  14. 18
      src/Avalonia.Controls/Notifications/INotificationManager.cs
  15. 60
      src/Avalonia.Controls/Notifications/Notification.cs
  16. 160
      src/Avalonia.Controls/Notifications/NotificationCard.cs
  17. 17
      src/Avalonia.Controls/Notifications/NotificationPosition.cs
  18. 16
      src/Avalonia.Controls/Notifications/NotificationType.cs
  19. 64
      src/Avalonia.Controls/Notifications/ReversibleStackPanel.cs
  20. 163
      src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
  21. 9
      src/Avalonia.Controls/Primitives/AdornerDecorator.cs
  22. 6
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  23. 3
      src/Avalonia.Controls/Properties/AssemblyInfo.cs
  24. 5
      src/Avalonia.Controls/StackPanel.cs
  25. 3
      src/Avalonia.Controls/TopLevel.cs
  26. 6
      src/Avalonia.Themes.Default/Accents/BaseDark.xaml
  27. 6
      src/Avalonia.Themes.Default/Accents/BaseLight.xaml
  28. 2
      src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj
  29. 2
      src/Avalonia.Themes.Default/DefaultTheme.xaml
  30. 88
      src/Avalonia.Themes.Default/NotificationCard.xaml
  31. 45
      src/Avalonia.Themes.Default/WindowNotificationManager.xaml
  32. 2
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github

2
nukebuild/Numerge

@ -1 +1 @@
Subproject commit aef10ae67dc55c95f49b52a505a0be33bfa297a5
Subproject commit 4464343aef5c8ab7a42fcb20a483a6058199f8b8

1
samples/ControlCatalog/MainView.xaml

@ -29,6 +29,7 @@
<TabItem Header="LayoutTransformControl"><pages:LayoutTransformControlPage/></TabItem>
<TabItem Header="ListBox"><pages:ListBoxPage/></TabItem>
<TabItem Header="Menu"><pages:MenuPage/></TabItem>
<TabItem Header="Notifications"><pages:NotificationsPage/></TabItem>
<TabItem Header="NumericUpDown"><pages:NumericUpDownPage/></TabItem>
<TabItem Header="ProgressBar"><pages:ProgressBarPage/></TabItem>
<TabItem Header="RadioButton"><pages:RadioButtonPage/></TabItem>

11
samples/ControlCatalog/MainWindow.xaml

@ -4,6 +4,15 @@
Icon="/Assets/test_icon.ico"
xmlns:local="clr-namespace:ControlCatalog"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:ControlCatalog.ViewModels"
xmlns:v="clr-namespace:ControlCatalog.Views"
x:Class="ControlCatalog.MainWindow">
<local:MainView/>
<Window.DataTemplates>
<DataTemplate DataType="vm:NotificationViewModel">
<v:CustomNotificationView />
</DataTemplate>
</Window.DataTemplates>
<Panel>
<local:MainView/>
</Panel>
</Window>

15
samples/ControlCatalog/MainWindow.xaml.cs

@ -1,18 +1,33 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Notifications;
using Avalonia.Controls.Primitives;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using ControlCatalog.ViewModels;
using System;
using System.Threading.Tasks;
namespace ControlCatalog
{
public class MainWindow : Window
{
private WindowNotificationManager _notificationArea;
public MainWindow()
{
this.InitializeComponent();
this.AttachDevTools();
//Renderer.DrawFps = true;
//Renderer.DrawDirtyRects = Renderer.DrawFps = true;
_notificationArea = new WindowNotificationManager(this)
{
Position = NotificationPosition.TopRight,
MaxItems = 3
};
DataContext = new MainWindowViewModel(_notificationArea);
}
private void InitializeComponent()

10
samples/ControlCatalog/Pages/NotificationsPage.xaml

@ -0,0 +1,10 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.NotificationsPage">
<StackPanel Orientation="Vertical" Spacing="4" HorizontalAlignment="Left">
<TextBlock Classes="h1">Notifications</TextBlock>
<Button Content="Show Standard Managed Notification" Command="{Binding ShowManagedNotificationCommand}" />
<Button Content="Show Custom Managed Notification" Command="{Binding ShowCustomManagedNotificationCommand}" />
<Button Content="Show Native Notification" Command="{Binding ShowNativeNotificationCommand}" />
</StackPanel>
</UserControl>

18
samples/ControlCatalog/Pages/NotificationsPage.xaml.cs

@ -0,0 +1,18 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace ControlCatalog.Pages
{
public class NotificationsPage : UserControl
{
public NotificationsPage()
{
this.InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

44
samples/ControlCatalog/ViewModels/MainWindowViewModel.cs

@ -0,0 +1,44 @@
using System.Reactive;
using Avalonia.Controls.Notifications;
using Avalonia.Diagnostics.ViewModels;
using ReactiveUI;
namespace ControlCatalog.ViewModels
{
class MainWindowViewModel : ViewModelBase
{
private IManagedNotificationManager _notificationManager;
public MainWindowViewModel(IManagedNotificationManager notificationManager)
{
_notificationManager = notificationManager;
ShowCustomManagedNotificationCommand = ReactiveCommand.Create(() =>
{
NotificationManager.Show(new NotificationViewModel(NotificationManager) { Title = "Hey There!", Message = "Did you know that Avalonia now supports Custom In-Window Notifications?" });
});
ShowManagedNotificationCommand = ReactiveCommand.Create(() =>
{
NotificationManager.Show(new Avalonia.Controls.Notifications.Notification("Welcome", "Avalonia now supports Notifications.", NotificationType.Information));
});
ShowNativeNotificationCommand = ReactiveCommand.Create(() =>
{
NotificationManager.Show(new Avalonia.Controls.Notifications.Notification("Error", "Native Notifications are not quite ready. Coming soon.", NotificationType.Error));
});
}
public IManagedNotificationManager NotificationManager
{
get { return _notificationManager; }
set { this.RaiseAndSetIfChanged(ref _notificationManager, value); }
}
public ReactiveCommand<Unit, Unit> ShowCustomManagedNotificationCommand { get; }
public ReactiveCommand<Unit, Unit> ShowManagedNotificationCommand { get; }
public ReactiveCommand<Unit, Unit> ShowNativeNotificationCommand { get; }
}
}

30
samples/ControlCatalog/ViewModels/NotificationViewModel.cs

@ -0,0 +1,30 @@
using System.Reactive;
using Avalonia.Controls.Notifications;
using ReactiveUI;
namespace ControlCatalog.ViewModels
{
public class NotificationViewModel
{
public NotificationViewModel(INotificationManager manager)
{
YesCommand = ReactiveCommand.Create(() =>
{
manager.Show(new Avalonia.Controls.Notifications.Notification("Avalonia Notifications", "Start adding notifications to your app today."));
});
NoCommand = ReactiveCommand.Create(() =>
{
manager.Show(new Avalonia.Controls.Notifications.Notification("Avalonia Notifications", "Start adding notifications to your app today. To find out more visit..."));
});
}
public string Title { get; set; }
public string Message { get; set; }
public ReactiveCommand<Unit, Unit> YesCommand { get; }
public ReactiveCommand<Unit, Unit> NoCommand { get; }
}
}

19
samples/ControlCatalog/Views/CustomNotificationView.xaml

@ -0,0 +1,19 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Views.CustomNotificationView">
<Border Padding="12" MinHeight="20" Background="DodgerBlue">
<Grid ColumnDefinitions="Auto,*">
<Panel Margin="0,0,12,0" Width="25" Height="25" VerticalAlignment="Top">
<TextBlock Text="&#xE115;" FontFamily="Segoe UI Symbol" FontSize="20" TextAlignment="Center" VerticalAlignment="Center"/>
</Panel>
<DockPanel Grid.Column="1">
<TextBlock DockPanel.Dock="Top" Text="{Binding Title}" FontWeight="Medium" />
<StackPanel Spacing="20" DockPanel.Dock="Bottom" Margin="0,8,0,0" Orientation="Horizontal">
<Button Content="No" DockPanel.Dock="Right" NotificationCard.CloseOnClick="True" Command="{Binding NoCommand}" Margin="0,0,8,0" />
<Button Content="Yes" DockPanel.Dock="Right" NotificationCard.CloseOnClick="True" Command="{Binding YesCommand}" />
</StackPanel>
<TextBlock Text="{Binding Message}" TextWrapping="Wrap" Opacity=".8" Margin="0,8,0,0"/>
</DockPanel>
</Grid>
</Border>
</UserControl>

18
samples/ControlCatalog/Views/CustomNotificationView.xaml.cs

@ -0,0 +1,18 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace ControlCatalog.Views
{
public class CustomNotificationView : UserControl
{
public CustomNotificationView()
{
this.InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

50
src/Avalonia.Controls/LayoutTransformControl.cs

@ -20,6 +20,9 @@ namespace Avalonia.Controls
public static readonly AvaloniaProperty<Transform> LayoutTransformProperty =
AvaloniaProperty.Register<LayoutTransformControl, Transform>(nameof(LayoutTransform));
public static readonly AvaloniaProperty<bool> UseRenderTransformProperty =
AvaloniaProperty.Register<LayoutTransformControl, bool>(nameof(LayoutTransform));
static LayoutTransformControl()
{
ClipToBoundsProperty.OverrideDefaultValue<LayoutTransformControl>(true);
@ -29,6 +32,7 @@ namespace Avalonia.Controls
ChildProperty.Changed
.AddClassHandler<LayoutTransformControl>(x => x.OnChildChanged);
UseRenderTransformProperty.Changed.AddClassHandler<LayoutTransformControl>(x => x.OnUseRenderTransformPropertyChanged);
}
/// <summary>
@ -40,6 +44,15 @@ namespace Avalonia.Controls
set { SetValue(LayoutTransformProperty, value); }
}
/// <summary>
/// Utilize the <see cref="RenderTransformProperty"/> for layout transforms.
/// </summary>
public bool UseRenderTransform
{
get { return GetValue(UseRenderTransformProperty); }
set { SetValue(UseRenderTransformProperty, value); }
}
public IControl TransformRoot => Child;
/// <summary>
@ -51,6 +64,7 @@ namespace Avalonia.Controls
{
if (TransformRoot == null || LayoutTransform == null)
{
LayoutTransform = RenderTransform;
return base.ArrangeOverride(finalSize);
}
@ -133,6 +147,42 @@ namespace Avalonia.Controls
return transformedDesiredSize;
}
IDisposable _renderTransformChangedEvent;
private void OnUseRenderTransformPropertyChanged(AvaloniaPropertyChangedEventArgs e)
{
// HACK: In theory, this method and the UseRenderTransform shouldn't exist but
// it's hard to animate this particular control with style animations without
// PropertyPaths.
//
// So until we get that implemented, we'll stick on this not-so-good
// workaround.
var target = e.Sender as LayoutTransformControl;
var shouldUseRenderTransform = (bool)e.NewValue;
if (target != null)
{
if (shouldUseRenderTransform)
{
_renderTransformChangedEvent = RenderTransformProperty.Changed
.Subscribe(
(x) =>
{
var target2 = x.Sender as LayoutTransformControl;
if (target2 != null)
{
target2.LayoutTransform = target2.RenderTransform;
}
});
}
else
{
_renderTransformChangedEvent?.Dispose();
LayoutTransform = null;
}
}
}
private void OnChildChanged(AvaloniaPropertyChangedEventArgs e)
{
if (null != TransformRoot)

23
src/Avalonia.Controls/Notifications/IManagedNotificationManager.cs

@ -0,0 +1,23 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Controls.Notifications
{
/// <summary>
/// Represents a notification manager that can show arbitrary content.
/// Managed notification managers can show any content.
/// </summary>
/// <remarks>
/// Because notification managers of this type are implemented purely in managed code, they
/// can display arbitrary content, as opposed to notification managers which display notifications
/// using the host operating system's notification mechanism.
/// </remarks>
public interface IManagedNotificationManager : INotificationManager
{
/// <summary>
/// Shows a notification.
/// </summary>
/// <param name="content">The content to be displayed.</param>
void Show(object content);
}
}

44
src/Avalonia.Controls/Notifications/INotification.cs

@ -0,0 +1,44 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Avalonia.Controls.Notifications
{
/// <summary>
/// Represents a notification that can be shown in a window or by the host operating system.
/// </summary>
public interface INotification
{
/// <summary>
/// Gets the Title of the notification.
/// </summary>
string Title { get; }
/// <summary>
/// Gets the notification message.
/// </summary>
string Message { get; }
/// <summary>
/// Gets the <see cref="NotificationType"/> of the notification.
/// </summary>
NotificationType Type { get; }
/// <summary>
/// Gets the expiration time of the notification after which it will automatically close.
/// If the value is <see cref="TimeSpan.Zero"/> then the notification will remain open until the user closes it.
/// </summary>
TimeSpan Expiration { get; }
/// <summary>
/// Gets an Action to be run when the notification is clicked.
/// </summary>
Action OnClick { get; }
/// <summary>
/// Gets an Action to be run when the notification is closed.
/// </summary>
Action OnClose { get; }
}
}

18
src/Avalonia.Controls/Notifications/INotificationManager.cs

@ -0,0 +1,18 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Controls.Notifications
{
/// <summary>
/// Represents a notification manager that can be used to show notifications in a window or using
/// the host operating system.
/// </summary>
public interface INotificationManager
{
/// <summary>
/// Show a notification.
/// </summary>
/// <param name="notification">The notification to be displayed.</param>
void Show(INotification notification);
}
}

60
src/Avalonia.Controls/Notifications/Notification.cs

@ -0,0 +1,60 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Avalonia.Controls.Notifications
{
/// <summary>
/// A notification that can be shown in a window or by the host operating system.
/// </summary>
/// <remarks>
/// This class represents a notification that can be displayed either in a window using
/// <see cref="WindowNotificationManager"/> or by the host operating system (to be implemented).
/// </remarks>
public class Notification : INotification
{
/// <summary>
/// Initializes a new instance of the <see cref="Notification"/> class.
/// </summary>
/// <param name="title">The title of the notification.</param>
/// <param name="message">The message to be displayed in the notification.</param>
/// <param name="type">The <see cref="NotificationType"/> of the notification.</param>
/// <param name="expiration">The expiry time at which the notification will close.
/// Use <see cref="TimeSpan.Zero"/> for notifications that will remain open.</param>
/// <param name="onClick">An Action to call when the notification is clicked.</param>
/// <param name="onClose">An Action to call when the notification is closed.</param>
public Notification(string title,
string message,
NotificationType type = NotificationType.Information,
TimeSpan? expiration = null,
Action onClick = null,
Action onClose = null)
{
Title = title;
Message = message;
Type = type;
Expiration = expiration.HasValue ? expiration.Value : TimeSpan.FromSeconds(5);
OnClick = onClick;
OnClose = onClose;
}
/// <inheritdoc/>
public string Title { get; private set; }
/// <inheritdoc/>
public string Message { get; private set; }
/// <inheritdoc/>
public NotificationType Type { get; private set; }
/// <inheritdoc/>
public TimeSpan Expiration { get; private set; }
/// <inheritdoc/>
public Action OnClick { get; private set; }
/// <inheritdoc/>
public Action OnClose { get; private set; }
}
}

160
src/Avalonia.Controls/Notifications/NotificationCard.cs

@ -0,0 +1,160 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
namespace Avalonia.Controls.Notifications
{
/// <summary>
/// Control that represents and displays a notification.
/// </summary>
public class NotificationCard : ContentControl
{
private bool _isClosed;
private bool _isClosing;
/// <summary>
/// Initializes a new instance of the <see cref="NotificationCard"/> class.
/// </summary>
public NotificationCard()
{
this.GetObservable(IsClosedProperty)
.Subscribe(x =>
{
if (!IsClosing && !IsClosed)
{
return;
}
RaiseEvent(new RoutedEventArgs(NotificationClosedEvent));
});
this.GetObservable(ContentProperty)
.OfType<Notification>()
.Subscribe(x =>
{
switch (x.Type)
{
case NotificationType.Error:
PseudoClasses.Add(":error");
break;
case NotificationType.Information:
PseudoClasses.Add(":information");
break;
case NotificationType.Success:
PseudoClasses.Add(":success");
break;
case NotificationType.Warning:
PseudoClasses.Add(":warning");
break;
}
});
}
/// <summary>
/// Determines if the notification is already closing.
/// </summary>
public bool IsClosing
{
get { return _isClosing; }
private set { SetAndRaise(IsClosingProperty, ref _isClosing, value); }
}
/// <summary>
/// Defines the <see cref="IsClosing"/> property.
/// </summary>
public static readonly DirectProperty<NotificationCard, bool> IsClosingProperty =
AvaloniaProperty.RegisterDirect<NotificationCard, bool>(nameof(IsClosing), o => o.IsClosing);
/// <summary>
/// Determines if the notification is closed.
/// </summary>
public bool IsClosed
{
get { return _isClosed; }
set { SetAndRaise(IsClosedProperty, ref _isClosed, value); }
}
/// <summary>
/// Defines the <see cref="IsClosed"/> property.
/// </summary>
public static readonly DirectProperty<NotificationCard, bool> IsClosedProperty =
AvaloniaProperty.RegisterDirect<NotificationCard, bool>(nameof(IsClosed), o => o.IsClosed, (o, v) => o.IsClosed = v);
/// <summary>
/// Defines the <see cref="NotificationClosed"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> NotificationClosedEvent =
RoutedEvent.Register<NotificationCard, RoutedEventArgs>(nameof(NotificationClosed), RoutingStrategies.Bubble);
/// <summary>
/// Raised when the <see cref="NotificationCard"/> has closed.
/// </summary>
public event EventHandler<RoutedEventArgs> NotificationClosed
{
add { AddHandler(NotificationClosedEvent, value); }
remove { RemoveHandler(NotificationClosedEvent, value); }
}
public static bool GetCloseOnClick(Button obj)
{
return (bool)obj.GetValue(CloseOnClickProperty);
}
public static void SetCloseOnClick(Button obj, bool value)
{
obj.SetValue(CloseOnClickProperty, value);
}
/// <summary>
/// Defines the CloseOnClick property.
/// </summary>
public static readonly AvaloniaProperty CloseOnClickProperty =
AvaloniaProperty.RegisterAttached<Button, bool>("CloseOnClick", typeof(NotificationCard), validate: CloseOnClickChanged);
private static bool CloseOnClickChanged(Button button, bool value)
{
if (value)
{
button.Click += Button_Click;
}
else
{
button.Click -= Button_Click;
}
return true;
}
/// <summary>
/// Called when a button inside the Notification is clicked.
/// </summary>
private static void Button_Click(object sender, RoutedEventArgs e)
{
var btn = sender as ILogical;
var notification = btn.GetLogicalAncestors().OfType<NotificationCard>().FirstOrDefault();
notification?.Close();
}
/// <summary>
/// Closes the <see cref="NotificationCard"/>.
/// </summary>
public void Close()
{
if (IsClosing)
{
return;
}
IsClosing = true;
}
}
}

17
src/Avalonia.Controls/Notifications/NotificationPosition.cs

@ -0,0 +1,17 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Controls.Notifications
{
/// <summary>
/// Describes the possible positions for <see cref="Notification"/>s that are displayed by a
/// <see cref="WindowNotificationManager"/>.
/// </summary>
public enum NotificationPosition
{
TopLeft,
TopRight,
BottomLeft,
BottomRight
}
}

16
src/Avalonia.Controls/Notifications/NotificationType.cs

@ -0,0 +1,16 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Controls.Notifications
{
/// <summary>
/// Enumeration of types for <see cref="INotification"/>.
/// </summary>
public enum NotificationType
{
Information,
Success,
Warning,
Error
}
}

64
src/Avalonia.Controls/Notifications/ReversibleStackPanel.cs

@ -0,0 +1,64 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Linq;
using Avalonia.Layout;
namespace Avalonia.Controls
{
/// <summary>
/// Implements a <see cref="StackPanel"/> where the flow direction of its items can be reversed.
/// </summary>
public class ReversibleStackPanel : StackPanel
{
/// <summary>
/// Defines the <see cref="ReverseOrder"/> property.
/// </summary>
public static readonly StyledProperty<bool> ReverseOrderProperty =
AvaloniaProperty.Register<ReversibleStackPanel, bool>(nameof(ReverseOrder));
/// <summary>
/// Gets or sets if the child controls will be layed out in reverse order.
/// </summary>
public bool ReverseOrder
{
get => GetValue(ReverseOrderProperty);
set => SetValue(ReverseOrderProperty, value);
}
/// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize)
{
var orientation = Orientation;
var spacing = Spacing;
var finalRect = new Rect(finalSize);
var pos = 0.0;
var children = ReverseOrder ? Children.Reverse() : Children;
foreach (Control child in children)
{
double childWidth = child.DesiredSize.Width;
double childHeight = child.DesiredSize.Height;
if (orientation == Orientation.Vertical)
{
var rect = new Rect(0, pos, childWidth, childHeight)
.Align(finalRect, child.HorizontalAlignment, VerticalAlignment.Top);
ArrangeChild(child, rect, finalSize, orientation);
pos += childHeight + spacing;
}
else
{
var rect = new Rect(pos, 0, childWidth, childHeight)
.Align(finalRect, HorizontalAlignment.Left, child.VerticalAlignment);
ArrangeChild(child, rect, finalSize, orientation);
pos += childWidth + spacing;
}
}
return finalSize;
}
}
}

163
src/Avalonia.Controls/Notifications/WindowNotificationManager.cs

@ -0,0 +1,163 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Avalonia.Controls.Primitives;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Notifications
{
/// <summary>
/// An <see cref="INotificationManager"/> that displays notifications in a <see cref="Window"/>.
/// </summary>
public class WindowNotificationManager : TemplatedControl, IManagedNotificationManager
{
private IList _items;
/// <summary>
/// Defines the <see cref="Position"/> property.
/// </summary>
public static readonly StyledProperty<NotificationPosition> PositionProperty =
AvaloniaProperty.Register<WindowNotificationManager, NotificationPosition>(nameof(Position), NotificationPosition.TopRight);
/// <summary>
/// Defines which corner of the screen notifications can be displayed in.
/// </summary>
/// <seealso cref="NotificationPosition"/>
public NotificationPosition Position
{
get { return GetValue(PositionProperty); }
set { SetValue(PositionProperty, value); }
}
/// <summary>
/// Defines the <see cref="MaxItems"/> property.
/// </summary>
public static readonly StyledProperty<int> MaxItemsProperty =
AvaloniaProperty.Register<WindowNotificationManager, int>(nameof(MaxItems), 5);
/// <summary>
/// Defines the maximum number of notifications visible at once.
/// </summary>
public int MaxItems
{
get { return GetValue(MaxItemsProperty); }
set { SetValue(MaxItemsProperty, value); }
}
/// <summary>
/// Initializes a new instance of the <see cref="WindowNotificationManager"/> class.
/// </summary>
/// <param name="host">The window that will host the control.</param>
public WindowNotificationManager(Window host)
{
if (VisualChildren.Count != 0)
{
Install(host);
}
else
{
Observable.FromEventPattern<TemplateAppliedEventArgs>(host, nameof(host.TemplateApplied)).Take(1)
.Subscribe(_ =>
{
Install(host);
});
}
}
static WindowNotificationManager()
{
PseudoClass<WindowNotificationManager, NotificationPosition>(PositionProperty, x => x == NotificationPosition.TopLeft, ":topleft");
PseudoClass<WindowNotificationManager, NotificationPosition>(PositionProperty, x => x == NotificationPosition.TopRight, ":topright");
PseudoClass<WindowNotificationManager, NotificationPosition>(PositionProperty, x => x == NotificationPosition.BottomLeft, ":bottomleft");
PseudoClass<WindowNotificationManager, NotificationPosition>(PositionProperty, x => x == NotificationPosition.BottomRight, ":bottomright");
HorizontalAlignmentProperty.OverrideDefaultValue<WindowNotificationManager>(Layout.HorizontalAlignment.Stretch);
VerticalAlignmentProperty.OverrideDefaultValue<WindowNotificationManager>(Layout.VerticalAlignment.Stretch);
}
/// <inheritdoc/>
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
{
base.OnTemplateApplied(e);
var itemsControl = e.NameScope.Find<Panel>("PART_Items");
_items = itemsControl?.Children;
}
/// <inheritdoc/>
public void Show(INotification content)
{
Show(content as object);
}
/// <inheritdoc/>
public async void Show(object content)
{
var notification = content as INotification;
var notificationControl = new NotificationCard
{
Content = content
};
if (notification != null)
{
notificationControl.NotificationClosed += (sender, args) =>
{
notification.OnClose?.Invoke();
_items.Remove(sender);
};
}
notificationControl.PointerPressed += (sender, args) =>
{
if (notification != null && notification.OnClick != null)
{
notification.OnClick.Invoke();
}
(sender as NotificationCard)?.Close();
};
_items.Add(notificationControl);
if (_items.OfType<NotificationCard>().Count(i => !i.IsClosing) > MaxItems)
{
_items.OfType<NotificationCard>().First(i => !i.IsClosing).Close();
}
if (notification != null && notification.Expiration == TimeSpan.Zero)
{
return;
}
await Task.Delay(notification?.Expiration ?? TimeSpan.FromSeconds(5));
notificationControl.Close();
}
/// <summary>
/// Installs the <see cref="WindowNotificationManager"/> within the <see cref="AdornerLayer"/>
/// of the host <see cref="Window"/>.
/// </summary>
/// <param name="host">The <see cref="Window"/> that will be the host.</param>
private void Install(Window host)
{
var adornerLayer = host.GetVisualDescendants()
.OfType<AdornerDecorator>()
.FirstOrDefault()
?.AdornerLayer;
if (adornerLayer != null)
{
adornerLayer.Children.Add(this);
}
}
}
}

9
src/Avalonia.Controls/Primitives/AdornerDecorator.cs

@ -1,6 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.LogicalTree;
namespace Avalonia.Controls.Primitives
{
public class AdornerDecorator : Decorator
@ -13,6 +15,13 @@ namespace Avalonia.Controls.Primitives
VisualChildren.Add(AdornerLayer);
}
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnAttachedToLogicalTree(e);
((ILogical)AdornerLayer).NotifyAttachedToLogicalTree(e);
}
public AdornerLayer AdornerLayer
{
get;

6
src/Avalonia.Controls/Primitives/AdornerLayer.cs

@ -4,14 +4,14 @@
using System;
using System.Collections.Specialized;
using System.Linq;
using Avalonia.VisualTree;
using Avalonia.Media;
using Avalonia.Rendering;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives
{
// TODO: Need to track position of adorned elements and move the adorner if they move.
public class AdornerLayer : Panel, ICustomSimpleHitTest
public class AdornerLayer : Canvas, ICustomSimpleHitTest
{
public static readonly AttachedProperty<Visual> AdornedElementProperty =
AvaloniaProperty.RegisterAttached<AdornerLayer, Visual, Visual>("AdornedElement");
@ -64,7 +64,7 @@ namespace Avalonia.Controls.Primitives
}
else
{
child.Arrange(new Rect(child.DesiredSize));
child.Arrange(new Rect(finalSize));
}
}

3
src/Avalonia.Controls/Properties/AssemblyInfo.cs

@ -14,4 +14,5 @@ using Avalonia.Metadata;
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Presenters")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Primitives")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Shapes")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Templates")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Templates")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Notifications")]

5
src/Avalonia.Controls/StackPanel.cs

@ -233,6 +233,11 @@ namespace Avalonia.Controls
foreach (Control child in Children)
{
if (!child.IsVisible)
{
continue;
}
double childWidth = child.DesiredSize.Width;
double childHeight = child.DesiredSize.Height;

3
src/Avalonia.Controls/TopLevel.cs

@ -2,7 +2,9 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Controls.Notifications;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Input.Raw;
@ -87,6 +89,7 @@ namespace Avalonia.Controls
}
PlatformImpl = impl;
dependencyResolver = dependencyResolver ?? AvaloniaLocator.Current;
var styler = TryGetService<IStyler>(dependencyResolver);

6
src/Avalonia.Themes.Default/Accents/BaseDark.xaml

@ -46,6 +46,12 @@
<SolidColorBrush x:Key="ErrorBrush" Color="{DynamicResource ErrorColor}"></SolidColorBrush>
<SolidColorBrush x:Key="ErrorLowBrush" Color="{DynamicResource ErrorLowColor}"></SolidColorBrush>
<SolidColorBrush x:Key="NotificationCardBackgroundBrush" Color="#444444"/>
<SolidColorBrush x:Key="NotificationCardInformationBackgroundBrush" Color="Teal"/>
<SolidColorBrush x:Key="NotificationCardSuccessBackgroundBrush" Color="LimeGreen"/>
<SolidColorBrush x:Key="NotificationCardWarningBackgroundBrush" Color="Orange"/>
<SolidColorBrush x:Key="NotificationCardErrorBackgroundBrush" Color="OrangeRed"/>
<Thickness x:Key="ThemeBorderThickness">1,1,1,1</Thickness>
<sys:Double x:Key="ThemeDisabledOpacity">0.5</sys:Double>

6
src/Avalonia.Themes.Default/Accents/BaseLight.xaml

@ -46,6 +46,12 @@
<SolidColorBrush x:Key="ErrorBrush" Color="{DynamicResource ErrorColor}"></SolidColorBrush>
<SolidColorBrush x:Key="ErrorLowBrush" Color="{DynamicResource ErrorLowColor}"></SolidColorBrush>
<SolidColorBrush x:Key="NotificationCardBackgroundBrush" Color="#444444"/>
<SolidColorBrush x:Key="NotificationCardInformationBackgroundBrush" Color="Teal"/>
<SolidColorBrush x:Key="NotificationCardSuccessBackgroundBrush" Color="LimeGreen"/>
<SolidColorBrush x:Key="NotificationCardWarningBackgroundBrush" Color="Orange"/>
<SolidColorBrush x:Key="NotificationCardErrorBackgroundBrush" Color="OrangeRed"/>
<Thickness x:Key="ThemeBorderThickness">1</Thickness>
<sys:Double x:Key="ThemeDisabledOpacity">0.5</sys:Double>

2
src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj

@ -18,5 +18,7 @@
<EmbeddedResource Include="**/*.xaml"/>
</ItemGroup>
<Import Project="..\..\build\BuildTargets.targets"/>
<Import Project="..\..\build\EmbedXaml.props" />
<Import Project="..\..\build\BuildTargets.targets" />
<Import Project="..\..\build\Rx.props" />
</Project>

2
src/Avalonia.Themes.Default/DefaultTheme.xaml

@ -48,4 +48,6 @@
<StyleInclude Source="resm:Avalonia.Themes.Default.ButtonSpinner.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.NumericUpDown.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.AutoCompleteBox.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.WindowNotificationManager.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.NotificationCard.xaml?assembly=Avalonia.Themes.Default"/>
</Styles>

88
src/Avalonia.Themes.Default/NotificationCard.xaml

@ -0,0 +1,88 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="NotificationCard">
<Setter Property="UseLayoutRounding" Value="True"/>
<Setter Property="Width" Value="350"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="RenderTransformOrigin" Value="50%,75%"/>
<Setter Property="Template">
<ControlTemplate>
<LayoutTransformControl Name="PART_LayoutTransformControl" UseRenderTransform="True">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Margin="8,8,0,0">
<ContentControl MinHeight="150" Content="{TemplateBinding Content}" />
</Border>
</LayoutTransformControl>
</ControlTemplate>
</Setter>
<Style.Animations>
<Animation Duration="0:0:0.45" Easing="QuadraticEaseIn" FillMode="Forward">
<KeyFrame Cue="0%">
<Setter Property="Opacity" Value="0"/>
<Setter Property="TranslateTransform.Y" Value="20"/>
<Setter Property="ScaleTransform.ScaleX" Value="0.85"/>
<Setter Property="ScaleTransform.ScaleY" Value="0.85"/>
</KeyFrame>
<KeyFrame Cue="30%">
<Setter Property="TranslateTransform.Y" Value="-20"/>
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="Opacity" Value="1"/>
<Setter Property="TranslateTransform.Y" Value="0"/>
<Setter Property="ScaleTransform.ScaleX" Value="1"/>
<Setter Property="ScaleTransform.ScaleY" Value="1"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="NotificationCard[IsClosing=true] /template/ LayoutTransformControl#PART_LayoutTransformControl">
<Setter Property="RenderTransformOrigin" Value="50%,0%"/>
<Style.Animations>
<Animation Duration="0:0:0.75" Easing="QuadraticEaseOut" FillMode="Forward">
<KeyFrame Cue="0%">
<Setter Property="TranslateTransform.X" Value="0"/>
<Setter Property="ScaleTransform.ScaleY" Value="1"/>
</KeyFrame>
<KeyFrame Cue="70%">
<Setter Property="TranslateTransform.X" Value="800"/>
<Setter Property="ScaleTransform.ScaleY" Value="1"/>
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="ScaleTransform.ScaleY" Value="0"/>
<Setter Property="TranslateTransform.X" Value="800"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="NotificationCard[IsClosing=true]">
<Style.Animations>
<Animation Duration="0:0:1.25" Easing="QuadraticEaseOut" FillMode="Forward">
<KeyFrame Cue="100%">
<Setter Property="IsClosed" Value="True"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="NotificationCard">
<Setter Property="Background" Value="{DynamicResource NotificationCardBackgroundBrush}"/>
</Style>
<Style Selector="NotificationCard:information">
<Setter Property="Background" Value="{DynamicResource NotificationCardInformationBackgroundBrush}"/>
</Style>
<Style Selector="NotificationCard:success">
<Setter Property="Background" Value="{DynamicResource NotificationCardSuccessBackgroundBrush}"/>
</Style>
<Style Selector="NotificationCard:warning">
<Setter Property="Background" Value="{DynamicResource NotificationCardWarningBackgroundBrush}"/>
</Style>
<Style Selector="NotificationCard:error">
<Setter Property="Background" Value="{DynamicResource NotificationCardErrorBackgroundBrush}"/>
</Style>
</Styles>

45
src/Avalonia.Themes.Default/WindowNotificationManager.xaml

@ -0,0 +1,45 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="WindowNotificationManager">
<Setter Property="Margin" Value="0 0 8 8"/>
<Setter Property="Template">
<ControlTemplate>
<ReversibleStackPanel Name="PART_Items">
<ReversibleStackPanel.DataTemplates>
<DataTemplate DataType="INotification">
<StackPanel Spacing="8" Margin="12">
<TextBlock Text="{Binding Title}" FontWeight="Medium" />
<TextBlock MaxHeight="80" Text="{Binding Message}" TextWrapping="Wrap" Margin="0,0,12,0"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="x:String">
<TextBlock Text="{Binding }" Margin="12" />
</DataTemplate>
</ReversibleStackPanel.DataTemplates>
</ReversibleStackPanel>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="WindowNotificationManager:topleft /template/ ReversibleStackPanel#PART_Items">
<Setter Property="VerticalAlignment" Value="Top"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
<Style Selector="WindowNotificationManager:topright /template/ ReversibleStackPanel#PART_Items">
<Setter Property="VerticalAlignment" Value="Top"/>
<Setter Property="HorizontalAlignment" Value="Right"/>
</Style>
<Style Selector="WindowNotificationManager:bottomleft /template/ ReversibleStackPanel#PART_Items">
<Setter Property="ReverseOrder" Value="True"/>
<Setter Property="VerticalAlignment" Value="Bottom"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
<Style Selector="WindowNotificationManager:bottomright /template/ ReversibleStackPanel#PART_Items">
<Setter Property="ReverseOrder" Value="True"/>
<Setter Property="VerticalAlignment" Value="Bottom"/>
<Setter Property="HorizontalAlignment" Value="Right"/>
</Style>
</Styles>

2
src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github

@ -1 +1 @@
Subproject commit 7452b23169e4948907fa10e2c115b672897d0e04
Subproject commit ab5526173722b8988bc5ca3c03c8752ce89c0975
Loading…
Cancel
Save