Browse Source

Merge remote-tracking branch 'origin/master' into feature/fluent-repeatbutton2

pull/4110/head
Dan Walmsley 6 years ago
parent
commit
67d69e6d2b
  1. 3
      samples/ControlCatalog/MainView.xaml
  2. 18
      samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml
  3. 14
      samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml.cs
  4. 15
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  5. 9
      src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs
  6. 2
      src/Avalonia.Base/Properties/AssemblyInfo.cs
  7. 94
      src/Avalonia.Base/Utilities/MathUtilities.cs
  8. 4
      src/Avalonia.Controls/Calendar/Calendar.cs
  9. 134
      src/Avalonia.Controls/Calendar/CalendarDatePicker.cs
  10. 4
      src/Avalonia.Controls/Calendar/CalendarItem.cs
  11. 35
      src/Avalonia.Controls/Grid.cs
  12. 3
      src/Avalonia.Controls/Slider.cs
  13. 3
      src/Avalonia.Controls/Utils/BorderRenderHelper.cs
  14. 4
      src/Avalonia.Themes.Default/CalendarDatePicker.xaml
  15. 2
      src/Avalonia.Themes.Default/DefaultTheme.xaml
  16. 4
      src/Avalonia.Themes.Fluent/CalendarDatePicker.xaml
  17. 2
      src/Avalonia.Themes.Fluent/FluentTheme.xaml
  18. 5
      src/Avalonia.Visuals/Media/DrawingContext.cs
  19. 3
      src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs
  20. 2
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  21. 2
      tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs
  22. 119
      tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs
  23. 47
      tests/Avalonia.Benchmarks/Data/AccessorTestObject.cs
  24. 54
      tests/Avalonia.Benchmarks/Data/PropertyAccessorBenchmarks.cs
  25. 60
      tests/Avalonia.Benchmarks/Data/PropertyAccessorPluginBenchmarks.cs
  26. 14
      tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs
  27. 2
      tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Property.cs

3
samples/ControlCatalog/MainView.xaml

@ -29,7 +29,8 @@
ScrollViewer.HorizontalScrollBarVisibility="Disabled"> ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<pages:DataGridPage/> <pages:DataGridPage/>
</TabItem> </TabItem>
<TabItem Header="DatePicker"><pages:DatePickerPage/></TabItem> <TabItem Header="CalendarDatePicker">
<pages:CalendarDatePickerPage/></TabItem>
<TabItem Header="Drag+Drop"><pages:DragAndDropPage/></TabItem> <TabItem Header="Drag+Drop"><pages:DragAndDropPage/></TabItem>
<TabItem Header="Expander"><pages:ExpanderPage/></TabItem> <TabItem Header="Expander"><pages:ExpanderPage/></TabItem>
<TabItem Header="Image" <TabItem Header="Image"

18
samples/ControlCatalog/Pages/DatePickerPage.xaml → samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml

@ -1,8 +1,8 @@
<UserControl xmlns="https://github.com/avaloniaui" <UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.DatePickerPage"> x:Class="ControlCatalog.Pages.CalendarDatePickerPage">
<StackPanel Orientation="Vertical" Spacing="4"> <StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">DatePicker</TextBlock> <TextBlock Classes="h1">CalendarDatePicker</TextBlock>
<TextBlock Classes="h2">A control for selecting dates with a calendar drop-down</TextBlock> <TextBlock Classes="h2">A control for selecting dates with a calendar drop-down</TextBlock>
<StackPanel Orientation="Horizontal" <StackPanel Orientation="Horizontal"
@ -12,34 +12,34 @@
<StackPanel Orientation="Vertical" <StackPanel Orientation="Vertical"
Width="200"> Width="200">
<TextBlock Text="SelectedDateFormat: Short"/> <TextBlock Text="SelectedDateFormat: Short"/>
<DatePicker Name="DatePicker1" <CalendarDatePicker Name="DatePicker1"
SelectedDateFormat="Short" SelectedDateFormat="Short"
Margin="0,0,0,8"/> Margin="0,0,0,8"/>
<TextBlock Text="SelectedDateFormat: Long"/> <TextBlock Text="SelectedDateFormat: Long"/>
<DatePicker Name="DatePicker2" <CalendarDatePicker Name="DatePicker2"
SelectedDateFormat="Long" SelectedDateFormat="Long"
Margin="0,0,0,8"/> Margin="0,0,0,8"/>
<TextBlock Text="SelectedDateFormat: Custom"/> <TextBlock Text="SelectedDateFormat: Custom"/>
<DatePicker Name="DatePicker3" <CalendarDatePicker Name="DatePicker3"
SelectedDateFormat="Custom" SelectedDateFormat="Custom"
CustomDateFormatString="ddd, MMM d" CustomDateFormatString="ddd, MMM d"
Margin="0,0,0,8"/> Margin="0,0,0,8"/>
<TextBlock Text="Blackout Dates"/> <TextBlock Text="Blackout Dates"/>
<DatePicker Name="DatePicker4" <CalendarDatePicker Name="DatePicker4"
Margin="0,0,0,8"/> Margin="0,0,0,8"/>
<DatePicker Margin="0,0,0,8" <CalendarDatePicker Margin="0,0,0,8"
Watermark="Watermark"/> Watermark="Watermark"/>
<DatePicker Margin="0,0,0,8" <CalendarDatePicker Margin="0,0,0,8"
Name="DatePicker5" Name="DatePicker5"
Watermark="Floating Watermark" Watermark="Floating Watermark"
UseFloatingWatermark="True"/> UseFloatingWatermark="True"/>
<TextBlock Text="Disabled"/> <TextBlock Text="Disabled"/>
<DatePicker IsEnabled="False"/> <CalendarDatePicker IsEnabled="False"/>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>

14
samples/ControlCatalog/Pages/DatePickerPage.xaml.cs → samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml.cs

