diff --git a/readme.md b/readme.md
index 8ae3f1ad66..9d317cdd06 100644
--- a/readme.md
+++ b/readme.md
@@ -1,24 +1,20 @@
-
+[](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) [](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/latest?definitionId=4) [](#backers) [](#sponsors) 
+
+[](https://www.nuget.org/packages/Avalonia) [](https://www.nuget.org/packages/Avalonia) [](https://www.myget.org/gallery/avalonia-ci) 
-# Avalonia
+
-| Gitter Chat | Build Status (Win, Linux, OSX) | Open Collective | NuGet | MyGet |
-|---|---|---|---|---|
-| [](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) | [](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/latest?definitionId=4) | [](#backers) [](#sponsors) | [](https://www.nuget.org/packages/Avalonia) | [](https://www.myget.org/gallery/avalonia-ci) |
+## 📖 About AvaloniaUI
-## About
+Avalonia is a cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of Operating Systems such as Windows via .NET Framework and .NET Core, Linux via Xorg, macOS. Avalonia is ready for **General-Purpose Desktop App Development**. However, there may be some bugs and breaking changes as we continue along into this project's development.
-**Avalonia** is a cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of Operating Systems such as Windows (.NET Framework, .NET Core), Linux (via Xorg), macOS.
+
-**Avalonia** is ready for **General-Purpose Desktop App Development**. However, there may be some bugs and breaking changes as we continue along into this project's development.
+> **Note:** The UI theme you see in the picture above is still work-in-progress and will be available in the upcoming Avalonia 0.10.0 release. However, you can connect to our nightly build feed and install latest pre-release versions of Avalonia NuGet packages, if you are willing to help out with the development and testing. See [Using nightly build feed](https://github.com/AvaloniaUI/Avalonia/wiki/Using-nightly-build-feed) for more info.
-To see the status of some of our features, please see our [Roadmap here](https://github.com/AvaloniaUI/Avalonia/issues/2239).
+To see the status of some of our features, please see our [Roadmap](https://github.com/AvaloniaUI/Avalonia/issues/2239). You can also see what [breaking changes](https://github.com/AvaloniaUI/Avalonia/issues/3538) we have planned and what our [past breaking changes](https://github.com/AvaloniaUI/Avalonia/wiki/Breaking-Changes) have been. [Awesome Avalonia](https://github.com/AvaloniaCommunity/awesome-avalonia) is community-curated list of awesome Avalonia UI tools, libraries, projects and resources. Go and see what people are building with Avalonia!
-You can also see what [breaking changes](https://github.com/AvaloniaUI/Avalonia/issues/3538) we have planned and what our [past breaking changes](https://github.com/AvaloniaUI/Avalonia/wiki/Breaking-Changes) have been.
-
-[Awesome Avalonia](https://github.com/AvaloniaCommunity/awesome-avalonia) is community-curated list of awesome Avalonia UI tools, libraries, projects and resources. Go and see what people are building with Avalonia!
-
-## Getting Started
+## 🚀 Getting Started
The Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio) contains project and control templates that will help you get started, or you can use the .NET Core CLI. For a starer guide see our [documentation](http://avaloniaui.net/docs/quickstart/create-new-project).
@@ -30,6 +26,15 @@ Install-Package Avalonia
Install-Package Avalonia.Desktop
```
+## Showcase
+
+Examples of UIs built with Avalonia
+
+
+
+
+
+
## JetBrains Rider
If you need to develop Avalonia app with JetBrains Rider, go and *vote* on [this issue](https://youtrack.jetbrains.com/issue/RIDER-39247) in their tracker. JetBrains won't do things without their users telling them that they want the feature, so only **YOU** can make it happen.
diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml
index 7956ee6169..af2d093bc7 100644
--- a/samples/ControlCatalog/MainView.xaml
+++ b/samples/ControlCatalog/MainView.xaml
@@ -29,7 +29,8 @@
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
-
+
+
-
+
+
diff --git a/samples/ControlCatalog/Pages/DatePickerPage.xaml b/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml
similarity index 75%
rename from samples/ControlCatalog/Pages/DatePickerPage.xaml
rename to samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml
index 30d5a7506f..107472105a 100644
--- a/samples/ControlCatalog/Pages/DatePickerPage.xaml
+++ b/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml
@@ -1,8 +1,8 @@
+ x:Class="ControlCatalog.Pages.CalendarDatePickerPage">
- DatePicker
+ CalendarDatePickerA control for selecting dates with a calendar drop-down
-
-
-
-
-
-
-
+
diff --git a/samples/ControlCatalog/Pages/DatePickerPage.xaml.cs b/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml.cs
similarity index 57%
rename from samples/ControlCatalog/Pages/DatePickerPage.xaml.cs
rename to samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml.cs
index ef01887c9e..95bdeb363a 100644
--- a/samples/ControlCatalog/Pages/DatePickerPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml.cs
@@ -4,17 +4,17 @@ using System;
namespace ControlCatalog.Pages
{
- public class DatePickerPage : UserControl
+ public class CalendarDatePickerPage : UserControl
{
- public DatePickerPage()
+ public CalendarDatePickerPage()
{
InitializeComponent();
- var dp1 = this.FindControl("DatePicker1");
- var dp2 = this.FindControl("DatePicker2");
- var dp3 = this.FindControl("DatePicker3");
- var dp4 = this.FindControl("DatePicker4");
- var dp5 = this.FindControl("DatePicker5");
+ var dp1 = this.FindControl("DatePicker1");
+ var dp2 = this.FindControl("DatePicker2");
+ var dp3 = this.FindControl("DatePicker3");
+ var dp4 = this.FindControl("DatePicker4");
+ var dp5 = this.FindControl("DatePicker5");
dp1.SelectedDate = DateTime.Today;
dp2.SelectedDate = DateTime.Today.AddDays(10);
diff --git a/samples/ControlCatalog/Pages/NumericUpDownPage.xaml b/samples/ControlCatalog/Pages/NumericUpDownPage.xaml
index e605a92da0..0d7e5da17f 100644
--- a/samples/ControlCatalog/Pages/NumericUpDownPage.xaml
+++ b/samples/ControlCatalog/Pages/NumericUpDownPage.xaml
@@ -50,22 +50,22 @@
Text:
-
+ Minimum:
+ CultureInfo="{Binding #upDown.CultureInfo}" VerticalAlignment="Center" Margin="2" HorizontalAlignment="Center"/>
Maximum:
+ CultureInfo="{Binding #upDown.CultureInfo}" VerticalAlignment="Center" Margin="2" HorizontalAlignment="Center"/>
Increment:
+ Margin="2" HorizontalAlignment="Center"/>
Value:
+ Margin="2" HorizontalAlignment="Center"/>
@@ -73,7 +73,7 @@
Usage of NumericUpDown:
diff --git a/samples/ControlCatalog/Pages/ToolTipPage.xaml b/samples/ControlCatalog/Pages/ToolTipPage.xaml
index cbe1e3059c..73d83e08f1 100644
--- a/samples/ControlCatalog/Pages/ToolTipPage.xaml
+++ b/samples/ControlCatalog/Pages/ToolTipPage.xaml
@@ -18,7 +18,7 @@
ToolTip.Tip="This is a ToolTip">
Hover Here
-
+
+
+
diff --git a/samples/RenderDemo/Pages/TransitionsPage.xaml b/samples/RenderDemo/Pages/TransitionsPage.xaml
new file mode 100644
index 0000000000..df7130a925
--- /dev/null
+++ b/samples/RenderDemo/Pages/TransitionsPage.xaml
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Hover to activate Transform Keyframe Animations.
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/RenderDemo/Pages/TransitionsPage.xaml.cs b/samples/RenderDemo/Pages/TransitionsPage.xaml.cs
new file mode 100644
index 0000000000..5f446c9e99
--- /dev/null
+++ b/samples/RenderDemo/Pages/TransitionsPage.xaml.cs
@@ -0,0 +1,37 @@
+using Avalonia.Animation;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Markup.Xaml;
+using RenderDemo.ViewModels;
+
+namespace RenderDemo.Pages
+{
+ public class TransitionsPage : UserControl
+ {
+ public TransitionsPage()
+ {
+ InitializeComponent();
+ this.DataContext = new AnimationsPageViewModel();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ private void ToggleClock(object sender, RoutedEventArgs args)
+ {
+ var button = sender as Button;
+ var clock = button.Clock;
+
+ if (clock.PlayState == PlayState.Run)
+ {
+ clock.PlayState = PlayState.Pause;
+ }
+ else if (clock.PlayState == PlayState.Pause)
+ {
+ clock.PlayState = PlayState.Run;
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
index 84ef0fb695..8fc2a7b77c 100644
--- a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
+++ b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
@@ -12,7 +12,7 @@ namespace Avalonia.Data.Core.Plugins
public class InpcPropertyAccessorPlugin : IPropertyAccessorPlugin
{
///
- public bool Match(object obj, string propertyName) => true;
+ public bool Match(object obj, string propertyName) => GetPropertyWithName(obj.GetType(), propertyName) != null;
///
/// Starts monitoring the value of a property on an object.
@@ -30,10 +30,7 @@ namespace Avalonia.Data.Core.Plugins
reference.TryGetTarget(out object instance);
- const BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Public |
- BindingFlags.Static | BindingFlags.Instance;
-
- var p = instance.GetType().GetProperty(propertyName, bindingFlags);
+ var p = GetPropertyWithName(instance.GetType(), propertyName);
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
{
private readonly WeakReference
public static class MathUtilities
{
+ // smallest such that 1.0+DoubleEpsilon != 1.0
+ internal static readonly double DoubleEpsilon = 2.2204460492503131e-016;
+
+ private const float FloatEpsilon = 1.192092896e-07F;
+
///
/// AreClose - Returns whether or not two doubles are "close". That is, whether or
/// not they are within epsilon of each other.
@@ -18,11 +22,26 @@ namespace Avalonia.Utilities
{
//in case they are Infinities (then epsilon check does not work)
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;
return (-eps < delta) && (eps > delta);
}
+ ///
+ /// AreClose - Returns whether or not two floats are "close". That is, whether or
+ /// not they are within epsilon of each other.
+ ///
+ /// The first float to compare.
+ /// The second float to compare.
+ 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);
+ }
+
///
/// 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
@@ -35,6 +54,18 @@ namespace Avalonia.Utilities
return (value1 < value2) && !AreClose(value1, value2);
}
+ ///
+ /// 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.
+ ///
+ /// The first single float to compare.
+ /// The second single float to compare.
+ public static bool LessThan(float value1, float value2)
+ {
+ return (value1 < value2) && !AreClose(value1, value2);
+ }
+
///
/// 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
@@ -47,6 +78,18 @@ namespace Avalonia.Utilities
return (value1 > value2) && !AreClose(value1, value2);
}
+ ///
+ /// 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.
+ ///
+ /// The first float to compare.
+ /// The second float to compare.
+ public static bool GreaterThan(float value1, float value2)
+ {
+ return (value1 > value2) && !AreClose(value1, value2);
+ }
+
///
/// 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
@@ -59,6 +102,18 @@ namespace Avalonia.Utilities
return (value1 < value2) || AreClose(value1, value2);
}
+ ///
+ /// 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.
+ ///
+ /// The first float to compare.
+ /// The second float to compare.
+ public static bool LessThanOrClose(float value1, float value2)
+ {
+ return (value1 < value2) || AreClose(value1, value2);
+ }
+
///
/// 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
@@ -71,6 +126,18 @@ namespace Avalonia.Utilities
return (value1 > value2) || AreClose(value1, value2);
}
+ ///
+ /// 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.
+ ///
+ /// The first float to compare.
+ /// The second float to compare.
+ public static bool GreaterThanOrClose(float value1, float value2)
+ {
+ return (value1 > value2) || AreClose(value1, value2);
+ }
+
///
/// IsOne - Returns whether or not the double is "close" to 1. Same as AreClose(double, 1),
/// but this is faster.
@@ -78,7 +145,17 @@ namespace Avalonia.Utilities
/// The double to compare to 1.
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;
+ }
+
+ ///
+ /// IsOne - Returns whether or not the float is "close" to 1. Same as AreClose(float, 1),
+ /// but this is faster.
+ ///
+ /// The float to compare to 1.
+ public static bool IsOne(float value)
+ {
+ return Math.Abs(value - 1.0f) < 10.0f * FloatEpsilon;
}
///
@@ -88,7 +165,17 @@ namespace Avalonia.Utilities
/// The double to compare to 0.
public static bool IsZero(double value)
{
- return Math.Abs(value) < 10.0 * double.Epsilon;
+ return Math.Abs(value) < 10.0 * DoubleEpsilon;
+ }
+
+ ///
+ /// IsZero - Returns whether or not the float is "close" to 0. Same as AreClose(float, 0),
+ /// but this is faster.
+ ///
+ /// The float to compare to 0.
+ public static bool IsZero(float value)
+ {
+ return Math.Abs(value) < 10.0f * FloatEpsilon;
}
///
@@ -100,6 +187,11 @@ namespace Avalonia.Utilities
/// The clamped value.
public static double Clamp(double val, double min, double max)
{
+ if (min > max)
+ {
+ ThrowCannotBeGreaterThanException(min, max);
+ }
+
if (val < min)
{
return min;
@@ -128,7 +220,7 @@ namespace Avalonia.Utilities
double newValue;
// If DPI == 1, don't use DPI-aware rounding.
- if (!MathUtilities.AreClose(dpiScale, 1.0))
+ if (!MathUtilities.IsOne(dpiScale))
{
newValue = Math.Round(value * dpiScale) / dpiScale;
// If rounding produces a value unacceptable to layout (NaN, Infinity or MaxValue), use the original value.
@@ -158,7 +250,7 @@ namespace Avalonia.Utilities
{
if (min > max)
{
- throw new ArgumentException($"{min} cannot be greater than {max}.");
+ ThrowCannotBeGreaterThanException(min, max);
}
if (val < min)
@@ -174,5 +266,40 @@ namespace Avalonia.Utilities
return val;
}
}
+
+ ///
+ /// Converts an angle in degrees to radians.
+ ///
+ /// The angle in degrees.
+ /// The angle in radians.
+ public static double Deg2Rad(double angle)
+ {
+ return angle * (Math.PI / 180d);
+ }
+
+ ///
+ /// Converts an angle in gradians to radians.
+ ///
+ /// The angle in gradians.
+ /// The angle in radians.
+ public static double Grad2Rad(double angle)
+ {
+ return angle * (Math.PI / 200d);
+ }
+
+ ///
+ /// Converts an angle in turns to radians.
+ ///
+ /// The angle in turns.
+ /// The angle in radians.
+ public static double Turn2Rad(double angle)
+ {
+ return angle * 2 * Math.PI;
+ }
+
+ private static void ThrowCannotBeGreaterThanException(double min, double max)
+ {
+ throw new ArgumentException($"{min} cannot be greater than {max}.");
+ }
}
}
diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs
index cfe47a09d5..7e893d131e 100644
--- a/src/Avalonia.Controls.DataGrid/DataGrid.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs
@@ -2681,7 +2681,7 @@ namespace Avalonia.Controls
{
return;
}
- Debug.Assert(DoubleUtil.LessThanOrClose(_vScrollBar.Value, _vScrollBar.Maximum));
+ Debug.Assert(MathUtilities.LessThanOrClose(_vScrollBar.Value, _vScrollBar.Maximum));
_verticalScrollChangesIgnored++;
try
@@ -2698,7 +2698,7 @@ namespace Avalonia.Controls
}
else if (scrollEventType == ScrollEventType.SmallDecrement)
{
- if (DoubleUtil.GreaterThan(NegVerticalOffset, 0))
+ if (MathUtilities.GreaterThan(NegVerticalOffset, 0))
{
DisplayData.PendingVerticalScrollHeight -= NegVerticalOffset;
}
@@ -2717,7 +2717,7 @@ namespace Avalonia.Controls
DisplayData.PendingVerticalScrollHeight = _vScrollBar.Value - _verticalOffset;
}
- if (!DoubleUtil.IsZero(DisplayData.PendingVerticalScrollHeight))
+ if (!MathUtilities.IsZero(DisplayData.PendingVerticalScrollHeight))
{
// Invalidate so the scroll happens on idle
InvalidateRowsMeasure(invalidateIndividualElements: false);
@@ -3346,22 +3346,22 @@ namespace Avalonia.Controls
bool needHorizScrollbarWithoutVertScrollbar = false;
if (allowHorizScrollbar &&
- DoubleUtil.GreaterThan(totalVisibleWidth, cellsWidth) &&
- DoubleUtil.LessThan(totalVisibleFrozenWidth, cellsWidth) &&
- DoubleUtil.LessThanOrClose(horizScrollBarHeight, cellsHeight))
+ MathUtilities.GreaterThan(totalVisibleWidth, cellsWidth) &&
+ MathUtilities.LessThan(totalVisibleFrozenWidth, cellsWidth) &&
+ MathUtilities.LessThanOrClose(horizScrollBarHeight, cellsHeight))
{
double oldDataHeight = cellsHeight;
cellsHeight -= horizScrollBarHeight;
Debug.Assert(cellsHeight >= 0);
needHorizScrollbarWithoutVertScrollbar = needHorizScrollbar = true;
- if (allowVertScrollbar && (DoubleUtil.LessThanOrClose(totalVisibleWidth - cellsWidth, vertScrollBarWidth) ||
- DoubleUtil.LessThanOrClose(cellsWidth - totalVisibleFrozenWidth, vertScrollBarWidth)))
+ if (allowVertScrollbar && (MathUtilities.LessThanOrClose(totalVisibleWidth - cellsWidth, vertScrollBarWidth) ||
+ MathUtilities.LessThanOrClose(cellsWidth - totalVisibleFrozenWidth, vertScrollBarWidth)))
{
// Would we still need a horizontal scrollbar without the vertical one?
UpdateDisplayedRows(DisplayData.FirstScrollingSlot, cellsHeight);
if (DisplayData.NumTotallyDisplayedScrollingElements != VisibleSlotCount)
{
- needHorizScrollbar = DoubleUtil.LessThan(totalVisibleFrozenWidth, cellsWidth - vertScrollBarWidth);
+ needHorizScrollbar = MathUtilities.LessThan(totalVisibleFrozenWidth, cellsWidth - vertScrollBarWidth);
}
}
@@ -3374,8 +3374,8 @@ namespace Avalonia.Controls
UpdateDisplayedRows(DisplayData.FirstScrollingSlot, cellsHeight);
if (allowVertScrollbar &&
- DoubleUtil.GreaterThan(cellsHeight, 0) &&
- DoubleUtil.LessThanOrClose(vertScrollBarWidth, cellsWidth) &&
+ MathUtilities.GreaterThan(cellsHeight, 0) &&
+ MathUtilities.LessThanOrClose(vertScrollBarWidth, cellsWidth) &&
DisplayData.NumTotallyDisplayedScrollingElements != VisibleSlotCount)
{
cellsWidth -= vertScrollBarWidth;
@@ -3389,9 +3389,9 @@ namespace Avalonia.Controls
if (allowHorizScrollbar &&
needVertScrollbar && !needHorizScrollbar &&
- DoubleUtil.GreaterThan(totalVisibleWidth, cellsWidth) &&
- DoubleUtil.LessThan(totalVisibleFrozenWidth, cellsWidth) &&
- DoubleUtil.LessThanOrClose(horizScrollBarHeight, cellsHeight))
+ MathUtilities.GreaterThan(totalVisibleWidth, cellsWidth) &&
+ MathUtilities.LessThan(totalVisibleFrozenWidth, cellsWidth) &&
+ MathUtilities.LessThanOrClose(horizScrollBarHeight, cellsHeight))
{
cellsWidth += vertScrollBarWidth;
cellsHeight -= horizScrollBarHeight;
@@ -3422,7 +3422,7 @@ namespace Avalonia.Controls
if (allowVertScrollbar)
{
if (cellsHeight > 0 &&
- DoubleUtil.LessThanOrClose(vertScrollBarWidth, cellsWidth) &&
+ MathUtilities.LessThanOrClose(vertScrollBarWidth, cellsWidth) &&
DisplayData.NumTotallyDisplayedScrollingElements != VisibleSlotCount)
{
cellsWidth -= vertScrollBarWidth;
@@ -3439,9 +3439,9 @@ namespace Avalonia.Controls
if (allowHorizScrollbar)
{
if (cellsWidth > 0 &&
- DoubleUtil.LessThanOrClose(horizScrollBarHeight, cellsHeight) &&
- DoubleUtil.GreaterThan(totalVisibleWidth, cellsWidth) &&
- DoubleUtil.LessThan(totalVisibleFrozenWidth, cellsWidth))
+ MathUtilities.LessThanOrClose(horizScrollBarHeight, cellsHeight) &&
+ MathUtilities.GreaterThan(totalVisibleWidth, cellsWidth) &&
+ MathUtilities.LessThan(totalVisibleFrozenWidth, cellsWidth))
{
cellsHeight -= horizScrollBarHeight;
Debug.Assert(cellsHeight >= 0);
@@ -5387,7 +5387,7 @@ namespace Avalonia.Controls
private void SetVerticalOffset(double newVerticalOffset)
{
_verticalOffset = newVerticalOffset;
- if (_vScrollBar != null && !DoubleUtil.AreClose(newVerticalOffset, _vScrollBar.Value))
+ if (_vScrollBar != null && !MathUtilities.AreClose(newVerticalOffset, _vScrollBar.Value))
{
_vScrollBar.Value = _verticalOffset;
}
diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs
index df2b03798a..e6cc7e5e40 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs
@@ -301,7 +301,7 @@ namespace Avalonia.Controls
private static bool CanResizeColumn(DataGridColumn column)
{
if (column.OwningGrid != null && column.OwningGrid.ColumnsInternal != null && column.OwningGrid.UsesStarSizing &&
- (column.OwningGrid.ColumnsInternal.LastVisibleColumn == column || !DoubleUtil.AreClose(column.OwningGrid.ColumnsInternal.VisibleEdgedColumnsWidth, column.OwningGrid.CellsWidth)))
+ (column.OwningGrid.ColumnsInternal.LastVisibleColumn == column || !MathUtilities.AreClose(column.OwningGrid.ColumnsInternal.VisibleEdgedColumnsWidth, column.OwningGrid.CellsWidth)))
{
return false;
}
diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumns.cs b/src/Avalonia.Controls.DataGrid/DataGridColumns.cs
index 16b63ad696..5b75bc73f9 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridColumns.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridColumns.cs
@@ -44,7 +44,7 @@ namespace Avalonia.Controls
/// The remaining amount of adjustment.
internal double AdjustColumnWidths(int displayIndex, double amount, bool userInitiated)
{
- if (!DoubleUtil.IsZero(amount))
+ if (!MathUtilities.IsZero(amount))
{
if (amount < 0)
{
@@ -777,7 +777,7 @@ namespace Avalonia.Controls
private double AdjustStarColumnWidths(int displayIndex, double adjustment, bool userInitiated)
{
double remainingAdjustment = adjustment;
- if (DoubleUtil.IsZero(remainingAdjustment))
+ if (MathUtilities.IsZero(remainingAdjustment))
{
return remainingAdjustment;
}
@@ -843,7 +843,7 @@ namespace Avalonia.Controls
/// The remaining amount of adjustment.
private double AdjustStarColumnWidths(int displayIndex, double remainingAdjustment, bool userInitiated, Func targetWidth)
{
- if (DoubleUtil.IsZero(remainingAdjustment))
+ if (MathUtilities.IsZero(remainingAdjustment))
{
return remainingAdjustment;
}
@@ -1244,7 +1244,7 @@ namespace Avalonia.Controls
Debug.Assert(amount < 0);
Debug.Assert(column.Width.UnitType != DataGridLengthUnitType.Star);
- if (DoubleUtil.GreaterThanOrClose(targetWidth, column.Width.DisplayValue))
+ if (MathUtilities.GreaterThanOrClose(targetWidth, column.Width.DisplayValue))
{
return amount;
}
@@ -1271,7 +1271,7 @@ namespace Avalonia.Controls
/// The remaining amount of adjustment.
private double DecreaseNonStarColumnWidths(int displayIndex, Func targetWidth, double amount, bool reverse, bool affectNewColumns)
{
- if (DoubleUtil.GreaterThanOrClose(amount, 0))
+ if (MathUtilities.GreaterThanOrClose(amount, 0))
{
return amount;
}
@@ -1285,7 +1285,7 @@ namespace Avalonia.Controls
(affectNewColumns || column.IsInitialDesiredWidthDetermined)))
{
amount = DecreaseNonStarColumnWidth(column, Math.Max(column.ActualMinWidth, targetWidth(column)), amount);
- if (DoubleUtil.IsZero(amount))
+ if (MathUtilities.IsZero(amount))
{
break;
}
@@ -1392,7 +1392,7 @@ namespace Avalonia.Controls
/// The remaining amount of adjustment.
private double IncreaseNonStarColumnWidths(int displayIndex, Func targetWidth, double amount, bool reverse, bool affectNewColumns)
{
- if (DoubleUtil.LessThanOrClose(amount, 0))
+ if (MathUtilities.LessThanOrClose(amount, 0))
{
return amount;
}
@@ -1406,7 +1406,7 @@ namespace Avalonia.Controls
(affectNewColumns || column.IsInitialDesiredWidthDetermined)))
{
amount = IncreaseNonStarColumnWidth(column, Math.Min(column.ActualMaxWidth, targetWidth(column)), amount);
- if (DoubleUtil.IsZero(amount))
+ if (MathUtilities.IsZero(amount))
{
break;
}
diff --git a/src/Avalonia.Controls.DataGrid/DataGridLength.cs b/src/Avalonia.Controls.DataGrid/DataGridLength.cs
index 6a545a35ec..4841ddd494 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridLength.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridLength.cs
@@ -529,7 +529,7 @@ namespace Avalonia.Controls
// in this case drop value part and print only "Star"
case DataGridLengthUnitType.Star:
return (
- DoubleUtil.AreClose(1.0, dataGridLength.Value.Value)
+ MathUtilities.AreClose(1.0, dataGridLength.Value.Value)
? _starSuffix
: Convert.ToString(dataGridLength.Value.Value, culture ?? CultureInfo.CurrentCulture) + DataGridLengthConverter._starSuffix);
diff --git a/src/Avalonia.Controls.DataGrid/DataGridRow.cs b/src/Avalonia.Controls.DataGrid/DataGridRow.cs
index df200240ff..830eff1102 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridRow.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridRow.cs
@@ -879,7 +879,7 @@ namespace Avalonia.Controls
&& (double.IsNaN(_detailsContent.Height))
&& (AreDetailsVisible)
&& (!double.IsNaN(_detailsDesiredHeight))
- && !DoubleUtil.AreClose(_detailsContent.Bounds.Inflate(_detailsContent.Margin).Height, _detailsDesiredHeight)
+ && !MathUtilities.AreClose(_detailsContent.Bounds.Inflate(_detailsContent.Margin).Height, _detailsDesiredHeight)
&& Slot != -1)
{
_detailsDesiredHeight = _detailsContent.Bounds.Inflate(_detailsContent.Margin).Height;
diff --git a/src/Avalonia.Controls.DataGrid/DataGridRows.cs b/src/Avalonia.Controls.DataGrid/DataGridRows.cs
index fda14cf5b4..924156f5f4 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridRows.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridRows.cs
@@ -329,7 +329,7 @@ namespace Avalonia.Controls
internal void OnRowsMeasure()
{
- if (!DoubleUtil.IsZero(DisplayData.PendingVerticalScrollHeight))
+ if (!MathUtilities.IsZero(DisplayData.PendingVerticalScrollHeight))
{
ScrollSlotsByHeight(DisplayData.PendingVerticalScrollHeight);
DisplayData.PendingVerticalScrollHeight = 0;
@@ -432,7 +432,7 @@ namespace Avalonia.Controls
}
else if (DisplayData.FirstScrollingSlot == slot && slot != -1)
{
- if (!DoubleUtil.IsZero(NegVerticalOffset))
+ if (!MathUtilities.IsZero(NegVerticalOffset))
{
// First displayed row is partially scrolled of. Let's scroll it so that NegVerticalOffset becomes 0.
DisplayData.PendingVerticalScrollHeight = -NegVerticalOffset;
@@ -447,7 +447,7 @@ namespace Avalonia.Controls
{
// Scroll up to the new row so it becomes the first displayed row
firstFullSlot = DisplayData.FirstScrollingSlot - 1;
- if (DoubleUtil.GreaterThan(NegVerticalOffset, 0))
+ if (MathUtilities.GreaterThan(NegVerticalOffset, 0))
{
deltaY = -NegVerticalOffset;
}
@@ -470,7 +470,7 @@ namespace Avalonia.Controls
// Figure out how much of the last row is cut off
double rowHeight = GetExactSlotElementHeight(DisplayData.LastScrollingSlot);
double availableHeight = AvailableSlotElementRoom + rowHeight;
- if (DoubleUtil.AreClose(rowHeight, availableHeight))
+ if (MathUtilities.AreClose(rowHeight, availableHeight))
{
if (DisplayData.LastScrollingSlot == slot)
{
@@ -499,7 +499,7 @@ namespace Avalonia.Controls
{
ResetDisplayedRows();
}
- if (DoubleUtil.GreaterThanOrClose(GetExactSlotElementHeight(slot), CellsHeight))
+ if (MathUtilities.GreaterThanOrClose(GetExactSlotElementHeight(slot), CellsHeight))
{
// The entire row won't fit in the DataGrid so we start showing it from the top
NegVerticalOffset = 0;
@@ -519,7 +519,7 @@ namespace Avalonia.Controls
}
//
- Debug.Assert(DoubleUtil.LessThanOrClose(NegVerticalOffset, _verticalOffset));
+ Debug.Assert(MathUtilities.LessThanOrClose(NegVerticalOffset, _verticalOffset));
SetVerticalOffset(_verticalOffset);
@@ -1660,7 +1660,7 @@ namespace Avalonia.Controls
private void ScrollSlotsByHeight(double height)
{
Debug.Assert(DisplayData.FirstScrollingSlot >= 0);
- Debug.Assert(!DoubleUtil.IsZero(height));
+ Debug.Assert(!MathUtilities.IsZero(height));
_scrollingByHeight = true;
try
@@ -1672,7 +1672,7 @@ namespace Avalonia.Controls
{
// Scrolling Down
int lastVisibleSlot = GetPreviousVisibleSlot(SlotCount);
- if (_vScrollBar != null && DoubleUtil.AreClose(_vScrollBar.Maximum, newVerticalOffset))
+ if (_vScrollBar != null && MathUtilities.AreClose(_vScrollBar.Maximum, newVerticalOffset))
{
// We've scrolled to the bottom of the ScrollBar, automatically place the user at the very bottom
// of the DataGrid. If this produces very odd behavior, evaluate the coping strategy used by
@@ -1684,7 +1684,7 @@ namespace Avalonia.Controls
else
{
deltaY = GetSlotElementHeight(newFirstScrollingSlot) - NegVerticalOffset;
- if (DoubleUtil.LessThan(height, deltaY))
+ if (MathUtilities.LessThan(height, deltaY))
{
// We've merely covered up more of the same row we're on
NegVerticalOffset += height;
@@ -1707,7 +1707,7 @@ namespace Avalonia.Controls
}
else
{
- while (DoubleUtil.LessThanOrClose(deltaY, height))
+ while (MathUtilities.LessThanOrClose(deltaY, height))
{
if (newFirstScrollingSlot < lastVisibleSlot)
{
@@ -1727,7 +1727,7 @@ namespace Avalonia.Controls
double rowHeight = GetExactSlotElementHeight(newFirstScrollingSlot);
double remainingHeight = height - deltaY;
- if (DoubleUtil.LessThanOrClose(rowHeight, remainingHeight))
+ if (MathUtilities.LessThanOrClose(rowHeight, remainingHeight))
{
deltaY += rowHeight;
}
@@ -1744,7 +1744,7 @@ namespace Avalonia.Controls
else
{
// Scrolling Up
- if (DoubleUtil.GreaterThanOrClose(height + NegVerticalOffset, 0))
+ if (MathUtilities.GreaterThanOrClose(height + NegVerticalOffset, 0))
{
// We've merely exposing more of the row we're on
NegVerticalOffset += height;
@@ -1778,7 +1778,7 @@ namespace Avalonia.Controls
else
{
int lastScrollingSlot = DisplayData.LastScrollingSlot;
- while (DoubleUtil.GreaterThan(deltaY, height))
+ while (MathUtilities.GreaterThan(deltaY, height))
{
if (newFirstScrollingSlot > 0)
{
@@ -1797,7 +1797,7 @@ namespace Avalonia.Controls
}
double rowHeight = GetExactSlotElementHeight(newFirstScrollingSlot);
double remainingHeight = height - deltaY;
- if (DoubleUtil.LessThanOrClose(rowHeight + remainingHeight, 0))
+ if (MathUtilities.LessThanOrClose(rowHeight + remainingHeight, 0))
{
deltaY -= rowHeight;
}
@@ -1809,7 +1809,7 @@ namespace Avalonia.Controls
}
}
}
- if (DoubleUtil.GreaterThanOrClose(0, newVerticalOffset) && newFirstScrollingSlot != 0)
+ if (MathUtilities.GreaterThanOrClose(0, newVerticalOffset) && newFirstScrollingSlot != 0)
{
// We've scrolled to the top of the ScrollBar, automatically place the user at the very top
// of the DataGrid. If this produces very odd behavior, evaluate the RowHeight estimate.
@@ -1822,7 +1822,7 @@ namespace Avalonia.Controls
}
double firstRowHeight = GetExactSlotElementHeight(newFirstScrollingSlot);
- if (DoubleUtil.LessThan(firstRowHeight, NegVerticalOffset))
+ if (MathUtilities.LessThan(firstRowHeight, NegVerticalOffset))
{
// We've scrolled off more of the first row than what's possible. This can happen
// if the first row got shorter (Ex: Collpasing RowDetails) or if the user has a recycling
@@ -1838,11 +1838,11 @@ namespace Avalonia.Controls
UpdateDisplayedRows(newFirstScrollingSlot, CellsHeight);
double firstElementHeight = GetExactSlotElementHeight(DisplayData.FirstScrollingSlot);
- if (DoubleUtil.GreaterThan(NegVerticalOffset, firstElementHeight))
+ if (MathUtilities.GreaterThan(NegVerticalOffset, firstElementHeight))
{
int firstElementSlot = DisplayData.FirstScrollingSlot;
// We filled in some rows at the top and now we have a NegVerticalOffset that's greater than the first element
- while (newFirstScrollingSlot > 0 && DoubleUtil.GreaterThan(NegVerticalOffset, firstElementHeight))
+ while (newFirstScrollingSlot > 0 && MathUtilities.GreaterThan(NegVerticalOffset, firstElementHeight))
{
int previousSlot = GetPreviousVisibleSlot(firstElementSlot);
if (previousSlot == -1)
@@ -1872,7 +1872,7 @@ namespace Avalonia.Controls
{
_verticalOffset = NegVerticalOffset;
}
- else if (DoubleUtil.GreaterThan(NegVerticalOffset, newVerticalOffset))
+ else if (MathUtilities.GreaterThan(NegVerticalOffset, newVerticalOffset))
{
// The scrolled-in row was larger than anticipated. Adjust the DataGrid so the ScrollBar thumb
// can stay in the same place
@@ -1890,8 +1890,8 @@ namespace Avalonia.Controls
DisplayData.FullyRecycleElements();
- Debug.Assert(DoubleUtil.GreaterThanOrClose(NegVerticalOffset, 0));
- Debug.Assert(DoubleUtil.GreaterThanOrClose(_verticalOffset, NegVerticalOffset));
+ Debug.Assert(MathUtilities.GreaterThanOrClose(NegVerticalOffset, 0));
+ Debug.Assert(MathUtilities.GreaterThanOrClose(_verticalOffset, NegVerticalOffset));
}
finally
{
@@ -2032,7 +2032,7 @@ namespace Avalonia.Controls
double deltaY = -NegVerticalOffset;
int visibleScrollingRows = 0;
- if (DoubleUtil.LessThanOrClose(displayHeight, 0) || SlotCount == 0 || ColumnsItemsInternal.Count == 0)
+ if (MathUtilities.LessThanOrClose(displayHeight, 0) || SlotCount == 0 || ColumnsItemsInternal.Count == 0)
{
return;
}
@@ -2044,7 +2044,7 @@ namespace Avalonia.Controls
}
int slot = firstDisplayedScrollingSlot;
- while (slot < SlotCount && !DoubleUtil.GreaterThanOrClose(deltaY, displayHeight))
+ while (slot < SlotCount && !MathUtilities.GreaterThanOrClose(deltaY, displayHeight))
{
deltaY += GetExactSlotElementHeight(slot);
visibleScrollingRows++;
@@ -2052,7 +2052,7 @@ namespace Avalonia.Controls
slot = GetNextVisibleSlot(slot);
}
- while (DoubleUtil.LessThan(deltaY, displayHeight) && slot >= 0)
+ while (MathUtilities.LessThan(deltaY, displayHeight) && slot >= 0)
{
slot = GetPreviousVisibleSlot(firstDisplayedScrollingSlot);
if (slot >= 0)
@@ -2063,14 +2063,14 @@ namespace Avalonia.Controls
}
}
// If we're up to the first row, and we still have room left, uncover as much of the first row as we can
- if (firstDisplayedScrollingSlot == 0 && DoubleUtil.LessThan(deltaY, displayHeight))
+ if (firstDisplayedScrollingSlot == 0 && MathUtilities.LessThan(deltaY, displayHeight))
{
double newNegVerticalOffset = Math.Max(0, NegVerticalOffset - displayHeight + deltaY);
deltaY += NegVerticalOffset - newNegVerticalOffset;
NegVerticalOffset = newNegVerticalOffset;
}
- if (DoubleUtil.GreaterThan(deltaY, displayHeight) || (DoubleUtil.AreClose(deltaY, displayHeight) && DoubleUtil.GreaterThan(NegVerticalOffset, 0)))
+ if (MathUtilities.GreaterThan(deltaY, displayHeight) || (MathUtilities.AreClose(deltaY, displayHeight) && MathUtilities.GreaterThan(NegVerticalOffset, 0)))
{
DisplayData.NumTotallyDisplayedScrollingElements = visibleScrollingRows - 1;
}
@@ -2108,7 +2108,7 @@ namespace Avalonia.Controls
double deltaY = 0;
int visibleScrollingRows = 0;
- if (DoubleUtil.LessThanOrClose(displayHeight, 0) || SlotCount == 0 || ColumnsItemsInternal.Count == 0)
+ if (MathUtilities.LessThanOrClose(displayHeight, 0) || SlotCount == 0 || ColumnsItemsInternal.Count == 0)
{
ResetDisplayedRows();
return;
@@ -2120,7 +2120,7 @@ namespace Avalonia.Controls
}
int slot = lastDisplayedScrollingRow;
- while (DoubleUtil.LessThan(deltaY, displayHeight) && slot >= 0)
+ while (MathUtilities.LessThan(deltaY, displayHeight) && slot >= 0)
{
deltaY += GetExactSlotElementHeight(slot);
visibleScrollingRows++;
@@ -2542,7 +2542,7 @@ namespace Avalonia.Controls
double heightChange = UpdateRowGroupVisibility(rowGroupInfo, isVisible, isDisplayed: false);
// Use epsilon instead of 0 here so that in the off chance that our estimates put the vertical offset negative
// the user can still scroll to the top since the offset is non-zero
- SetVerticalOffset(Math.Max(DoubleUtil.DBL_EPSILON, _verticalOffset + heightChange));
+ SetVerticalOffset(Math.Max(MathUtilities.DoubleEpsilon, _verticalOffset + heightChange));
}
else
{
@@ -3024,4 +3024,4 @@ namespace Avalonia.Controls
}
#endif
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs b/src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs
index 0f513e7f42..6e0703c90f 100644
--- a/src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs
+++ b/src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs
@@ -310,9 +310,9 @@ namespace Avalonia.Controls.Primitives
double leftEdge = column.IsFrozen ? frozenLeftEdge : scrollingLeftEdge;
double rightEdge = leftEdge + column.ActualWidth;
return
- DoubleUtil.GreaterThan(rightEdge, 0) &&
- DoubleUtil.LessThanOrClose(leftEdge, OwningGrid.CellsWidth) &&
- DoubleUtil.GreaterThan(rightEdge, frozenLeftEdge); // scrolling column covered up by frozen column(s)
+ MathUtilities.GreaterThan(rightEdge, 0) &&
+ MathUtilities.LessThanOrClose(leftEdge, OwningGrid.CellsWidth) &&
+ MathUtilities.GreaterThan(rightEdge, frozenLeftEdge); // scrolling column covered up by frozen column(s)
}
}
}
diff --git a/src/Avalonia.Controls.DataGrid/Utils/DoubleUtil.cs b/src/Avalonia.Controls.DataGrid/Utils/DoubleUtil.cs
deleted file mode 100644
index ec0e8836d7..0000000000
--- a/src/Avalonia.Controls.DataGrid/Utils/DoubleUtil.cs
+++ /dev/null
@@ -1,136 +0,0 @@
-// (c) Copyright Microsoft Corporation.
-// This source is subject to the Microsoft Public License (Ms-PL).
-// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
-// All other rights reserved.
-
-using System;
-
-namespace Avalonia.Controls.Utils
-{
- internal static class DoubleUtil
- {
- internal const double DBL_EPSILON = 1e-6;
-
- ///
- /// AreClose - Returns whether or not two doubles are "close". That is, whether or
- /// not they are within epsilon of each other. Note that this epsilon is proportional
- /// to the numbers themselves to that AreClose survives scalar multiplication.
- /// There are plenty of ways for this to return false even for numbers which
- /// are theoretically identical, so no code calling this should fail to work if this
- /// returns false. This is important enough to repeat:
- /// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be
- /// used for optimizations *only*.
- ///
- ///
- /// bool - the result of the AreClose comparison.
- ///
- /// The first double to compare.
- /// The second double to compare.
- public static bool AreClose(double value1, double value2)
- {
- //in case they are Infinities (then epsilon check does not work)
- if (value1 == value2) return true;
- // This computes (|value1-value2| / (|value1| + |value2| + 10.0)) < DBL_EPSILON
- double eps = (Math.Abs(value1) + Math.Abs(value2) + 10.0) * DBL_EPSILON;
- double delta = value1 - value2;
- return (-eps < delta) && (eps > delta);
- }
-
- ///
- /// 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
- /// the other number. Note that this epsilon is proportional to the numbers themselves
- /// to that AreClose survives scalar multiplication. Note,
- /// There are plenty of ways for this to return false even for numbers which
- /// are theoretically identical, so no code calling this should fail to work if this
- /// returns false. This is important enough to repeat:
- /// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be
- /// used for optimizations *only*.
- ///
- ///
- /// bool - the result of the GreaterThan comparison.
- ///
- /// The first double to compare.
- /// The second double to compare.
- public static bool GreaterThan(double value1, double value2)
- {
- return (value1 > value2) && !AreClose(value1, value2);
- }
-
- ///
- /// 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
- /// epsilon of the other number. Note that this epsilon is proportional to the numbers
- /// themselves to that AreClose survives scalar multiplication. Note,
- /// There are plenty of ways for this to return false even for numbers which
- /// are theoretically identical, so no code calling this should fail to work if this
- /// returns false. This is important enough to repeat:
- /// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be
- /// used for optimizations *only*.
- ///
- ///
- /// bool - the result of the GreaterThanOrClose comparison.
- ///
- /// The first double to compare.
- /// The second double to compare.
- public static bool GreaterThanOrClose(double value1, double value2)
- {
- return (value1 > value2) || AreClose(value1, value2);
- }
-
- ///
- /// IsZero - Returns whether or not the double is "close" to 0. Same as AreClose(double, 0),
- /// but this is faster.
- ///
- ///
- /// bool - the result of the IsZero comparison.
- ///
- /// The double to compare to 0.
- public static bool IsZero(double value)
- {
- return Math.Abs(value) < 10.0 * DBL_EPSILON;
- }
-
- ///
- /// 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
- /// the other number. Note that this epsilon is proportional to the numbers themselves
- /// to that AreClose survives scalar multiplication. Note,
- /// There are plenty of ways for this to return false even for numbers which
- /// are theoretically identical, so no code calling this should fail to work if this
- /// returns false. This is important enough to repeat:
- /// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be
- /// used for optimizations *only*.
- ///
- ///
- /// bool - the result of the LessThan comparison.
- ///
- /// The first double to compare.
- /// The second double to compare.
- public static bool LessThan(double value1, double value2)
- {
- return (value1 < value2) && !AreClose(value1, value2);
- }
-
- ///
- /// 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
- /// epsilon of the other number. Note that this epsilon is proportional to the numbers
- /// themselves to that AreClose survives scalar multiplication. Note,
- /// There are plenty of ways for this to return false even for numbers which
- /// are theoretically identical, so no code calling this should fail to work if this
- /// returns false. This is important enough to repeat:
- /// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be
- /// used for optimizations *only*.
- ///
- ///
- /// bool - the result of the LessThanOrClose comparison.
- ///
- /// The first double to compare.
- /// The second double to compare.
- public static bool LessThanOrClose(double value1, double value2)
- {
- return (value1 < value2) || AreClose(value1, value2);
- }
- }
-}
diff --git a/src/Avalonia.Controls/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox.cs
index b38cc56a17..31101dc0f1 100644
--- a/src/Avalonia.Controls/AutoCompleteBox.cs
+++ b/src/Avalonia.Controls/AutoCompleteBox.cs
@@ -468,10 +468,11 @@ namespace Avalonia.Controls
///
/// dependency property.
public static readonly DirectProperty TextProperty =
- AvaloniaProperty.RegisterDirect(
- nameof(Text),
+ TextBlock.TextProperty.AddOwnerWithDataValidation(
o => o.Text,
- (o, v) => o.Text = v);
+ (o, v) => o.Text = v,
+ defaultBindingMode: BindingMode.TwoWay,
+ enableDataValidation: true);
///
/// Identifies the
@@ -1244,6 +1245,20 @@ namespace Avalonia.Controls
base.OnApplyTemplate(e);
}
+
+ ///
+ /// Called to update the validation state for properties for which data validation is
+ /// enabled.
+ ///
+ /// The property.
+ /// The new binding value for the property.
+ protected override void UpdateDataValidation(AvaloniaProperty property, BindingValue value)
+ {
+ if (property == TextProperty)
+ {
+ DataValidationErrors.SetError(this, value.Error);
+ }
+ }
///
/// Provides handling for the
diff --git a/src/Avalonia.Controls/Calendar/Calendar.cs b/src/Avalonia.Controls/Calendar/Calendar.cs
index 53c6a54b4d..4cf7db74d9 100644
--- a/src/Avalonia.Controls/Calendar/Calendar.cs
+++ b/src/Avalonia.Controls/Calendar/Calendar.cs
@@ -998,10 +998,10 @@ namespace Avalonia.Controls
///
- /// 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.
///
- internal bool DatePickerDisplayDateFlag { get; set; }
+ internal bool CalendarDatePickerDisplayDateFlag { get; set; }
internal CalendarDayButton FindDayButtonFromDay(DateTime day)
{
diff --git a/src/Avalonia.Controls/Calendar/DatePicker.cs b/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs
similarity index 87%
rename from src/Avalonia.Controls/Calendar/DatePicker.cs
rename to src/Avalonia.Controls/Calendar/CalendarDatePicker.cs
index 0f53dc1364..b987f065be 100644
--- a/src/Avalonia.Controls/Calendar/DatePicker.cs
+++ b/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs
@@ -16,29 +16,29 @@ namespace Avalonia.Controls
{
///
/// Provides data for the
- ///
+ ///
/// event.
///
- public class DatePickerDateValidationErrorEventArgs : EventArgs
+ public class CalendarDatePickerDateValidationErrorEventArgs : EventArgs
{
private bool _throwException;
///
/// Initializes a new instance of the
- ///
+ ///
/// class.
///
///
/// The initial exception from the
- ///
+ ///
/// event.
///
///
/// The text that caused the
- ///
+ ///
/// event.
///
- public DatePickerDateValidationErrorEventArgs(Exception exception, string text)
+ public CalendarDatePickerDateValidationErrorEventArgs(Exception exception, string text)
{
this.Text = text;
this.Exception = exception;
@@ -46,7 +46,7 @@ namespace Avalonia.Controls
///
/// Gets the initial exception associated with the
- ///
+ ///
/// event.
///
///
@@ -56,7 +56,7 @@ namespace Avalonia.Controls
///
/// Gets the text that caused the
- ///
+ ///
/// event.
///
///
@@ -66,7 +66,7 @@ namespace Avalonia.Controls
///
/// Gets or sets a value indicating whether
- ///
+ ///
/// should be thrown.
///
///
@@ -74,7 +74,7 @@ namespace Avalonia.Controls
///
///
/// If set to true and
- ///
+ ///
/// is null.
///
public bool ThrowException
@@ -93,9 +93,9 @@ namespace Avalonia.Controls
///
/// Specifies date formats for a
- /// .
+ /// .
///
- public enum DatePickerFormat
+ public enum CalendarDatePickerFormat
{
///
/// Specifies that the date should be displayed using unabbreviated days
@@ -115,7 +115,7 @@ namespace Avalonia.Controls
Custom = 2
}
- public class DatePicker : TemplatedControl
+ public class CalendarDatePicker : TemplatedControl
{
private const string ElementTextBox = "PART_TextBox";
private const string ElementButton = "PART_Button";
@@ -154,59 +154,59 @@ namespace Avalonia.Controls
///
public CalendarBlackoutDatesCollection BlackoutDates { get; private set; }
- public static readonly DirectProperty DisplayDateProperty =
- AvaloniaProperty.RegisterDirect(
+ public static readonly DirectProperty DisplayDateProperty =
+ AvaloniaProperty.RegisterDirect(
nameof(DisplayDate),
o => o.DisplayDate,
(o, v) => o.DisplayDate = v);
- public static readonly DirectProperty DisplayDateStartProperty =
- AvaloniaProperty.RegisterDirect(
+ public static readonly DirectProperty DisplayDateStartProperty =
+ AvaloniaProperty.RegisterDirect(
nameof(DisplayDateStart),
o => o.DisplayDateStart,
(o, v) => o.DisplayDateStart = v);
- public static readonly DirectProperty DisplayDateEndProperty =
- AvaloniaProperty.RegisterDirect(
+ public static readonly DirectProperty DisplayDateEndProperty =
+ AvaloniaProperty.RegisterDirect(
nameof(DisplayDateEnd),
o => o.DisplayDateEnd,
(o, v) => o.DisplayDateEnd = v);
public static readonly StyledProperty FirstDayOfWeekProperty =
- AvaloniaProperty.Register(nameof(FirstDayOfWeek));
+ AvaloniaProperty.Register(nameof(FirstDayOfWeek));
- public static readonly DirectProperty IsDropDownOpenProperty =
- AvaloniaProperty.RegisterDirect(
+ public static readonly DirectProperty IsDropDownOpenProperty =
+ AvaloniaProperty.RegisterDirect(
nameof(IsDropDownOpen),
o => o.IsDropDownOpen,
(o, v) => o.IsDropDownOpen = v);
public static readonly StyledProperty IsTodayHighlightedProperty =
- AvaloniaProperty.Register(nameof(IsTodayHighlighted));
- public static readonly DirectProperty SelectedDateProperty =
- AvaloniaProperty.RegisterDirect(
+ AvaloniaProperty.Register(nameof(IsTodayHighlighted));
+ public static readonly DirectProperty SelectedDateProperty =
+ AvaloniaProperty.RegisterDirect(
nameof(SelectedDate),
o => o.SelectedDate,
(o, v) => o.SelectedDate = v);
- public static readonly StyledProperty SelectedDateFormatProperty =
- AvaloniaProperty.Register(
+ public static readonly StyledProperty SelectedDateFormatProperty =
+ AvaloniaProperty.Register(
nameof(SelectedDateFormat),
- defaultValue: DatePickerFormat.Short,
+ defaultValue: CalendarDatePickerFormat.Short,
validate: IsValidSelectedDateFormat);
public static readonly StyledProperty CustomDateFormatStringProperty =
- AvaloniaProperty.Register(
+ AvaloniaProperty.Register(
nameof(CustomDateFormatString),
defaultValue: "d",
validate: IsValidDateFormatString);
- public static readonly DirectProperty TextProperty =
- AvaloniaProperty.RegisterDirect(
+ public static readonly DirectProperty TextProperty =
+ AvaloniaProperty.RegisterDirect(
nameof(Text),
o => o.Text,
(o, v) => o.Text = v);
public static readonly StyledProperty WatermarkProperty =
- TextBox.WatermarkProperty.AddOwner();
+ TextBox.WatermarkProperty.AddOwner();
public static readonly StyledProperty UseFloatingWatermarkProperty =
- TextBox.UseFloatingWatermarkProperty.AddOwner();
+ TextBox.UseFloatingWatermarkProperty.AddOwner();
///
@@ -218,9 +218,9 @@ namespace Avalonia.Controls
///
///
/// The specified date is not in the range defined by
- ///
+ ///
/// and
- /// .
+ /// .
///
public DateTime DisplayDate
{
@@ -320,7 +320,7 @@ namespace Avalonia.Controls
///
/// An specified format is not valid.
///
- public DatePickerFormat SelectedDateFormat
+ public CalendarDatePickerFormat SelectedDateFormat
{
get { return GetValue(SelectedDateFormatProperty); }
set { SetValue(SelectedDateFormatProperty, value); }
@@ -380,33 +380,33 @@ namespace Avalonia.Controls
/// Occurs when
/// is assigned a value that cannot be interpreted as a date.
///
- public event EventHandler DateValidationError;
+ public event EventHandler DateValidationError;
///
/// Occurs when the
- ///
+ ///
/// property is changed.
///
public event EventHandler SelectedDateChanged;
- static DatePicker()
+ static CalendarDatePicker()
{
- FocusableProperty.OverrideDefaultValue(true);
-
- DisplayDateProperty.Changed.AddClassHandler((x,e) => x.OnDisplayDateChanged(e));
- DisplayDateStartProperty.Changed.AddClassHandler((x,e) => x.OnDisplayDateStartChanged(e));
- DisplayDateEndProperty.Changed.AddClassHandler((x,e) => x.OnDisplayDateEndChanged(e));
- IsDropDownOpenProperty.Changed.AddClassHandler((x,e) => x.OnIsDropDownOpenChanged(e));
- SelectedDateProperty.Changed.AddClassHandler((x,e) => x.OnSelectedDateChanged(e));
- SelectedDateFormatProperty.Changed.AddClassHandler((x,e) => x.OnSelectedDateFormatChanged(e));
- CustomDateFormatStringProperty.Changed.AddClassHandler((x,e) => x.OnCustomDateFormatStringChanged(e));
- TextProperty.Changed.AddClassHandler((x,e) => x.OnTextChanged(e));
+ FocusableProperty.OverrideDefaultValue(true);
+
+ DisplayDateProperty.Changed.AddClassHandler((x,e) => x.OnDisplayDateChanged(e));
+ DisplayDateStartProperty.Changed.AddClassHandler((x,e) => x.OnDisplayDateStartChanged(e));
+ DisplayDateEndProperty.Changed.AddClassHandler((x,e) => x.OnDisplayDateEndChanged(e));
+ IsDropDownOpenProperty.Changed.AddClassHandler((x,e) => x.OnIsDropDownOpenChanged(e));
+ SelectedDateProperty.Changed.AddClassHandler((x,e) => x.OnSelectedDateChanged(e));
+ SelectedDateFormatProperty.Changed.AddClassHandler((x,e) => x.OnSelectedDateFormatChanged(e));
+ CustomDateFormatStringProperty.Changed.AddClassHandler((x,e) => x.OnCustomDateFormatStringChanged(e));
+ TextProperty.Changed.AddClassHandler((x,e) => x.OnTextChanged(e));
}
///
/// Initializes a new instance of the
/// class.
///
- public DatePicker()
+ public CalendarDatePicker()
{
FirstDayOfWeek = DateTimeHelper.GetCurrentDateFormat().FirstDayOfWeek;
_defaultText = string.Empty;
@@ -662,12 +662,12 @@ namespace Avalonia.Controls
// change is coming from the Calendar UI itself, so, we
// shouldn't change the DisplayDate since it will automatically
// 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;
}
if(_calendar != null)
- _calendar.DatePickerDisplayDateFlag = false;
+ _calendar.CalendarDatePickerDisplayDateFlag = false;
}
else
{
@@ -707,7 +707,7 @@ namespace Avalonia.Controls
}
private void OnCustomDateFormatStringChanged(AvaloniaPropertyChangedEventArgs e)
{
- if(SelectedDateFormat == DatePickerFormat.Custom)
+ if(SelectedDateFormat == CalendarDatePickerFormat.Custom)
{
OnDateFormatChanged();
}
@@ -752,15 +752,15 @@ namespace Avalonia.Controls
///
/// Raises the
- ///
+ ///
/// event.
///
///
/// A
- ///
+ ///
/// that contains the event data.
///
- protected virtual void OnDateValidationError(DatePickerDateValidationErrorEventArgs e)
+ protected virtual void OnDateValidationError(CalendarDatePickerDateValidationErrorEventArgs e)
{
DateValidationError?.Invoke(this, e);
}
@@ -959,7 +959,7 @@ namespace Avalonia.Controls
}
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);
if (dateValidationError.ThrowException)
@@ -970,7 +970,7 @@ namespace Avalonia.Controls
}
catch (FormatException ex)
{
- DatePickerDateValidationErrorEventArgs textParseError = new DatePickerDateValidationErrorEventArgs(ex, text);
+ CalendarDatePickerDateValidationErrorEventArgs textParseError = new CalendarDatePickerDateValidationErrorEventArgs(ex, text);
OnDateValidationError(textParseError);
if (textParseError.ThrowException)
@@ -986,11 +986,11 @@ namespace Avalonia.Controls
switch (SelectedDateFormat)
{
- case DatePickerFormat.Short:
+ case CalendarDatePickerFormat.Short:
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));
- case DatePickerFormat.Custom:
+ case CalendarDatePickerFormat.Custom:
return string.Format(CultureInfo.CurrentCulture, d.ToString(CustomDateFormatString, dtfi));
}
return null;
@@ -1118,12 +1118,12 @@ namespace Avalonia.Controls
switch (SelectedDateFormat)
{
- case DatePickerFormat.Long:
+ case CalendarDatePickerFormat.Long:
{
watermarkText = string.Format(CultureInfo.CurrentCulture, watermarkFormat, dtfi.LongDatePattern.ToString());
break;
}
- case DatePickerFormat.Short:
+ case CalendarDatePickerFormat.Short:
default:
{
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
- || value == DatePickerFormat.Short
- || value == DatePickerFormat.Custom;
+ return value == CalendarDatePickerFormat.Long
+ || value == CalendarDatePickerFormat.Short
+ || value == CalendarDatePickerFormat.Custom;
}
private static bool IsValidDateFormatString(string formatString)
{
diff --git a/src/Avalonia.Controls/Calendar/CalendarItem.cs b/src/Avalonia.Controls/Calendar/CalendarItem.cs
index ece0ef97d9..0be7c4f67e 100644
--- a/src/Avalonia.Controls/Calendar/CalendarItem.cs
+++ b/src/Avalonia.Controls/Calendar/CalendarItem.cs
@@ -909,7 +909,7 @@ namespace Avalonia.Controls.Primitives
case CalendarSelectionMode.SingleDate:
{
DateTime selectedDate = (DateTime)b.DataContext;
- Owner.DatePickerDisplayDateFlag = true;
+ Owner.CalendarDatePickerDisplayDateFlag = true;
if (Owner.SelectedDates.Count == 0)
{
Owner.SelectedDates.Add(selectedDate);
@@ -981,7 +981,7 @@ namespace Avalonia.Controls.Primitives
}
case CalendarSelectionMode.SingleDate:
{
- Owner.DatePickerDisplayDateFlag = true;
+ Owner.CalendarDatePickerDisplayDateFlag = true;
if (Owner.SelectedDates.Count == 0)
{
Owner.SelectedDates.Add(selectedDate);
diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs
index 86499530da..5929dd39d4 100644
--- a/src/Avalonia.Controls/ContextMenu.cs
+++ b/src/Avalonia.Controls/ContextMenu.cs
@@ -1,17 +1,18 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
-using System.Linq;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
-using Avalonia.LogicalTree;
using Avalonia.Styling;
+#nullable enable
+
namespace Avalonia.Controls
{
///
@@ -19,11 +20,59 @@ namespace Avalonia.Controls
///
public class ContextMenu : MenuBase, ISetterValue
{
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty HorizontalOffsetProperty =
+ Popup.HorizontalOffsetProperty.AddOwner();
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty VerticalOffsetProperty =
+ Popup.VerticalOffsetProperty.AddOwner();
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty PlacementAnchorProperty =
+ Popup.PlacementAnchorProperty.AddOwner();
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty PlacementConstraintAdjustmentProperty =
+ Popup.PlacementConstraintAdjustmentProperty.AddOwner();
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty PlacementGravityProperty =
+ Popup.PlacementGravityProperty.AddOwner();
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty PlacementModeProperty =
+ Popup.PlacementModeProperty.AddOwner();
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty PlacementRectProperty =
+ AvaloniaProperty.Register(nameof(PlacementRect));
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty PlacementTargetProperty =
+ Popup.PlacementTargetProperty.AddOwner();
+
private static readonly ITemplate DefaultPanel =
new FuncTemplate(() => new StackPanel { Orientation = Orientation.Vertical });
- private Popup _popup;
- private List _attachedControls;
- private IInputElement _previousFocus;
+ private Popup? _popup;
+ private List? _attachedControls;
+ private IInputElement? _previousFocus;
///
/// Initializes a new instance of the class.
@@ -47,23 +96,107 @@ namespace Avalonia.Controls
///
static ContextMenu()
{
- ItemsPanelProperty.OverrideDefaultValue(typeof(ContextMenu), DefaultPanel);
+ ItemsPanelProperty.OverrideDefaultValue(DefaultPanel);
+ PlacementModeProperty.OverrideDefaultValue(PlacementMode.Pointer);
ContextMenuProperty.Changed.Subscribe(ContextMenuChanged);
}
+ ///
+ /// Gets or sets the Horizontal offset of the context menu in relation to the .
+ ///
+ public double HorizontalOffset
+ {
+ get { return GetValue(HorizontalOffsetProperty); }
+ set { SetValue(HorizontalOffsetProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the Vertical offset of the context menu in relation to the .
+ ///
+ public double VerticalOffset
+ {
+ get { return GetValue(VerticalOffsetProperty); }
+ set { SetValue(VerticalOffsetProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the anchor point on the when
+ /// is .
+ ///
+ public PopupAnchor PlacementAnchor
+ {
+ get { return GetValue(PlacementAnchorProperty); }
+ set { SetValue(PlacementAnchorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value describing how the context menu position will be adjusted if the
+ /// unadjusted position would result in the context menu being partly constrained.
+ ///
+ public PopupPositionerConstraintAdjustment PlacementConstraintAdjustment
+ {
+ get { return GetValue(PlacementConstraintAdjustmentProperty); }
+ set { SetValue(PlacementConstraintAdjustmentProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value which defines in what direction the context menu should open
+ /// when is .
+ ///
+ public PopupGravity PlacementGravity
+ {
+ get { return GetValue(PlacementGravityProperty); }
+ set { SetValue(PlacementGravityProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the placement mode of the context menu in relation to the.
+ ///
+ public PlacementMode PlacementMode
+ {
+ get { return GetValue(PlacementModeProperty); }
+ set { SetValue(PlacementModeProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the the anchor rectangle within the parent that the context menu will be placed
+ /// relative to when is .
+ ///
+ ///
+ /// The placement rect defines a rectangle relative to around
+ /// which the popup will be opened, with determining which edge
+ /// of the placement target is used.
+ ///
+ /// If unset, the anchor rectangle will be the bounds of the .
+ ///
+ public Rect? PlacementRect
+ {
+ get { return GetValue(PlacementRectProperty); }
+ set { SetValue(PlacementRectProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the control that is used to determine the popup's position.
+ ///
+ public Control? PlacementTarget
+ {
+ get { return GetValue(PlacementTargetProperty); }
+ set { SetValue(PlacementTargetProperty, value); }
+ }
+
///
/// Occurs when the value of the
///
/// property is changing from false to true.
///
- public event CancelEventHandler ContextMenuOpening;
+ public event CancelEventHandler? ContextMenuOpening;
///
/// Occurs when the value of the
///
/// property is changing from true to false.
///
- public event CancelEventHandler ContextMenuClosing;
+ public event CancelEventHandler? ContextMenuClosing;
///
/// Called when the property changes on a control.
@@ -77,7 +210,7 @@ namespace Avalonia.Controls
{
control.PointerReleased -= ControlPointerReleased;
oldMenu._attachedControls?.Remove(control);
- ((ISetLogicalParent)oldMenu._popup)?.SetParent(null);
+ ((ISetLogicalParent?)oldMenu._popup)?.SetParent(null);
}
if (e.NewValue is ContextMenu newMenu)
@@ -97,7 +230,7 @@ namespace Avalonia.Controls
/// Opens a context menu on the specified control.
///
/// The control.
- public void Open(Control control)
+ public void Open(Control? control)
{
if (control is null && (_attachedControls is null || _attachedControls.Count == 0))
{
@@ -113,7 +246,7 @@ namespace Avalonia.Controls
nameof(control));
}
- control ??= _attachedControls[0];
+ control ??= _attachedControls![0];
if (IsOpen)
{
@@ -124,8 +257,14 @@ namespace Avalonia.Controls
{
_popup = new Popup
{
- PlacementMode = PlacementMode.Pointer,
- PlacementTarget = control,
+ HorizontalOffset = HorizontalOffset,
+ VerticalOffset = VerticalOffset,
+ PlacementAnchor = PlacementAnchor,
+ PlacementConstraintAdjustment = PlacementConstraintAdjustment,
+ PlacementGravity = PlacementGravity,
+ PlacementMode = PlacementMode,
+ PlacementRect = PlacementRect,
+ PlacementTarget = PlacementTarget ?? control,
StaysOpen = false
};
@@ -204,7 +343,7 @@ namespace Avalonia.Controls
if (_attachedControls is null || _attachedControls.Count == 0)
{
- ((ISetLogicalParent)_popup).SetParent(null);
+ ((ISetLogicalParent)_popup!).SetParent(null);
}
// HACK: Reset the focus when the popup is closed. We need to fix this so it's automatic.
diff --git a/src/Avalonia.Controls/Converters/MarginMultiplierConverter.cs b/src/Avalonia.Controls/Converters/MarginMultiplierConverter.cs
index 54bd6bcf39..9f3a6da9da 100644
--- a/src/Avalonia.Controls/Converters/MarginMultiplierConverter.cs
+++ b/src/Avalonia.Controls/Converters/MarginMultiplierConverter.cs
@@ -18,10 +18,24 @@ namespace Avalonia.Controls.Converters
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
- if (!(value is int depth))
- return new Thickness(0);
-
- return new Thickness(Left ? Indent * depth : 0, Top ? Indent * depth : 0, Right ? Indent * depth : 0, Bottom ? Indent * depth : 0);
+ if (value is int scalarDepth)
+ {
+ return new Thickness(
+ Left ? Indent * scalarDepth : 0,
+ Top ? Indent * scalarDepth : 0,
+ Right ? Indent * scalarDepth : 0,
+ Bottom ? Indent * scalarDepth : 0);
+ }
+ else if (value is Thickness thinknessDepth)
+ {
+ return new Thickness(
+ Left ? Indent * thinknessDepth.Left : 0,
+ Top ? Indent * thinknessDepth.Top : 0,
+ Right ? Indent * thinknessDepth.Right : 0,
+ Bottom ? Indent * thinknessDepth.Bottom : 0);
+ }
+ return new Thickness(0);
+
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
diff --git a/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs b/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs
new file mode 100644
index 0000000000..e6420fe342
--- /dev/null
+++ b/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using Avalonia.Controls.Primitives;
+using Avalonia.Data.Converters;
+using Avalonia.Utilities;
+
+namespace Avalonia.Controls.Converters
+{
+ public class MenuScrollingVisibilityConverter : IMultiValueConverter
+ {
+ public static readonly MenuScrollingVisibilityConverter Instance = new MenuScrollingVisibilityConverter();
+
+ public object Convert(IList
public class LayoutTransformControl : Decorator
{
- public static readonly StyledProperty LayoutTransformProperty =
- AvaloniaProperty.Register(nameof(LayoutTransform));
+ public static readonly StyledProperty LayoutTransformProperty =
+ AvaloniaProperty.Register(nameof(LayoutTransform));
public static readonly StyledProperty UseRenderTransformProperty =
AvaloniaProperty.Register(nameof(LayoutTransform));
@@ -37,7 +37,7 @@ namespace Avalonia.Controls
///
/// Gets or sets a graphics transformation that should apply to this element when layout is performed.
///
- public Transform LayoutTransform
+ public ITransform LayoutTransform
{
get { return GetValue(LayoutTransformProperty); }
set { SetValue(LayoutTransformProperty, value); }
diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs
index b08519963b..912abc6de3 100644
--- a/src/Avalonia.Controls/MenuItem.cs
+++ b/src/Avalonia.Controls/MenuItem.cs
@@ -105,6 +105,7 @@ namespace Avalonia.Controls
static MenuItem()
{
SelectableMixin.Attach