diff --git a/.gitignore b/.gitignore index 44fe5e4ba4..84faae1806 100644 --- a/.gitignore +++ b/.gitignore @@ -212,3 +212,6 @@ coc-settings.json *.map src/Web/Avalonia.Web.Blazor/wwwroot/*.js src/Web/Avalonia.Web.Blazor/Interop/Typescript/*.js +node_modules +src/Web/Avalonia.Web.Blazor/webapp/package-lock.json +src/Web/Avalonia.Web.Blazor/wwwroot diff --git a/samples/ControlCatalog/Converter/HexConverter.cs b/samples/ControlCatalog/Converter/HexConverter.cs index 83a52212f6..31cce5ba67 100644 --- a/samples/ControlCatalog/Converter/HexConverter.cs +++ b/samples/ControlCatalog/Converter/HexConverter.cs @@ -7,7 +7,7 @@ namespace ControlCatalog.Converter; public class HexConverter : IValueConverter { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { var str = value?.ToString(); if (str == null) @@ -18,7 +18,7 @@ public class HexConverter : IValueConverter } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { try { diff --git a/src/Avalonia.Base/Layout/LayoutHelper.cs b/src/Avalonia.Base/Layout/LayoutHelper.cs index 404d19906a..0851dbaea9 100644 --- a/src/Avalonia.Base/Layout/LayoutHelper.cs +++ b/src/Avalonia.Base/Layout/LayoutHelper.cs @@ -251,6 +251,17 @@ namespace Avalonia.Layout { double newValue; + // Round the value to avoid FP errors. This is needed because if `value` has a floating + // point precision error (e.g. 79.333333333333343) then when it's multiplied by + // `dpiScale` and rounded up, it will be rounded up to a value one greater than it + // should be. +#if NET6_0_OR_GREATER + value = Math.Round(value, 8, MidpointRounding.ToZero); +#else + // MidpointRounding.ToZero isn't available in netstandard2.0. + value = Math.Truncate(value * 1e8) / 1e8; +#endif + // If DPI == 1, don't use DPI-aware rounding. if (!MathUtilities.IsOne(dpiScale)) { diff --git a/src/Avalonia.Base/Media/DashStyle.cs b/src/Avalonia.Base/Media/DashStyle.cs index abee580020..3a30b2d32f 100644 --- a/src/Avalonia.Base/Media/DashStyle.cs +++ b/src/Avalonia.Base/Media/DashStyle.cs @@ -35,7 +35,6 @@ namespace Avalonia.Media /// Initializes a new instance of the class. /// public DashStyle() - : this(null, 0) { } diff --git a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs index 9742a6b3ba..e47c8e7e31 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs @@ -87,7 +87,16 @@ public class CompositingRenderer : IRendererWithCompositor /// public IEnumerable HitTest(Point p, IVisual root, Func? filter) { - var res = CompositionTarget.TryHitTest(p, filter); + Func? f = null; + if (filter != null) + f = v => + { + if (v is CompositionDrawListVisual dlv) + return filter(dlv.Visual); + return true; + }; + + var res = CompositionTarget.TryHitTest(p, f); if(res == null) yield break; foreach(var v in res) diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs b/src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs index 77b392eee5..b019d1792b 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs @@ -54,13 +54,11 @@ internal class CompositionDrawListVisual : CompositionContainerVisual Visual = visual; } - internal override bool HitTest(Point pt, Func? filter) + internal override bool HitTest(Point pt) { var custom = Visual as ICustomHitTest; if (DrawList == null && custom == null) return false; - if (filter != null && !filter(Visual)) - return false; if (custom != null) { // Simulate the old behavior diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs index 4e53e163ec..eb499604e0 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs @@ -31,7 +31,7 @@ namespace Avalonia.Rendering.Composition /// /// /// - public PooledList? TryHitTest(Point point, Func? filter) + public PooledList? TryHitTest(Point point, Func? filter) { Server.Readback.NextRead(); if (Root == null) @@ -88,10 +88,14 @@ namespace Avalonia.Rendering.Composition } void HitTestCore(CompositionVisual visual, Point globalPoint, PooledList result, - Func? filter) + Func? filter) { if (visual.Visible == false) return; + + if (filter != null && !filter(visual)) + return; + if (!TryTransformTo(visual, globalPoint, out var point)) return; @@ -111,7 +115,7 @@ namespace Avalonia.Rendering.Composition } // Hit-test the current node - if (visual.HitTest(point, filter)) + if (visual.HitTest(point)) result.Add(visual); } diff --git a/src/Avalonia.Base/Rendering/Composition/Visual.cs b/src/Avalonia.Base/Rendering/Composition/Visual.cs index 7356b7b9e8..6d6818256a 100644 --- a/src/Avalonia.Base/Rendering/Composition/Visual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Visual.cs @@ -53,6 +53,6 @@ namespace Avalonia.Rendering.Composition internal object? Tag { get; set; } - internal virtual bool HitTest(Point point, Func? filter) => true; + internal virtual bool HitTest(Point point) => true; } } diff --git a/src/Avalonia.Controls.ColorPicker/Converters/DoNothingForNullConverter.cs b/src/Avalonia.Controls.ColorPicker/Converters/DoNothingForNullConverter.cs new file mode 100644 index 0000000000..421a5acb28 --- /dev/null +++ b/src/Avalonia.Controls.ColorPicker/Converters/DoNothingForNullConverter.cs @@ -0,0 +1,35 @@ +using System; +using System.Globalization; +using Avalonia.Data; +using Avalonia.Data.Converters; + +namespace Avalonia.Controls.Converters +{ + /// + /// Converter that will do nothing (not update bound values) when a null value is encountered. + /// This converter enables binding nullable with non-nullable properties in some scenarios. + /// + public class DoNothingForNullConverter : IValueConverter + { + /// + public object? Convert( + object? value, + Type targetType, + object? parameter, + CultureInfo culture) + { + return value ?? BindingOperations.DoNothing; + } + + /// + public object? ConvertBack( + object? value, + Type targetType, + object? parameter, + CultureInfo culture) + { + return value ?? BindingOperations.DoNothing; + } + } +} + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml index 59cc48975f..dab4142001 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml @@ -11,6 +11,7 @@ + 48 30 @@ -205,7 +206,7 @@ diff --git a/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs index d63c738133..69e6766bfd 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs @@ -23,10 +23,10 @@ namespace Avalonia.Controls [PseudoClasses(":pressed", ":current", ":expanded")] public class DataGridRowGroupHeader : TemplatedControl { - private const string DATAGRIDROWGROUPHEADER_expanderButton = "ExpanderButton"; - private const string DATAGRIDROWGROUPHEADER_indentSpacer = "IndentSpacer"; - private const string DATAGRIDROWGROUPHEADER_itemCountElement = "ItemCountElement"; - private const string DATAGRIDROWGROUPHEADER_propertyNameElement = "PropertyNameElement"; + private const string DATAGRIDROWGROUPHEADER_expanderButton = "PART_ExpanderButton"; + private const string DATAGRIDROWGROUPHEADER_indentSpacer = "PART_IndentSpacer"; + private const string DATAGRIDROWGROUPHEADER_itemCountElement = "PART_ItemCountElement"; + private const string DATAGRIDROWGROUPHEADER_propertyNameElement = "PART_PropertyNameElement"; private bool _areIsCheckedHandlersSuspended; private ToggleButton _expanderButton; diff --git a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml index 364a2a52e3..b4bae02b9b 100644 --- a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml +++ b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml @@ -409,9 +409,9 @@ ColumnDefinitions="Auto,Auto,Auto,Auto,*" RowDefinitions="*,Auto"> - - - - diff --git a/src/Avalonia.Controls.DataGrid/Themes/Simple.xaml b/src/Avalonia.Controls.DataGrid/Themes/Simple.xaml index 6a748f399e..b43addff86 100644 --- a/src/Avalonia.Controls.DataGrid/Themes/Simple.xaml +++ b/src/Avalonia.Controls.DataGrid/Themes/Simple.xaml @@ -258,10 +258,10 @@ ColumnDefinitions="Auto,Auto,Auto,Auto" RowDefinitions="Auto,*,Auto"> - - - - diff --git a/src/Avalonia.Controls/Calendar/Calendar.cs b/src/Avalonia.Controls/Calendar/Calendar.cs index bb838a4f3f..0de068a416 100644 --- a/src/Avalonia.Controls/Calendar/Calendar.cs +++ b/src/Avalonia.Controls/Calendar/Calendar.cs @@ -2109,8 +2109,8 @@ namespace Avalonia.Controls RemovedItems = new Collection(); } - private const string PART_ElementRoot = "Root"; - private const string PART_ElementMonth = "CalendarItem"; + private const string PART_ElementRoot = "PART_Root"; + private const string PART_ElementMonth = "PART_CalendarItem"; /// /// Builds the visual tree for the diff --git a/src/Avalonia.Controls/Calendar/CalendarItem.cs b/src/Avalonia.Controls/Calendar/CalendarItem.cs index 75e9f52621..e2eabb5f28 100644 --- a/src/Avalonia.Controls/Calendar/CalendarItem.cs +++ b/src/Avalonia.Controls/Calendar/CalendarItem.cs @@ -32,11 +32,11 @@ namespace Avalonia.Controls.Primitives /// private const int NumberOfDaysPerWeek = 7; - private const string PART_ElementHeaderButton = "HeaderButton"; - private const string PART_ElementPreviousButton = "PreviousButton"; - private const string PART_ElementNextButton = "NextButton"; - private const string PART_ElementMonthView = "MonthView"; - private const string PART_ElementYearView = "YearView"; + private const string PART_ElementHeaderButton = "PART_HeaderButton"; + private const string PART_ElementPreviousButton = "PART_PreviousButton"; + private const string PART_ElementNextButton = "PART_NextButton"; + private const string PART_ElementMonthView = "PART_MonthView"; + private const string PART_ElementYearView = "PART_YearView"; private Button? _headerButton; private Button? _nextButton; diff --git a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs index f2b808fe0d..fb27feb521 100644 --- a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs +++ b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs @@ -14,15 +14,15 @@ namespace Avalonia.Controls /// /// A control to allow the user to select a date /// - [TemplatePart("ButtonContentGrid", typeof(Grid))] - [TemplatePart("DayText", typeof(TextBlock))] - [TemplatePart("FirstSpacer", typeof(Rectangle))] - [TemplatePart("FlyoutButton", typeof(Button))] - [TemplatePart("MonthText", typeof(TextBlock))] - [TemplatePart("PickerPresenter", typeof(DatePickerPresenter))] - [TemplatePart("Popup", typeof(Popup))] - [TemplatePart("SecondSpacer", typeof(Rectangle))] - [TemplatePart("YearText", typeof(TextBlock))] + [TemplatePart("PART_ButtonContentGrid", typeof(Grid))] + [TemplatePart("PART_DayTextBlock", typeof(TextBlock))] + [TemplatePart("PART_FirstSpacer", typeof(Rectangle))] + [TemplatePart("PART_FlyoutButton", typeof(Button))] + [TemplatePart("PART_MonthTextBlock", typeof(TextBlock))] + [TemplatePart("PART_PickerPresenter", typeof(DatePickerPresenter))] + [TemplatePart("PART_Popup", typeof(Popup))] + [TemplatePart("PART_SecondSpacer", typeof(Rectangle))] + [TemplatePart("PART_YearTextBlock", typeof(TextBlock))] [PseudoClasses(":hasnodate")] public class DatePicker : TemplatedControl { @@ -280,15 +280,15 @@ namespace Avalonia.Controls } base.OnApplyTemplate(e); - _flyoutButton = e.NameScope.Find