@ -4,17 +4,17 @@ using System;
namespace ControlCatalog.Pages namespace ControlCatalog.Pages
{ {
public class DatePickerPage : UserControl public class CalendarDatePickerPage : UserControl
{ {
public DatePickerPage() public CalendarDatePickerPage()
{ {
InitializeComponent(); InitializeComponent();
var dp1 = this.FindControl<DatePicker>("DatePicker1"); var dp1 = this.FindControl<CalendarDatePicker>("DatePicker1");
var dp2 = this.FindControl<DatePicker>("DatePicker2"); var dp2 = this.FindControl<CalendarDatePicker>("DatePicker2");
var dp3 = this.FindControl<DatePicker>("DatePicker3"); var dp3 = this.FindControl<CalendarDatePicker>("DatePicker3");
var dp4 = this.FindControl<DatePicker>("DatePicker4"); var dp4 = this.FindControl<CalendarDatePicker>("DatePicker4");
var dp5 = this.FindControl<DatePicker>("DatePicker5"); var dp5 = this.FindControl<CalendarDatePicker>("DatePicker5");
dp1.SelectedDate = DateTime.Today; dp1.SelectedDate = DateTime.Today;
dp2.SelectedDate = DateTime.Today.AddDays(10); dp2.SelectedDate = DateTime.Today.AddDays(10);

15
src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs

@ -12,7 +12,7 @@ namespace Avalonia.Data.Core.Plugins
public class InpcPropertyAccessorPlugin : IPropertyAccessorPlugin public class InpcPropertyAccessorPlugin : IPropertyAccessorPlugin
{ {
/// <inheritdoc/> /// <inheritdoc/>
public bool Match(object obj, string propertyName) => true; public bool Match(object obj, string propertyName) => GetPropertyWithName(obj.GetType(), propertyName) != null;
/// <summary> /// <summary>
/// Starts monitoring the value of a property on an object. /// Starts monitoring the value of a property on an object.
@ -30,10 +30,7 @@ namespace Avalonia.Data.Core.Plugins
reference.TryGetTarget(out object instance); reference.TryGetTarget(out object instance);
const BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | var p = GetPropertyWithName(instance.GetType(), propertyName);
BindingFlags.Static | BindingFlags.Instance;
var p = instance.GetType().GetProperty(propertyName, bindingFlags);
if (p != null) if (p != null)
{ {
@ -47,6 +44,14 @@ namespace Avalonia.Data.Core.Plugins
} }
} }
private static PropertyInfo GetPropertyWithName(Type type, string propertyName)
{
const BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Public |
BindingFlags.Static | BindingFlags.Instance;
return type.GetProperty(propertyName, bindingFlags);
}
private class Accessor : PropertyAccessorBase, IWeakSubscriber<PropertyChangedEventArgs> private class Accessor : PropertyAccessorBase, IWeakSubscriber<PropertyChangedEventArgs>
{ {
private readonly WeakReference<object> _reference; private readonly WeakReference<object> _reference;

9
src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs

@ -62,8 +62,13 @@ namespace Avalonia.Data.Core
if (accessor == null) if (accessor == null)
{ {
throw new NotSupportedException( reference.TryGetTarget(out object instance);
$"Could not find a matching property accessor for {PropertyName}.");
var message = $"Could not find a matching property accessor for '{PropertyName}' on '{instance}'";
var exception = new MissingMemberException(message);
accessor = new PropertyError(new BindingNotification(exception, BindingErrorType.Error));
} }
_accessor = accessor; _accessor = accessor;

2
src/Avalonia.Base/Properties/AssemblyInfo.cs

@ -7,4 +7,4 @@ using Avalonia.Metadata;
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Data.Converters")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Data.Converters")]
[assembly: InternalsVisibleTo("Avalonia.Base.UnitTests")] [assembly: InternalsVisibleTo("Avalonia.Base.UnitTests")]
[assembly: InternalsVisibleTo("Avalonia.UnitTests")] [assembly: InternalsVisibleTo("Avalonia.UnitTests")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]

94
src/Avalonia.Base/Utilities/MathUtilities.cs

@ -8,6 +8,11 @@ namespace Avalonia.Utilities
/// </summary> /// </summary>
public static class MathUtilities public static class MathUtilities
{ {
// smallest such that 1.0+DoubleEpsilon != 1.0
private const double DoubleEpsilon = 2.2204460492503131e-016;
private const float FloatEpsilon = 1.192092896e-07F;
/// <summary> /// <summary>
/// AreClose - Returns whether or not two doubles are "close". That is, whether or /// AreClose - Returns whether or not two doubles are "close". That is, whether or
/// not they are within epsilon of each other. /// not they are within epsilon of each other.
@ -18,11 +23,26 @@ namespace Avalonia.Utilities
{ {
//in case they are Infinities (then epsilon check does not work) //in case they are Infinities (then epsilon check does not work)
if (value1 == value2) return true; if (value1 == value2) return true;
double eps = (Math.Abs(value1) + Math.Abs(value2) + 10.0) * double.Epsilon; double eps = (Math.Abs(value1) + Math.Abs(value2) + 10.0) * DoubleEpsilon;
double delta = value1 - value2; double delta = value1 - value2;
return (-eps < delta) && (eps > delta); return (-eps < delta) && (eps > delta);
} }
/// <summary>
/// AreClose - Returns whether or not two floats are "close". That is, whether or
/// not they are within epsilon of each other.
/// </summary>
/// <param name="value1"> The first float to compare. </param>
/// <param name="value2"> The second float to compare. </param>
public static bool AreClose(float value1, float value2)
{
//in case they are Infinities (then epsilon check does not work)
if (value1 == value2) return true;
float eps = (Math.Abs(value1) + Math.Abs(value2) + 10.0f) * FloatEpsilon;
float delta = value1 - value2;
return (-eps < delta) && (eps > delta);
}
/// <summary> /// <summary>
/// LessThan - Returns whether or not the first double is less than the second double. /// LessThan - Returns whether or not the first double is less than the second double.
/// That is, whether or not the first is strictly less than *and* not within epsilon of /// That is, whether or not the first is strictly less than *and* not within epsilon of
@ -35,6 +55,18 @@ namespace Avalonia.Utilities
return (value1 < value2) && !AreClose(value1, value2); return (value1 < value2) && !AreClose(value1, value2);
} }
/// <summary>
/// LessThan - Returns whether or not the first float is less than the second float.
/// That is, whether or not the first is strictly less than *and* not within epsilon of
/// the other number.
/// </summary>
/// <param name="value1"> The first single float to compare. </param>
/// <param name="value2"> The second single float to compare. </param>
public static bool LessThan(float value1, float value2)
{
return (value1 < value2) && !AreClose(value1, value2);
}
/// <summary> /// <summary>
/// GreaterThan - Returns whether or not the first double is greater than the second double. /// GreaterThan - Returns whether or not the first double is greater than the second double.
/// That is, whether or not the first is strictly greater than *and* not within epsilon of /// That is, whether or not the first is strictly greater than *and* not within epsilon of
@ -47,6 +79,18 @@ namespace Avalonia.Utilities
return (value1 > value2) && !AreClose(value1, value2); return (value1 > value2) && !AreClose(value1, value2);
} }
/// <summary>
/// GreaterThan - Returns whether or not the first float is greater than the second float.
/// That is, whether or not the first is strictly greater than *and* not within epsilon of
/// the other number.
/// </summary>
/// <param name="value1"> The first float to compare. </param>
/// <param name="value2"> The second float to compare. </param>
public static bool GreaterThan(float value1, float value2)
{
return (value1 > value2) && !AreClose(value1, value2);
}
/// <summary> /// <summary>
/// LessThanOrClose - Returns whether or not the first double is less than or close to /// LessThanOrClose - Returns whether or not the first double is less than or close to
/// the second double. That is, whether or not the first is strictly less than or within /// the second double. That is, whether or not the first is strictly less than or within
@ -59,6 +103,18 @@ namespace Avalonia.Utilities
return (value1 < value2) || AreClose(value1, value2); return (value1 < value2) || AreClose(value1, value2);
} }
/// <summary>
/// LessThanOrClose - Returns whether or not the first float is less than or close to
/// the second float. That is, whether or not the first is strictly less than or within
/// epsilon of the other number.
/// </summary>
/// <param name="value1"> The first float to compare. </param>
/// <param name="value2"> The second float to compare. </param>
public static bool LessThanOrClose(float value1, float value2)
{
return (value1 < value2) || AreClose(value1, value2);
}
/// <summary> /// <summary>
/// GreaterThanOrClose - Returns whether or not the first double is greater than or close to /// GreaterThanOrClose - Returns whether or not the first double is greater than or close to
/// the second double. That is, whether or not the first is strictly greater than or within /// the second double. That is, whether or not the first is strictly greater than or within
@ -71,6 +127,18 @@ namespace Avalonia.Utilities
return (value1 > value2) || AreClose(value1, value2); return (value1 > value2) || AreClose(value1, value2);
} }
/// <summary>
/// GreaterThanOrClose - Returns whether or not the first float is greater than or close to
/// the second float. That is, whether or not the first is strictly greater than or within
/// epsilon of the other number.
/// </summary>
/// <param name="value1"> The first float to compare. </param>
/// <param name="value2"> The second float to compare. </param>
public static bool GreaterThanOrClose(float value1, float value2)
{
return (value1 > value2) || AreClose(value1, value2);
}
/// <summary> /// <summary>
/// IsOne - Returns whether or not the double is "close" to 1. Same as AreClose(double, 1), /// IsOne - Returns whether or not the double is "close" to 1. Same as AreClose(double, 1),
/// but this is faster. /// but this is faster.
@ -78,7 +146,17 @@ namespace Avalonia.Utilities
/// <param name="value"> The double to compare to 1. </param> /// <param name="value"> The double to compare to 1. </param>
public static bool IsOne(double value) public static bool IsOne(double value)
{ {
return Math.Abs(value - 1.0) < 10.0 * double.Epsilon; return Math.Abs(value - 1.0) < 10.0 * DoubleEpsilon;
}
/// <summary>
/// IsOne - Returns whether or not the float is "close" to 1. Same as AreClose(float, 1),
/// but this is faster.
/// </summary>
/// <param name="value"> The float to compare to 1. </param>
public static bool IsOne(float value)
{
return Math.Abs(value - 1.0f) < 10.0f * FloatEpsilon;
} }
/// <summary> /// <summary>
@ -88,7 +166,17 @@ namespace Avalonia.Utilities
/// <param name="value"> The double to compare to 0. </param> /// <param name="value"> The double to compare to 0. </param>
public static bool IsZero(double value) public static bool IsZero(double value)
{ {
return Math.Abs(value) < 10.0 * double.Epsilon; return Math.Abs(value) < 10.0 * DoubleEpsilon;
}
/// <summary>
/// IsZero - Returns whether or not the float is "close" to 0. Same as AreClose(float, 0),
/// but this is faster.
/// </summary>
/// <param name="value"> The float to compare to 0. </param>
public static bool IsZero(float value)
{
return Math.Abs(value) < 10.0f * FloatEpsilon;
} }
/// <summary> /// <summary>

