diff --git a/nukebuild/Numerge b/nukebuild/Numerge
index aef10ae67d..4464343aef 160000
--- a/nukebuild/Numerge
+++ b/nukebuild/Numerge
@@ -1 +1 @@
-Subproject commit aef10ae67dc55c95f49b52a505a0be33bfa297a5
+Subproject commit 4464343aef5c8ab7a42fcb20a483a6058199f8b8
diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml
index b79db9f053..9f1899acc5 100644
--- a/samples/ControlCatalog/MainView.xaml
+++ b/samples/ControlCatalog/MainView.xaml
@@ -29,6 +29,7 @@
+
diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml
index 35740d444f..6a9e865e26 100644
--- a/samples/ControlCatalog/MainWindow.xaml
+++ b/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">
-
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/MainWindow.xaml.cs b/samples/ControlCatalog/MainWindow.xaml.cs
index e620a77e52..91d9f034a5 100644
--- a/samples/ControlCatalog/MainWindow.xaml.cs
+++ b/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()
diff --git a/samples/ControlCatalog/Pages/NotificationsPage.xaml b/samples/ControlCatalog/Pages/NotificationsPage.xaml
new file mode 100644
index 0000000000..94e2314dc7
--- /dev/null
+++ b/samples/ControlCatalog/Pages/NotificationsPage.xaml
@@ -0,0 +1,10 @@
+
+
+ Notifications
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/NotificationsPage.xaml.cs b/samples/ControlCatalog/Pages/NotificationsPage.xaml.cs
new file mode 100644
index 0000000000..eadf92b602
--- /dev/null
+++ b/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);
+ }
+ }
+}
diff --git a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
new file mode 100644
index 0000000000..28cb84dad0
--- /dev/null
+++ b/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 ShowCustomManagedNotificationCommand { get; }
+
+ public ReactiveCommand ShowManagedNotificationCommand { get; }
+
+ public ReactiveCommand ShowNativeNotificationCommand { get; }
+ }
+}
diff --git a/samples/ControlCatalog/ViewModels/NotificationViewModel.cs b/samples/ControlCatalog/ViewModels/NotificationViewModel.cs
new file mode 100644
index 0000000000..8724ba344b
--- /dev/null
+++ b/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 YesCommand { get; }
+
+ public ReactiveCommand NoCommand { get; }
+
+ }
+}
diff --git a/samples/ControlCatalog/Views/CustomNotificationView.xaml b/samples/ControlCatalog/Views/CustomNotificationView.xaml
new file mode 100644
index 0000000000..5b99ed8e4d
--- /dev/null
+++ b/samples/ControlCatalog/Views/CustomNotificationView.xaml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Views/CustomNotificationView.xaml.cs b/samples/ControlCatalog/Views/CustomNotificationView.xaml.cs
new file mode 100644
index 0000000000..50a782276a
--- /dev/null
+++ b/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);
+ }
+ }
+}
diff --git a/src/Avalonia.Controls/LayoutTransformControl.cs b/src/Avalonia.Controls/LayoutTransformControl.cs
index 950d4f34da..07372eb714 100644
--- a/src/Avalonia.Controls/LayoutTransformControl.cs
+++ b/src/Avalonia.Controls/LayoutTransformControl.cs
@@ -20,6 +20,9 @@ namespace Avalonia.Controls
public static readonly AvaloniaProperty LayoutTransformProperty =
AvaloniaProperty.Register(nameof(LayoutTransform));
+ public static readonly AvaloniaProperty UseRenderTransformProperty =
+ AvaloniaProperty.Register(nameof(LayoutTransform));
+
static LayoutTransformControl()
{
ClipToBoundsProperty.OverrideDefaultValue(true);
@@ -29,6 +32,7 @@ namespace Avalonia.Controls
ChildProperty.Changed
.AddClassHandler(x => x.OnChildChanged);
+ UseRenderTransformProperty.Changed.AddClassHandler(x => x.OnUseRenderTransformPropertyChanged);
}
///
@@ -40,6 +44,15 @@ namespace Avalonia.Controls
set { SetValue(LayoutTransformProperty, value); }
}
+ ///
+ /// Utilize the for layout transforms.
+ ///
+ public bool UseRenderTransform
+ {
+ get { return GetValue(UseRenderTransformProperty); }
+ set { SetValue(UseRenderTransformProperty, value); }
+ }
+
public IControl TransformRoot => Child;
///
@@ -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)
diff --git a/src/Avalonia.Controls/Notifications/IManagedNotificationManager.cs b/src/Avalonia.Controls/Notifications/IManagedNotificationManager.cs
new file mode 100644
index 0000000000..03c2fe85b8
--- /dev/null
+++ b/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
+{
+ ///
+ /// Represents a notification manager that can show arbitrary content.
+ /// Managed notification managers can show any content.
+ ///
+ ///
+ /// 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.
+ ///
+ public interface IManagedNotificationManager : INotificationManager
+ {
+ ///
+ /// Shows a notification.
+ ///
+ /// The content to be displayed.
+ void Show(object content);
+ }
+}
diff --git a/src/Avalonia.Controls/Notifications/INotification.cs b/src/Avalonia.Controls/Notifications/INotification.cs
new file mode 100644
index 0000000000..2c6cb90133
--- /dev/null
+++ b/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
+{
+ ///
+ /// Represents a notification that can be shown in a window or by the host operating system.
+ ///
+ public interface INotification
+ {
+ ///
+ /// Gets the Title of the notification.
+ ///
+ string Title { get; }
+
+ ///
+ /// Gets the notification message.
+ ///
+ string Message { get; }
+
+ ///
+ /// Gets the of the notification.
+ ///
+ NotificationType Type { get; }
+
+ ///
+ /// Gets the expiration time of the notification after which it will automatically close.
+ /// If the value is then the notification will remain open until the user closes it.
+ ///
+ TimeSpan Expiration { get; }
+
+ ///
+ /// Gets an Action to be run when the notification is clicked.
+ ///
+ Action OnClick { get; }
+
+ ///
+ /// Gets an Action to be run when the notification is closed.
+ ///
+ Action OnClose { get; }
+ }
+}
diff --git a/src/Avalonia.Controls/Notifications/INotificationManager.cs b/src/Avalonia.Controls/Notifications/INotificationManager.cs
new file mode 100644
index 0000000000..21430c900d
--- /dev/null
+++ b/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
+{
+ ///
+ /// Represents a notification manager that can be used to show notifications in a window or using
+ /// the host operating system.
+ ///
+ public interface INotificationManager
+ {
+ ///
+ /// Show a notification.
+ ///
+ /// The notification to be displayed.
+ void Show(INotification notification);
+ }
+}
diff --git a/src/Avalonia.Controls/Notifications/Notification.cs b/src/Avalonia.Controls/Notifications/Notification.cs
new file mode 100644
index 0000000000..4204ad0aee
--- /dev/null
+++ b/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
+{
+ ///
+ /// A notification that can be shown in a window or by the host operating system.
+ ///
+ ///
+ /// This class represents a notification that can be displayed either in a window using
+ /// or by the host operating system (to be implemented).
+ ///
+ public class Notification : INotification
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The title of the notification.
+ /// The message to be displayed in the notification.
+ /// The of the notification.
+ /// The expiry time at which the notification will close.
+ /// Use for notifications that will remain open.
+ /// An Action to call when the notification is clicked.
+ /// An Action to call when the notification is closed.
+ 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;
+ }
+
+ ///
+ public string Title { get; private set; }
+
+ ///
+ public string Message { get; private set; }
+
+ ///
+ public NotificationType Type { get; private set; }
+
+ ///
+ public TimeSpan Expiration { get; private set; }
+
+ ///
+ public Action OnClick { get; private set; }
+
+ ///
+ public Action OnClose { get; private set; }
+ }
+}
diff --git a/src/Avalonia.Controls/Notifications/NotificationCard.cs b/src/Avalonia.Controls/Notifications/NotificationCard.cs
new file mode 100644
index 0000000000..7f69afaeeb
--- /dev/null
+++ b/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
+{
+ ///
+ /// Control that represents and displays a notification.
+ ///
+ public class NotificationCard : ContentControl
+ {
+ private bool _isClosed;
+ private bool _isClosing;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public NotificationCard()
+ {
+ this.GetObservable(IsClosedProperty)
+ .Subscribe(x =>
+ {
+ if (!IsClosing && !IsClosed)
+ {
+ return;
+ }
+
+ RaiseEvent(new RoutedEventArgs(NotificationClosedEvent));
+ });
+
+ this.GetObservable(ContentProperty)
+ .OfType()
+ .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;
+ }
+ });
+ }
+
+ ///
+ /// Determines if the notification is already closing.
+ ///
+ public bool IsClosing
+ {
+ get { return _isClosing; }
+ private set { SetAndRaise(IsClosingProperty, ref _isClosing, value); }
+ }
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly DirectProperty IsClosingProperty =
+ AvaloniaProperty.RegisterDirect(nameof(IsClosing), o => o.IsClosing);
+
+ ///
+ /// Determines if the notification is closed.
+ ///
+ public bool IsClosed
+ {
+ get { return _isClosed; }
+ set { SetAndRaise(IsClosedProperty, ref _isClosed, value); }
+ }
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly DirectProperty IsClosedProperty =
+ AvaloniaProperty.RegisterDirect(nameof(IsClosed), o => o.IsClosed, (o, v) => o.IsClosed = v);
+
+ ///
+ /// Defines the event.
+ ///
+ public static readonly RoutedEvent NotificationClosedEvent =
+ RoutedEvent.Register(nameof(NotificationClosed), RoutingStrategies.Bubble);
+
+
+ ///
+ /// Raised when the has closed.
+ ///
+ public event EventHandler 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);
+ }
+
+ ///
+ /// Defines the CloseOnClick property.
+ ///
+ public static readonly AvaloniaProperty CloseOnClickProperty =
+ AvaloniaProperty.RegisterAttached