diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml
index 5a376b8c39..ca210300ee 100644
--- a/samples/ControlCatalog/MainView.xaml
+++ b/samples/ControlCatalog/MainView.xaml
@@ -29,6 +29,9 @@
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
+
+
+
@@ -54,6 +57,7 @@
+
diff --git a/samples/ControlCatalog/Pages/DateTimePickerPage.xaml b/samples/ControlCatalog/Pages/DateTimePickerPage.xaml
new file mode 100644
index 0000000000..af6b6e8605
--- /dev/null
+++ b/samples/ControlCatalog/Pages/DateTimePickerPage.xaml
@@ -0,0 +1,123 @@
+
+
+ DatePicker and TimePicker
+
+
+
+
+ A simple DatePicker with a header
+
+
+
+
+
+
+
+
+ <DatePicker Header="Pick a date" />
+
+
+
+
+
+
+ A DatePicker with day formatted and year hidden.
+
+
+
+
+
+
+
+
+ <DatePicker DayFormat="d (ddd)" YearVisible="False" />
+
+
+
+
+
+
+
+
+ A simple TimePicker.
+
+
+
+
+
+
+
+
+ <TimePicker />
+
+
+
+
+
+
+ A TimePicker with a header and minute increments specified.
+
+
+
+
+
+
+
+
+ <TimePicker Header="Arrival time" MinuteIncrement="15" />
+
+
+
+
+
+
+ A TimePicker using a 12-hour clock.
+
+
+
+
+
+
+
+
+ <TimePicker ClockIdentifier="12HourClock" Header="12 hour clock" />
+
+
+
+
+
+
+ A TimePicker using a 24-hour clock.
+
+
+
+
+
+
+
+
+ <TimePicker ClockIdentifier="24HourClock" Header="24 hour clock" />
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs b/samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs
new file mode 100644
index 0000000000..6c7ae3437e
--- /dev/null
+++ b/samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs
@@ -0,0 +1,30 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace ControlCatalog.Pages
+{
+ public class DateTimePickerPage : UserControl
+ {
+ public DateTimePickerPage()
+ {
+ this.InitializeComponent();
+ this.FindControl("DatePickerDesc").Text = "Use a DatePicker to let users set a date in your app, " +
+ "for example to schedule an appointment. The DatePicker displays three controls for month, day, and year. " +
+ "These controls are easy to use with touch or mouse, and they can be styled and configured in several different ways. " +
+ "Order of month, day, and year is dynamically set based on user date settings";
+
+ this.FindControl("TimePickerDesc").Text = "Use a TimePicker to let users set a time in your app, for example " +
+ "to set a reminder. The TimePicker displays three controls for hour, minute, and AM / PM(if necessary).These controls " +
+ "are easy to use with touch or mouse, and they can be styled and configured in several different ways. " +
+ "12 - hour or 24 - hour clock and visiblility of AM / PM is dynamically set based on user time settings, or can be overridden.";
+
+
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/samples/ControlCatalog/Pages/SplitViewPage.xaml b/samples/ControlCatalog/Pages/SplitViewPage.xaml
new file mode 100644
index 0000000000..7e629db2da
--- /dev/null
+++ b/samples/ControlCatalog/Pages/SplitViewPage.xaml
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Inline
+ CompactInline
+ Overlay
+ CompactOverlay
+
+
+
+
+ SystemControlBackgroundChromeMediumLowBrush
+ Red
+ Blue
+ Green
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/SplitViewPage.xaml.cs b/samples/ControlCatalog/Pages/SplitViewPage.xaml.cs
new file mode 100644
index 0000000000..cbf217c94a
--- /dev/null
+++ b/samples/ControlCatalog/Pages/SplitViewPage.xaml.cs
@@ -0,0 +1,21 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using ControlCatalog.ViewModels;
+
+namespace ControlCatalog.Pages
+{
+ public class SplitViewPage : UserControl
+ {
+ public SplitViewPage()
+ {
+ this.InitializeComponent();
+ DataContext = new SplitViewPageViewModel();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/samples/ControlCatalog/ViewModels/SplitViewPageViewModel.cs b/samples/ControlCatalog/ViewModels/SplitViewPageViewModel.cs
new file mode 100644
index 0000000000..f27f605a8b
--- /dev/null
+++ b/samples/ControlCatalog/ViewModels/SplitViewPageViewModel.cs
@@ -0,0 +1,46 @@
+using System;
+using Avalonia.Controls;
+using ReactiveUI;
+
+namespace ControlCatalog.ViewModels
+{
+ public class SplitViewPageViewModel : ReactiveObject
+ {
+ private bool _isLeft = true;
+ private int _displayMode = 3; //CompactOverlay
+
+ public bool IsLeft
+ {
+ get => _isLeft;
+ set
+ {
+ this.RaiseAndSetIfChanged(ref _isLeft, value);
+ this.RaisePropertyChanged(nameof(PanePlacement));
+ }
+ }
+
+ public int DisplayMode
+ {
+ get => _displayMode;
+ set
+ {
+ this.RaiseAndSetIfChanged(ref _displayMode, value);
+ this.RaisePropertyChanged(nameof(CurrentDisplayMode));
+ }
+ }
+
+ public SplitViewPanePlacement PanePlacement => _isLeft ? SplitViewPanePlacement.Left : SplitViewPanePlacement.Right;
+
+ public SplitViewDisplayMode CurrentDisplayMode
+ {
+ get
+ {
+ if (Enum.IsDefined(typeof(SplitViewDisplayMode), _displayMode))
+ {
+ return (SplitViewDisplayMode)_displayMode;
+ }
+ return SplitViewDisplayMode.CompactOverlay;
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Animation/Easing/Easing.cs b/src/Avalonia.Animation/Easing/Easing.cs
index 5b0dea6c60..e006459652 100644
--- a/src/Avalonia.Animation/Easing/Easing.cs
+++ b/src/Avalonia.Animation/Easing/Easing.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
+using System.Globalization;
using System.Linq;
namespace Avalonia.Animation.Easings
@@ -25,6 +26,11 @@ namespace Avalonia.Animation.Easings
/// Returns the instance of the parsed type.
public static Easing Parse(string e)
{
+ if (e.Contains(','))
+ {
+ return new SplineEasing(KeySpline.Parse(e, CultureInfo.InvariantCulture));
+ }
+
if (_easingTypes == null)
{
_easingTypes = new Dictionary();
diff --git a/src/Avalonia.Animation/Easing/SplineEasing.cs b/src/Avalonia.Animation/Easing/SplineEasing.cs
new file mode 100644
index 0000000000..975fcc4746
--- /dev/null
+++ b/src/Avalonia.Animation/Easing/SplineEasing.cs
@@ -0,0 +1,85 @@
+namespace Avalonia.Animation.Easings
+{
+ ///
+ /// Eases a value
+ /// using a user-defined cubic bezier curve.
+ /// Good for custom easing functions that doesn't quite
+ /// fit with the built-in ones.
+ ///
+ public class SplineEasing : Easing
+ {
+ ///
+ /// X coordinate of the first control point
+ ///
+ public double X1
+ {
+ get => _internalKeySpline.ControlPointX1;
+ set
+ {
+ _internalKeySpline.ControlPointX1 = value;
+ }
+ }
+
+ ///
+ /// Y coordinate of the first control point
+ ///
+ public double Y1
+ {
+ get => _internalKeySpline.ControlPointY1;
+ set
+ {
+ _internalKeySpline.ControlPointY1 = value;
+ }
+ }
+
+ ///
+ /// X coordinate of the second control point
+ ///
+ public double X2
+ {
+ get => _internalKeySpline.ControlPointX2;
+ set
+ {
+ _internalKeySpline.ControlPointX2 = value;
+ }
+ }
+
+ ///
+ /// Y coordinate of the second control point
+ ///
+ public double Y2
+ {
+ get => _internalKeySpline.ControlPointY2;
+ set
+ {
+ _internalKeySpline.ControlPointY2 = value;
+ }
+ }
+
+ private readonly KeySpline _internalKeySpline;
+
+ public SplineEasing(double x1 = 0d, double y1 = 0d, double x2 = 1d, double y2 = 1d)
+ {
+ _internalKeySpline = new KeySpline();
+
+ this.X1 = x1;
+ this.Y1 = y1;
+ this.X2 = x2;
+ this.Y1 = y2;
+ }
+
+ public SplineEasing(KeySpline keySpline)
+ {
+ _internalKeySpline = keySpline;
+ }
+
+ public SplineEasing()
+ {
+ _internalKeySpline = new KeySpline();
+ }
+
+ ///
+ public override double Ease(double progress) =>
+ _internalKeySpline.GetSplineProgress(progress);
+ }
+}
diff --git a/src/Avalonia.Animation/KeySpline.cs b/src/Avalonia.Animation/KeySpline.cs
index 5a4f7a15a3..a6e9769186 100644
--- a/src/Avalonia.Animation/KeySpline.cs
+++ b/src/Avalonia.Animation/KeySpline.cs
@@ -81,7 +81,10 @@ namespace Avalonia.Animation
/// A with the appropriate values set
public static KeySpline Parse(string value, CultureInfo culture)
{
- using (var tokenizer = new StringTokenizer((string)value, CultureInfo.InvariantCulture, exceptionMessage: "Invalid KeySpline."))
+ if (culture is null)
+ culture = CultureInfo.InvariantCulture;
+
+ using (var tokenizer = new StringTokenizer((string)value, culture, exceptionMessage: $"Invalid KeySpline string: \"{value}\"."))
{
return new KeySpline(tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble());
}
@@ -98,6 +101,7 @@ namespace Avalonia.Animation
if (IsValidXValue(value))
{
_controlPointX1 = value;
+ _isDirty = true;
}
else
{
@@ -112,7 +116,11 @@ namespace Avalonia.Animation
public double ControlPointY1
{
get => _controlPointY1;
- set => _controlPointY1 = value;
+ set
+ {
+ _controlPointY1 = value;
+ _isDirty = true;
+ }
}
///
@@ -126,6 +134,7 @@ namespace Avalonia.Animation
if (IsValidXValue(value))
{
_controlPointX2 = value;
+ _isDirty = true;
}
else
{
@@ -140,7 +149,11 @@ namespace Avalonia.Animation
public double ControlPointY2
{
get => _controlPointY2;
- set => _controlPointY2 = value;
+ set
+ {
+ _controlPointY2 = value;
+ _isDirty = true;
+ }
}
///
@@ -330,20 +343,4 @@ namespace Avalonia.Animation
}
}
}
-
- ///
- /// Converts string values to values
- ///
- public class KeySplineTypeConverter : TypeConverter
- {
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- return sourceType == typeof(string);
- }
-
- public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
- {
- return KeySpline.Parse((string)value, culture);
- }
- }
}
diff --git a/src/Avalonia.Animation/KeySplineTypeConverter.cs b/src/Avalonia.Animation/KeySplineTypeConverter.cs
new file mode 100644
index 0000000000..cd7427a37d
--- /dev/null
+++ b/src/Avalonia.Animation/KeySplineTypeConverter.cs
@@ -0,0 +1,25 @@
+using System;
+using System.ComponentModel;
+using System.Globalization;
+
+// Ported from WPF open-source code.
+// https://github.com/dotnet/wpf/blob/ae1790531c3b993b56eba8b1f0dd395a3ed7de75/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/Animation/KeySpline.cs
+
+namespace Avalonia.Animation
+{
+ ///
+ /// Converts string values to values
+ ///
+ public class KeySplineTypeConverter : TypeConverter
+ {
+ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+ {
+ return sourceType == typeof(string);
+ }
+
+ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+ {
+ return KeySpline.Parse((string)value, culture);
+ }
+ }
+}
diff --git a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs
new file mode 100644
index 0000000000..5d3311e8c6
--- /dev/null
+++ b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs
@@ -0,0 +1,412 @@
+using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Shapes;
+using Avalonia.Controls.Templates;
+using Avalonia.Interactivity;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+
+namespace Avalonia.Controls
+{
+ ///
+ /// A control to allow the user to select a date
+ ///
+ public class DatePicker : TemplatedControl
+ {
+ ///
+ /// Define the Property
+ ///
+ public static readonly DirectProperty DayFormatProperty =
+ AvaloniaProperty.RegisterDirect(nameof(DayFormat),
+ x => x.DayFormat, (x, v) => x.DayFormat = v);
+
+ ///
+ /// Defines the Property
+ ///
+ public static readonly DirectProperty DayVisibleProperty =
+ AvaloniaProperty.RegisterDirect(nameof(DayVisible),
+ x => x.DayVisible, (x, v) => x.DayVisible = v);
+
+ ///
+ /// Defines the Property
+ ///
+ public static readonly StyledProperty