4
src/Avalonia.Controls/Calendar/Calendar.cs

@ -998,10 +998,10 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Gets or sets a value indicating whether DatePicker should change its /// Gets or sets a value indicating whether CalendarDatePicker should change its
/// DisplayDate because of a SelectedDate change on its Calendar. /// DisplayDate because of a SelectedDate change on its Calendar.
/// </summary> /// </summary>
internal bool DatePickerDisplayDateFlag { get; set; } internal bool CalendarDatePickerDisplayDateFlag { get; set; }
internal CalendarDayButton FindDayButtonFromDay(DateTime day) internal CalendarDayButton FindDayButtonFromDay(DateTime day)
{ {

134
src/Avalonia.Controls/Calendar/DatePicker.cs → src/Avalonia.Controls/Calendar/CalendarDatePicker.cs

@ -16,29 +16,29 @@ namespace Avalonia.Controls
{ {
/// <summary> /// <summary>
/// Provides data for the /// Provides data for the
/// <see cref="E:Avalonia.Controls.DatePicker.DateValidationError" /> /// <see cref="E:Avalonia.Controls.CalendarDatePicker.DateValidationError" />
/// event. /// event.
/// </summary> /// </summary>
public class DatePickerDateValidationErrorEventArgs : EventArgs public class CalendarDatePickerDateValidationErrorEventArgs : EventArgs
{ {
private bool _throwException; private bool _throwException;
/// <summary> /// <summary>
/// Initializes a new instance of the /// Initializes a new instance of the
/// <see cref="T:Avalonia.Controls.DatePickerDateValidationErrorEventArgs" /> /// <see cref="T:Avalonia.Controls.CalendarDatePickerDateValidationErrorEventArgs" />
/// class. /// class.
/// </summary> /// </summary>
/// <param name="exception"> /// <param name="exception">
/// The initial exception from the /// The initial exception from the
/// <see cref="E:Avalonia.Controls.DatePicker.DateValidationError" /> /// <see cref="E:Avalonia.Controls.CalendarDatePicker.DateValidationError" />
/// event. /// event.
/// </param> /// </param>
/// <param name="text"> /// <param name="text">
/// The text that caused the /// The text that caused the
/// <see cref="E:Avalonia.Controls.DatePicker.DateValidationError" /> /// <see cref="E:Avalonia.Controls.CalendarDatePicker.DateValidationError" />
/// event. /// event.
/// </param> /// </param>
public DatePickerDateValidationErrorEventArgs(Exception exception, string text) public CalendarDatePickerDateValidationErrorEventArgs(Exception exception, string text)
{ {
this.Text = text; this.Text = text;
this.Exception = exception; this.Exception = exception;
@ -46,7 +46,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Gets the initial exception associated with the /// Gets the initial exception associated with the
/// <see cref="E:Avalonia.Controls.DatePicker.DateValidationError" /> /// <see cref="E:Avalonia.Controls.CalendarDatePicker.DateValidationError" />
/// event. /// event.
/// </summary> /// </summary>
/// <value> /// <value>
@ -56,7 +56,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Gets the text that caused the /// Gets the text that caused the
/// <see cref="E:Avalonia.Controls.DatePicker.DateValidationError" /> /// <see cref="E:Avalonia.Controls.CalendarDatePicker.DateValidationError" />
/// event. /// event.
/// </summary> /// </summary>
/// <value> /// <value>
@ -66,7 +66,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Gets or sets a value indicating whether /// Gets or sets a value indicating whether
/// <see cref="P:Avalonia.Controls.DatePickerDateValidationErrorEventArgs.Exception" /> /// <see cref="P:Avalonia.Controls.CalendarDatePickerDateValidationErrorEventArgs.Exception" />
/// should be thrown. /// should be thrown.
/// </summary> /// </summary>
/// <value> /// <value>
@ -74,7 +74,7 @@ namespace Avalonia.Controls
/// </value> /// </value>
/// <exception cref="T:System.ArgumentException"> /// <exception cref="T:System.ArgumentException">
/// If set to true and /// If set to true and
/// <see cref="P:Avalonia.Controls.DatePickerDateValidationErrorEventArgs.Exception" /> /// <see cref="P:Avalonia.Controls.CalendarDatePickerDateValidationErrorEventArgs.Exception" />
/// is null. /// is null.
/// </exception> /// </exception>
public bool ThrowException public bool ThrowException
@ -93,9 +93,9 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Specifies date formats for a /// Specifies date formats for a
/// <see cref="T:Avalonia.Controls.DatePicker" />. /// <see cref="T:Avalonia.Controls.CalendarDatePicker" />.
/// </summary> /// </summary>
public enum DatePickerFormat public enum CalendarDatePickerFormat
{ {
/// <summary> /// <summary>
/// Specifies that the date should be displayed using unabbreviated days /// Specifies that the date should be displayed using unabbreviated days
@ -115,7 +115,7 @@ namespace Avalonia.Controls
Custom = 2 Custom = 2
} }
public class DatePicker : TemplatedControl public class CalendarDatePicker : TemplatedControl
{ {
private const string ElementTextBox = "PART_TextBox"; private const string ElementTextBox = "PART_TextBox";
private const string ElementButton = "PART_Button"; private const string ElementButton = "PART_Button";
@ -154,59 +154,59 @@ namespace Avalonia.Controls
/// </value> /// </value>
public CalendarBlackoutDatesCollection BlackoutDates { get; private set; } public CalendarBlackoutDatesCollection BlackoutDates { get; private set; }
public static readonly DirectProperty<DatePicker, DateTime> DisplayDateProperty = public static readonly DirectProperty<CalendarDatePicker, DateTime> DisplayDateProperty =
AvaloniaProperty.RegisterDirect<DatePicker, DateTime>( AvaloniaProperty.RegisterDirect<CalendarDatePicker, DateTime>(
nameof(DisplayDate), nameof(DisplayDate),
o => o.DisplayDate, o => o.DisplayDate,
(o, v) => o.DisplayDate = v); (o, v) => o.DisplayDate = v);
public static readonly DirectProperty<DatePicker, DateTime?> DisplayDateStartProperty = public static readonly DirectProperty<CalendarDatePicker, DateTime?> DisplayDateStartProperty =
AvaloniaProperty.RegisterDirect<DatePicker, DateTime?>( AvaloniaProperty.RegisterDirect<CalendarDatePicker, DateTime?>(
nameof(DisplayDateStart), nameof(DisplayDateStart),
o => o.DisplayDateStart, o => o.DisplayDateStart,
(o, v) => o.DisplayDateStart = v); (o, v) => o.DisplayDateStart = v);
public static readonly DirectProperty<DatePicker, DateTime?> DisplayDateEndProperty = public static readonly DirectProperty<CalendarDatePicker, DateTime?> DisplayDateEndProperty =
AvaloniaProperty.RegisterDirect<DatePicker, DateTime?>( AvaloniaProperty.RegisterDirect<CalendarDatePicker, DateTime?>(
nameof(DisplayDateEnd), nameof(DisplayDateEnd),
o => o.DisplayDateEnd, o => o.DisplayDateEnd,
(o, v) => o.DisplayDateEnd = v); (o, v) => o.DisplayDateEnd = v);
public static readonly StyledProperty<DayOfWeek> FirstDayOfWeekProperty = public static readonly StyledProperty<DayOfWeek> FirstDayOfWeekProperty =
AvaloniaProperty.Register<DatePicker, DayOfWeek>(nameof(FirstDayOfWeek)); AvaloniaProperty.Register<CalendarDatePicker, DayOfWeek>(nameof(FirstDayOfWeek));
public static readonly DirectProperty<DatePicker, bool> IsDropDownOpenProperty = public static readonly DirectProperty<CalendarDatePicker, bool> IsDropDownOpenProperty =
AvaloniaProperty.RegisterDirect<DatePicker, bool>( AvaloniaProperty.RegisterDirect<CalendarDatePicker, bool>(
nameof(IsDropDownOpen), nameof(IsDropDownOpen),
o => o.IsDropDownOpen, o => o.IsDropDownOpen,
(o, v) => o.IsDropDownOpen = v); (o, v) => o.IsDropDownOpen = v);
public static readonly StyledProperty<bool> IsTodayHighlightedProperty = public static readonly StyledProperty<bool> IsTodayHighlightedProperty =
AvaloniaProperty.Register<DatePicker, bool>(nameof(IsTodayHighlighted)); AvaloniaProperty.Register<CalendarDatePicker, bool>(nameof(IsTodayHighlighted));
public static readonly DirectProperty<DatePicker, DateTime?> SelectedDateProperty = public static readonly DirectProperty<CalendarDatePicker, DateTime?> SelectedDateProperty =
AvaloniaProperty.RegisterDirect<DatePicker, DateTime?>( AvaloniaProperty.RegisterDirect<CalendarDatePicker, DateTime?>(
nameof(SelectedDate), nameof(SelectedDate),
o => o.SelectedDate, o => o.SelectedDate,
(o, v) => o.SelectedDate = v); (o, v) => o.SelectedDate = v);
public static readonly StyledProperty<DatePickerFormat> SelectedDateFormatProperty = public static readonly StyledProperty<CalendarDatePickerFormat> SelectedDateFormatProperty =
AvaloniaProperty.Register<DatePicker, DatePickerFormat>( AvaloniaProperty.Register<CalendarDatePicker, CalendarDatePickerFormat>(
nameof(SelectedDateFormat), nameof(SelectedDateFormat),
defaultValue: DatePickerFormat.Short, defaultValue: CalendarDatePickerFormat.Short,
validate: IsValidSelectedDateFormat); validate: IsValidSelectedDateFormat);
public static readonly StyledProperty<string> CustomDateFormatStringProperty = public static readonly StyledProperty<string> CustomDateFormatStringProperty =
AvaloniaProperty.Register<DatePicker, string>( AvaloniaProperty.Register<CalendarDatePicker, string>(
nameof(CustomDateFormatString), nameof(CustomDateFormatString),
defaultValue: "d", defaultValue: "d",
validate: IsValidDateFormatString); validate: IsValidDateFormatString);
public static readonly DirectProperty<DatePicker, string> TextProperty = public static readonly DirectProperty<CalendarDatePicker, string> TextProperty =
AvaloniaProperty.RegisterDirect<DatePicker, string>( AvaloniaProperty.RegisterDirect<CalendarDatePicker, string>(
nameof(Text), nameof(Text),
o => o.Text, o => o.Text,
(o, v) => o.Text = v); (o, v) => o.Text = v);
public static readonly StyledProperty<string> WatermarkProperty = public static readonly StyledProperty<string> WatermarkProperty =
TextBox.WatermarkProperty.AddOwner<DatePicker>(); TextBox.WatermarkProperty.AddOwner<CalendarDatePicker>();
public static readonly StyledProperty<bool> UseFloatingWatermarkProperty = public static readonly StyledProperty<bool> UseFloatingWatermarkProperty =
TextBox.UseFloatingWatermarkProperty.AddOwner<DatePicker>(); TextBox.UseFloatingWatermarkProperty.AddOwner<CalendarDatePicker>();
/// <summary> /// <summary>
@ -218,9 +218,9 @@ namespace Avalonia.Controls
/// </value> /// </value>
/// <exception cref="T:System.ArgumentOutOfRangeException"> /// <exception cref="T:System.ArgumentOutOfRangeException">
/// The specified date is not in the range defined by /// The specified date is not in the range defined by
/// <see cref="P:Avalonia.Controls.DatePicker.DisplayDateStart" /> /// <see cref="P:Avalonia.Controls.CalendarDatePicker.DisplayDateStart" />
/// and /// and
/// <see cref="P:Avalonia.Controls.DatePicker.DisplayDateEnd" />. /// <see cref="P:Avalonia.Controls.CalendarDatePicker.DisplayDateEnd" />.
/// </exception> /// </exception>
public DateTime DisplayDate public DateTime DisplayDate
{ {
@ -320,7 +320,7 @@ namespace Avalonia.Controls
/// <exception cref="T:System.ArgumentOutOfRangeException"> /// <exception cref="T:System.ArgumentOutOfRangeException">
/// An specified format is not valid. /// An specified format is not valid.
/// </exception> /// </exception>
public DatePickerFormat SelectedDateFormat public CalendarDatePickerFormat SelectedDateFormat
{ {
get { return GetValue(SelectedDateFormatProperty); } get { return GetValue(SelectedDateFormatProperty); }
set { SetValue(SelectedDateFormatProperty, value); } set { SetValue(SelectedDateFormatProperty, value); }
@ -380,33 +380,33 @@ namespace Avalonia.Controls
/// Occurs when <see cref="P:Avalonia.Controls.DatePicker.Text" /> /// Occurs when <see cref="P:Avalonia.Controls.DatePicker.Text" />
/// is assigned a value that cannot be interpreted as a date. /// is assigned a value that cannot be interpreted as a date.
/// </summary> /// </summary>
public event EventHandler<DatePickerDateValidationErrorEventArgs> DateValidationError; public event EventHandler<CalendarDatePickerDateValidationErrorEventArgs> DateValidationError;
/// <summary> /// <summary>
/// Occurs when the /// Occurs when the
/// <see cref="P:Avalonia.Controls.DatePicker.SelectedDate" /> /// <see cref="P:Avalonia.Controls.CalendarDatePicker.SelectedDate" />
/// property is changed. /// property is changed.
/// </summary> /// </summary>
public event EventHandler<SelectionChangedEventArgs> SelectedDateChanged; public event EventHandler<SelectionChangedEventArgs> SelectedDateChanged;
static DatePicker() static CalendarDatePicker()
{ {
FocusableProperty.OverrideDefaultValue<DatePicker>(true); FocusableProperty.OverrideDefaultValue<CalendarDatePicker>(true);
DisplayDateProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnDisplayDateChanged(e)); DisplayDateProperty.Changed.AddClassHandler<CalendarDatePicker>((x,e) => x.OnDisplayDateChanged(e));
DisplayDateStartProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnDisplayDateStartChanged(e)); DisplayDateStartProperty.Changed.AddClassHandler<CalendarDatePicker>((x,e) => x.OnDisplayDateStartChanged(e));
DisplayDateEndProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnDisplayDateEndChanged(e)); DisplayDateEndProperty.Changed.AddClassHandler<CalendarDatePicker>((x,e) => x.OnDisplayDateEndChanged(e));
IsDropDownOpenProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnIsDropDownOpenChanged(e)); IsDropDownOpenProperty.Changed.AddClassHandler<CalendarDatePicker>((x,e) => x.OnIsDropDownOpenChanged(e));
SelectedDateProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnSelectedDateChanged(e)); SelectedDateProperty.Changed.AddClassHandler<CalendarDatePicker>((x,e) => x.OnSelectedDateChanged(e));
SelectedDateFormatProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnSelectedDateFormatChanged(e)); SelectedDateFormatProperty.Changed.AddClassHandler<CalendarDatePicker>((x,e) => x.OnSelectedDateFormatChanged(e));
CustomDateFormatStringProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnCustomDateFormatStringChanged(e)); CustomDateFormatStringProperty.Changed.AddClassHandler<CalendarDatePicker>((x,e) => x.OnCustomDateFormatStringChanged(e));
TextProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnTextChanged(e)); TextProperty.Changed.AddClassHandler<CalendarDatePicker>((x,e) => x.OnTextChanged(e));
} }
/// <summary> /// <summary>
/// Initializes a new instance of the /// Initializes a new instance of the
/// <see cref="T:Avalonia.Controls.DatePicker" /> class. /// <see cref="T:Avalonia.Controls.DatePicker" /> class.
/// </summary> /// </summary>
public DatePicker() public CalendarDatePicker()
{ {
FirstDayOfWeek = DateTimeHelper.GetCurrentDateFormat().FirstDayOfWeek; FirstDayOfWeek = DateTimeHelper.GetCurrentDateFormat().FirstDayOfWeek;
_defaultText = string.Empty; _defaultText = string.Empty;
@ -662,12 +662,12 @@ namespace Avalonia.Controls
// change is coming from the Calendar UI itself, so, we // change is coming from the Calendar UI itself, so, we
// shouldn't change the DisplayDate since it will automatically // shouldn't change the DisplayDate since it will automatically
// be changed by the Calendar // be changed by the Calendar
if ((day.Month != DisplayDate.Month || day.Year != DisplayDate.Year) && (_calendar == null || !_calendar.DatePickerDisplayDateFlag)) if ((day.Month != DisplayDate.Month || day.Year != DisplayDate.Year) && (_calendar == null || !_calendar.CalendarDatePickerDisplayDateFlag))
{ {
DisplayDate = day; DisplayDate = day;
} }
if(_calendar != null) if(_calendar != null)
_calendar.DatePickerDisplayDateFlag = false; _calendar.CalendarDatePickerDisplayDateFlag = false;
} }
else else
{ {
@ -707,7 +707,7 @@ namespace Avalonia.Controls
} }
private void OnCustomDateFormatStringChanged(AvaloniaPropertyChangedEventArgs e) private void OnCustomDateFormatStringChanged(AvaloniaPropertyChangedEventArgs e)
{ {
if(SelectedDateFormat == DatePickerFormat.Custom) if(SelectedDateFormat == CalendarDatePickerFormat.Custom)
{ {
OnDateFormatChanged(); OnDateFormatChanged();
} }
@ -752,15 +752,15 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Raises the /// Raises the
/// <see cref="E:Avalonia.Controls.DatePicker.DateValidationError" /> /// <see cref="E:Avalonia.Controls.CalendarDatePicker.DateValidationError" />
/// event. /// event.
/// </summary> /// </summary>
/// <param name="e"> /// <param name="e">
/// A /// A
/// <see cref="T:Avalonia.Controls.DatePickerDateValidationErrorEventArgs" /> /// <see cref="T:Avalonia.Controls.CalendarDatePickerDateValidationErrorEventArgs" />
/// that contains the event data. /// that contains the event data.
/// </param> /// </param>
protected virtual void OnDateValidationError(DatePickerDateValidationErrorEventArgs e) protected virtual void OnDateValidationError(CalendarDatePickerDateValidationErrorEventArgs e)
{ {
DateValidationError?.Invoke(this, e); DateValidationError?.Invoke(this, e);
} }
@ -959,7 +959,7 @@ namespace Avalonia.Controls
} }
else else
{ {
var dateValidationError = new DatePickerDateValidationErrorEventArgs(new ArgumentOutOfRangeException(nameof(text), "SelectedDate value is not valid."), text); var dateValidationError = new CalendarDatePickerDateValidationErrorEventArgs(new ArgumentOutOfRangeException(nameof(text), "SelectedDate value is not valid."), text);
OnDateValidationError(dateValidationError); OnDateValidationError(dateValidationError);
if (dateValidationError.ThrowException) if (dateValidationError.ThrowException)
@ -970,7 +970,7 @@ namespace Avalonia.Controls
} }
catch (FormatException ex) catch (FormatException ex)
{ {
DatePickerDateValidationErrorEventArgs textParseError = new DatePickerDateValidationErrorEventArgs(ex, text); CalendarDatePickerDateValidationErrorEventArgs textParseError = new CalendarDatePickerDateValidationErrorEventArgs(ex, text);
OnDateValidationError(textParseError); OnDateValidationError(textParseError);
if (textParseError.ThrowException) if (textParseError.ThrowException)
@ -986,11 +986,11 @@ namespace Avalonia.Controls
switch (SelectedDateFormat) switch (SelectedDateFormat)
{ {
case DatePickerFormat.Short: case CalendarDatePickerFormat.Short:
return string.Format(CultureInfo.CurrentCulture, d.ToString(dtfi.ShortDatePattern, dtfi)); return string.Format(CultureInfo.CurrentCulture, d.ToString(dtfi.ShortDatePattern, dtfi));
case DatePickerFormat.Long: case CalendarDatePickerFormat.Long:
return string.Format(CultureInfo.CurrentCulture, d.ToString(dtfi.LongDatePattern, dtfi)); return string.Format(CultureInfo.CurrentCulture, d.ToString(dtfi.LongDatePattern, dtfi));
case DatePickerFormat.Custom: case CalendarDatePickerFormat.Custom:
return string.Format(CultureInfo.CurrentCulture, d.ToString(CustomDateFormatString, dtfi)); return string.Format(CultureInfo.CurrentCulture, d.ToString(CustomDateFormatString, dtfi));
} }
return null; return null;
@ -1118,12 +1118,12 @@ namespace Avalonia.Controls
switch (SelectedDateFormat) switch (SelectedDateFormat)
{ {
case DatePickerFormat.Long: case CalendarDatePickerFormat.Long:
{ {
watermarkText = string.Format(CultureInfo.CurrentCulture, watermarkFormat, dtfi.LongDatePattern.ToString()); watermarkText = string.Format(CultureInfo.CurrentCulture, watermarkFormat, dtfi.LongDatePattern.ToString());
break; break;
} }
case DatePickerFormat.Short: case CalendarDatePickerFormat.Short:
default: default:
{ {
watermarkText = string.Format(CultureInfo.CurrentCulture, watermarkFormat, dtfi.ShortDatePattern.ToString()); watermarkText = string.Format(CultureInfo.CurrentCulture, watermarkFormat, dtfi.ShortDatePattern.ToString());
@ -1139,11 +1139,11 @@ namespace Avalonia.Controls
} }
} }
private static bool IsValidSelectedDateFormat(DatePickerFormat value) private static bool IsValidSelectedDateFormat(CalendarDatePickerFormat value)
{ {
return value == DatePickerFormat.Long return value == CalendarDatePickerFormat.Long
|| value == DatePickerFormat.Short || value == CalendarDatePickerFormat.Short
|| value == DatePickerFormat.Custom; || value == CalendarDatePickerFormat.Custom;
} }
private static bool IsValidDateFormatString(string formatString) private static bool IsValidDateFormatString(string formatString)
{ {

4
src/Avalonia.Controls/Calendar/CalendarItem.cs

@ -909,7 +909,7 @@ namespace Avalonia.Controls.Primitives
case CalendarSelectionMode.SingleDate: case CalendarSelectionMode.SingleDate:
{ {
DateTime selectedDate = (DateTime)b.DataContext; DateTime selectedDate = (DateTime)b.DataContext;
Owner.DatePickerDisplayDateFlag = true; Owner.CalendarDatePickerDisplayDateFlag = true;
if (Owner.SelectedDates.Count == 0) if (Owner.SelectedDates.Count == 0)
{ {
Owner.SelectedDates.Add(selectedDate); Owner.SelectedDates.Add(selectedDate);
@ -981,7 +981,7 @@ namespace Avalonia.Controls.Primitives
} }
case CalendarSelectionMode.SingleDate: case CalendarSelectionMode.SingleDate:
{ {
Owner.DatePickerDisplayDateFlag = true; Owner.CalendarDatePickerDisplayDateFlag = true;
if (Owner.SelectedDates.Count == 0) if (Owner.SelectedDates.Count == 0)
{ {
Owner.SelectedDates.Add(selectedDate); Owner.SelectedDates.Add(selectedDate);

35
src/Avalonia.Controls/Grid.cs

@ -1228,7 +1228,7 @@ namespace Avalonia.Controls
Debug.Assert(1 < count && 0 <= start && (start + count) <= definitions.Count); Debug.Assert(1 < count && 0 <= start && (start + count) <= definitions.Count);
// avoid processing when asked to distribute "0" // avoid processing when asked to distribute "0"
if (!_IsZero(requestedSize)) if (!MathUtilities.IsZero(requestedSize))
{ {
DefinitionBase[] tempDefinitions = TempDefinitions; // temp array used to remember definitions for sorting DefinitionBase[] tempDefinitions = TempDefinitions; // temp array used to remember definitions for sorting
int end = start + count; int end = start + count;
@ -1306,7 +1306,7 @@ namespace Avalonia.Controls
} }
// sanity check: requested size must all be distributed // sanity check: requested size must all be distributed
Debug.Assert(_IsZero(sizeToDistribute)); Debug.Assert(MathUtilities.IsZero(sizeToDistribute));
} }
else if (requestedSize <= rangeMaxSize) else if (requestedSize <= rangeMaxSize)
{ {
@ -1346,7 +1346,7 @@ namespace Avalonia.Controls
} }
// sanity check: requested size must all be distributed // sanity check: requested size must all be distributed
Debug.Assert(_IsZero(sizeToDistribute)); Debug.Assert(MathUtilities.IsZero(sizeToDistribute));
} }
else else
{ {
@ -1358,7 +1358,7 @@ namespace Avalonia.Controls
double equalSize = requestedSize / count; double equalSize = requestedSize / count;
if (equalSize < maxMaxSize if (equalSize < maxMaxSize
&& !_AreClose(equalSize, maxMaxSize)) && !MathUtilities.AreClose(equalSize, maxMaxSize))
{ {
// equi-size is less than maximum of maxSizes. // equi-size is less than maximum of maxSizes.
// in this case distribute so that smaller definitions grow faster than // in this case distribute so that smaller definitions grow faster than
@ -2151,7 +2151,7 @@ namespace Avalonia.Controls
// and precision of floating-point computation. (However, the resulting // and precision of floating-point computation. (However, the resulting
// display is subject to anti-aliasing problems. TANSTAAFL.) // display is subject to anti-aliasing problems. TANSTAAFL.)
if (!_AreClose(roundedTakenSize, finalSize)) if (!MathUtilities.AreClose(roundedTakenSize, finalSize))
{ {
// Compute deltas // Compute deltas
for (int i = 0; i < definitions.Count; ++i) for (int i = 0; i < definitions.Count; ++i)
@ -2168,7 +2168,7 @@ namespace Avalonia.Controls
if (roundedTakenSize > finalSize) if (roundedTakenSize > finalSize)
{ {
int i = definitions.Count - 1; int i = definitions.Count - 1;
while ((adjustedSize > finalSize && !_AreClose(adjustedSize, finalSize)) && i >= 0) while ((adjustedSize > finalSize && !MathUtilities.AreClose(adjustedSize, finalSize)) && i >= 0)
{ {
DefinitionBase definition = definitions[definitionIndices[i]]; DefinitionBase definition = definitions[definitionIndices[i]];
double final = definition.SizeCache - dpiIncrement; double final = definition.SizeCache - dpiIncrement;
@ -2184,7 +2184,7 @@ namespace Avalonia.Controls
else if (roundedTakenSize < finalSize) else if (roundedTakenSize < finalSize)
{ {
int i = 0; int i = 0;
while ((adjustedSize < finalSize && !_AreClose(adjustedSize, finalSize)) && i < definitions.Count) while ((adjustedSize < finalSize && !MathUtilities.AreClose(adjustedSize, finalSize)) && i < definitions.Count)
{ {
DefinitionBase definition = definitions[definitionIndices[i]]; DefinitionBase definition = definitions[definitionIndices[i]];
double final = definition.SizeCache + dpiIncrement; double final = definition.SizeCache + dpiIncrement;
@ -2595,27 +2595,6 @@ namespace Avalonia.Controls
set { SetFlags(value, Flags.HasGroup3CellsInAutoRows); } set { SetFlags(value, Flags.HasGroup3CellsInAutoRows); }
} }
/// <summary>
/// fp version of <c>d == 0</c>.
/// </summary>
/// <param name="d">Value to check.</param>
/// <returns><c>true</c> if d == 0.</returns>
private static bool _IsZero(double d)
{
return (Math.Abs(d) < double.Epsilon);
}
/// <summary>
/// fp version of <c>d1 == d2</c>
/// </summary>
/// <param name="d1">First value to compare</param>
/// <param name="d2">Second value to compare</param>
/// <returns><c>true</c> if d1 == d2</returns>
private static bool _AreClose(double d1, double d2)
{
return (Math.Abs(d1 - d2) < double.Epsilon);
}
/// <summary> /// <summary>
/// Returns reference to extended data bag. /// Returns reference to extended data bag.
/// </summary> /// </summary>

3
src/Avalonia.Controls/Slider.cs

@ -193,7 +193,8 @@ namespace Avalonia.Controls
var orient = Orientation == Orientation.Horizontal; var orient = Orientation == Orientation.Horizontal;
var pointDen = orient ? _track.Bounds.Width : _track.Bounds.Height; var pointDen = orient ? _track.Bounds.Width : _track.Bounds.Height;
pointDen += double.Epsilon; // Just add epsilon to avoid divide by zero exceptions. // Just add epsilon to avoid NaN in case 0/0
pointDen += double.Epsilon;
var pointNum = orient ? x.Position.X : x.Position.Y; var pointNum = orient ? x.Position.X : x.Position.Y;
var logicalPos = MathUtilities.Clamp(pointNum / pointDen, 0.0d, 1.0d); var logicalPos = MathUtilities.Clamp(pointNum / pointDen, 0.0d, 1.0d);

3
src/Avalonia.Controls/Utils/BorderRenderHelper.cs

@ -1,6 +1,7 @@
using System; using System;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Utilities;
namespace Avalonia.Controls.Utils namespace Avalonia.Controls.Utils
{ {
@ -119,7 +120,7 @@ namespace Avalonia.Controls.Utils
} }
var rect = new Rect(_size); var rect = new Rect(_size);
if (Math.Abs(borderThickness) > double.Epsilon) if (!MathUtilities.IsZero(borderThickness))
rect = rect.Deflate(borderThickness * 0.5); rect = rect.Deflate(borderThickness * 0.5);
var rrect = new RoundedRect(rect, _cornerRadius.TopLeft, _cornerRadius.TopRight, var rrect = new RoundedRect(rect, _cornerRadius.TopLeft, _cornerRadius.TopRight,
_cornerRadius.BottomRight, _cornerRadius.BottomLeft); _cornerRadius.BottomRight, _cornerRadius.BottomLeft);

4
src/Avalonia.Themes.Fluent/DatePicker.xaml → src/Avalonia.Themes.Default/CalendarDatePicker.xaml

@ -8,7 +8,7 @@
<Styles xmlns="https://github.com/avaloniaui" <Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard"> xmlns:sys="clr-namespace:System;assembly=netstandard">
<Style Selector="DatePicker"> <Style Selector="CalendarDatePicker">
<Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/> <Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/> <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
@ -119,7 +119,7 @@
</Setter> </Setter>
</Style> </Style>
<Style Selector="DatePicker:focus /template/ TextBox#PART_TextBox"> <Style Selector="CalendarDatePicker:focus /template/ TextBox#PART_TextBox">
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderHighBrush}"/> <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderHighBrush}"/>
</Style> </Style>

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

@ -45,7 +45,7 @@
<StyleInclude Source="resm:Avalonia.Themes.Default.CalendarDayButton.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.CalendarDayButton.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.CalendarItem.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.CalendarItem.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.Calendar.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.Calendar.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.DatePicker.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.CalendarDatePicker.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.ButtonSpinner.xaml?assembly=Avalonia.Themes.Default"/> <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.NumericUpDown.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.AutoCompleteBox.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.AutoCompleteBox.xaml?assembly=Avalonia.Themes.Default"/>

4
src/Avalonia.Themes.Default/DatePicker.xaml → src/Avalonia.Themes.Fluent/CalendarDatePicker.xaml

@ -8,7 +8,7 @@
<Styles xmlns="https://github.com/avaloniaui" <Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard"> xmlns:sys="clr-namespace:System;assembly=netstandard">
<Style Selector="DatePicker"> <Style Selector="CalendarDatePicker">
<Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/> <Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/> <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
@ -119,7 +119,7 @@
</Setter> </Setter>
</Style> </Style>
<Style Selector="DatePicker:focus /template/ TextBox#PART_TextBox"> <Style Selector="CalendarDatePicker:focus /template/ TextBox#PART_TextBox">
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderHighBrush}"/> <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderHighBrush}"/>
</Style> </Style>

2
src/Avalonia.Themes.Fluent/FluentTheme.xaml

@ -45,7 +45,7 @@
<StyleInclude Source="resm:Avalonia.Themes.Fluent.CalendarDayButton.xaml?assembly=Avalonia.Themes.Fluent"/> <StyleInclude Source="resm:Avalonia.Themes.Fluent.CalendarDayButton.xaml?assembly=Avalonia.Themes.Fluent"/>
<StyleInclude Source="resm:Avalonia.Themes.Fluent.CalendarItem.xaml?assembly=Avalonia.Themes.Fluent"/> <StyleInclude Source="resm:Avalonia.Themes.Fluent.CalendarItem.xaml?assembly=Avalonia.Themes.Fluent"/>
<StyleInclude Source="resm:Avalonia.Themes.Fluent.Calendar.xaml?assembly=Avalonia.Themes.Fluent"/> <StyleInclude Source="resm:Avalonia.Themes.Fluent.Calendar.xaml?assembly=Avalonia.Themes.Fluent"/>
<StyleInclude Source="resm:Avalonia.Themes.Fluent.DatePicker.xaml?assembly=Avalonia.Themes.Fluent"/> <StyleInclude Source="resm:Avalonia.Themes.Fluent.CalendarDatePicker.xaml?assembly=Avalonia.Themes.Fluent"/>
<StyleInclude Source="resm:Avalonia.Themes.Fluent.ButtonSpinner.xaml?assembly=Avalonia.Themes.Fluent"/> <StyleInclude Source="resm:Avalonia.Themes.Fluent.ButtonSpinner.xaml?assembly=Avalonia.Themes.Fluent"/>
<StyleInclude Source="resm:Avalonia.Themes.Fluent.NumericUpDown.xaml?assembly=Avalonia.Themes.Fluent"/> <StyleInclude Source="resm:Avalonia.Themes.Fluent.NumericUpDown.xaml?assembly=Avalonia.Themes.Fluent"/>
<StyleInclude Source="resm:Avalonia.Themes.Fluent.AutoCompleteBox.xaml?assembly=Avalonia.Themes.Fluent"/> <StyleInclude Source="resm:Avalonia.Themes.Fluent.AutoCompleteBox.xaml?assembly=Avalonia.Themes.Fluent"/>

5
src/Avalonia.Visuals/Media/DrawingContext.cs

@ -4,6 +4,7 @@ using Avalonia.Media.Imaging;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering.SceneGraph; using Avalonia.Rendering.SceneGraph;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.Utilities;
using Avalonia.Visuals.Media.Imaging; using Avalonia.Visuals.Media.Imaging;
namespace Avalonia.Media namespace Avalonia.Media
@ -154,12 +155,12 @@ namespace Avalonia.Media
return; return;
} }
if (Math.Abs(radiusX) > double.Epsilon) if (!MathUtilities.IsZero(radiusX))
{ {
radiusX = Math.Min(radiusX, rect.Width / 2); radiusX = Math.Min(radiusX, rect.Width / 2);
} }
if (Math.Abs(radiusY) > double.Epsilon) if (!MathUtilities.IsZero(radiusY))
{ {
radiusY = Math.Min(radiusY, rect.Height / 2); radiusY = Math.Min(radiusY, rect.Height / 2);
} }

3
src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs

@ -4,6 +4,7 @@ using System.Linq;
using Avalonia.Media.Immutable; using Avalonia.Media.Immutable;
using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Utilities;
using Avalonia.Utility; using Avalonia.Utility;
namespace Avalonia.Media.TextFormatting namespace Avalonia.Media.TextFormatting
@ -184,7 +185,7 @@ namespace Avalonia.Media.TextFormatting
/// </summary> /// </summary>
private void UpdateLayout() private void UpdateLayout()
{ {
if (_text.IsEmpty || Math.Abs(MaxWidth) < double.Epsilon || Math.Abs(MaxHeight) < double.Epsilon) if (_text.IsEmpty || MathUtilities.IsZero(MaxWidth) || MathUtilities.IsZero(MaxHeight))
{ {
var textLine = CreateEmptyTextLine(0); var textLine = CreateEmptyTextLine(0);

2
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@ -236,7 +236,7 @@ namespace Avalonia.Direct2D1.Media
Math.Max(rrect.RadiiTopRight.X, Math.Max(rrect.RadiiBottomRight.X, rrect.RadiiBottomLeft.X))); Math.Max(rrect.RadiiTopRight.X, Math.Max(rrect.RadiiBottomRight.X, rrect.RadiiBottomLeft.X)));
var radiusY = Math.Max(rrect.RadiiTopLeft.Y, var radiusY = Math.Max(rrect.RadiiTopLeft.Y,
Math.Max(rrect.RadiiTopRight.Y, Math.Max(rrect.RadiiBottomRight.Y, rrect.RadiiBottomLeft.Y))); Math.Max(rrect.RadiiTopRight.Y, Math.Max(rrect.RadiiBottomRight.Y, rrect.RadiiBottomLeft.Y)));
var isRounded = Math.Abs(radiusX) > double.Epsilon || Math.Abs(radiusY) > double.Epsilon; var isRounded = !MathUtilities.IsZero(radiusX) || !MathUtilities.IsZero(radiusY);
if (brush != null) if (brush != null)
{ {

2
tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs

@ -322,7 +322,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
{ {
"bar", "bar",
new BindingNotification( new BindingNotification(
new MissingMemberException("Could not find CLR property 'Bar' on 'Avalonia.Base.UnitTests.Data.Core.ExpressionObserverTests_Property+WithoutBar'"), new MissingMemberException("Could not find a matching property accessor for 'Bar' on 'Avalonia.Base.UnitTests.Data.Core.ExpressionObserverTests_Property+WithoutBar'"),
BindingErrorType.Error), BindingErrorType.Error),
"baz", "baz",
}, },

119
tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs

@ -0,0 +1,119 @@
using System;
using Avalonia.Utilities;
using Xunit;
namespace Avalonia.Base.UnitTests.Utilities
{
public class MathUtilitiesTests
{
private const double AnyValue = 42.42;
private readonly double _calculatedAnyValue;
private readonly double _one;
private readonly double _zero;
public MathUtilitiesTests()
{
_calculatedAnyValue = 0.0;
_one = 0.0;
_zero = 1.0;
const int N = 10;
var dxAny = AnyValue / N;
var dxOne = 1.0 / N;
var dxZero = _zero / N;
for (var i = 0; i < N; ++i)
{
_calculatedAnyValue += dxAny;
_one += dxOne;
_zero -= dxZero;
}
}
[Fact]
public void Two_Equivalent_Double_Values_Are_Close()
{
var actual = MathUtilities.AreClose(AnyValue, _calculatedAnyValue);
Assert.True(actual);
Assert.Equal(AnyValue, Math.Round(_calculatedAnyValue, 14));
}
[Fact]
public void Two_Equivalent_Single_Values_Are_Close()
{
var expectedValue = (float)AnyValue;
var actualValue = (float)_calculatedAnyValue;
var actual = MathUtilities.AreClose(expectedValue, actualValue);
Assert.True(actual);
Assert.Equal((float) Math.Round(expectedValue, 5), (float) Math.Round(actualValue, 4));
}
[Fact]
public void Calculated_Double_One_Is_One()
{
var actual = MathUtilities.IsOne(_one);
Assert.True(actual);
Assert.Equal(1.0, Math.Round(_one, 15));
}
[Fact]
public void Calculated_Single_One_Is_One()
{
var actualValue = (float)_one;
var actual = MathUtilities.IsOne(actualValue);
Assert.True(actual);
Assert.Equal(1.0f, (float) Math.Round(actualValue, 7));
}
[Fact]
public void Calculated_Double_Zero_Is_Zero()
{
var actual = MathUtilities.IsZero(_zero);
Assert.True(actual);
Assert.Equal(0.0, Math.Round(_zero, 15));
}
[Fact]
public void Calculated_Single_Zero_Is_Zero()
{
var actualValue = (float)_zero;
var actual = MathUtilities.IsZero(actualValue);
Assert.True(actual);
Assert.Equal(0.0f, (float) Math.Round(actualValue, 7));
}
[Fact]
public void Clamp_Input_NaN_Return_NaN()
{
var clamp = MathUtilities.Clamp(double.NaN, 0.0, 1.0);
Assert.True(double.IsNaN(clamp));
}
[Fact]
public void Clamp_Input_NegativeInfinity_Return_Min()
{
const double min = 0.0;
const double max = 1.0;
var actual = MathUtilities.Clamp(double.NegativeInfinity, min, max);
Assert.Equal(min, actual);
}
[Fact]
public void Clamp_Input_PositiveInfinity_Return_Max()
{
const double min = 0.0;
const double max = 1.0;
var actual = MathUtilities.Clamp(double.PositiveInfinity, min, max);
Assert.Equal(max, actual);
}
}
}

47
tests/Avalonia.Benchmarks/Data/AccessorTestObject.cs

@ -0,0 +1,47 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
namespace Avalonia.Benchmarks.Data
{
internal class AccessorTestObject : INotifyPropertyChanged
{
private string _test;
public string Test
{
get => _test;
set
{
if (_test == value)
{
return;
}
_test = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void Execute()
{
}
public void Execute(object p0)
{
}
public void Execute(object p0, object p1)
{
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

54
tests/Avalonia.Benchmarks/Data/PropertyAccessorBenchmarks.cs

@ -1,9 +1,6 @@
using System; using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Avalonia.Data.Core.Plugins; using Avalonia.Data.Core.Plugins;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using JetBrains.Annotations;
namespace Avalonia.Benchmarks.Data namespace Avalonia.Benchmarks.Data
{ {
@ -12,7 +9,7 @@ namespace Avalonia.Benchmarks.Data
{ {
private readonly InpcPropertyAccessorPlugin _inpcPlugin = new InpcPropertyAccessorPlugin(); private readonly InpcPropertyAccessorPlugin _inpcPlugin = new InpcPropertyAccessorPlugin();
private readonly MethodAccessorPlugin _methodPlugin = new MethodAccessorPlugin(); private readonly MethodAccessorPlugin _methodPlugin = new MethodAccessorPlugin();
private readonly TestObject _targetStrongRef = new TestObject(); private readonly AccessorTestObject _targetStrongRef = new AccessorTestObject();
private readonly WeakReference<object> _targetWeakRef; private readonly WeakReference<object> _targetWeakRef;
public PropertyAccessorBenchmarks() public PropertyAccessorBenchmarks()
@ -23,66 +20,25 @@ namespace Avalonia.Benchmarks.Data
[Benchmark] [Benchmark]
public void InpcAccessorMatch() public void InpcAccessorMatch()
{ {
_inpcPlugin.Match(_targetWeakRef, nameof(TestObject.Test)); _inpcPlugin.Match(_targetWeakRef, nameof(AccessorTestObject.Test));
} }
[Benchmark] [Benchmark]
public void InpcAccessorStart() public void InpcAccessorStart()
{ {
_inpcPlugin.Start(_targetWeakRef, nameof(TestObject.Test)); _inpcPlugin.Start(_targetWeakRef, nameof(AccessorTestObject.Test));
} }
[Benchmark] [Benchmark]
public void MethodAccessorMatch() public void MethodAccessorMatch()
{ {
_methodPlugin.Match(_targetWeakRef, nameof(TestObject.Execute)); _methodPlugin.Match(_targetWeakRef, nameof(AccessorTestObject.Execute));
} }
[Benchmark] [Benchmark]
public void MethodAccessorStart() public void MethodAccessorStart()
{ {
_methodPlugin.Start(_targetWeakRef, nameof(TestObject.Execute)); _methodPlugin.Start(_targetWeakRef, nameof(AccessorTestObject.Execute));
}
private class TestObject : INotifyPropertyChanged
{
private string _test;
public string Test
{
get => _test;
set
{
if (_test == value)
{
return;
}
_test = value;
OnPropertyChanged();
}
}
public void Execute()
{
}
public void Execute(object p0)
{
}
public void Execute(object p0, object p1)
{
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
} }
} }
} }

60
tests/Avalonia.Benchmarks/Data/PropertyAccessorPluginBenchmarks.cs

@ -0,0 +1,60 @@
using System.Collections.Generic;
using Avalonia.Data.Core.Plugins;
using BenchmarkDotNet.Attributes;
namespace Avalonia.Benchmarks.Data
{
[MemoryDiagnoser, InProcess]
public class PropertyAccessorPluginBenchmarks
{
private readonly AccessorTestObject _targetStrongRef = new AccessorTestObject();
private readonly List<IPropertyAccessorPlugin> _oldPlugins;
private readonly List<IPropertyAccessorPlugin> _newPlugins;
public PropertyAccessorPluginBenchmarks()
{
_oldPlugins = new List<IPropertyAccessorPlugin>
{
new AvaloniaPropertyAccessorPlugin(),
new MethodAccessorPlugin(),
new InpcPropertyAccessorPlugin()
};
_newPlugins = new List<IPropertyAccessorPlugin>
{
new AvaloniaPropertyAccessorPlugin(),
new InpcPropertyAccessorPlugin(),
new MethodAccessorPlugin()
};
}
[Benchmark]
public void MatchAccessorOld()
{
var propertyName = nameof(AccessorTestObject.Test);
foreach (IPropertyAccessorPlugin x in _oldPlugins)
{
if (x.Match(_targetStrongRef, propertyName))
{
break;
}
}
}
[Benchmark]
public void MatchAccessorNew()
{
var propertyName = nameof(AccessorTestObject.Test);
foreach (IPropertyAccessorPlugin x in _newPlugins)
{
if (x.Match(_targetStrongRef, propertyName))
{
break;
}
}
}
}
}

14
tests/Avalonia.Controls.UnitTests/DatePickerTests.cs → tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs

@ -15,7 +15,7 @@ using Xunit;
namespace Avalonia.Controls.UnitTests namespace Avalonia.Controls.UnitTests
{ {
public class DatePickerTests public class CalendarDatePickerTests
{ {
private static bool CompareDates(DateTime first, DateTime second) private static bool CompareDates(DateTime first, DateTime second)
{ {
@ -30,7 +30,7 @@ namespace Avalonia.Controls.UnitTests
using (UnitTestApplication.Start(Services)) using (UnitTestApplication.Start(Services))
{ {
bool handled = false; bool handled = false;
DatePicker datePicker = CreateControl(); CalendarDatePicker datePicker = CreateControl();
datePicker.SelectedDateChanged += (s,e) => datePicker.SelectedDateChanged += (s,e) =>
{ {
handled = true; handled = true;
@ -47,7 +47,7 @@ namespace Avalonia.Controls.UnitTests
{ {
using (UnitTestApplication.Start(Services)) using (UnitTestApplication.Start(Services))
{ {
DatePicker datePicker = CreateControl(); CalendarDatePicker datePicker = CreateControl();
datePicker.BlackoutDates.AddDatesInPast(); datePicker.BlackoutDates.AddDatesInPast();
DateTime goodValue = DateTime.Today.AddDays(1); DateTime goodValue = DateTime.Today.AddDays(1);
@ -65,7 +65,7 @@ namespace Avalonia.Controls.UnitTests
{ {
using (UnitTestApplication.Start(Services)) using (UnitTestApplication.Start(Services))
{ {
DatePicker datePicker = CreateControl(); CalendarDatePicker datePicker = CreateControl();
datePicker.SelectedDate = DateTime.Today.AddDays(5); datePicker.SelectedDate = DateTime.Today.AddDays(5);
Assert.ThrowsAny<ArgumentOutOfRangeException>( Assert.ThrowsAny<ArgumentOutOfRangeException>(
@ -76,10 +76,10 @@ namespace Avalonia.Controls.UnitTests
private static TestServices Services => TestServices.MockThreadingInterface.With( private static TestServices Services => TestServices.MockThreadingInterface.With(
standardCursorFactory: Mock.Of<IStandardCursorFactory>()); standardCursorFactory: Mock.Of<IStandardCursorFactory>());
private DatePicker CreateControl() private CalendarDatePicker CreateControl()
{ {
var datePicker = var datePicker =
new DatePicker new CalendarDatePicker
{ {
Template = CreateTemplate() Template = CreateTemplate()
}; };
@ -90,7 +90,7 @@ namespace Avalonia.Controls.UnitTests
private IControlTemplate CreateTemplate() private IControlTemplate CreateTemplate()
{ {
return new FuncControlTemplate<DatePicker>((control, scope) => return new FuncControlTemplate<CalendarDatePicker>((control, scope) =>
{ {
var textBox = var textBox =
new TextBox new TextBox

2
tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Property.cs

@ -22,7 +22,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
Assert.Equal( Assert.Equal(
new BindingNotification( new BindingNotification(
new MissingMemberException("Could not find CLR property 'Baz' on '1'"), BindingErrorType.Error), new MissingMemberException("Could not find a matching property accessor for 'Baz' on '1'"), BindingErrorType.Error),
result); result);
GC.KeepAlive(data); GC.KeepAlive(data);

Loading…
Cancel
Save