From 8526d98d28d90912bd4ea8e5712d733d0ebc0448 Mon Sep 17 00:00:00 2001 From: 0x90d <46010672+0x90d@users.noreply.github.com> Date: Tue, 10 Aug 2021 03:35:43 +0200 Subject: [PATCH 01/62] Fix datagrid right click selection --- src/Avalonia.Controls.DataGrid/DataGrid.cs | 35 +++++++++++++++++ .../DataGridCell.cs | 39 ++++++++++++------- .../DataGridRowGroupHeader.cs | 15 ++++++- .../DataGridRowHeader.cs | 17 +++++++- 4 files changed, 90 insertions(+), 16 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index 2f170c617d..00ae1b0e7d 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -3002,6 +3002,12 @@ namespace Avalonia.Controls } } + //TODO: Ensure right button is checked for + internal bool UpdateStateOnMouseRightButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit) + { + KeyboardHelper.GetMetaKeyState(pointerPressedEventArgs.KeyModifiers, out bool ctrl, out bool shift); + return UpdateStateOnMouseRightButtonDown(pointerPressedEventArgs, columnIndex, slot, allowEdit, shift, ctrl); + } //TODO: Ensure left button is checked for internal bool UpdateStateOnMouseLeftButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit) { @@ -5674,6 +5680,35 @@ namespace Avalonia.Controls VerticalScroll?.Invoke(sender, e); } + //TODO: Ensure right button is checked for + private bool UpdateStateOnMouseRightButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit, bool shift, bool ctrl) + { + Debug.Assert(slot >= 0); + + if (shift || ctrl) + { + return true; + } + if (IsSlotOutOfBounds(slot)) + { + return true; + } + if (GetRowSelection(slot)) + { + return true; + } + // Unselect everything except the row that was clicked on + try + { + UpdateSelectionAndCurrency(columnIndex, slot, DataGridSelectionAction.SelectCurrent, scrollIntoView: false); + } + finally + { + NoSelectionChangeCount--; + } + return true; + } + //TODO: Ensure left button is checked for private bool UpdateStateOnMouseLeftButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit, bool shift, bool ctrl) { diff --git a/src/Avalonia.Controls.DataGrid/DataGridCell.cs b/src/Avalonia.Controls.DataGrid/DataGridCell.cs index 0de4612958..1029d8ce25 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridCell.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridCell.cs @@ -161,21 +161,34 @@ namespace Avalonia.Controls private void DataGridCell_PointerPressed(PointerPressedEventArgs e) { // OwningGrid is null for TopLeftHeaderCell and TopRightHeaderCell because they have no OwningRow - if (OwningGrid != null) + if (OwningGrid == null) { - OwningGrid.OnCellPointerPressed(new DataGridCellPointerPressedEventArgs(this, OwningRow, OwningColumn, e)); - if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) + return; + } + OwningGrid.OnCellPointerPressed(new DataGridCellPointerPressedEventArgs(this, OwningRow, OwningColumn, e)); + if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) + { + if (!e.Handled) + //if (!e.Handled && OwningGrid.IsTabStop) { - if (!e.Handled) - //if (!e.Handled && OwningGrid.IsTabStop) - { - OwningGrid.Focus(); - } - if (OwningRow != null) - { - e.Handled = OwningGrid.UpdateStateOnMouseLeftButtonDown(e, ColumnIndex, OwningRow.Slot, !e.Handled); - OwningGrid.UpdatedStateOnMouseLeftButtonDown = true; - } + OwningGrid.Focus(); + } + if (OwningRow != null) + { + e.Handled = OwningGrid.UpdateStateOnMouseLeftButtonDown(e, ColumnIndex, OwningRow.Slot, !e.Handled); + OwningGrid.UpdatedStateOnMouseLeftButtonDown = true; + } + } + else if (e.GetCurrentPoint(this).Properties.IsRightButtonPressed) + { + if (!e.Handled) + //if (!e.Handled && OwningGrid.IsTabStop) + { + OwningGrid.Focus(); + } + if (OwningRow != null) + { + e.Handled = OwningGrid.UpdateStateOnMouseRightButtonDown(e, ColumnIndex, OwningRow.Slot, !e.Handled); } } } diff --git a/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs index 1e03b134b1..49ca23d34c 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs @@ -283,7 +283,11 @@ namespace Avalonia.Controls //TODO TabStop private void DataGridRowGroupHeader_PointerPressed(PointerPressedEventArgs e) { - if (OwningGrid != null && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) + if (OwningGrid == null) + { + return; + } + if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) { if (OwningGrid.IsDoubleClickRecordsClickOnCall(this) && !e.Handled) { @@ -300,6 +304,15 @@ namespace Avalonia.Controls e.Handled = OwningGrid.UpdateStateOnMouseLeftButtonDown(e, OwningGrid.CurrentColumnIndex, RowGroupInfo.Slot, allowEdit: false); } } + else if (e.GetCurrentPoint(this).Properties.IsRightButtonPressed) + { + if (!e.Handled) + { + OwningGrid.Focus(); + } + e.Handled = OwningGrid.UpdateStateOnMouseRightButtonDown(e, OwningGrid.CurrentColumnIndex, RowGroupInfo.Slot, allowEdit: false); + } + } private void EnsureChildClip(Visual child, double frozenLeftEdge) diff --git a/src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs index 0cd3589a57..510072174f 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs @@ -179,12 +179,12 @@ namespace Avalonia.Controls.Primitives //TODO TabStop private void DataGridRowHeader_PointerPressed(object sender, PointerPressedEventArgs e) { - if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) + if (OwningGrid == null) { return; } - if (OwningGrid != null) + if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) { if (!e.Handled) //if (!e.Handled && OwningGrid.IsTabStop) @@ -199,6 +199,19 @@ namespace Avalonia.Controls.Primitives OwningGrid.UpdatedStateOnMouseLeftButtonDown = true; } } + else if (e.GetCurrentPoint(this).Properties.IsRightButtonPressed) + { + if (!e.Handled) + { + OwningGrid.Focus(); + } + if (OwningRow != null) + { + Debug.Assert(sender is DataGridRowHeader); + Debug.Assert(sender == this); + e.Handled = OwningGrid.UpdateStateOnMouseRightButtonDown(e, -1, Slot, false); + } + } } } From 7a027792cbfc4426bea431ea4b24268ef68c68a3 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Mon, 30 Aug 2021 18:39:21 +0300 Subject: [PATCH 02/62] add failing test --- .../Avalonia.Base.UnitTests.csproj | 1 + .../Logging/LoggingTests.cs | 214 ++++++++++++++++++ 2 files changed, 215 insertions(+) create mode 100644 tests/Avalonia.Base.UnitTests/Logging/LoggingTests.cs diff --git a/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj b/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj index 32f90d5cbf..c4c1f49346 100644 --- a/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj +++ b/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj @@ -14,6 +14,7 @@ + diff --git a/tests/Avalonia.Base.UnitTests/Logging/LoggingTests.cs b/tests/Avalonia.Base.UnitTests/Logging/LoggingTests.cs new file mode 100644 index 0000000000..193d5e3a45 --- /dev/null +++ b/tests/Avalonia.Base.UnitTests/Logging/LoggingTests.cs @@ -0,0 +1,214 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Avalonia.Controls; +using Avalonia.Controls.Shapes; +using Avalonia.Logging; +using Avalonia.Markup.Xaml; +using Avalonia.UnitTests; +using Avalonia.Utilities; +using Xunit; + +namespace Avalonia.Base.UnitTests.Logging +{ + public class LoggingTests + { + [Fact] + public void Control_Should_Not_Log_Binding_Errors_When_Detached_From_Visual_Tree() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + + + +"; + + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + using var logSink = new StubLogSink(LogEventLevel.Warning); + var panel = window.FindControl("panel"); + var rect = window.FindControl("rect"); + window.ApplyTemplate(); + window.Presenter.ApplyTemplate(); + panel.Children.Remove(rect); + Assert.Equal(0, logSink.Results.Count); + } + } + } + + class StubLogSink : ILogSink, IDisposable + { + LogEventLevel _level; + public StubLogSink(LogEventLevel level) + { + _level = level; + Logger.Sink = this; + } + public void Dispose() + { + Logger.Sink = null; + } + public List Results { get; set; } = new List(); + + public bool IsEnabled(LogEventLevel level, string area) + { + return true; + } + + public void Log(LogEventLevel level, string area, object source, string messageTemplate) + { + if (level >= _level) + { + Results.Add(Format(area, messageTemplate, source)); + } + } + + public void Log(LogEventLevel level, string area, object source, string messageTemplate, T0 propertyValue0) + { + if (level >= _level) + { + Results.Add(Format(area, messageTemplate, source, propertyValue0)); + } + } + + public void Log(LogEventLevel level, string area, object source, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + if (level >= _level) + { + Results.Add(Format(area, messageTemplate, source, propertyValue0, propertyValue1)); + } + } + + public void Log(LogEventLevel level, string area, object source, string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) + { + if (level >= _level) + { + Results.Add(Format(area, messageTemplate, source, propertyValue0, propertyValue1, propertyValue2)); + } + } + + public void Log(LogEventLevel level, string area, object source, string messageTemplate, params object[] propertyValues) + { + if (level >= _level) + { + Results.Add(Format(area, messageTemplate, source, propertyValues)); + } + } + #region Copy-Pasta + private static string Format( + string area, + string template, + object source, + T0 v0 = default, + T1 v1 = default, + T2 v2 = default) + { + var result = new StringBuilder(template.Length); + var r = new CharacterReader(template.AsSpan()); + var i = 0; + + result.Append('['); + result.Append(area); + result.Append("] "); + + while (!r.End) + { + var c = r.Take(); + + if (c != '{') + { + result.Append(c); + } + else + { + if (r.Peek != '{') + { + result.Append('\''); + result.Append(i++ switch + { + 0 => v0, + 1 => v1, + 2 => v2, + _ => null + }); + result.Append('\''); + r.TakeUntil('}'); + r.Take(); + } + else + { + result.Append('{'); + r.Take(); + } + } + } + + if (source is object) + { + result.Append(" ("); + result.Append(source.GetType().Name); + result.Append(" #"); + result.Append(source.GetHashCode()); + result.Append(')'); + } + + return result.ToString(); + } + + private static string Format( + string area, + string template, + object source, + object[] v) + { + var result = new StringBuilder(template.Length); + var r = new CharacterReader(template.AsSpan()); + var i = 0; + + result.Append('['); + result.Append(area); + result.Append(']'); + + while (!r.End) + { + var c = r.Take(); + + if (c != '{') + { + result.Append(c); + } + else + { + if (r.Peek != '{') + { + result.Append('\''); + result.Append(i < v.Length ? v[i++] : null); + result.Append('\''); + r.TakeUntil('}'); + r.Take(); + } + else + { + result.Append('{'); + r.Take(); + } + } + } + + if (source is object) + { + result.Append('('); + result.Append(source.GetType().Name); + result.Append(" #"); + result.Append(source.GetHashCode()); + result.Append(')'); + } + + return result.ToString(); + } + #endregion + } +} From 59ebe6e0c8edbaecf24d0edc5325b43502ad2e8c Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 31 Aug 2021 10:33:34 +0200 Subject: [PATCH 03/62] fixes(Dialogs): Warning CS0642 Possible mistaken empty statement --- src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml.cs b/src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml.cs index 55e30396e1..5d7619d184 100644 --- a/src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml.cs +++ b/src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml.cs @@ -30,13 +30,13 @@ namespace Avalonia.Dialogs } else { - using (Process process = Process.Start(new ProcessStartInfo + using Process process = Process.Start(new ProcessStartInfo { FileName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? url : "open", Arguments = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? $"{url}" : "", CreateNoWindow = true, UseShellExecute = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - })); + }); } } From 98a0f43f9ec5ccff309813ba36ad21987fc03bf6 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 31 Aug 2021 10:36:20 +0200 Subject: [PATCH 04/62] fixes(Dialogs): Suppress warning CS0618 'PointerPressedEventArgs.ClickCount' is obsolete: 'Use DoubleTapped event or Gestures.DoubleRightTapped attached event' --- src/Avalonia.Dialogs/ManagedFileChooser.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Dialogs/ManagedFileChooser.cs b/src/Avalonia.Dialogs/ManagedFileChooser.cs index f9f38ac474..9058c405a3 100644 --- a/src/Avalonia.Dialogs/ManagedFileChooser.cs +++ b/src/Avalonia.Dialogs/ManagedFileChooser.cs @@ -1,13 +1,11 @@ using System; using System.Linq; using System.Threading.Tasks; -using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.LogicalTree; -using Avalonia.Markup.Xaml; namespace Avalonia.Dialogs { @@ -35,7 +33,9 @@ namespace Avalonia.Dialogs if (_quickLinksRoot != null) { var isQuickLink = _quickLinksRoot.IsLogicalAncestorOf(e.Source as Control); +#pragma warning disable CS0618 // Type or member is obsolete if (e.ClickCount == 2 || isQuickLink) +#pragma warning restore CS0618 // Type or member is obsolete { if (model.ItemType == ManagedFileChooserItemType.File) { From 17229f80a733964f68fc360faecbd3a150ab7b08 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 31 Aug 2021 10:41:20 +0200 Subject: [PATCH 05/62] fixes(Dialogs): Warning CS0168 The variable '_' is declared but never used --- src/Avalonia.Dialogs/ManagedFileChooserSources.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Dialogs/ManagedFileChooserSources.cs b/src/Avalonia.Dialogs/ManagedFileChooserSources.cs index 050d618ce1..a217a67bc6 100644 --- a/src/Avalonia.Dialogs/ManagedFileChooserSources.cs +++ b/src/Avalonia.Dialogs/ManagedFileChooserSources.cs @@ -67,7 +67,7 @@ namespace Avalonia.Dialogs { Directory.GetFiles(x.VolumePath); } - catch (Exception _) + catch (Exception) { return null; } From dc71df166ce493b00c8e521adbb0b00bd5e1bd93 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 31 Aug 2021 12:05:28 +0200 Subject: [PATCH 06/62] fixes(Visuals): Suppress warning CS0618 'IVisualWithRoundRectClip' is obsolete: 'Internal API, will be removed in future versions, you've been warned' --- src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs | 7 +++++-- src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs | 4 +++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs index 85feb06c44..52427c4ae6 100644 --- a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs @@ -289,11 +289,14 @@ namespace Avalonia.Rendering using (context.PushPostTransform(m)) using (context.PushOpacity(opacity)) - using (clipToBounds - ? visual is IVisualWithRoundRectClip roundClipVisual + using (clipToBounds +#pragma warning disable CS0618 // Type or member is obsolete + ? visual is IVisualWithRoundRectClip roundClipVisual ? context.PushClip(new RoundedRect(bounds, roundClipVisual.ClipToBoundsRadius)) : context.PushClip(bounds) : default(DrawingContext.PushedState)) +#pragma warning restore CS0618 // Type or member is obsolete + using (visual.Clip != null ? context.PushGeometryClip(visual.Clip) : default(DrawingContext.PushedState)) using (visual.OpacityMask != null ? context.PushOpacityMask(visual.OpacityMask, bounds) : default(DrawingContext.PushedState)) using (context.PushTransformContainer()) diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs index c6cdf474bb..b9131c26f4 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs @@ -164,10 +164,12 @@ namespace Avalonia.Rendering.SceneGraph var visual = node.Visual; var opacity = visual.Opacity; var clipToBounds = visual.ClipToBounds; +#pragma warning disable CS0618 // Type or member is obsolete var clipToBoundsRadius = visual is IVisualWithRoundRectClip roundRectClip ? roundRectClip.ClipToBoundsRadius : default; - +#pragma warning restore CS0618 // Type or member is obsolete + var bounds = new Rect(visual.Bounds.Size); var contextImpl = (DeferredDrawingContextImpl)context.PlatformImpl; From 5d3796092178f90fa625407ca875c99e0a14381c Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 31 Aug 2021 12:11:55 +0200 Subject: [PATCH 07/62] fixes(Input): Suppress warning CS0618 'MouseDevice.Position' is obsolete --- src/Avalonia.Input/MouseDevice.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs index cfa3690daf..401c6cb2ac 100644 --- a/src/Avalonia.Input/MouseDevice.cs +++ b/src/Avalonia.Input/MouseDevice.cs @@ -75,7 +75,9 @@ namespace Avalonia.Input throw new InvalidOperationException("Control is not attached to visual tree."); } +#pragma warning disable CS0618 // Type or member is obsolete var rootPoint = relativeTo.VisualRoot.PointToClient(Position); +#pragma warning restore CS0618 // Type or member is obsolete var transform = relativeTo.VisualRoot.TransformToVisual(relativeTo); return rootPoint * transform!.Value; } From 22432a002dc9ba86429d582c08103e17f3e50433 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Tue, 31 Aug 2021 13:14:59 +0300 Subject: [PATCH 08/62] test --- src/Avalonia.Styling/LogicalTree/ControlLocator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Styling/LogicalTree/ControlLocator.cs b/src/Avalonia.Styling/LogicalTree/ControlLocator.cs index 6d0302ace4..e89bf55404 100644 --- a/src/Avalonia.Styling/LogicalTree/ControlLocator.cs +++ b/src/Avalonia.Styling/LogicalTree/ControlLocator.cs @@ -57,7 +57,7 @@ namespace Avalonia.LogicalTree private void Detached(object sender, LogicalTreeAttachmentEventArgs e) { _value = null; - PublishNext(null); + // PublishNext(null); } private void Update() From 91c9281511ad7697d322173edb7ce19ea4dbd0d7 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 31 Aug 2021 12:15:03 +0200 Subject: [PATCH 09/62] fixes(Input): Suppress warning CS0618 'PointerPressedEventArgs.ClickCount' is obsolete --- src/Avalonia.Input/Gestures.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Input/Gestures.cs b/src/Avalonia.Input/Gestures.cs index f2cc9e9072..8d74001309 100644 --- a/src/Avalonia.Input/Gestures.cs +++ b/src/Avalonia.Input/Gestures.cs @@ -81,17 +81,21 @@ namespace Avalonia.Input var e = (PointerPressedEventArgs)ev; var visual = (IVisual)ev.Source; - if (e.ClickCount <= 1) +#pragma warning disable CS0618 // Type or member is obsolete + var clickCount = e.ClickCount; +#pragma warning restore CS0618 // Type or member is obsolete + if (clickCount <= 1) { s_lastPress = new WeakReference(ev.Source); } - else if (s_lastPress != null && e.ClickCount == 2 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed) + else if (s_lastPress != null && clickCount == 2 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed) { if (s_lastPress.TryGetTarget(out var target) && target == e.Source) { e.Source.RaiseEvent(new TappedEventArgs(DoubleTappedEvent, e)); } } + } } From 68370e37a5d5340613369e49f8aa9f9e5b7f31d1 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 31 Aug 2021 16:14:15 +0200 Subject: [PATCH 10/62] fixes(DataGrid): Issue #6508 does not re-enter the edit mode after pressing ESC --- src/Avalonia.Controls.DataGrid/DataGrid.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index ab1aff9220..cb1ce0c731 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -4489,8 +4489,19 @@ namespace Avalonia.Controls element = dataGridColumn.GenerateEditingElementInternal(dataGridCell, dataGridRow.DataContext); if (element != null) { - // Subscribe to the new element's events - element.Initialized += EditingElement_Initialized; + + if (element.IsInitialized) + { + Threading.Dispatcher.UIThread.Post(() => + { + PreparingCellForEditPrivate(element as Control); + }); + } + else + { + // Subscribe to the new element's events + element.Initialized += EditingElement_Initialized; + } } } else From 063e5e7be3b94d4a3f929acbd2ac552534877e41 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Wed, 1 Sep 2021 11:42:16 +0300 Subject: [PATCH 11/62] test --- src/Avalonia.Styling/LogicalTree/ControlLocator.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Styling/LogicalTree/ControlLocator.cs b/src/Avalonia.Styling/LogicalTree/ControlLocator.cs index e89bf55404..501a26c848 100644 --- a/src/Avalonia.Styling/LogicalTree/ControlLocator.cs +++ b/src/Avalonia.Styling/LogicalTree/ControlLocator.cs @@ -57,7 +57,10 @@ namespace Avalonia.LogicalTree private void Detached(object sender, LogicalTreeAttachmentEventArgs e) { _value = null; - // PublishNext(null); + if (_relativeTo.IsAttachedToLogicalTree) + { + PublishNext(null); + } } private void Update() From 01518b37e78b44dff6f7839672b6fe5575771a08 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Wed, 1 Sep 2021 12:30:23 +0300 Subject: [PATCH 12/62] test --- src/Avalonia.Styling/LogicalTree/ControlLocator.cs | 6 ++---- src/Avalonia.Visuals/Visual.cs | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Styling/LogicalTree/ControlLocator.cs b/src/Avalonia.Styling/LogicalTree/ControlLocator.cs index 501a26c848..3f5b1ee0b5 100644 --- a/src/Avalonia.Styling/LogicalTree/ControlLocator.cs +++ b/src/Avalonia.Styling/LogicalTree/ControlLocator.cs @@ -57,10 +57,8 @@ namespace Avalonia.LogicalTree private void Detached(object sender, LogicalTreeAttachmentEventArgs e) { _value = null; - if (_relativeTo.IsAttachedToLogicalTree) - { - PublishNext(null); - } + PublishNext(null); + } private void Update() diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index 2c96d87bb6..163c3db5fd 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -493,7 +493,7 @@ namespace Avalonia // In theory this should only need to check for logical tree attachment, but in practise // due to ContentControlMixin only taking effect when the template has finished being // applied, some controls are attached to the visual tree before the logical tree. - if (((ILogical)this).IsAttachedToLogicalTree || ((IVisual)this).IsAttachedToVisualTree) + if (((ILogical)this).IsAttachedToLogicalTree) { if (e is BindingChainException b && string.IsNullOrEmpty(b.ExpressionErrorPoint) && From 3b65579b1abc74b289f355037a0e36f2ebb853ce Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 1 Sep 2021 12:50:02 +0200 Subject: [PATCH 13/62] fixes(DevTools): Warning CS8604 Possible null reference argument for parameter 'topLevel' --- .../Diagnostics/Views/MainWindow.xaml.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs index ea06c33e4d..73d867bf10 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs @@ -160,13 +160,19 @@ namespace Avalonia.Diagnostics.Views return; } + var root = Root; + if (root is null) + { + return; + } + switch (e.Modifiers) { case RawInputModifiers.Control | RawInputModifiers.Shift: { IControl? control = null; - foreach (var popupRoot in GetPopupRoots(Root)) + foreach (var popupRoot in GetPopupRoots(root)) { control = GetHoveredControl(popupRoot); @@ -176,7 +182,7 @@ namespace Avalonia.Diagnostics.Views } } - control ??= GetHoveredControl(Root); + control ??= GetHoveredControl(root); if (control != null) { @@ -190,7 +196,7 @@ namespace Avalonia.Diagnostics.Views { vm.FreezePopups = !vm.FreezePopups; - foreach (var popupRoot in GetPopupRoots(Root)) + foreach (var popupRoot in GetPopupRoots(root)) { if (popupRoot.Parent is Popup popup) { From 5c1fd27d1f0f31cf2c5c5a60aa634c7d3fc58914 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Wed, 1 Sep 2021 15:00:08 +0300 Subject: [PATCH 14/62] wip --- src/Avalonia.Visuals/Visual.cs | 5 +- .../Logging/LoggingTests.cs | 191 +----------------- 2 files changed, 12 insertions(+), 184 deletions(-) diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index 163c3db5fd..322b630e83 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -489,10 +489,7 @@ namespace Avalonia protected internal sealed override void LogBindingError(AvaloniaProperty property, Exception e) { - // Don't log a binding error unless the control is attached to a logical or visual tree. - // In theory this should only need to check for logical tree attachment, but in practise - // due to ContentControlMixin only taking effect when the template has finished being - // applied, some controls are attached to the visual tree before the logical tree. + // Don't log a binding error unless the control is attached to a logical tree. if (((ILogical)this).IsAttachedToLogicalTree) { if (e is BindingChainException b && diff --git a/tests/Avalonia.Base.UnitTests/Logging/LoggingTests.cs b/tests/Avalonia.Base.UnitTests/Logging/LoggingTests.cs index 193d5e3a45..9e1885463d 100644 --- a/tests/Avalonia.Base.UnitTests/Logging/LoggingTests.cs +++ b/tests/Avalonia.Base.UnitTests/Logging/LoggingTests.cs @@ -1,12 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Avalonia.Controls; +using Avalonia.Controls; using Avalonia.Controls.Shapes; -using Avalonia.Logging; using Avalonia.Markup.Xaml; using Avalonia.UnitTests; -using Avalonia.Utilities; using Xunit; namespace Avalonia.Base.UnitTests.Logging @@ -28,187 +23,23 @@ namespace Avalonia.Base.UnitTests.Logging "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); - using var logSink = new StubLogSink(LogEventLevel.Warning); + var calledTimes = 0; + using var logSink = TestLogSink.Start((l, a, s, m, d) => + { + if (l >= Avalonia.Logging.LogEventLevel.Warning) + { + calledTimes++; + } + }); var panel = window.FindControl("panel"); var rect = window.FindControl("rect"); window.ApplyTemplate(); window.Presenter.ApplyTemplate(); panel.Children.Remove(rect); - Assert.Equal(0, logSink.Results.Count); - } - } - } - - class StubLogSink : ILogSink, IDisposable - { - LogEventLevel _level; - public StubLogSink(LogEventLevel level) - { - _level = level; - Logger.Sink = this; - } - public void Dispose() - { - Logger.Sink = null; - } - public List Results { get; set; } = new List(); - - public bool IsEnabled(LogEventLevel level, string area) - { - return true; - } - - public void Log(LogEventLevel level, string area, object source, string messageTemplate) - { - if (level >= _level) - { - Results.Add(Format(area, messageTemplate, source)); - } - } - - public void Log(LogEventLevel level, string area, object source, string messageTemplate, T0 propertyValue0) - { - if (level >= _level) - { - Results.Add(Format(area, messageTemplate, source, propertyValue0)); - } - } - - public void Log(LogEventLevel level, string area, object source, string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - if (level >= _level) - { - Results.Add(Format(area, messageTemplate, source, propertyValue0, propertyValue1)); - } - } - - public void Log(LogEventLevel level, string area, object source, string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) - { - if (level >= _level) - { - Results.Add(Format(area, messageTemplate, source, propertyValue0, propertyValue1, propertyValue2)); - } - } - - public void Log(LogEventLevel level, string area, object source, string messageTemplate, params object[] propertyValues) - { - if (level >= _level) - { - Results.Add(Format(area, messageTemplate, source, propertyValues)); - } - } - #region Copy-Pasta - private static string Format( - string area, - string template, - object source, - T0 v0 = default, - T1 v1 = default, - T2 v2 = default) - { - var result = new StringBuilder(template.Length); - var r = new CharacterReader(template.AsSpan()); - var i = 0; - - result.Append('['); - result.Append(area); - result.Append("] "); - - while (!r.End) - { - var c = r.Take(); - - if (c != '{') - { - result.Append(c); - } - else - { - if (r.Peek != '{') - { - result.Append('\''); - result.Append(i++ switch - { - 0 => v0, - 1 => v1, - 2 => v2, - _ => null - }); - result.Append('\''); - r.TakeUntil('}'); - r.Take(); - } - else - { - result.Append('{'); - r.Take(); - } - } + Assert.Equal(0, calledTimes); } - - if (source is object) - { - result.Append(" ("); - result.Append(source.GetType().Name); - result.Append(" #"); - result.Append(source.GetHashCode()); - result.Append(')'); - } - - return result.ToString(); } + } - private static string Format( - string area, - string template, - object source, - object[] v) - { - var result = new StringBuilder(template.Length); - var r = new CharacterReader(template.AsSpan()); - var i = 0; - - result.Append('['); - result.Append(area); - result.Append(']'); - - while (!r.End) - { - var c = r.Take(); - - if (c != '{') - { - result.Append(c); - } - else - { - if (r.Peek != '{') - { - result.Append('\''); - result.Append(i < v.Length ? v[i++] : null); - result.Append('\''); - r.TakeUntil('}'); - r.Take(); - } - else - { - result.Append('{'); - r.Take(); - } - } - } - - if (source is object) - { - result.Append('('); - result.Append(source.GetType().Name); - result.Append(" #"); - result.Append(source.GetHashCode()); - result.Append(')'); - } - return result.ToString(); - } - #endregion - } } From 77f4a6e808a37664c5a4aa23e171129f02dd9971 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Wed, 1 Sep 2021 15:17:16 +0300 Subject: [PATCH 15/62] more tests --- .../Logging/LoggingTests.cs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/Avalonia.Base.UnitTests/Logging/LoggingTests.cs b/tests/Avalonia.Base.UnitTests/Logging/LoggingTests.cs index 9e1885463d..d160713023 100644 --- a/tests/Avalonia.Base.UnitTests/Logging/LoggingTests.cs +++ b/tests/Avalonia.Base.UnitTests/Logging/LoggingTests.cs @@ -39,6 +39,34 @@ namespace Avalonia.Base.UnitTests.Logging Assert.Equal(0, calledTimes); } } + + [Fact] + public void Control_Should_Log_Binding_Errors_When_No_Ancestor_With_Such_Name() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + + + +"; + var calledTimes = 0; + using var logSink = TestLogSink.Start((l, a, s, m, d) => + { + if (l >= Avalonia.Logging.LogEventLevel.Warning && s is Rectangle) + { + calledTimes++; + } + }); + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + window.ApplyTemplate(); + window.Presenter.ApplyTemplate(); + Assert.Equal(1, calledTimes); + } + } } From df5a9c7afc1726beb5a1338c7175ee56243afea2 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Wed, 1 Sep 2021 15:18:17 +0300 Subject: [PATCH 16/62] wip --- src/Avalonia.Styling/LogicalTree/ControlLocator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Avalonia.Styling/LogicalTree/ControlLocator.cs b/src/Avalonia.Styling/LogicalTree/ControlLocator.cs index 3f5b1ee0b5..6d0302ace4 100644 --- a/src/Avalonia.Styling/LogicalTree/ControlLocator.cs +++ b/src/Avalonia.Styling/LogicalTree/ControlLocator.cs @@ -58,7 +58,6 @@ namespace Avalonia.LogicalTree { _value = null; PublishNext(null); - } private void Update() From 2a09574ba70b0bebdfb9339279fc38ef2a525c11 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 2 Sep 2021 09:52:34 +0200 Subject: [PATCH 17/62] Revert "Fixed gradient brush target rectangles." Reverts #6066. Keeps the render tests to demonstrate that the WPF behavior can be reproduced by pushing a translation. --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 47 +++++++++---------- src/Skia/Avalonia.Skia/FormattedTextImpl.cs | 4 +- .../Media/AvaloniaTextRenderer.cs | 4 +- .../Media/DrawingContextImpl.cs | 32 +++++++------ .../Media/LinearGradientBrushImpl.cs | 7 ++- .../Media/RadialGradientBrushImpl.cs | 11 ++--- .../Media/ConicGradientBrushTests.cs | 3 +- .../Media/LinearGradientBrushTests.cs | 12 +++-- .../Media/RadialGradientBrushTests.cs | 3 +- 9 files changed, 63 insertions(+), 60 deletions(-) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 1f186396ab..d812f6a059 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -164,7 +164,7 @@ namespace Avalonia.Skia /// public void DrawLine(IPen pen, Point p1, Point p2) { - using (var paint = CreatePaint(_strokePaint, pen, new Rect(p1, p2).Normalize())) + using (var paint = CreatePaint(_strokePaint, pen, new Size(Math.Abs(p2.X - p1.X), Math.Abs(p2.Y - p1.Y)))) { if (paint.Paint is object) { @@ -177,10 +177,10 @@ namespace Avalonia.Skia public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry) { var impl = (GeometryImpl) geometry; - var rect = geometry.Bounds; + var size = geometry.Bounds.Size; - using (var fill = brush != null ? CreatePaint(_fillPaint, brush, rect) : default(PaintWrapper)) - using (var stroke = pen?.Brush != null ? CreatePaint(_strokePaint, pen, rect) : default(PaintWrapper)) + using (var fill = brush != null ? CreatePaint(_fillPaint, brush, size) : default(PaintWrapper)) + using (var stroke = pen?.Brush != null ? CreatePaint(_strokePaint, pen, size) : default(PaintWrapper)) { if (fill.Paint != null) { @@ -354,7 +354,7 @@ namespace Avalonia.Skia if (brush != null) { - using (var paint = CreatePaint(_fillPaint, brush, rect.Rect)) + using (var paint = CreatePaint(_fillPaint, brush, rect.Rect.Size)) { if (isRounded) { @@ -397,7 +397,7 @@ namespace Avalonia.Skia if (pen?.Brush != null) { - using (var paint = CreatePaint(_strokePaint, pen, rect.Rect)) + using (var paint = CreatePaint(_strokePaint, pen, rect.Rect.Size)) { if (paint.Paint is object) { @@ -417,7 +417,7 @@ namespace Avalonia.Skia /// public void DrawText(IBrush foreground, Point origin, IFormattedTextImpl text) { - using (var paint = CreatePaint(_fillPaint, foreground, text.Bounds)) + using (var paint = CreatePaint(_fillPaint, foreground, text.Bounds.Size)) { var textImpl = (FormattedTextImpl) text; textImpl.Draw(this, Canvas, origin.ToSKPoint(), paint, _canTextUseLcdRendering); @@ -427,7 +427,7 @@ namespace Avalonia.Skia /// public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) { - using (var paintWrapper = CreatePaint(_fillPaint, foreground, new Rect(glyphRun.Size))) + using (var paintWrapper = CreatePaint(_fillPaint, foreground, glyphRun.Size)) { var glyphRunImpl = (GlyphRunImpl)glyphRun.GlyphRunImpl; @@ -537,7 +537,7 @@ namespace Avalonia.Skia var paint = new SKPaint(); Canvas.SaveLayer(paint); - _maskStack.Push(CreatePaint(paint, mask, bounds, true)); + _maskStack.Push(CreatePaint(paint, mask, bounds.Size, true)); } /// @@ -593,19 +593,18 @@ namespace Avalonia.Skia /// Paint wrapper. /// Target bound rect. /// Gradient brush. - private void ConfigureGradientBrush(ref PaintWrapper paintWrapper, Rect targetRect, IGradientBrush gradientBrush) + private void ConfigureGradientBrush(ref PaintWrapper paintWrapper, Size targetSize, IGradientBrush gradientBrush) { var tileMode = gradientBrush.SpreadMethod.ToSKShaderTileMode(); var stopColors = gradientBrush.GradientStops.Select(s => s.Color.ToSKColor()).ToArray(); var stopOffsets = gradientBrush.GradientStops.Select(s => (float)s.Offset).ToArray(); - var position = targetRect.Position.ToSKPoint(); switch (gradientBrush) { case ILinearGradientBrush linearGradient: { - var start = position + linearGradient.StartPoint.ToPixels(targetRect.Size).ToSKPoint(); - var end = position + linearGradient.EndPoint.ToPixels(targetRect.Size).ToSKPoint(); + var start = linearGradient.StartPoint.ToPixels(targetSize).ToSKPoint(); + var end = linearGradient.EndPoint.ToPixels(targetSize).ToSKPoint(); // would be nice to cache these shaders possibly? using (var shader = @@ -618,10 +617,10 @@ namespace Avalonia.Skia } case IRadialGradientBrush radialGradient: { - var center = position + radialGradient.Center.ToPixels(targetRect.Size).ToSKPoint(); - var radius = (float)(radialGradient.Radius * targetRect.Width); + var center = radialGradient.Center.ToPixels(targetSize).ToSKPoint(); + var radius = (float)(radialGradient.Radius * targetSize.Width); - var origin = position + radialGradient.GradientOrigin.ToPixels(targetRect.Size).ToSKPoint(); + var origin = radialGradient.GradientOrigin.ToPixels(targetSize).ToSKPoint(); if (origin.Equals(center)) { @@ -666,7 +665,7 @@ namespace Avalonia.Skia } case IConicGradientBrush conicGradient: { - var center = position + conicGradient.Center.ToPixels(targetRect.Size).ToSKPoint(); + var center = conicGradient.Center.ToPixels(targetSize).ToSKPoint(); // Skia's default is that angle 0 is from the right hand side of the center point // but we are matching CSS where the vertical point above the center is 0. @@ -868,10 +867,10 @@ namespace Avalonia.Skia /// /// The paint to wrap. /// Source brush. - /// Target rect. + /// Target size. /// Optional dispose of the supplied paint. /// Paint wrapper for given brush. - internal PaintWrapper CreatePaint(SKPaint paint, IBrush brush, Rect targetRect, bool disposePaint = false) + internal PaintWrapper CreatePaint(SKPaint paint, IBrush brush, Size targetSize, bool disposePaint = false) { var paintWrapper = new PaintWrapper(paint, disposePaint); @@ -890,7 +889,7 @@ namespace Avalonia.Skia if (brush is IGradientBrush gradient) { - ConfigureGradientBrush(ref paintWrapper, targetRect, gradient); + ConfigureGradientBrush(ref paintWrapper, targetSize, gradient); return paintWrapper; } @@ -910,7 +909,7 @@ namespace Avalonia.Skia if (tileBrush != null && tileBrushImage != null) { - ConfigureTileBrush(ref paintWrapper, targetRect.Size, tileBrush, tileBrushImage); + ConfigureTileBrush(ref paintWrapper, targetSize, tileBrush, tileBrushImage); } else { @@ -925,10 +924,10 @@ namespace Avalonia.Skia /// /// The paint to wrap. /// Source pen. - /// Target rect. + /// Target size. /// Optional dispose of the supplied paint. /// - private PaintWrapper CreatePaint(SKPaint paint, IPen pen, Rect targetRect, bool disposePaint = false) + private PaintWrapper CreatePaint(SKPaint paint, IPen pen, Size targetSize, bool disposePaint = false) { // In Skia 0 thickness means - use hairline rendering // and for us it means - there is nothing rendered. @@ -937,7 +936,7 @@ namespace Avalonia.Skia return default; } - var rv = CreatePaint(paint, pen.Brush, targetRect, disposePaint); + var rv = CreatePaint(paint, pen.Brush, targetSize, disposePaint); paint.IsStroke = true; paint.StrokeWidth = (float) pen.Thickness; diff --git a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs index 3eca42faa9..5f4980e461 100644 --- a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs +++ b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs @@ -278,9 +278,9 @@ namespace Avalonia.Skia if (fb != null) { - //TODO: figure out how to get the brush rect + //TODO: figure out how to get the brush size currentWrapper = context.CreatePaint(new SKPaint { IsAntialias = true }, fb, - default); + new Size()); } else { diff --git a/src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs b/src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs index e4b2405290..22c998df93 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs @@ -34,10 +34,10 @@ namespace Avalonia.Direct2D1.Media { var wrapper = clientDrawingEffect as BrushWrapper; - // TODO: Work out how to get the rect below rather than passing default. + // TODO: Work out how to get the size below rather than passing new Size(). var brush = (wrapper == null) ? _foreground : - _context.CreateBrush(wrapper.Brush, default).PlatformBrush; + _context.CreateBrush(wrapper.Brush, new Size()).PlatformBrush; _renderTarget.DrawGlyphRun( new RawVector2 { X = baselineOriginX, Y = baselineOriginY }, diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index 09e5b7c71a..622f47f953 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -192,7 +192,7 @@ namespace Avalonia.Direct2D1.Media { using (var d2dSource = ((BitmapImpl)source.Item).GetDirect2DBitmap(_deviceContext)) using (var sourceBrush = new BitmapBrush(_deviceContext, d2dSource.Value)) - using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect)) + using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect.Size)) using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(Direct2D1Platform.Direct2D1Factory, destRect.ToDirect2D())) { if (d2dOpacityMask.PlatformBrush != null) @@ -217,7 +217,9 @@ namespace Avalonia.Direct2D1.Media { if (pen != null) { - using (var d2dBrush = CreateBrush(pen.Brush, new Rect(p1, p2).Normalize())) + var size = new Rect(p1, p2).Size; + + using (var d2dBrush = CreateBrush(pen.Brush, size)) using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext)) { if (d2dBrush.PlatformBrush != null) @@ -243,7 +245,7 @@ namespace Avalonia.Direct2D1.Media { if (brush != null) { - using (var d2dBrush = CreateBrush(brush, geometry.Bounds)) + using (var d2dBrush = CreateBrush(brush, geometry.Bounds.Size)) { if (d2dBrush.PlatformBrush != null) { @@ -255,7 +257,7 @@ namespace Avalonia.Direct2D1.Media if (pen != null) { - using (var d2dBrush = CreateBrush(pen.Brush, geometry.GetRenderBounds(pen))) + using (var d2dBrush = CreateBrush(pen.Brush, geometry.GetRenderBounds(pen).Size)) using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext)) { if (d2dBrush.PlatformBrush != null) @@ -280,7 +282,7 @@ namespace Avalonia.Direct2D1.Media if (brush != null) { - using (var b = CreateBrush(brush, rect)) + using (var b = CreateBrush(brush, rect.Size)) { if (b.PlatformBrush != null) { @@ -309,7 +311,7 @@ namespace Avalonia.Direct2D1.Media if (pen?.Brush != null) { - using (var wrapper = CreateBrush(pen.Brush, rect)) + using (var wrapper = CreateBrush(pen.Brush, rect.Size)) using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext)) { if (wrapper.PlatformBrush != null) @@ -347,7 +349,7 @@ namespace Avalonia.Direct2D1.Media { var impl = (FormattedTextImpl)text; - using (var brush = CreateBrush(foreground, impl.Bounds)) + using (var brush = CreateBrush(foreground, impl.Bounds.Size)) using (var renderer = new AvaloniaTextRenderer(this, _deviceContext, brush.PlatformBrush)) { if (brush.PlatformBrush != null) @@ -365,7 +367,7 @@ namespace Avalonia.Direct2D1.Media /// The glyph run. public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) { - using (var brush = CreateBrush(foreground, new Rect(glyphRun.Size))) + using (var brush = CreateBrush(foreground, glyphRun.Size)) { var glyphRunImpl = (GlyphRunImpl)glyphRun.GlyphRunImpl; @@ -456,9 +458,9 @@ namespace Avalonia.Direct2D1.Media /// Creates a Direct2D brush wrapper for a Avalonia brush. /// /// The avalonia brush. - /// The brush's target area. + /// The size of the brush's target area. /// The Direct2D brush wrapper. - public BrushImpl CreateBrush(IBrush brush, Rect destinationRect) + public BrushImpl CreateBrush(IBrush brush, Size destinationSize) { var solidColorBrush = brush as ISolidColorBrush; var linearGradientBrush = brush as ILinearGradientBrush; @@ -473,11 +475,11 @@ namespace Avalonia.Direct2D1.Media } else if (linearGradientBrush != null) { - return new LinearGradientBrushImpl(linearGradientBrush, _deviceContext, destinationRect); + return new LinearGradientBrushImpl(linearGradientBrush, _deviceContext, destinationSize); } else if (radialGradientBrush != null) { - return new RadialGradientBrushImpl(radialGradientBrush, _deviceContext, destinationRect); + return new RadialGradientBrushImpl(radialGradientBrush, _deviceContext, destinationSize); } else if (conicGradientBrush != null) { @@ -490,7 +492,7 @@ namespace Avalonia.Direct2D1.Media imageBrush, _deviceContext, (BitmapImpl)imageBrush.Source.PlatformImpl.Item, - destinationRect.Size); + destinationSize); } else if (visualBrush != null) { @@ -521,7 +523,7 @@ namespace Avalonia.Direct2D1.Media visualBrush, _deviceContext, new D2DBitmapImpl(intermediate.Bitmap), - destinationRect.Size); + destinationSize); } } } @@ -573,7 +575,7 @@ namespace Avalonia.Direct2D1.Media ContentBounds = PrimitiveExtensions.RectangleInfinite, MaskTransform = PrimitiveExtensions.Matrix3x2Identity, Opacity = 1, - OpacityBrush = CreateBrush(mask, bounds).PlatformBrush + OpacityBrush = CreateBrush(mask, bounds.Size).PlatformBrush }; var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_deviceContext); _deviceContext.PushLayer(ref parameters, layer); diff --git a/src/Windows/Avalonia.Direct2D1/Media/LinearGradientBrushImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/LinearGradientBrushImpl.cs index 69b45455ac..0e63d4cc03 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/LinearGradientBrushImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/LinearGradientBrushImpl.cs @@ -8,7 +8,7 @@ namespace Avalonia.Direct2D1.Media public LinearGradientBrushImpl( ILinearGradientBrush brush, SharpDX.Direct2D1.RenderTarget target, - Rect destinationRect) + Size destinationSize) { if (brush.GradientStops.Count == 0) { @@ -21,9 +21,8 @@ namespace Avalonia.Direct2D1.Media Position = (float)s.Offset }).ToArray(); - var position = destinationRect.Position; - var startPoint = position + brush.StartPoint.ToPixels(destinationRect.Size); - var endPoint = position + brush.EndPoint.ToPixels(destinationRect.Size); + var startPoint = brush.StartPoint.ToPixels(destinationSize); + var endPoint = brush.EndPoint.ToPixels(destinationSize); using (var stops = new SharpDX.Direct2D1.GradientStopCollection( target, diff --git a/src/Windows/Avalonia.Direct2D1/Media/RadialGradientBrushImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/RadialGradientBrushImpl.cs index 7dcfd7e1e0..1fca6d4e33 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/RadialGradientBrushImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/RadialGradientBrushImpl.cs @@ -8,7 +8,7 @@ namespace Avalonia.Direct2D1.Media public RadialGradientBrushImpl( IRadialGradientBrush brush, SharpDX.Direct2D1.RenderTarget target, - Rect destinationRect) + Size destinationSize) { if (brush.GradientStops.Count == 0) { @@ -21,13 +21,12 @@ namespace Avalonia.Direct2D1.Media Position = (float)s.Offset }).ToArray(); - var position = destinationRect.Position; - var centerPoint = position + brush.Center.ToPixels(destinationRect.Size); - var gradientOrigin = position + brush.GradientOrigin.ToPixels(destinationRect.Size) - centerPoint; + var centerPoint = brush.Center.ToPixels(destinationSize); + var gradientOrigin = brush.GradientOrigin.ToPixels(destinationSize) - centerPoint; // Note: Direct2D supports RadiusX and RadiusY but Cairo backend supports only Radius property - var radiusX = brush.Radius * destinationRect.Width; - var radiusY = brush.Radius * destinationRect.Height; + var radiusX = brush.Radius * destinationSize.Width; + var radiusY = brush.Radius * destinationSize.Height; using (var stops = new SharpDX.Direct2D1.GradientStopCollection( target, diff --git a/tests/Avalonia.RenderTests/Media/ConicGradientBrushTests.cs b/tests/Avalonia.RenderTests/Media/ConicGradientBrushTests.cs index 3d2f09e2a8..ef400410a4 100644 --- a/tests/Avalonia.RenderTests/Media/ConicGradientBrushTests.cs +++ b/tests/Avalonia.RenderTests/Media/ConicGradientBrushTests.cs @@ -200,7 +200,8 @@ namespace Avalonia.Direct2D1.RenderTests.Media Child = new DrawnControl(c => { c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100)); - c.DrawRectangle(brush, null, new Rect(100, 100, 100, 100)); + using (c.PushPreTransform(Matrix.CreateTranslation(100, 100))) + c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100)); }), }; diff --git a/tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs b/tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs index 28701f2f97..dd9d2f9b39 100644 --- a/tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs +++ b/tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs @@ -81,10 +81,10 @@ namespace Avalonia.Direct2D1.RenderTests.Media StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative), EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative), GradientStops = - { - new GradientStop { Color = Colors.Red, Offset = 0 }, - new GradientStop { Color = Colors.Blue, Offset = 1 } - } + { + new GradientStop { Color = Colors.Red, Offset = 0 }, + new GradientStop { Color = Colors.Blue, Offset = 1 } + } }; Decorator target = new Decorator @@ -94,7 +94,9 @@ namespace Avalonia.Direct2D1.RenderTests.Media Child = new DrawnControl(c => { c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100)); - c.DrawRectangle(brush, null, new Rect(100, 100, 100, 100)); + + using (c.PushPreTransform(Matrix.CreateTranslation(100, 100))) + c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100)); }), }; diff --git a/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs b/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs index 95fae7f2fa..e52f844359 100644 --- a/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs +++ b/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs @@ -185,7 +185,8 @@ namespace Avalonia.Direct2D1.RenderTests.Media Child = new DrawnControl(c => { c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100)); - c.DrawRectangle(brush, null, new Rect(100, 100, 100, 100)); + using (c.PushPreTransform(Matrix.CreateTranslation(100, 100))) + c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100)); }), }; From 907b55cb7e6c3ea93e78a1ed097b3b0441d196ab Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 9 Sep 2021 16:02:58 +0100 Subject: [PATCH 18/62] CompiledBinding correctly locates IDataContextProvider as anchor. (implementation was in ReflectionBinding but missing from CompiledBinding) --- .../MarkupExtensions/CompiledBindingExtension.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs index 17d2ea7ae9..5c4d9315d5 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs @@ -44,6 +44,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions // the context. object anchor = provider.GetFirstParent(); + if (anchor is null) + { + // Try to find IDataContextProvider, this was added to allow us to find + // a datacontext for Application class when using NativeMenuItems. + anchor = provider.GetFirstParent(); + } + // If a control was not found, then try to find the highest-level style as the XAML // file could be a XAML file containing only styles. return anchor ?? From 8f833dbb2fba5d9a4df0946eef1122bc4f097d87 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Thu, 9 Sep 2021 19:16:32 +0300 Subject: [PATCH 19/62] it works --- packages/Avalonia/AvaloniaBuildTasks.targets | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets index 45a7f1aa44..a18930527f 100644 --- a/packages/Avalonia/AvaloniaBuildTasks.targets +++ b/packages/Avalonia/AvaloniaBuildTasks.targets @@ -42,12 +42,20 @@ - $(BuildAvaloniaResourcesDependsOn);AddAvaloniaResources;ResolveReferences + $(BuildAvaloniaResourcesDependsOn);AddAvaloniaResources;ResolveReferences;ReGenerateAvaloniaResourcesOnResourceDeletion + + + + + + + + From aa5ae2360748056304b643d806e46fc061c607c2 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 9 Sep 2021 18:17:32 +0100 Subject: [PATCH 20/62] move duplicated code to an extension method. --- .../Avalonia.Markup.Xaml.csproj | 1 + .../CompiledBindingExtension.cs | 25 +--------------- .../IServiceProviderExtensions.cs | 30 +++++++++++++++++++ .../ReflectionBindingExtension.cs | 23 +------------- 4 files changed, 33 insertions(+), 46 deletions(-) create mode 100644 src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/IServiceProviderExtensions.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index 217da2d50d..8f118c7b2f 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -26,6 +26,7 @@ + diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs index 5c4d9315d5..41de2355aa 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs @@ -1,7 +1,5 @@ using System; using Avalonia.Data; -using Avalonia.Controls; -using Avalonia.Styling; using Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings; using Avalonia.Data.Core; using Avalonia.Markup.Parsers; @@ -33,31 +31,10 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions Priority = Priority, StringFormat = StringFormat, Source = Source, - DefaultAnchor = new WeakReference(GetDefaultAnchor(provider)) + DefaultAnchor = new WeakReference(provider.GetDefaultAnchor()) }; } - private static object GetDefaultAnchor(IServiceProvider provider) - { - // If the target is not a control, so we need to find an anchor that will let us look - // up named controls and style resources. First look for the closest IControl in - // the context. - object anchor = provider.GetFirstParent(); - - if (anchor is null) - { - // Try to find IDataContextProvider, this was added to allow us to find - // a datacontext for Application class when using NativeMenuItems. - anchor = provider.GetFirstParent(); - } - - // If a control was not found, then try to find the highest-level style as the XAML - // file could be a XAML file containing only styles. - return anchor ?? - provider.GetService()?.RootObject as IStyle ?? - provider.GetLastParent(); - } - protected override ExpressionObserver CreateExpressionObserver(IAvaloniaObject target, AvaloniaProperty targetProperty, object anchor, bool enableDataValidation) { if (Source != null) diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/IServiceProviderExtensions.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/IServiceProviderExtensions.cs new file mode 100644 index 0000000000..a2bc19bbce --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/IServiceProviderExtensions.cs @@ -0,0 +1,30 @@ +using System; +using Avalonia.Controls; +using Avalonia.Styling; + +namespace Avalonia.Markup.Xaml.MarkupExtensions +{ + internal static class IServiceProviderExtensions + { + public static object GetDefaultAnchor(this IServiceProvider provider) + { + // If the target is not a control, so we need to find an anchor that will let us look + // up named controls and style resources. First look for the closest IControl in + // the context. + object anchor = provider.GetFirstParent(); + + if (anchor is null) + { + // Try to find IDataContextProvider, this was added to allow us to find + // a datacontext for Application class when using NativeMenuItems. + anchor = provider.GetFirstParent(); + } + + // If a control was not found, then try to find the highest-level style as the XAML + // file could be a XAML file containing only styles. + return anchor ?? + provider.GetService()?.RootObject as IStyle ?? + provider.GetLastParent(); + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ReflectionBindingExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ReflectionBindingExtension.cs index 10770365a3..d373ed852a 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ReflectionBindingExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ReflectionBindingExtension.cs @@ -37,33 +37,12 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions Source = Source, StringFormat = StringFormat, RelativeSource = RelativeSource, - DefaultAnchor = new WeakReference(GetDefaultAnchor(descriptorContext)), + DefaultAnchor = new WeakReference(descriptorContext.GetDefaultAnchor()), TargetNullValue = TargetNullValue, NameScope = new WeakReference(serviceProvider.GetService()) }; } - private static object GetDefaultAnchor(IServiceProvider context) - { - // If the target is not a control, so we need to find an anchor that will let us look - // up named controls and style resources. First look for the closest IControl in - // the context. - object anchor = context.GetFirstParent(); - - if(anchor is null) - { - // Try to find IDataContextProvider, this was added to allow us to find - // a datacontext for Application class when using NativeMenuItems. - anchor = context.GetFirstParent(); - } - - // If a control was not found, then try to find the highest-level style as the XAML - // file could be a XAML file containing only styles. - return anchor ?? - context.GetService()?.RootObject as IStyle ?? - context.GetLastParent(); - } - public IValueConverter Converter { get; set; } public object ConverterParameter { get; set; } From 42763792f44ec9317b621bf3633deeacf770dfb9 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 9 Sep 2021 18:37:44 +0100 Subject: [PATCH 21/62] use existing extensions class. --- src/Markup/Avalonia.Markup.Xaml/Extensions.cs | 23 ++++++++++++++ .../IServiceProviderExtensions.cs | 30 ------------------- 2 files changed, 23 insertions(+), 30 deletions(-) delete mode 100644 src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/IServiceProviderExtensions.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/Extensions.cs b/src/Markup/Avalonia.Markup.Xaml/Extensions.cs index fe3fd44c1c..263750c316 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Extensions.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Extensions.cs @@ -2,7 +2,9 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using Avalonia.Controls; using Avalonia.Markup.Xaml.XamlIl.Runtime; +using Avalonia.Styling; namespace Avalonia.Markup.Xaml { @@ -32,5 +34,26 @@ namespace Avalonia.Markup.Xaml string name = string.IsNullOrEmpty(namespacePrefix) ? type : $"{namespacePrefix}:{type}"; return tr?.Resolve(name); } + + public static object GetDefaultAnchor(this IServiceProvider provider) + { + // If the target is not a control, so we need to find an anchor that will let us look + // up named controls and style resources. First look for the closest IControl in + // the context. + object anchor = provider.GetFirstParent(); + + if (anchor is null) + { + // Try to find IDataContextProvider, this was added to allow us to find + // a datacontext for Application class when using NativeMenuItems. + anchor = provider.GetFirstParent(); + } + + // If a control was not found, then try to find the highest-level style as the XAML + // file could be a XAML file containing only styles. + return anchor ?? + provider.GetService()?.RootObject as IStyle ?? + provider.GetLastParent(); + } } } diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/IServiceProviderExtensions.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/IServiceProviderExtensions.cs deleted file mode 100644 index a2bc19bbce..0000000000 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/IServiceProviderExtensions.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using Avalonia.Controls; -using Avalonia.Styling; - -namespace Avalonia.Markup.Xaml.MarkupExtensions -{ - internal static class IServiceProviderExtensions - { - public static object GetDefaultAnchor(this IServiceProvider provider) - { - // If the target is not a control, so we need to find an anchor that will let us look - // up named controls and style resources. First look for the closest IControl in - // the context. - object anchor = provider.GetFirstParent(); - - if (anchor is null) - { - // Try to find IDataContextProvider, this was added to allow us to find - // a datacontext for Application class when using NativeMenuItems. - anchor = provider.GetFirstParent(); - } - - // If a control was not found, then try to find the highest-level style as the XAML - // file could be a XAML file containing only styles. - return anchor ?? - provider.GetService()?.RootObject as IStyle ?? - provider.GetLastParent(); - } - } -} From 5b28a2d94cf52e573f63a0164237010d50058113 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 9 Sep 2021 19:42:14 +0100 Subject: [PATCH 22/62] fix build. --- src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index 8f118c7b2f..217da2d50d 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -26,7 +26,6 @@ - From 890eabbd1fb2e211aaadb3a8bb8b3cb1cf6b4e68 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Fri, 10 Sep 2021 11:27:17 +0300 Subject: [PATCH 23/62] upd --- packages/Avalonia/AvaloniaBuildTasks.targets | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets index a18930527f..7fbe939390 100644 --- a/packages/Avalonia/AvaloniaBuildTasks.targets +++ b/packages/Avalonia/AvaloniaBuildTasks.targets @@ -50,12 +50,12 @@ - + From c919109be60d96900961cb1917314903eada4bfb Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 10 Sep 2021 11:41:07 +0200 Subject: [PATCH 24/62] fixes(DataGrid): Issue #6567 New line in Editable DataGrid, Not editable --- .../DataGridDataConnection.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGridDataConnection.cs b/src/Avalonia.Controls.DataGrid/DataGridDataConnection.cs index a94acdec57..fade597ca1 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridDataConnection.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridDataConnection.cs @@ -233,7 +233,7 @@ namespace Avalonia.Controls else { editableCollectionView.EditItem(dataItem); - return editableCollectionView.IsEditingItem; + return editableCollectionView.IsEditingItem || editableCollectionView.IsAddingNew; } } @@ -314,7 +314,14 @@ namespace Avalonia.Controls CommittingEdit = true; try { - editableCollectionView.CommitEdit(); + if (editableCollectionView.IsAddingNew) + { + editableCollectionView.CommitNew(); + } + else + { + editableCollectionView.CommitEdit(); + } } finally { From 89cb07677824ed70584b5379b314e63ec0e18fb6 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 10 Sep 2021 15:16:47 +0200 Subject: [PATCH 25/62] Implement GeometryGroup. --- .../HeadlessPlatformRenderInterface.cs | 1 + .../Media/GeometryCollection.cs | 37 +++++ src/Avalonia.Visuals/Media/GeometryGroup.cs | 80 +++++++++ .../Platform/IPlatformRenderInterface.cs | 8 + src/Skia/Avalonia.Skia/GeometryGroupImpl.cs | 36 ++++ .../Avalonia.Skia/PlatformRenderInterface.cs | 5 + .../Avalonia.Direct2D1/Direct2D1Platform.cs | 1 + .../Media/GeometryGroupImpl.cs | 33 ++++ .../NullRenderingPlatform.cs | 5 + .../Media/GeometryGroupTests.cs | 154 ++++++++++++++++++ .../MockPlatformRenderInterface.cs | 5 + .../Media/GeometryGroupTests.cs | 26 +++ .../VisualTree/MockRenderInterface.cs | 5 + .../FillRule_EvenOdd.expected.png | Bin 0 -> 2349 bytes .../FillRule_EvenOdd_Stroke.expected.png | Bin 0 -> 4047 bytes .../FillRule_NonZero.expected.png | Bin 0 -> 1966 bytes .../FillRule_NonZero_Stroke.expected.png | Bin 0 -> 3880 bytes .../FillRule_EvenOdd.expected.png | Bin 0 -> 2607 bytes .../FillRule_EvenOdd_Stroke.expected.png | Bin 0 -> 3814 bytes .../FillRule_NonZero.expected.png | Bin 0 -> 2137 bytes .../FillRule_NonZero_Stroke.expected.png | Bin 0 -> 3693 bytes 21 files changed, 396 insertions(+) create mode 100644 src/Avalonia.Visuals/Media/GeometryCollection.cs create mode 100644 src/Avalonia.Visuals/Media/GeometryGroup.cs create mode 100644 src/Skia/Avalonia.Skia/GeometryGroupImpl.cs create mode 100644 src/Windows/Avalonia.Direct2D1/Media/GeometryGroupImpl.cs create mode 100644 tests/Avalonia.RenderTests/Media/GeometryGroupTests.cs create mode 100644 tests/Avalonia.Visuals.UnitTests/Media/GeometryGroupTests.cs create mode 100644 tests/TestFiles/Direct2D1/Media/GeometryGroup/FillRule_EvenOdd.expected.png create mode 100644 tests/TestFiles/Direct2D1/Media/GeometryGroup/FillRule_EvenOdd_Stroke.expected.png create mode 100644 tests/TestFiles/Direct2D1/Media/GeometryGroup/FillRule_NonZero.expected.png create mode 100644 tests/TestFiles/Direct2D1/Media/GeometryGroup/FillRule_NonZero_Stroke.expected.png create mode 100644 tests/TestFiles/Skia/Media/GeometryGroup/FillRule_EvenOdd.expected.png create mode 100644 tests/TestFiles/Skia/Media/GeometryGroup/FillRule_EvenOdd_Stroke.expected.png create mode 100644 tests/TestFiles/Skia/Media/GeometryGroup/FillRule_NonZero.expected.png create mode 100644 tests/TestFiles/Skia/Media/GeometryGroup/FillRule_NonZero_Stroke.expected.png diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index 268171d467..abc07d0e71 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -47,6 +47,7 @@ namespace Avalonia.Headless } public IStreamGeometryImpl CreateStreamGeometry() => new HeadlessStreamingGeometryStub(); + public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList children) => throw new NotImplementedException(); public IRenderTarget CreateRenderTarget(IEnumerable surfaces) => new HeadlessRenderTarget(); diff --git a/src/Avalonia.Visuals/Media/GeometryCollection.cs b/src/Avalonia.Visuals/Media/GeometryCollection.cs new file mode 100644 index 0000000000..0bd02d5438 --- /dev/null +++ b/src/Avalonia.Visuals/Media/GeometryCollection.cs @@ -0,0 +1,37 @@ +using System.Collections; +using System.Collections.Generic; +using Avalonia.Animation; + +#nullable enable + +namespace Avalonia.Media +{ + public class GeometryCollection : Animatable, IList, IReadOnlyList + { + private List _inner; + + public GeometryCollection() => _inner = new List(); + public GeometryCollection(IEnumerable collection) => _inner = new List(collection); + public GeometryCollection(int capacity) => _inner = new List(capacity); + + public Geometry this[int index] + { + get => _inner[index]; + set => _inner[index] = value; + } + + public int Count => _inner.Count; + public bool IsReadOnly => false; + + public void Add(Geometry item) => _inner.Add(item); + public void Clear() => _inner.Clear(); + public bool Contains(Geometry item) => _inner.Contains(item); + public void CopyTo(Geometry[] array, int arrayIndex) => _inner.CopyTo(array, arrayIndex); + public IEnumerator GetEnumerator() => _inner.GetEnumerator(); + public int IndexOf(Geometry item) => _inner.IndexOf(item); + public void Insert(int index, Geometry item) => _inner.Insert(index, item); + public bool Remove(Geometry item) => _inner.Remove(item); + public void RemoveAt(int index) => _inner.RemoveAt(index); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/src/Avalonia.Visuals/Media/GeometryGroup.cs b/src/Avalonia.Visuals/Media/GeometryGroup.cs new file mode 100644 index 0000000000..a3ce6a9dcd --- /dev/null +++ b/src/Avalonia.Visuals/Media/GeometryGroup.cs @@ -0,0 +1,80 @@ +using System.Collections.Generic; +using System.Linq; +using Avalonia.Metadata; +using Avalonia.Platform; + +#nullable enable + +namespace Avalonia.Media +{ + /// + /// Represents a composite geometry, composed of other objects. + /// + public class GeometryGroup : Geometry + { + public static readonly DirectProperty ChildrenProperty = + AvaloniaProperty.RegisterDirect ( + nameof(Children), + o => o.Children, + (o, v) => o.Children = v); + + public static readonly StyledProperty FillRuleProperty = + AvaloniaProperty.Register(nameof(FillRule)); + + private GeometryCollection? _children; + private bool _childrenSet; + + /// + /// Gets or sets the collection that contains the child geometries. + /// + [Content] + public GeometryCollection? Children + { + get => _children ??= (!_childrenSet ? new GeometryCollection() : null); + set + { + SetAndRaise(ChildrenProperty, ref _children, value); + _childrenSet = true; + } + } + + /// + /// Gets or sets how the intersecting areas of the objects contained in this + /// are combined. The default is . + /// + public FillRule FillRule + { + get => GetValue(FillRuleProperty); + set => SetValue(FillRuleProperty, value); + } + + public override Geometry Clone() + { + var result = new GeometryGroup { FillRule = FillRule }; + if (_children?.Count > 0) + result.Children = new GeometryCollection(_children); + return result; + } + + protected override IGeometryImpl? CreateDefiningGeometry() + { + if (_children?.Count > 0) + { + var factory = AvaloniaLocator.Current.GetService(); + return factory.CreateGeometryGroup(FillRule, _children); + } + + return null; + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == ChildrenProperty || change.Property == FillRuleProperty) + { + InvalidateGeometry(); + } + } + } +} diff --git a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs index de67aca5a8..2cc44681d5 100644 --- a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs @@ -59,6 +59,14 @@ namespace Avalonia.Platform /// An . IStreamGeometryImpl CreateStreamGeometry(); + /// + /// Creates a geometry group implementation. + /// + /// The fill rule. + /// The geometries to group. + /// A combined geometry. + IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList children); + /// /// Creates a renderer. /// diff --git a/src/Skia/Avalonia.Skia/GeometryGroupImpl.cs b/src/Skia/Avalonia.Skia/GeometryGroupImpl.cs new file mode 100644 index 0000000000..d6f19612c1 --- /dev/null +++ b/src/Skia/Avalonia.Skia/GeometryGroupImpl.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using Avalonia.Media; +using SkiaSharp; + +#nullable enable + +namespace Avalonia.Skia +{ + /// + /// A Skia implementation of a . + /// + internal class GeometryGroupImpl : GeometryImpl + { + public GeometryGroupImpl(FillRule fillRule, IReadOnlyList children) + { + var path = new SKPath + { + FillType = fillRule == FillRule.NonZero ? SKPathFillType.Winding : SKPathFillType.EvenOdd, + }; + + var count = children.Count; + + for (var i = 0; i < count; ++i) + { + if (children[i]?.PlatformImpl is GeometryImpl child) + path.AddPath(child.EffectivePath); + } + + EffectivePath = path; + Bounds = path.Bounds.ToAvaloniaRect(); + } + + public override Rect Bounds { get; } + public override SKPath EffectivePath { get; } + } +} diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index 7bc83ec85b..447d683a2c 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -62,6 +62,11 @@ namespace Avalonia.Skia return new StreamGeometryImpl(); } + public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList children) + { + return new GeometryGroupImpl(fillRule, children); + } + /// public IBitmapImpl LoadBitmap(string fileName) { diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index f50167b39a..91f81223a0 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -175,6 +175,7 @@ namespace Avalonia.Direct2D1 public IGeometryImpl CreateLineGeometry(Point p1, Point p2) => new LineGeometryImpl(p1, p2); public IGeometryImpl CreateRectangleGeometry(Rect rect) => new RectangleGeometryImpl(rect); public IStreamGeometryImpl CreateStreamGeometry() => new StreamGeometryImpl(); + public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList children) => new GeometryGroupImpl(fillRule, children); /// public IBitmapImpl LoadBitmap(string fileName) diff --git a/src/Windows/Avalonia.Direct2D1/Media/GeometryGroupImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GeometryGroupImpl.cs new file mode 100644 index 0000000000..352708bf03 --- /dev/null +++ b/src/Windows/Avalonia.Direct2D1/Media/GeometryGroupImpl.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using SharpDX.Direct2D1; +using AM = Avalonia.Media; + +namespace Avalonia.Direct2D1.Media +{ + /// + /// A Direct2D implementation of a . + /// + internal class GeometryGroupImpl : GeometryImpl + { + /// + /// Initializes a new instance of the class. + /// + public GeometryGroupImpl(AM.FillRule fillRule, IReadOnlyList geometry) + : base(CreateGeometry(fillRule, geometry)) + { + } + + private static Geometry CreateGeometry(AM.FillRule fillRule, IReadOnlyList children) + { + var count = children.Count; + var c = new Geometry[count]; + + for (var i = 0; i < count; ++i) + { + c[i] = ((GeometryImpl)children[i].PlatformImpl).Geometry; + } + + return new GeometryGroup(Direct2D1Platform.Direct2D1Factory, (FillMode)fillRule, c); + } + } +} diff --git a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs index 876a0de643..fd8dd3ff94 100644 --- a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs +++ b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs @@ -36,6 +36,11 @@ namespace Avalonia.Benchmarks return new MockStreamGeometryImpl(); } + public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList children) + { + throw new NotImplementedException(); + } + public IRenderTarget CreateRenderTarget(IEnumerable surfaces) { throw new NotImplementedException(); diff --git a/tests/Avalonia.RenderTests/Media/GeometryGroupTests.cs b/tests/Avalonia.RenderTests/Media/GeometryGroupTests.cs new file mode 100644 index 0000000000..6201c2c55e --- /dev/null +++ b/tests/Avalonia.RenderTests/Media/GeometryGroupTests.cs @@ -0,0 +1,154 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Controls.Shapes; +using Avalonia.Media; +using Avalonia.Media.Imaging; +using Xunit; + +#if AVALONIA_SKIA +namespace Avalonia.Skia.RenderTests +#else +namespace Avalonia.Direct2D1.RenderTests.Media +#endif +{ + public class GeometryGroupTests : TestBase + { + public GeometryGroupTests() + : base(@"Media\GeometryGroup") + { + } + + [Fact] + public async Task FillRule_EvenOdd() + { + var target = new Border + { + Width = 200, + Height = 200, + Background = Brushes.White, + Child = new Path + { + Data = new GeometryGroup + { + FillRule = FillRule.EvenOdd, + Children = + { + new RectangleGeometry(new Rect(25, 25, 100, 100)), + new EllipseGeometry + { + Center = new Point(125, 125), + RadiusX = 50, + RadiusY = 50, + }, + } + }, + Fill = Brushes.Blue, + } + }; + + await RenderToFile(target); + CompareImages(); + } + + [Fact] + public async Task FillRule_NonZero() + { + var target = new Border + { + Width = 200, + Height = 200, + Background = Brushes.White, + Child = new Path + { + Data = new GeometryGroup + { + FillRule = FillRule.NonZero, + Children = + { + new RectangleGeometry(new Rect(25, 25, 100, 100)), + new EllipseGeometry + { + Center = new Point(125, 125), + RadiusX = 50, + RadiusY = 50, + }, + } + }, + Fill = Brushes.Blue, + } + }; + + await RenderToFile(target); + CompareImages(); + } + + [Fact] + public async Task FillRule_EvenOdd_Stroke() + { + var target = new Border + { + Width = 200, + Height = 200, + Background = Brushes.White, + Child = new Path + { + Data = new GeometryGroup + { + FillRule = FillRule.EvenOdd, + Children = + { + new RectangleGeometry(new Rect(25, 25, 100, 100)), + new EllipseGeometry + { + Center = new Point(125, 125), + RadiusX = 50, + RadiusY = 50, + }, + } + }, + Fill = Brushes.Blue, + Stroke = Brushes.Red, + StrokeThickness = 1, + } + }; + + await RenderToFile(target); + CompareImages(); + } + + [Fact] + public async Task FillRule_NonZero_Stroke() + { + var target = new Border + { + Width = 200, + Height = 200, + Background = Brushes.White, + Child = new Path + { + Data = new GeometryGroup + { + FillRule = FillRule.NonZero, + Children = + { + new RectangleGeometry(new Rect(25, 25, 100, 100)), + new EllipseGeometry + { + Center = new Point(125, 125), + RadiusX = 50, + RadiusY = 50, + }, + } + }, + Fill = Brushes.Blue, + Stroke = Brushes.Red, + StrokeThickness = 1, + } + }; + + await RenderToFile(target); + CompareImages(); + } + } +} diff --git a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs index 74366f9e26..34697b8616 100644 --- a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs +++ b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs @@ -52,6 +52,11 @@ namespace Avalonia.UnitTests return new MockStreamGeometryImpl(); } + public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList children) + { + return Mock.Of(); + } + public IWriteableBitmapImpl CreateWriteableBitmap( PixelSize size, Vector dpi, diff --git a/tests/Avalonia.Visuals.UnitTests/Media/GeometryGroupTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/GeometryGroupTests.cs new file mode 100644 index 0000000000..8f80238903 --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Media/GeometryGroupTests.cs @@ -0,0 +1,26 @@ +using Avalonia.Media; +using Xunit; + +namespace Avalonia.Visuals.UnitTests.Media +{ + public class GeometryGroupTests + { + [Fact] + public void Children_Should_Have_Initial_Collection() + { + var target = new GeometryGroup(); + + Assert.NotNull(target.Children); + } + + [Fact] + public void Children_Can_Be_Set_To_Null() + { + var target = new GeometryGroup(); + + target.Children = null; + + Assert.Null(target.Children); + } + } +} diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs index 51ea1e893f..dded3caa88 100644 --- a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs +++ b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs @@ -37,6 +37,11 @@ namespace Avalonia.Visuals.UnitTests.VisualTree return new MockStreamGeometry(); } + public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList children) + { + throw new NotImplementedException(); + } + public IBitmapImpl LoadBitmap(Stream stream) { throw new NotImplementedException(); diff --git a/tests/TestFiles/Direct2D1/Media/GeometryGroup/FillRule_EvenOdd.expected.png b/tests/TestFiles/Direct2D1/Media/GeometryGroup/FillRule_EvenOdd.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..ad8eb361584591b82d6d2206bd8d4caa881672a5 GIT binary patch literal 2349 zcmd5;dpHwp8{cNM&!G{GmUk#Qgv=aDquSZWD5De11h2wPeLSz z>r6nE$IcrbB^HTxA&iIX--U0)vb~;mJq|Y-Zsdheb(fhhm-N?pU8=fNa;a*oeiI(w zgtkRVKZFVgbNMv^fo&Q2U-^ogpAC#X5%CsHmEJxtY+sz?%F|y@dv1QVVY7qE&#ike zdmuysq)Fw?h5pDRv~fy4}PC3&nMVE zd*a^egVmB%*6J`4tci(`4Di}L(Zef}r>9S2VkfThmr2ORzFWp`F&o4ge4Sdvm7~U> zthB}R?7Tcq26kodF>>779;pc*6COc37tj_bRFJFT=MD0&O!G7IE^%N>`|w#Fa9Bb4uXw<77Dr z-w4k9ha>E{fo7UDx8*9X*eo>TgyR*tG^ReZVA=IW`4s~QXh)hEs|?97Rs2|`+PT+l zt6ZN!P*62HD_rnGjJ3CZf5x9K<8f2TPW?i)4XT_j;kXb5bpi zvK?72<;N^@*V79uIWc^xU`IZt9BWHWcvdrUirsNGN zbl9|J5#v-tXM_2|$43iB9g@J1T$(`)&3wve^UhmLn^T*??YDRcE-6a2W(c=1ac{h2 zJhxf$#xfetWH+Z1R!$5=IvuCoY=Ph~ecw{a9q|JeWPHqrlf`K~K*6`TEz3{YUR&<= zJ_mq8lVsk1dNvsoCC(Nj>MBSJn0wjfQEVSDbU@g$snY>iQ*d+Zf@*mnkYxw0#d;X{(2^k#oKyU)UT^&3o?o#jHKtU7#g_0ug;Isg#J#B0 zMb+O!>2P~Q!z`n0&5Q29BSsKCz z{cCI<@4w_|?+HwFP%8=*I{Hy?;rsVpFpp#6Sc-LFTB=ON)P;cAu-(7CGeB@3%+zaK zdNq4hrSOdSs9y|+P50MyCQ|!QYMN?)2QPI_S^D3U1hOs*)YnIEyp}z$W%18S9brAy z4F+aPRZenjEB;VdM-r`Y|B$I)lBj2Kt0+Nf-Lk8c(Sofq2(QRyFqPS|;>^3Q-SqA{ zIO^ZZAQg8njQB~~8LkAsaWy3MzwfEZAlpa)XD;8>Pd@CgmcSrbaMR%%t0_CNtr8^Z zB9K{>W`#QZ7s24|4S70ZF^9E=SB)04M4L@;&Q&D&Wzo11H@PS81B+Fi*gr!kc``zU zGoj))pRkI*R_j#PR&pX_5?mxTlO&z)!K9eIVQF=VQFf@pij6;K*(4a>j}YIj_RbH> zBm#O|-^*Tk`#q@;sz`kslySfyMK1V?e%QdFo|DJxv2wkDougfj1att&@gxmXiiVSx zn0$7SpCsi-Oy~UKcy*RMFobS4UtGL+*Q>{^(PMSwX)Prq=5P(13F6g%d>r8?5&FI+ zVa+gu2myu+ii^m6H~}3|ADQ=yrRu4JD3k|r^BKP`%ug~uR$Me%u!Ih<_)``f2@p=Z z5H8QPq?M5w!{9M6(GN%vbyf5LvV6SPw66ey%b_z`;l#X-Fent*3wnP}q8pgEq zrrIr3tx4B>bIfPOq4DC^ zTa}OSoV@qDmN@s*ZqoG~Ts+T2ELHZpH4L@wXx0F08p&;0QamWlu`~R=L&{&XMZ5Pw z&nwa$gGmm*zhfzSE(r!>lCp*9joxI1qayWPy_9_>=NKc;y}EO(rD=g`4Hp_bwQc@` zdt(o=cTcLhA#Moo?^wq98k+m6XcxFLPUF z*ShkI%gf#9hXutVquGH}k<6@BaEqhb1!T6Xo-EWInS9aTBJ@a*SOO3c|5gV4ua4Mk apZC!jx5}Ri(~A>J9)O+AacrfvZ{lA-+8WIO literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Direct2D1/Media/GeometryGroup/FillRule_EvenOdd_Stroke.expected.png b/tests/TestFiles/Direct2D1/Media/GeometryGroup/FillRule_EvenOdd_Stroke.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..4dd547c4bf5b01343a211277795b2201e639c16b GIT binary patch literal 4047 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 literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Skia/Media/GeometryGroup/FillRule_EvenOdd_Stroke.expected.png b/tests/TestFiles/Skia/Media/GeometryGroup/FillRule_EvenOdd_Stroke.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..80b91d12094545a721c032a4dd3dd569e745c212 GIT binary patch literal 3814 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 literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Skia/Media/GeometryGroup/FillRule_NonZero_Stroke.expected.png b/tests/TestFiles/Skia/Media/GeometryGroup/FillRule_NonZero_Stroke.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..a101525cb30d8c7dcc344174bf9ad8bae6490e0b GIT binary patch literal 3693 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 PBaMNssZNE~pHcq-(MI2G literal 0 HcmV?d00001 From 7a8650ae06867b16c716740b117bf61aef87653a Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 10 Sep 2021 18:05:37 +0200 Subject: [PATCH 26/62] fixes(Datagrid): Avoid using Threading.Dispatcher.UIThread.Post( --- src/Avalonia.Controls.DataGrid/DataGrid.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index cb1ce0c731..9d231ff8b2 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -4489,13 +4489,11 @@ namespace Avalonia.Controls element = dataGridColumn.GenerateEditingElementInternal(dataGridCell, dataGridRow.DataContext); if (element != null) { - + + dataGridCell.Content = element; if (element.IsInitialized) { - Threading.Dispatcher.UIThread.Post(() => - { - PreparingCellForEditPrivate(element as Control); - }); + PreparingCellForEditPrivate(element as Control); } else { @@ -4508,9 +4506,10 @@ namespace Avalonia.Controls { // Generate Element and apply column style if available element = dataGridColumn.GenerateElementInternal(dataGridCell, dataGridRow.DataContext); + dataGridCell.Content = element; } - dataGridCell.Content = element; + } private void PreparingCellForEditPrivate(Control editingElement) From 4eff3852ee640953fd48dcd770b2a2cc17434336 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Fri, 10 Sep 2021 20:43:47 +0200 Subject: [PATCH 27/62] Set MenuItem icon presenter size from style --- src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml b/src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml index 3903096933..72c25cea37 100644 --- a/src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml @@ -75,8 +75,6 @@ @@ -199,6 +197,8 @@