From a8b7e879387a1d83cd724877195c27ac3e320be3 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 12 Jul 2020 15:22:00 +0200 Subject: [PATCH 01/35] Added IRecyclingDataTemplate. In #4218 we imported `IElementFactory` from WinUI which is broadly analogous to a recycling datatemplate for lists. In Avalonia this implement `IDataTemplate` in order to have a common base class for all types of data templates. The problem with this is that `IDataTemplate` already had a `SupportsRecycling` property which is incompatible with the way recycling is implemented in `IElementFactory`. Instead, introduce an `IRecyclingDataTemplate` to signal data templates that support recycling. --- .../Generators/TreeItemContainerGenerator.cs | 1 - .../Presenters/ContentPresenter.cs | 21 +++++++------- .../Repeater/ElementFactory.cs | 2 -- .../Repeater/ItemTemplateWrapper.cs | 1 - .../Templates/FuncDataTemplate.cs | 29 ++++++++++++++----- .../Templates/FuncTemplate`2.cs | 6 ++-- .../Templates/IDataTemplate.cs | 12 ++++---- .../Templates/IRecyclingDataTemplate.cs | 25 ++++++++++++++++ .../Diagnostics/ViewLocator.cs | 2 -- .../Templates/DataTemplate.cs | 11 ++++--- .../Templates/TreeDataTemplate.cs | 2 -- .../TreeViewTests.cs | 2 -- 12 files changed, 71 insertions(+), 43 deletions(-) create mode 100644 src/Avalonia.Controls/Templates/IRecyclingDataTemplate.cs diff --git a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs index cd1ce3deae..9e65ef5f81 100644 --- a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs @@ -142,7 +142,6 @@ namespace Avalonia.Controls.Generators private readonly IDataTemplate _inner; public WrapperTreeDataTemplate(IDataTemplate inner) => _inner = inner; public IControl Build(object param) => _inner.Build(param); - public bool SupportsRecycling => _inner.SupportsRecycling; public bool Match(object data) => _inner.Match(data); public InstancedBinding ItemsSelector(object item) => null; } diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs index c4571505ba..8837901816 100644 --- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs @@ -86,7 +86,7 @@ namespace Avalonia.Controls.Presenters private IControl _child; private bool _createdChild; - private IDataTemplate _dataTemplate; + private IRecyclingDataTemplate _recyclingDataTemplate; private readonly BorderRenderHelper _borderRenderer = new BorderRenderHelper(); /// @@ -281,7 +281,7 @@ namespace Avalonia.Controls.Presenters protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) { base.OnAttachedToLogicalTree(e); - _dataTemplate = null; + _recyclingDataTemplate = null; _createdChild = false; InvalidateMeasure(); } @@ -307,22 +307,21 @@ namespace Avalonia.Controls.Presenters { var dataTemplate = this.FindDataTemplate(content, ContentTemplate) ?? FuncDataTemplate.Default; - // We have content and it isn't a control, so if the new data template is the same - // as the old data template, try to recycle the existing child control to display - // the new data. - if (dataTemplate == _dataTemplate && dataTemplate.SupportsRecycling) + if (dataTemplate is IRecyclingDataTemplate rdt) { - newChild = oldChild; + var toRecycle = rdt == _recyclingDataTemplate ? oldChild : null; + newChild = rdt.Build(content, toRecycle); + _recyclingDataTemplate = rdt; } else { - _dataTemplate = dataTemplate; - newChild = _dataTemplate.Build(content); + newChild = dataTemplate.Build(content); + _recyclingDataTemplate = null; } } else { - _dataTemplate = null; + _recyclingDataTemplate = null; } return newChild; @@ -422,7 +421,7 @@ namespace Avalonia.Controls.Presenters LogicalChildren.Remove(Child); ((ISetInheritanceParent)Child).SetParent(Child.Parent); Child = null; - _dataTemplate = null; + _recyclingDataTemplate = null; } InvalidateMeasure(); diff --git a/src/Avalonia.Controls/Repeater/ElementFactory.cs b/src/Avalonia.Controls/Repeater/ElementFactory.cs index 1c1b71af88..644e077221 100644 --- a/src/Avalonia.Controls/Repeater/ElementFactory.cs +++ b/src/Avalonia.Controls/Repeater/ElementFactory.cs @@ -4,8 +4,6 @@ namespace Avalonia.Controls { public abstract class ElementFactory : IElementFactory { - bool IDataTemplate.SupportsRecycling => false; - public IControl Build(object data) { return GetElementCore(new ElementFactoryGetArgs { Data = data }); diff --git a/src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs b/src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs index 4b784375a9..dd97cde218 100644 --- a/src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs +++ b/src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs @@ -13,7 +13,6 @@ namespace Avalonia.Controls public ItemTemplateWrapper(IDataTemplate dataTemplate) => _dataTemplate = dataTemplate; - public bool SupportsRecycling => false; public IControl Build(object param) => GetElement(null, param); public bool Match(object data) => _dataTemplate.Match(data); diff --git a/src/Avalonia.Controls/Templates/FuncDataTemplate.cs b/src/Avalonia.Controls/Templates/FuncDataTemplate.cs index d454a29021..1afd86a11e 100644 --- a/src/Avalonia.Controls/Templates/FuncDataTemplate.cs +++ b/src/Avalonia.Controls/Templates/FuncDataTemplate.cs @@ -6,7 +6,7 @@ namespace Avalonia.Controls.Templates /// /// Builds a control for a piece of data. /// - public class FuncDataTemplate : FuncTemplate, IDataTemplate + public class FuncDataTemplate : FuncTemplate, IRecyclingDataTemplate { /// /// The default data template used in the case where no matching data template is found. @@ -30,10 +30,8 @@ namespace Avalonia.Controls.Templates }, true); - /// - /// The implementation of the method. - /// private readonly Func _match; + private readonly bool _supportsRecycling; /// /// Initializes a new instance of the class. @@ -70,12 +68,9 @@ namespace Avalonia.Controls.Templates Contract.Requires(match != null); _match = match; - SupportsRecycling = supportsRecycling; + _supportsRecycling = supportsRecycling; } - /// - public bool SupportsRecycling { get; } - /// /// Checks to see if this data template matches the specified data. /// @@ -88,6 +83,24 @@ namespace Avalonia.Controls.Templates return _match(data); } + /// + /// Creates or recycles a control to display the specified data. + /// + /// The data to display. + /// An optional control to recycle. + /// + /// The control if supplied and applicable to + /// , otherwise a new control. + /// + /// + /// The caller should ensure that any control passed to + /// originated from the same data template. + /// + public IControl Build(object data, IControl existing) + { + return _supportsRecycling && existing is object ? existing : Build(data); + } + /// /// Determines of an object is of the specified type. /// diff --git a/src/Avalonia.Controls/Templates/FuncTemplate`2.cs b/src/Avalonia.Controls/Templates/FuncTemplate`2.cs index d08616b968..cd0e3ad603 100644 --- a/src/Avalonia.Controls/Templates/FuncTemplate`2.cs +++ b/src/Avalonia.Controls/Templates/FuncTemplate`2.cs @@ -1,5 +1,7 @@ using System; +#nullable enable + namespace Avalonia.Controls.Templates { /// @@ -18,9 +20,7 @@ namespace Avalonia.Controls.Templates /// The function used to create the control. public FuncTemplate(Func func) { - Contract.Requires(func != null); - - _func = func; + _func = func ?? throw new ArgumentNullException(nameof(func)); } /// diff --git a/src/Avalonia.Controls/Templates/IDataTemplate.cs b/src/Avalonia.Controls/Templates/IDataTemplate.cs index cfde029eb8..0368748a0b 100644 --- a/src/Avalonia.Controls/Templates/IDataTemplate.cs +++ b/src/Avalonia.Controls/Templates/IDataTemplate.cs @@ -1,3 +1,7 @@ +using System; + +#nullable enable + namespace Avalonia.Controls.Templates { /// @@ -5,12 +9,6 @@ namespace Avalonia.Controls.Templates /// public interface IDataTemplate : ITemplate { - /// - /// Gets a value indicating whether the data template supports recycling of the generated - /// control. - /// - bool SupportsRecycling { get; } - /// /// Checks to see if this data template matches the specified data. /// @@ -20,4 +18,4 @@ namespace Avalonia.Controls.Templates /// bool Match(object data); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/Templates/IRecyclingDataTemplate.cs b/src/Avalonia.Controls/Templates/IRecyclingDataTemplate.cs new file mode 100644 index 0000000000..25956a9c9a --- /dev/null +++ b/src/Avalonia.Controls/Templates/IRecyclingDataTemplate.cs @@ -0,0 +1,25 @@ +#nullable enable + +namespace Avalonia.Controls.Templates +{ + /// + /// An that supports recycling existing elements. + /// + public interface IRecyclingDataTemplate : IDataTemplate + { + /// + /// Creates or recycles a control to display the specified data. + /// + /// The data to display. + /// An optional control to recycle. + /// + /// The control if supplied and applicable to + /// , otherwise a new control. + /// + /// + /// The caller should ensure that any control passed to + /// originated from the same data template. + /// + IControl Build(object data, IControl? existing); + } +} diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs index c06fbec801..be3564e781 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs @@ -7,8 +7,6 @@ namespace Avalonia.Diagnostics { internal class ViewLocator : IDataTemplate { - public bool SupportsRecycling => false; - public IControl Build(object data) { var name = data.GetType().FullName.Replace("ViewModel", "View"); diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs index 5663d08412..07c5451135 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs @@ -5,7 +5,7 @@ using Avalonia.Metadata; namespace Avalonia.Markup.Xaml.Templates { - public class DataTemplate : IDataTemplate + public class DataTemplate : IRecyclingDataTemplate { public Type DataType { get; set; } @@ -14,8 +14,6 @@ namespace Avalonia.Markup.Xaml.Templates [TemplateContent] public object Content { get; set; } - public bool SupportsRecycling { get; set; } = true; - public bool Match(object data) { if (DataType == null) @@ -28,6 +26,11 @@ namespace Avalonia.Markup.Xaml.Templates } } - public IControl Build(object data) => TemplateContent.Load(Content).Control; + public IControl Build(object data) => Build(data, null); + + public IControl Build(object data, IControl existing) + { + return existing ?? TemplateContent.Load(Content).Control; + } } } diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs index b96486235a..b8e1c2df80 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs @@ -18,8 +18,6 @@ namespace Avalonia.Markup.Xaml.Templates [AssignBinding] public Binding ItemsSource { get; set; } - public bool SupportsRecycling { get; set; } = true; - public bool Match(object data) { if (DataType == null) diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index c1bd45bcad..c25ad19027 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -1273,8 +1273,6 @@ namespace Avalonia.Controls.UnitTests return new TextBlock { Text = node.Value }; } - public bool SupportsRecycling => false; - public InstancedBinding ItemsSelector(object item) { var obs = ExpressionObserver.Create(item, o => (o as Node).Children); From 62fd036d55e010fe6b59a35ae0178aab5247162f Mon Sep 17 00:00:00 2001 From: FoggyFinder Date: Wed, 15 Jul 2020 13:17:06 +0300 Subject: [PATCH 02/35] small adjustment to avoid IndexOutOfRangeException --- .../DateTimePickers/DatePicker.cs | 16 ++++++++++------ .../DateTimePickers/DatePickerPresenter.cs | 16 ++++++++-------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs index 5d3311e8c6..a41c159980 100644 --- a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs +++ b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs @@ -88,7 +88,7 @@ namespace Avalonia.Controls AvaloniaProperty.RegisterDirect(nameof(SelectedDate), x => x.SelectedDate, (x, v) => x.SelectedDate = v); - //Template Items + // Template Items private Button _flyoutButton; private TextBlock _dayText; private TextBlock _monthText; @@ -359,10 +359,14 @@ namespace Avalonia.Controls } } - Grid.SetColumn(_spacer1, 1); - Grid.SetColumn(_spacer2, 3); - _spacer1.IsVisible = columnIndex > 1; - _spacer2.IsVisible = columnIndex > 2; + var isSpacer1Visible = columnIndex > 1; + var isSpacer2Visible = columnIndex > 2; + // ternary conditional operator is used to make sure grid cells will be validated + Grid.SetColumn(_spacer1, isSpacer1Visible ? 1 : 0); + Grid.SetColumn(_spacer2, isSpacer2Visible ? 3 : 0); + + _spacer1.IsVisible = isSpacer1Visible; + _spacer2.IsVisible = isSpacer2Visible; } private void SetSelectedDateText() @@ -398,7 +402,7 @@ namespace Avalonia.Controls var deltaY = _presenter.GetOffsetForPopup(); - //The extra 5 px I think is related to default popup placement behavior + // The extra 5 px I think is related to default popup placement behavior _popup.Host.ConfigurePosition(_popup.PlacementTarget, PlacementMode.AnchorAndGravity, new Point(0, deltaY + 5), Primitives.PopupPositioning.PopupAnchor.Bottom, Primitives.PopupPositioning.PopupGravity.Bottom, Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY); diff --git a/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs b/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs index 8b86e46e88..0da46bb74a 100644 --- a/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs +++ b/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs @@ -77,7 +77,7 @@ namespace Avalonia.Controls DatePicker.YearVisibleProperty.AddOwner(x => x.YearVisible, (x, v) => x.YearVisible = v); - //Template Items + // Template Items private Grid _pickerContainer; private Button _acceptButton; private Button _dismissButton; @@ -107,7 +107,7 @@ namespace Avalonia.Controls private bool _yearVisible = true; private DateTimeOffset _syncDate; - private GregorianCalendar _calendar; + private readonly GregorianCalendar _calendar; private bool _suppressUpdateSelection; public DatePickerPresenter() @@ -234,7 +234,7 @@ namespace Avalonia.Controls protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); - //These are requirements, so throw if not found + // These are requirements, so throw if not found _pickerContainer = e.NameScope.Get("PickerContainer"); _monthHost = e.NameScope.Get("MonthHost"); _dayHost = e.NameScope.Get("DayHost"); @@ -326,7 +326,7 @@ namespace Avalonia.Controls /// private void InitPicker() { - //OnApplyTemplate must've been called before we can init here... + // OnApplyTemplate must've been called before we can init here... if (_pickerContainer == null) return; @@ -344,7 +344,7 @@ namespace Avalonia.Controls SetGrid(); - //Date should've been set when we reach this point + // Date should've been set when we reach this point var dt = Date; if (DayVisible) { @@ -433,12 +433,12 @@ namespace Avalonia.Controls } } - private void OnDismissButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) + private void OnDismissButtonClicked(object sender, RoutedEventArgs e) { OnDismiss(); } - private void OnAcceptButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) + private void OnAcceptButtonClicked(object sender, RoutedEventArgs e) { Date = _syncDate; OnConfirmed(); @@ -471,7 +471,7 @@ namespace Avalonia.Controls _syncDate = newDate; - //We don't need to update the days if not displaying day, not february + // We don't need to update the days if not displaying day, not february if (!DayVisible || _syncDate.Month != 2) return; From 8916fda98db209ed511f3395547b9179c5764ce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Onak?= Date: Wed, 15 Jul 2020 21:24:23 +0200 Subject: [PATCH 03/35] Force recalculation of column width after edit is completed --- src/Avalonia.Controls.DataGrid/DataGrid.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index fe9656a00b..7e921944ea 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -3922,6 +3922,12 @@ namespace Avalonia.Controls dataGridCell: editingCell); EditingRow.InvalidateDesiredHeight(); + var column = editingCell.OwningColumn; + if (column.Width.IsSizeToCells || column.Width.IsAuto) + {// Invalidate desired width and force recalculation + column.SetWidthDesiredValue(0); + EditingRow.OwningGrid.AutoSizeColumn(column, editingCell.DesiredSize.Width); + } } // We're done, so raise the CellEditEnded event From 5bce867f5258fba927267c84a1c252053199d6e1 Mon Sep 17 00:00:00 2001 From: FoggyFinder Date: Thu, 16 Jul 2020 09:47:30 +0300 Subject: [PATCH 04/35] additional check for presenter --- .../DateTimePickers/DatePickerPresenter.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs b/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs index 0da46bb74a..31527ccb16 100644 --- a/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs +++ b/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs @@ -348,8 +348,7 @@ namespace Avalonia.Controls var dt = Date; if (DayVisible) { - GregorianCalendar gc = new GregorianCalendar(); - var maxDays = gc.GetDaysInMonth(dt.Year, dt.Month); + var maxDays = _calendar.GetDaysInMonth(dt.Year, dt.Month); _daySelector.MaximumValue = maxDays; _daySelector.MinimumValue = 1; _daySelector.SelectedValue = dt.Day; @@ -407,10 +406,14 @@ namespace Avalonia.Controls } } - Grid.SetColumn(_spacer1, 1); - Grid.SetColumn(_spacer2, 3); - _spacer1.IsVisible = columnIndex > 1; - _spacer2.IsVisible = columnIndex > 2; + var isSpacer1Visible = columnIndex > 1; + var isSpacer2Visible = columnIndex > 2; + // ternary conditional operator is used to make sure grid cells will be validated + Grid.SetColumn(_spacer1, isSpacer1Visible ? 1 : 0); + Grid.SetColumn(_spacer2, isSpacer2Visible ? 3 : 0); + + _spacer1.IsVisible = isSpacer1Visible; + _spacer2.IsVisible = isSpacer2Visible; } private void SetInitialFocus() From bebe9ee3739364ed11ae4819acef07a9bb3890ac Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 13 Jul 2020 17:25:15 +0200 Subject: [PATCH 05/35] Bind ListBox.SelectedItems again. Was removed accidentally. --- samples/ControlCatalog/Pages/ListBoxPage.xaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/samples/ControlCatalog/Pages/ListBoxPage.xaml b/samples/ControlCatalog/Pages/ListBoxPage.xaml index 47b4ce7151..f4d81418ac 100644 --- a/samples/ControlCatalog/Pages/ListBoxPage.xaml +++ b/samples/ControlCatalog/Pages/ListBoxPage.xaml @@ -10,7 +10,13 @@ HorizontalAlignment="Center" Spacing="16"> - + From 0301d80c302cde4a60aeec2b6816e91cc66fa8d0 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 14 Jul 2020 22:04:47 +0200 Subject: [PATCH 06/35] Added failing test for removing selected item with BeginInit. --- .../Primitives/SelectingItemsControlTests.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index fe9c7b1261..9ef2750ff3 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -531,6 +531,7 @@ namespace Avalonia.Controls.UnitTests.Primitives }; target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); target.SelectedIndex = 1; Assert.Equal(items[1], target.SelectedItem); @@ -549,6 +550,45 @@ namespace Avalonia.Controls.UnitTests.Primitives Assert.NotNull(receivedArgs); Assert.Empty(receivedArgs.AddedItems); Assert.Equal(new[] { removed }, receivedArgs.RemovedItems); + Assert.False(items.Single().IsSelected); + } + + [Fact] + public void Removing_Selected_Item_Should_Clear_Selection_With_BeginInit() + { + var items = new AvaloniaList + { + new Item(), + new Item(), + }; + + var target = new SelectingItemsControl(); + target.BeginInit(); + target.Items = items; + target.Template = Template(); + target.EndInit(); + + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + target.SelectedIndex = 0; + + Assert.Equal(items[0], target.SelectedItem); + Assert.Equal(0, target.SelectedIndex); + + SelectionChangedEventArgs receivedArgs = null; + + target.SelectionChanged += (_, args) => receivedArgs = args; + + var removed = items[0]; + + items.RemoveAt(0); + + Assert.Null(target.SelectedItem); + Assert.Equal(-1, target.SelectedIndex); + Assert.NotNull(receivedArgs); + Assert.Empty(receivedArgs.AddedItems); + Assert.Equal(new[] { removed }, receivedArgs.RemovedItems); + Assert.False(items.Single().IsSelected); } [Fact] From 125ae96859df3bf6e602bc9f85171234fdd16f55 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 16 Jul 2020 12:29:33 +0200 Subject: [PATCH 07/35] Handle selected items being removed. Fixes #4293. --- .../Primitives/SelectingItemsControl.cs | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index c915dc70b6..59b7777b1b 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -692,14 +692,24 @@ namespace Avalonia.Controls.Primitives } } - foreach (var i in e.SelectedIndices) + if (e.SelectedIndices.Count > 0 || e.DeselectedIndices.Count > 0) { - Mark(i.GetAt(0), true); - } + foreach (var i in e.SelectedIndices) + { + Mark(i.GetAt(0), true); + } - foreach (var i in e.DeselectedIndices) + foreach (var i in e.DeselectedIndices) + { + Mark(i.GetAt(0), false); + } + } + else if (e.DeselectedItems.Count > 0) { - Mark(i.GetAt(0), false); + // (De)selected indices being empty means that a selected item was removed from + // the Items (it can't tell us the index of the item because the index is no longer + // valid). In this case, we just update the selection state of all containers. + UpdateContainerSelection(); } var newSelectedIndex = SelectedIndex; From 2ede354bbc7268f39c733342212a7730e976a7e0 Mon Sep 17 00:00:00 2001 From: Lorenzo Delana Date: Mon, 20 Jul 2020 14:53:19 +0200 Subject: [PATCH 08/35] fix X11 XDestroyWindow crash --- src/Avalonia.X11/X11Window.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 2a13999e8d..1f1f31db0a 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -760,11 +760,7 @@ namespace Avalonia.X11 public void Dispose() { - if (_handle != IntPtr.Zero) - { - XDestroyWindow(_x11.Display, _handle); - Cleanup(); - } + Cleanup(); } void Cleanup() @@ -787,8 +783,7 @@ namespace Avalonia.X11 } if (_useRenderWindow && _renderHandle != IntPtr.Zero) - { - XDestroyWindow(_x11.Display, _renderHandle); + { _renderHandle = IntPtr.Zero; } } From c5edf9bc5da4e1dabad5d5a9f3cd8b078bd254d2 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Mon, 20 Jul 2020 18:37:11 +0200 Subject: [PATCH 09/35] Initial --- .../Media/TextFormatting/TextLineImpl.cs | 20 ++++-- .../Media/TextFormatting/TextLineTests.cs | 62 +++++++++++++++++-- 2 files changed, 71 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs index 980b1a2d40..f73a7be759 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs @@ -181,7 +181,7 @@ namespace Avalonia.Media.TextFormatting return nextCharacterHit; } - return new CharacterHit(TextRange.End); // Can't move, we're after the last character + return characterHit; // Can't move, we're after the last character } /// @@ -192,7 +192,7 @@ namespace Avalonia.Media.TextFormatting return previousCharacterHit; } - return new CharacterHit(TextRange.Start); // Can't move, we're before the first character + return characterHit; // Can't move, we're before the first character } /// @@ -247,9 +247,13 @@ namespace Avalonia.Media.TextFormatting { var run = _textRuns[runIndex]; - nextCharacterHit = run.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex + characterHit.TrailingLength, out _); + var foundCharacterHit = run.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex + characterHit.TrailingLength, out _); - if (codepointIndex <= nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength) + nextCharacterHit = characterHit.TrailingLength != 0 ? + foundCharacterHit : + new CharacterHit(foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength); + + if (nextCharacterHit.FirstCharacterIndex > characterHit.FirstCharacterIndex) { return true; } @@ -283,9 +287,13 @@ namespace Avalonia.Media.TextFormatting { var run = _textRuns[runIndex]; - previousCharacterHit = run.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex - 1, out _); + var foundCharacterHit = run.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex - 1, out _); + + previousCharacterHit = characterHit.TrailingLength != 0 ? + foundCharacterHit : + new CharacterHit(foundCharacterHit.FirstCharacterIndex); - if (previousCharacterHit.FirstCharacterIndex < codepointIndex) + if (previousCharacterHit.FirstCharacterIndex < characterHit.FirstCharacterIndex) { return true; } diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs index f0951c61d3..09cbf3bf08 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs @@ -31,12 +31,37 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var nextCharacterHit = new CharacterHit(0); - for (var i = 1; i < clusters.Length; i++) + for (var i = 0; i < clusters.Length; i++) { + Assert.Equal(clusters[i], nextCharacterHit.FirstCharacterIndex); + nextCharacterHit = textLine.GetNextCaretCharacterHit(nextCharacterHit); + } + + var lastCharacterHit = nextCharacterHit; + + nextCharacterHit = textLine.GetNextCaretCharacterHit(lastCharacterHit); + + Assert.Equal(lastCharacterHit.FirstCharacterIndex, nextCharacterHit.FirstCharacterIndex); + + Assert.Equal(lastCharacterHit.TrailingLength, nextCharacterHit.TrailingLength); + + nextCharacterHit = new CharacterHit(0, clusters[1] - clusters[0]); - Assert.Equal(clusters[i], nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength); + for (var i = 0; i < clusters.Length; i++) + { + Assert.Equal(clusters[i], nextCharacterHit.FirstCharacterIndex); + + nextCharacterHit = textLine.GetNextCaretCharacterHit(nextCharacterHit); } + + lastCharacterHit = nextCharacterHit; + + nextCharacterHit = textLine.GetNextCaretCharacterHit(lastCharacterHit); + + Assert.Equal(lastCharacterHit.FirstCharacterIndex, nextCharacterHit.FirstCharacterIndex); + + Assert.Equal(lastCharacterHit.TrailingLength, nextCharacterHit.TrailingLength); } } @@ -60,14 +85,41 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var clusters = textLine.TextRuns.Cast().SelectMany(x => x.GlyphRun.GlyphClusters) .ToArray(); - var previousCharacterHit = new CharacterHit(clusters[^1]); + var previousCharacterHit = new CharacterHit(text.Length); - for (var i = clusters.Length - 2; i > 0; i--) + for (var i = clusters.Length - 1; i >= 0; i--) { previousCharacterHit = textLine.GetPreviousCaretCharacterHit(previousCharacterHit); - Assert.Equal(clusters[i], previousCharacterHit.FirstCharacterIndex); + Assert.Equal(clusters[i], + previousCharacterHit.FirstCharacterIndex + previousCharacterHit.TrailingLength); } + + var firstCharacterHit = previousCharacterHit; + + previousCharacterHit = textLine.GetPreviousCaretCharacterHit(firstCharacterHit); + + Assert.Equal(firstCharacterHit.FirstCharacterIndex, previousCharacterHit.FirstCharacterIndex); + + Assert.Equal(firstCharacterHit.TrailingLength, previousCharacterHit.TrailingLength); + + previousCharacterHit = new CharacterHit(clusters[^1], text.Length - clusters[^1]); + + for (var i = clusters.Length - 1; i > 0; i--) + { + previousCharacterHit = textLine.GetPreviousCaretCharacterHit(previousCharacterHit); + + Assert.Equal(clusters[i], + previousCharacterHit.FirstCharacterIndex + previousCharacterHit.TrailingLength); + } + + firstCharacterHit = previousCharacterHit; + + previousCharacterHit = textLine.GetPreviousCaretCharacterHit(firstCharacterHit); + + Assert.Equal(firstCharacterHit.FirstCharacterIndex, previousCharacterHit.FirstCharacterIndex); + + Assert.Equal(firstCharacterHit.TrailingLength, previousCharacterHit.TrailingLength); } } From 8c331534a9071a4b95e927cf9805ad5b455963c1 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 20 Jul 2020 15:44:19 -0300 Subject: [PATCH 10/35] fix nre on osx when tooltip closes. --- src/Avalonia.Native/WindowImplBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index f916e95d7c..88956e41e1 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -432,7 +432,7 @@ namespace Avalonia.Native TransparencyLevel = transparencyLevel; - _native.SetBlurEnabled(TransparencyLevel >= WindowTransparencyLevel.Blur); + _native?.SetBlurEnabled(TransparencyLevel >= WindowTransparencyLevel.Blur); TransparencyLevelChanged?.Invoke(TransparencyLevel); } } From 0148106f545a3afa3b4b87b3f7a731f04a54d9ea Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 20 Jul 2020 16:27:26 -0300 Subject: [PATCH 11/35] Seperate RenderScaling from DesktopScaling. --- .../Platform/SkiaPlatform/TopLevelImpl.cs | 2 +- .../Offscreen/OffscreenTopLevelImpl.cs | 2 +- .../Platform/ITopLevelImpl.cs | 6 +-- .../Platform/IWindowBaseImpl.cs | 5 +++ .../ManagedPopupPositionerPopupImplHelper.cs | 4 +- src/Avalonia.Controls/TopLevel.cs | 4 +- src/Avalonia.Controls/Window.cs | 2 +- .../Remote/PreviewerWindowImpl.cs | 1 + src/Avalonia.DesignerSupport/Remote/Stubs.cs | 3 +- src/Avalonia.Headless/HeadlessWindowImpl.cs | 9 +++-- ...sxManagedPopupPositionerPopupImplHelper.cs | 15 -------- src/Avalonia.Native/PopupImpl.cs | 2 +- src/Avalonia.Native/WindowImplBase.cs | 8 ++-- src/Avalonia.X11/X11NativeControlHost.cs | 4 +- src/Avalonia.X11/X11Window.cs | 38 ++++++++++--------- .../FramebufferToplevelImpl.cs | 4 +- .../Wpf/WpfTopLevelImpl.cs | 6 +-- src/Windows/Avalonia.Win32/PopupImpl.cs | 2 +- .../Avalonia.Win32/Win32NativeControlHost.cs | 4 +- .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 14 +++---- .../WindowImpl.CustomCaptionProc.cs | 2 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 28 ++++++++------ src/iOS/Avalonia.iOS/TopLevelImpl.cs | 2 +- .../ContextMenuTests.cs | 2 +- .../DesktopStyleApplicationLifetimeTests.cs | 2 +- .../TopLevelTests.cs | 4 +- .../WindowBaseTests.cs | 10 ++--- .../WindowTests.cs | 14 +++---- .../WindowingPlatformMock.cs | 4 +- tests/Avalonia.LeakTests/ControlTests.cs | 2 +- .../MockWindowingPlatform.cs | 4 +- 31 files changed, 105 insertions(+), 104 deletions(-) delete mode 100644 src/Avalonia.Native/OsxManagedPopupPositionerPopupImplHelper.cs diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 69fd2a9f13..71dce93ce7 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -126,7 +126,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform _view.Visibility = ViewStates.Visible; } - public double Scaling => 1; + public double RenderScaling => 1; void Draw() { diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs index f8bd2878d9..522103c7bd 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs @@ -35,7 +35,7 @@ namespace Avalonia.Controls.Embedding.Offscreen } } - public double Scaling + public double RenderScaling { get { return _scaling; } set diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index 0d77cbf802..7514f214aa 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -23,10 +23,10 @@ namespace Avalonia.Platform Size ClientSize { get; } /// - /// Gets the scaling factor for the toplevel. + /// Gets the scaling factor for the toplevel. This is used for rendering. /// - double Scaling { get; } - + double RenderScaling { get; } + /// /// The list of native platform's surfaces that can be consumed by rendering subsystems. /// diff --git a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs index b190c4f2e7..ecaf87d1ed 100644 --- a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs @@ -13,6 +13,11 @@ namespace Avalonia.Platform /// Hides the window. /// void Hide(); + + /// + /// Gets the scaling factor for Window positioning and sizing. + /// + double DesktopScaling { get; } /// /// Gets the position of the window in device pixels. diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs index b0e3d1ab08..91ed5d975d 100644 --- a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs +++ b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs @@ -40,9 +40,9 @@ namespace Avalonia.Controls.Primitives.PopupPositioning public void MoveAndResize(Point devicePoint, Size virtualSize) { - _moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _parent.Scaling); + _moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _parent.RenderScaling); } - public virtual double Scaling => _parent.Scaling; + public virtual double Scaling => _parent.DesktopScaling; } } diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 611f0c9290..3d24f60463 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -280,10 +280,10 @@ namespace Avalonia.Controls } /// - double ILayoutRoot.LayoutScaling => PlatformImpl?.Scaling ?? 1; + double ILayoutRoot.LayoutScaling => PlatformImpl?.RenderScaling ?? 1; /// - double IRenderRoot.RenderScaling => PlatformImpl?.Scaling ?? 1; + double IRenderRoot.RenderScaling => PlatformImpl?.RenderScaling ?? 1; IStyleHost IStyleHost.StylingParent => _globalStyles; diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 18d8c89f49..90e5c22c45 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -818,7 +818,7 @@ namespace Avalonia.Controls private void SetWindowStartupLocation(IWindowBaseImpl owner = null) { - var scaling = owner?.Scaling ?? PlatformImpl?.Scaling ?? 1; + var scaling = owner?.DesktopScaling ?? PlatformImpl?.DesktopScaling ?? 1; // TODO: We really need non-client size here. var rect = new PixelRect( diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index dce24df9d9..25c26be91e 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -36,6 +36,7 @@ namespace Avalonia.DesignerSupport.Remote { } + public double DesktopScaling => 1.0; public PixelPoint Position { get; set; } public Action PositionChanged { get; set; } public Action Deactivated { get; set; } diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 168cdbc03f..f377b1bcd1 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -21,7 +21,8 @@ namespace Avalonia.DesignerSupport.Remote public IPlatformHandle Handle { get; } public Size MaxAutoSizeHint { get; } public Size ClientSize { get; } - public double Scaling { get; } = 1.0; + public double RenderScaling { get; } = 1.0; + public double DesktopScaling => 1.0; public IEnumerable Surfaces { get; } public Action Input { get; set; } public Action Paint { get; set; } diff --git a/src/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Avalonia.Headless/HeadlessWindowImpl.cs index 5bd46b6714..8f4fa5e304 100644 --- a/src/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Avalonia.Headless/HeadlessWindowImpl.cs @@ -41,7 +41,8 @@ namespace Avalonia.Headless } public Size ClientSize { get; set; } - public double Scaling { get; } = 1; + public double RenderScaling { get; } = 1; + public double DesktopScaling => RenderScaling; public IEnumerable Surfaces { get; } public Action Input { get; set; } public Action Paint { get; set; } @@ -62,9 +63,9 @@ namespace Avalonia.Headless public IInputRoot InputRoot { get; set; } - public Point PointToClient(PixelPoint point) => point.ToPoint(Scaling); + public Point PointToClient(PixelPoint point) => point.ToPoint(RenderScaling); - public PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, Scaling); + public PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, RenderScaling); public void SetCursor(IPlatformHandle cursor) { @@ -201,7 +202,7 @@ namespace Avalonia.Headless public ILockedFramebuffer Lock() { - var bmp = new WriteableBitmap(PixelSize.FromSize(ClientSize, Scaling), new Vector(96, 96) * Scaling); + var bmp = new WriteableBitmap(PixelSize.FromSize(ClientSize, RenderScaling), new Vector(96, 96) * RenderScaling); var fb = bmp.Lock(); return new FramebufferProxy(fb, () => { diff --git a/src/Avalonia.Native/OsxManagedPopupPositionerPopupImplHelper.cs b/src/Avalonia.Native/OsxManagedPopupPositionerPopupImplHelper.cs deleted file mode 100644 index 8aa9b1a122..0000000000 --- a/src/Avalonia.Native/OsxManagedPopupPositionerPopupImplHelper.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Avalonia.Controls.Primitives.PopupPositioning; -using Avalonia.Platform; - -namespace Avalonia.Native -{ - class OsxManagedPopupPositionerPopupImplHelper : ManagedPopupPositionerPopupImplHelper - { - public OsxManagedPopupPositionerPopupImplHelper(IWindowBaseImpl parent, MoveResizeDelegate moveResize) : base(parent, moveResize) - { - - } - - public override double Scaling => 1; - } -} diff --git a/src/Avalonia.Native/PopupImpl.cs b/src/Avalonia.Native/PopupImpl.cs index b0da5fdc43..2d246e08d2 100644 --- a/src/Avalonia.Native/PopupImpl.cs +++ b/src/Avalonia.Native/PopupImpl.cs @@ -26,7 +26,7 @@ namespace Avalonia.Native var context = _opts.UseGpu ? glFeature?.DeferredContext : null; Init(factory.CreatePopup(e, context?.Context), factory.CreateScreens(), context); } - PopupPositioner = new ManagedPopupPositioner(new OsxManagedPopupPositionerPopupImplHelper(parent, MoveResize)); + PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(parent, MoveResize)); } private void MoveResize(PixelPoint position, Size size, double scaling) diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index f916e95d7c..42eecc36ea 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -81,7 +81,7 @@ namespace Avalonia.Native _glSurface = new GlPlatformSurface(window, _glContext); Screen = new ScreenImpl(screens); _savedLogicalSize = ClientSize; - _savedScaling = Scaling; + _savedScaling = RenderScaling; _nativeControlHost = new NativeControlHostImpl(_native.CreateNativeControlHost()); var monitor = Screen.AllScreens.OrderBy(x => x.PixelDensity) @@ -369,7 +369,9 @@ namespace Avalonia.Native _native.SetTopMost(value); } - public double Scaling => _native?.GetScaling() ?? 1; + public double RenderScaling => _native?.GetScaling() ?? 1; + + public double DesktopScaling => 1; public Action Deactivated { get; set; } public Action Activated { get; set; } @@ -432,7 +434,7 @@ namespace Avalonia.Native TransparencyLevel = transparencyLevel; - _native.SetBlurEnabled(TransparencyLevel >= WindowTransparencyLevel.Blur); + _native?.SetBlurEnabled(TransparencyLevel >= WindowTransparencyLevel.Blur); TransparencyLevelChanged?.Invoke(TransparencyLevel); } } diff --git a/src/Avalonia.X11/X11NativeControlHost.cs b/src/Avalonia.X11/X11NativeControlHost.cs index 23fb27f72b..6c4eb81c84 100644 --- a/src/Avalonia.X11/X11NativeControlHost.cs +++ b/src/Avalonia.X11/X11NativeControlHost.cs @@ -167,7 +167,7 @@ namespace Avalonia.X11 XUnmapWindow(_display, _holder.Handle); } - size *= _attachedTo.Window.Scaling; + size *= _attachedTo.Window.RenderScaling; XResizeWindow(_display, _child.Handle, Math.Max(1, (int)size.Width), Math.Max(1, (int)size.Height)); } @@ -179,7 +179,7 @@ namespace Avalonia.X11 CheckDisposed(); if (_attachedTo == null) throw new InvalidOperationException("The control isn't currently attached to a toplevel"); - bounds *= _attachedTo.Window.Scaling; + bounds *= _attachedTo.Window.RenderScaling; var pixelRect = new PixelRect((int)bounds.X, (int)bounds.Y, Math.Max(1, (int)bounds.Width), Math.Max(1, (int)bounds.Height)); diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 1f1f31db0a..c24abcd230 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -163,7 +163,7 @@ namespace Avalonia.X11 var surfaces = new List { new X11FramebufferSurface(_x11.DeferredDisplay, _renderHandle, - depth, () => Scaling) + depth, () => RenderScaling) }; if (egl != null) @@ -217,7 +217,7 @@ namespace Avalonia.X11 } } - public double Scaling => _window.Scaling; + public double Scaling => _window.RenderScaling; } void UpdateMotifHints() @@ -284,9 +284,9 @@ namespace Avalonia.X11 XSetWMNormalHints(_x11.Display, _handle, ref hints); } - public Size ClientSize => new Size(_realSize.Width / Scaling, _realSize.Height / Scaling); + public Size ClientSize => new Size(_realSize.Width / RenderScaling, _realSize.Height / RenderScaling); - public double Scaling + public double RenderScaling { get { @@ -296,6 +296,8 @@ namespace Avalonia.X11 } private set => _scaling = value; } + + public double DesktopScaling => RenderScaling; public IEnumerable Surfaces { get; } public Action Input { get; set; } @@ -538,14 +540,14 @@ namespace Avalonia.X11 { var monitor = _platform.X11Screens.Screens.OrderBy(x => x.PixelDensity) .FirstOrDefault(m => m.Bounds.Contains(Position)); - newScaling = monitor?.PixelDensity ?? Scaling; + newScaling = monitor?.PixelDensity ?? RenderScaling; } - if (Scaling != newScaling) + if (RenderScaling != newScaling) { var oldScaledSize = ClientSize; - Scaling = newScaling; - ScalingChanged?.Invoke(Scaling); + RenderScaling = newScaling; + ScalingChanged?.Invoke(RenderScaling); SetMinMaxSize(_scaledMinMaxSize.minSize, _scaledMinMaxSize.maxSize); if(!skipResize) Resize(oldScaledSize, true); @@ -707,9 +709,9 @@ namespace Avalonia.X11 private void ScheduleInput(RawInputEventArgs args) { if (args is RawPointerEventArgs mouse) - mouse.Position = mouse.Position / Scaling; + mouse.Position = mouse.Position / RenderScaling; if (args is RawDragEvent drag) - drag.Location = drag.Location / Scaling; + drag.Location = drag.Location / RenderScaling; _lastEvent = new InputEventContainer() {Event = args}; _inputQueue.Enqueue(_lastEvent); @@ -816,11 +818,11 @@ namespace Avalonia.X11 public void Hide() => XUnmapWindow(_x11.Display, _handle); - public Point PointToClient(PixelPoint point) => new Point((point.X - Position.X) / Scaling, (point.Y - Position.Y) / Scaling); + public Point PointToClient(PixelPoint point) => new Point((point.X - Position.X) / RenderScaling, (point.Y - Position.Y) / RenderScaling); public PixelPoint PointToScreen(Point point) => new PixelPoint( - (int)(point.X * Scaling + Position.X), - (int)(point.Y * Scaling + Position.Y)); + (int)(point.X * RenderScaling + Position.X), + (int)(point.Y * RenderScaling + Position.Y)); public void SetSystemDecorations(SystemDecorations enabled) { @@ -840,7 +842,7 @@ namespace Avalonia.X11 Resize(size, true); } - PixelSize ToPixelSize(Size size) => new PixelSize((int)(size.Width * Scaling), (int)(size.Height * Scaling)); + PixelSize ToPixelSize(Size size) => new PixelSize((int)(size.Width * RenderScaling), (int)(size.Height * RenderScaling)); void Resize(Size clientSize, bool force) { @@ -1020,13 +1022,13 @@ namespace Avalonia.X11 { _scaledMinMaxSize = (minSize, maxSize); var min = new PixelSize( - (int)(minSize.Width < 1 ? 1 : minSize.Width * Scaling), - (int)(minSize.Height < 1 ? 1 : minSize.Height * Scaling)); + (int)(minSize.Width < 1 ? 1 : minSize.Width * RenderScaling), + (int)(minSize.Height < 1 ? 1 : minSize.Height * RenderScaling)); const int maxDim = MaxWindowDimension; var max = new PixelSize( - (int)(maxSize.Width > maxDim ? maxDim : Math.Max(min.Width, maxSize.Width * Scaling)), - (int)(maxSize.Height > maxDim ? maxDim : Math.Max(min.Height, maxSize.Height * Scaling))); + (int)(maxSize.Width > maxDim ? maxDim : Math.Max(min.Width, maxSize.Width * RenderScaling)), + (int)(maxSize.Height > maxDim ? maxDim : Math.Max(min.Height, maxSize.Height * RenderScaling))); _minMaxSize = (min, max); UpdateSizeHints(null); diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index b8ae2eb4d8..0a101eec7a 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs @@ -65,7 +65,7 @@ namespace Avalonia.LinuxFramebuffer public IMouseDevice MouseDevice => new MouseDevice(); public IPopupImpl CreatePopup() => null; - public double Scaling => _outputBackend.Scaling; + public double RenderScaling => _outputBackend.Scaling; public IEnumerable Surfaces { get; } public Action Input { get; set; } public Action Paint { get; set; } @@ -77,7 +77,7 @@ namespace Avalonia.LinuxFramebuffer public Action Closed { get; set; } public Action LostFocus { get; set; } - public Size ScaledSize => _outputBackend.PixelSize.ToSize(Scaling); + public Size ScaledSize => _outputBackend.PixelSize.ToSize(RenderScaling); public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { } diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index f5d83611bb..3467a33d16 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -75,7 +75,7 @@ namespace Avalonia.Win32.Interop.Wpf private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled) { if (msg == (int)UnmanagedMethods.WindowsMessage.WM_DPICHANGED) - _ttl.ScalingChanged?.Invoke(_ttl.Scaling); + _ttl.ScalingChanged?.Invoke(_ttl.RenderScaling); return IntPtr.Zero; } @@ -84,7 +84,7 @@ namespace Avalonia.Win32.Interop.Wpf _currentHwndSource?.RemoveHook(_hook); _currentHwndSource = e.NewSource as HwndSource; _currentHwndSource?.AddHook(_hook); - _ttl.ScalingChanged?.Invoke(_ttl.Scaling); + _ttl.ScalingChanged?.Invoke(_ttl.RenderScaling); } public IRenderer CreateRenderer(IRenderRoot root) @@ -102,7 +102,7 @@ namespace Avalonia.Win32.Interop.Wpf Size ITopLevelImpl.ClientSize => _finalSize; IMouseDevice ITopLevelImpl.MouseDevice => _mouse; - double ITopLevelImpl.Scaling => PresentationSource.FromVisual(this)?.CompositionTarget?.TransformToDevice.M11 ?? 1; + double ITopLevelImpl.RenderScaling => PresentationSource.FromVisual(this)?.CompositionTarget?.TransformToDevice.M11 ?? 1; IEnumerable ITopLevelImpl.Surfaces => _surfaces; diff --git a/src/Windows/Avalonia.Win32/PopupImpl.cs b/src/Windows/Avalonia.Win32/PopupImpl.cs index 525e5e0d52..57da1c4d66 100644 --- a/src/Windows/Avalonia.Win32/PopupImpl.cs +++ b/src/Windows/Avalonia.Win32/PopupImpl.cs @@ -57,7 +57,7 @@ namespace Avalonia.Win32 { var info = UnmanagedMethods.MONITORINFO.Create(); UnmanagedMethods.GetMonitorInfo(monitor, ref info); - _maxAutoSize = info.rcWork.ToPixelRect().ToRect(Scaling).Size; + _maxAutoSize = info.rcWork.ToPixelRect().ToRect(RenderScaling).Size; } } diff --git a/src/Windows/Avalonia.Win32/Win32NativeControlHost.cs b/src/Windows/Avalonia.Win32/Win32NativeControlHost.cs index d7bb2c037e..8f62163d81 100644 --- a/src/Windows/Avalonia.Win32/Win32NativeControlHost.cs +++ b/src/Windows/Avalonia.Win32/Win32NativeControlHost.cs @@ -176,7 +176,7 @@ namespace Avalonia.Win32 UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE); if (_attachedTo == null || _child == null) return; - size *= _attachedTo.Window.Scaling; + size *= _attachedTo.Window.RenderScaling; UnmanagedMethods.MoveWindow(_child.Handle, 0, 0, Math.Max(1, (int)size.Width), Math.Max(1, (int)size.Height), false); } @@ -186,7 +186,7 @@ namespace Avalonia.Win32 CheckDisposed(); if (_attachedTo == null) throw new InvalidOperationException("The control isn't currently attached to a toplevel"); - bounds *= _attachedTo.Window.Scaling; + bounds *= _attachedTo.Window.RenderScaling; var pixelRect = new PixelRect((int)bounds.X, (int)bounds.Y, Math.Max(1, (int)bounds.Width), Math.Max(1, (int)bounds.Height)); diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index 0ba1d311bc..ee6845e2eb 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -343,7 +343,7 @@ namespace Avalonia.Win32 { if (BeginPaint(_hwnd, out PAINTSTRUCT ps) != IntPtr.Zero) { - var f = Scaling; + var f = RenderScaling; var r = ps.rcPaint; Paint?.Invoke(new Rect(r.left / f, r.top / f, (r.right - r.left) / f, (r.bottom - r.top) / f)); @@ -368,7 +368,7 @@ namespace Avalonia.Win32 size == SizeCommand.Maximized)) { var clientSize = new Size(ToInt32(lParam) & 0xffff, ToInt32(lParam) >> 16); - Resized(clientSize / Scaling); + Resized(clientSize / RenderScaling); } var windowState = size == SizeCommand.Maximized ? @@ -406,25 +406,25 @@ namespace Avalonia.Win32 if (_minSize.Width > 0) { mmi.ptMinTrackSize.X = - (int)((_minSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right); + (int)((_minSize.Width * RenderScaling) + BorderThickness.Left + BorderThickness.Right); } if (_minSize.Height > 0) { mmi.ptMinTrackSize.Y = - (int)((_minSize.Height * Scaling) + BorderThickness.Top + BorderThickness.Bottom); + (int)((_minSize.Height * RenderScaling) + BorderThickness.Top + BorderThickness.Bottom); } if (!double.IsInfinity(_maxSize.Width) && _maxSize.Width > 0) { mmi.ptMaxTrackSize.X = - (int)((_maxSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right); + (int)((_maxSize.Width * RenderScaling) + BorderThickness.Left + BorderThickness.Right); } if (!double.IsInfinity(_maxSize.Height) && _maxSize.Height > 0) { mmi.ptMaxTrackSize.Y = - (int)((_maxSize.Height * Scaling) + BorderThickness.Top + BorderThickness.Bottom); + (int)((_maxSize.Height * RenderScaling) + BorderThickness.Top + BorderThickness.Bottom); } Marshal.StructureToPtr(mmi, lParam, true); @@ -480,7 +480,7 @@ namespace Avalonia.Win32 private Point DipFromLParam(IntPtr lParam) { - return new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)) / Scaling; + return new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)) / RenderScaling; } private PixelPoint PointFromLParam(IntPtr lParam) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs index 2badf99f7f..a3b7574369 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs @@ -37,7 +37,7 @@ namespace Avalonia.Win32 if (_extendTitleBarHint >= 0) { - border_thickness.top = (int)(_extendedMargins.Top * Scaling); + border_thickness.top = (int)(_extendedMargins.Top * RenderScaling); } // Determine if the hit test is for resizing. Default middle (1,1). diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 0ee1342d27..6f22f94056 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -164,7 +164,9 @@ namespace Avalonia.Win32 } } - public double Scaling => _scaling; + public double RenderScaling => _scaling; + + public double DesktopScaling => RenderScaling; public Size ClientSize { @@ -172,7 +174,7 @@ namespace Avalonia.Win32 { GetClientRect(_hwnd, out var rect); - return new Size(rect.right, rect.bottom) / Scaling; + return new Size(rect.right, rect.bottom) / RenderScaling; } } @@ -180,7 +182,7 @@ namespace Avalonia.Win32 public IPlatformHandle Handle { get; private set; } - public virtual Size MaxAutoSizeHint => new Size(_maxTrackSize.X / Scaling, _maxTrackSize.Y / Scaling); + public virtual Size MaxAutoSizeHint => new Size(_maxTrackSize.X / RenderScaling, _maxTrackSize.Y / RenderScaling); public IMouseDevice MouseDevice => _mouseDevice; @@ -342,8 +344,8 @@ namespace Avalonia.Win32 public void Resize(Size value) { - int requestedClientWidth = (int)(value.Width * Scaling); - int requestedClientHeight = (int)(value.Height * Scaling); + int requestedClientWidth = (int)(value.Width * RenderScaling); + int requestedClientHeight = (int)(value.Height * RenderScaling); GetClientRect(_hwnd, out var clientRect); @@ -395,7 +397,7 @@ namespace Avalonia.Win32 public void Invalidate(Rect rect) { - var scaling = Scaling; + var scaling = RenderScaling; var r = new RECT { left = (int)Math.Floor(rect.X * scaling), @@ -411,12 +413,12 @@ namespace Avalonia.Win32 { var p = new POINT { X = point.X, Y = point.Y }; UnmanagedMethods.ScreenToClient(_hwnd, ref p); - return new Point(p.X, p.Y) / Scaling; + return new Point(p.X, p.Y) / RenderScaling; } public PixelPoint PointToScreen(Point point) { - point *= Scaling; + point *= RenderScaling; var p = new POINT { X = (int)point.X, Y = (int)point.Y }; ClientToScreen(_hwnd, ref p); return new PixelPoint(p.X, p.Y); @@ -710,19 +712,19 @@ namespace Avalonia.Win32 if (_extendTitleBarHint != -1) { - borderCaptionThickness.top = (int)(_extendTitleBarHint * Scaling); + borderCaptionThickness.top = (int)(_extendTitleBarHint * RenderScaling); } margins.cyTopHeight = _extendChromeHints.HasFlag(ExtendClientAreaChromeHints.SystemChrome) && !_extendChromeHints.HasFlag(ExtendClientAreaChromeHints.PreferSystemChrome) ? borderCaptionThickness.top : 1; if (WindowState == WindowState.Maximized) { - _extendedMargins = new Thickness(0, (borderCaptionThickness.top - borderThickness.top) / Scaling, 0, 0); - _offScreenMargin = new Thickness(borderThickness.left / Scaling, borderThickness.top / Scaling, borderThickness.right / Scaling, borderThickness.bottom / Scaling); + _extendedMargins = new Thickness(0, (borderCaptionThickness.top - borderThickness.top) / RenderScaling, 0, 0); + _offScreenMargin = new Thickness(borderThickness.left / RenderScaling, borderThickness.top / RenderScaling, borderThickness.right / RenderScaling, borderThickness.bottom / RenderScaling); } else { - _extendedMargins = new Thickness(0, (borderCaptionThickness.top) / Scaling, 0, 0); + _extendedMargins = new Thickness(0, (borderCaptionThickness.top) / RenderScaling, 0, 0); _offScreenMargin = new Thickness(); } @@ -1034,6 +1036,8 @@ namespace Avalonia.Win32 } } + double EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Scaling => RenderScaling; + IntPtr EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Handle => Handle.Handle; public void SetExtendClientAreaToDecorationsHint(bool hint) diff --git a/src/iOS/Avalonia.iOS/TopLevelImpl.cs b/src/iOS/Avalonia.iOS/TopLevelImpl.cs index 83a68990d7..5a85a5ea88 100644 --- a/src/iOS/Avalonia.iOS/TopLevelImpl.cs +++ b/src/iOS/Avalonia.iOS/TopLevelImpl.cs @@ -48,7 +48,7 @@ namespace Avalonia.iOS public new IPlatformHandle Handle => null; - public double Scaling => UIScreen.MainScreen.Scale; + public double RenderScaling => UIScreen.MainScreen.Scale; public override void LayoutSubviews() => Resized?.Invoke(ClientSize); diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs index cf8f7c266a..7a2109e5a7 100644 --- a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs @@ -296,7 +296,7 @@ namespace Avalonia.Controls.UnitTests var windowImpl = MockWindowingPlatform.CreateWindowMock(); popupImpl = MockWindowingPlatform.CreatePopupMock(windowImpl.Object); - popupImpl.SetupGet(x => x.Scaling).Returns(1); + popupImpl.SetupGet(x => x.RenderScaling).Returns(1); windowImpl.Setup(x => x.CreatePopup()).Returns(popupImpl.Object); windowImpl.Setup(x => x.Screen).Returns(screenImpl.Object); diff --git a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs index dee7a84812..e6deabfe25 100644 --- a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs +++ b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs @@ -191,7 +191,7 @@ namespace Avalonia.Controls.UnitTests { var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Closed); - windowImpl.Setup(x => x.Scaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); var services = TestServices.StyledWindow.With( windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object)); diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs index e49e273bec..6b30aed257 100644 --- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs @@ -93,7 +93,7 @@ namespace Avalonia.Controls.UnitTests { var impl = new Mock(); impl.SetupProperty(x => x.Resized); - impl.SetupGet(x => x.Scaling).Returns(1); + impl.SetupGet(x => x.RenderScaling).Returns(1); var target = new TestTopLevel(impl.Object) { @@ -290,7 +290,7 @@ namespace Avalonia.Controls.UnitTests using (UnitTestApplication.Start(TestServices.StyledWindow)) { var impl = new Mock(); - impl.SetupGet(x => x.Scaling).Returns(1); + impl.SetupGet(x => x.RenderScaling).Returns(1); var child = new Border { Classes = { "foo" } }; var target = new TestTopLevel(impl.Object) diff --git a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs index 697ea9cff8..7e3130377f 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs @@ -110,7 +110,7 @@ namespace Avalonia.Controls.UnitTests public void IsVisible_Should_Be_False_Atfer_Impl_Signals_Close() { var windowImpl = new Mock(); - windowImpl.Setup(x => x.Scaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); windowImpl.SetupProperty(x => x.Closed); using (UnitTestApplication.Start(TestServices.StyledWindow)) @@ -128,7 +128,7 @@ namespace Avalonia.Controls.UnitTests public void Setting_IsVisible_True_Shows_Window() { var windowImpl = new Mock(); - windowImpl.Setup(x => x.Scaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); using (UnitTestApplication.Start(TestServices.StyledWindow)) { @@ -143,7 +143,7 @@ namespace Avalonia.Controls.UnitTests public void Setting_IsVisible_False_Hides_Window() { var windowImpl = new Mock(); - windowImpl.Setup(x => x.Scaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); using (UnitTestApplication.Start(TestServices.StyledWindow)) { @@ -208,7 +208,7 @@ namespace Avalonia.Controls.UnitTests { var renderer = new Mock(); var windowImpl = new Mock(); - windowImpl.Setup(x => x.Scaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); windowImpl.SetupProperty(x => x.Closed); windowImpl.Setup(x => x.CreateRenderer(It.IsAny())).Returns(renderer.Object); @@ -237,7 +237,7 @@ namespace Avalonia.Controls.UnitTests public TestWindowBase(IRenderer renderer = null) : base(Mock.Of(x => - x.Scaling == 1 && + x.RenderScaling == 1 && x.CreateRenderer(It.IsAny()) == renderer)) { } diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index e2b0def00b..2b736ae38b 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -100,7 +100,7 @@ namespace Avalonia.Controls.UnitTests { var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Closed); - windowImpl.Setup(x => x.Scaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); var services = TestServices.StyledWindow.With( windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object)); @@ -206,7 +206,7 @@ namespace Avalonia.Controls.UnitTests var parent = new Mock(); var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Closed); - windowImpl.Setup(x => x.Scaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); var target = new Window(windowImpl.Object); var task = target.ShowDialog(parent.Object); @@ -245,7 +245,7 @@ namespace Avalonia.Controls.UnitTests var parent = new Mock(); var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Closed); - windowImpl.Setup(x => x.Scaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); var target = new Window(windowImpl.Object); var task = target.ShowDialog(parent.Object); @@ -273,7 +273,7 @@ namespace Avalonia.Controls.UnitTests var windowImpl = MockWindowingPlatform.CreateWindowMock(); windowImpl.Setup(x => x.ClientSize).Returns(new Size(800, 480)); - windowImpl.Setup(x => x.Scaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); windowImpl.Setup(x => x.Screen).Returns(screens.Object); using (UnitTestApplication.Start(TestServices.StyledWindow)) @@ -298,12 +298,12 @@ namespace Avalonia.Controls.UnitTests var parentWindowImpl = MockWindowingPlatform.CreateWindowMock(); parentWindowImpl.Setup(x => x.ClientSize).Returns(new Size(800, 480)); parentWindowImpl.Setup(x => x.MaxAutoSizeHint).Returns(new Size(1920, 1080)); - parentWindowImpl.Setup(x => x.Scaling).Returns(1); + parentWindowImpl.Setup(x => x.RenderScaling).Returns(1); var windowImpl = MockWindowingPlatform.CreateWindowMock(); windowImpl.Setup(x => x.ClientSize).Returns(new Size(320, 200)); windowImpl.Setup(x => x.MaxAutoSizeHint).Returns(new Size(1920, 1080)); - windowImpl.Setup(x => x.Scaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); var parentWindowServices = TestServices.StyledWindow.With( windowingPlatform: new MockWindowingPlatform(() => parentWindowImpl.Object)); @@ -565,7 +565,7 @@ namespace Avalonia.Controls.UnitTests private IWindowImpl CreateImpl(Mock renderer) { return Mock.Of(x => - x.Scaling == 1 && + x.RenderScaling == 1 && x.CreateRenderer(It.IsAny()) == renderer.Object); } diff --git a/tests/Avalonia.Controls.UnitTests/WindowingPlatformMock.cs b/tests/Avalonia.Controls.UnitTests/WindowingPlatformMock.cs index 25e8c82b1a..bf1322afbc 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowingPlatformMock.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowingPlatformMock.cs @@ -17,7 +17,7 @@ namespace Avalonia.Controls.UnitTests public IWindowImpl CreateWindow() { - return _windowImpl?.Invoke() ?? Mock.Of(x => x.Scaling == 1); + return _windowImpl?.Invoke() ?? Mock.Of(x => x.RenderScaling == 1); } public IWindowImpl CreateEmbeddableWindow() @@ -25,6 +25,6 @@ namespace Avalonia.Controls.UnitTests throw new NotImplementedException(); } - public IPopupImpl CreatePopup() => _popupImpl?.Invoke() ?? Mock.Of(x => x.Scaling == 1); + public IPopupImpl CreatePopup() => _popupImpl?.Invoke() ?? Mock.Of(x => x.RenderScaling == 1); } } diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs index 00ef503b8d..530b8fa20c 100644 --- a/tests/Avalonia.LeakTests/ControlTests.cs +++ b/tests/Avalonia.LeakTests/ControlTests.cs @@ -355,7 +355,7 @@ namespace Avalonia.LeakTests var renderer = new Mock(); renderer.Setup(x => x.Dispose()); var impl = new Mock(); - impl.SetupGet(x => x.Scaling).Returns(1); + impl.SetupGet(x => x.RenderScaling).Returns(1); impl.SetupProperty(x => x.Closed); impl.Setup(x => x.CreateRenderer(It.IsAny())).Returns(renderer.Object); impl.Setup(x => x.Dispose()).Callback(() => impl.Object.Closed()); diff --git a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs index 48a333dc54..e5fcce13c0 100644 --- a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs +++ b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs @@ -29,7 +29,7 @@ namespace Avalonia.UnitTests windowImpl.SetupAllProperties(); windowImpl.Setup(x => x.ClientSize).Returns(() => clientSize); windowImpl.Setup(x => x.MaxAutoSizeHint).Returns(s_screenSize); - windowImpl.Setup(x => x.Scaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); windowImpl.Setup(x => x.Screen).Returns(CreateScreenMock().Object); windowImpl.Setup(x => x.Position).Returns(() => position); SetupToplevel(windowImpl); @@ -81,7 +81,7 @@ namespace Avalonia.UnitTests popupImpl.SetupAllProperties(); popupImpl.Setup(x => x.ClientSize).Returns(() => clientSize); popupImpl.Setup(x => x.MaxAutoSizeHint).Returns(s_screenSize); - popupImpl.Setup(x => x.Scaling).Returns(1); + popupImpl.Setup(x => x.RenderScaling).Returns(1); popupImpl.Setup(x => x.PopupPositioner).Returns(positioner); SetupToplevel(popupImpl); From 2ad40026865fd2189fbabfaa20df1bdbd56f06ce Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 20 Jul 2020 16:56:00 -0300 Subject: [PATCH 12/35] fix unit tests. --- .../DesktopStyleApplicationLifetimeTests.cs | 2 +- tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs | 8 ++++---- tests/Avalonia.Controls.UnitTests/WindowTests.cs | 12 ++++++------ tests/Avalonia.UnitTests/MockWindowingPlatform.cs | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs index e6deabfe25..837a62f40e 100644 --- a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs +++ b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs @@ -191,7 +191,7 @@ namespace Avalonia.Controls.UnitTests { var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Closed); - windowImpl.Setup(x => x.RenderScaling).Returns(1); + windowImpl.Setup(x => x.DesktopScaling).Returns(1); var services = TestServices.StyledWindow.With( windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object)); diff --git a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs index 7e3130377f..1c72eb5069 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs @@ -110,7 +110,7 @@ namespace Avalonia.Controls.UnitTests public void IsVisible_Should_Be_False_Atfer_Impl_Signals_Close() { var windowImpl = new Mock(); - windowImpl.Setup(x => x.RenderScaling).Returns(1); + windowImpl.Setup(x => x.DesktopScaling).Returns(1); windowImpl.SetupProperty(x => x.Closed); using (UnitTestApplication.Start(TestServices.StyledWindow)) @@ -128,7 +128,7 @@ namespace Avalonia.Controls.UnitTests public void Setting_IsVisible_True_Shows_Window() { var windowImpl = new Mock(); - windowImpl.Setup(x => x.RenderScaling).Returns(1); + windowImpl.Setup(x => x.DesktopScaling).Returns(1); using (UnitTestApplication.Start(TestServices.StyledWindow)) { @@ -143,7 +143,7 @@ namespace Avalonia.Controls.UnitTests public void Setting_IsVisible_False_Hides_Window() { var windowImpl = new Mock(); - windowImpl.Setup(x => x.RenderScaling).Returns(1); + windowImpl.Setup(x => x.DesktopScaling).Returns(1); using (UnitTestApplication.Start(TestServices.StyledWindow)) { @@ -208,7 +208,7 @@ namespace Avalonia.Controls.UnitTests { var renderer = new Mock(); var windowImpl = new Mock(); - windowImpl.Setup(x => x.RenderScaling).Returns(1); + windowImpl.Setup(x => x.DesktopScaling).Returns(1); windowImpl.SetupProperty(x => x.Closed); windowImpl.Setup(x => x.CreateRenderer(It.IsAny())).Returns(renderer.Object); diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index 2b736ae38b..5cf65115bb 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -100,7 +100,7 @@ namespace Avalonia.Controls.UnitTests { var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Closed); - windowImpl.Setup(x => x.RenderScaling).Returns(1); + windowImpl.Setup(x => x.DesktopScaling).Returns(1); var services = TestServices.StyledWindow.With( windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object)); @@ -206,7 +206,7 @@ namespace Avalonia.Controls.UnitTests var parent = new Mock(); var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Closed); - windowImpl.Setup(x => x.RenderScaling).Returns(1); + windowImpl.Setup(x => x.DesktopScaling).Returns(1); var target = new Window(windowImpl.Object); var task = target.ShowDialog(parent.Object); @@ -245,7 +245,7 @@ namespace Avalonia.Controls.UnitTests var parent = new Mock(); var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Closed); - windowImpl.Setup(x => x.RenderScaling).Returns(1); + windowImpl.Setup(x => x.DesktopScaling).Returns(1); var target = new Window(windowImpl.Object); var task = target.ShowDialog(parent.Object); @@ -273,7 +273,7 @@ namespace Avalonia.Controls.UnitTests var windowImpl = MockWindowingPlatform.CreateWindowMock(); windowImpl.Setup(x => x.ClientSize).Returns(new Size(800, 480)); - windowImpl.Setup(x => x.RenderScaling).Returns(1); + windowImpl.Setup(x => x.DesktopScaling).Returns(1); windowImpl.Setup(x => x.Screen).Returns(screens.Object); using (UnitTestApplication.Start(TestServices.StyledWindow)) @@ -298,12 +298,12 @@ namespace Avalonia.Controls.UnitTests var parentWindowImpl = MockWindowingPlatform.CreateWindowMock(); parentWindowImpl.Setup(x => x.ClientSize).Returns(new Size(800, 480)); parentWindowImpl.Setup(x => x.MaxAutoSizeHint).Returns(new Size(1920, 1080)); - parentWindowImpl.Setup(x => x.RenderScaling).Returns(1); + parentWindowImpl.Setup(x => x.DesktopScaling).Returns(1); var windowImpl = MockWindowingPlatform.CreateWindowMock(); windowImpl.Setup(x => x.ClientSize).Returns(new Size(320, 200)); windowImpl.Setup(x => x.MaxAutoSizeHint).Returns(new Size(1920, 1080)); - windowImpl.Setup(x => x.RenderScaling).Returns(1); + windowImpl.Setup(x => x.DesktopScaling).Returns(1); var parentWindowServices = TestServices.StyledWindow.With( windowingPlatform: new MockWindowingPlatform(() => parentWindowImpl.Object)); diff --git a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs index e5fcce13c0..e265b49af3 100644 --- a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs +++ b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs @@ -29,7 +29,7 @@ namespace Avalonia.UnitTests windowImpl.SetupAllProperties(); windowImpl.Setup(x => x.ClientSize).Returns(() => clientSize); windowImpl.Setup(x => x.MaxAutoSizeHint).Returns(s_screenSize); - windowImpl.Setup(x => x.RenderScaling).Returns(1); + windowImpl.Setup(x => x.DesktopScaling).Returns(1); windowImpl.Setup(x => x.Screen).Returns(CreateScreenMock().Object); windowImpl.Setup(x => x.Position).Returns(() => position); SetupToplevel(windowImpl); From 87c326a8fd0c7e54c60750179b00231e8cef9168 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 20 Jul 2020 17:13:27 -0300 Subject: [PATCH 13/35] fix mocks. --- .../DesktopStyleApplicationLifetimeTests.cs | 1 + tests/Avalonia.UnitTests/MockWindowingPlatform.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs index 837a62f40e..84f02aeda5 100644 --- a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs +++ b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs @@ -192,6 +192,7 @@ namespace Avalonia.Controls.UnitTests var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Closed); windowImpl.Setup(x => x.DesktopScaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); var services = TestServices.StyledWindow.With( windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object)); diff --git a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs index e265b49af3..67503ef0d0 100644 --- a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs +++ b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs @@ -30,6 +30,7 @@ namespace Avalonia.UnitTests windowImpl.Setup(x => x.ClientSize).Returns(() => clientSize); windowImpl.Setup(x => x.MaxAutoSizeHint).Returns(s_screenSize); windowImpl.Setup(x => x.DesktopScaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); windowImpl.Setup(x => x.Screen).Returns(CreateScreenMock().Object); windowImpl.Setup(x => x.Position).Returns(() => position); SetupToplevel(windowImpl); From 4aa51e80da8fda5314b0913d42dc3495dced8b1d Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 20 Jul 2020 17:26:03 -0300 Subject: [PATCH 14/35] fix more tests. --- tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs | 4 ++++ tests/Avalonia.Controls.UnitTests/WindowTests.cs | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs index 1c72eb5069..84f212d1b3 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs @@ -111,6 +111,7 @@ namespace Avalonia.Controls.UnitTests { var windowImpl = new Mock(); windowImpl.Setup(x => x.DesktopScaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); windowImpl.SetupProperty(x => x.Closed); using (UnitTestApplication.Start(TestServices.StyledWindow)) @@ -129,6 +130,7 @@ namespace Avalonia.Controls.UnitTests { var windowImpl = new Mock(); windowImpl.Setup(x => x.DesktopScaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); using (UnitTestApplication.Start(TestServices.StyledWindow)) { @@ -144,6 +146,7 @@ namespace Avalonia.Controls.UnitTests { var windowImpl = new Mock(); windowImpl.Setup(x => x.DesktopScaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); using (UnitTestApplication.Start(TestServices.StyledWindow)) { @@ -209,6 +212,7 @@ namespace Avalonia.Controls.UnitTests var renderer = new Mock(); var windowImpl = new Mock(); windowImpl.Setup(x => x.DesktopScaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); windowImpl.SetupProperty(x => x.Closed); windowImpl.Setup(x => x.CreateRenderer(It.IsAny())).Returns(renderer.Object); diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index 5cf65115bb..ba29001cf3 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -101,6 +101,7 @@ namespace Avalonia.Controls.UnitTests var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Closed); windowImpl.Setup(x => x.DesktopScaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); var services = TestServices.StyledWindow.With( windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object)); @@ -207,6 +208,7 @@ namespace Avalonia.Controls.UnitTests var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Closed); windowImpl.Setup(x => x.DesktopScaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); var target = new Window(windowImpl.Object); var task = target.ShowDialog(parent.Object); @@ -246,6 +248,7 @@ namespace Avalonia.Controls.UnitTests var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Closed); windowImpl.Setup(x => x.DesktopScaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); var target = new Window(windowImpl.Object); var task = target.ShowDialog(parent.Object); @@ -274,6 +277,7 @@ namespace Avalonia.Controls.UnitTests var windowImpl = MockWindowingPlatform.CreateWindowMock(); windowImpl.Setup(x => x.ClientSize).Returns(new Size(800, 480)); windowImpl.Setup(x => x.DesktopScaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); windowImpl.Setup(x => x.Screen).Returns(screens.Object); using (UnitTestApplication.Start(TestServices.StyledWindow)) @@ -299,11 +303,13 @@ namespace Avalonia.Controls.UnitTests parentWindowImpl.Setup(x => x.ClientSize).Returns(new Size(800, 480)); parentWindowImpl.Setup(x => x.MaxAutoSizeHint).Returns(new Size(1920, 1080)); parentWindowImpl.Setup(x => x.DesktopScaling).Returns(1); + parentWindowImpl.Setup(x => x.RenderScaling).Returns(1); var windowImpl = MockWindowingPlatform.CreateWindowMock(); windowImpl.Setup(x => x.ClientSize).Returns(new Size(320, 200)); windowImpl.Setup(x => x.MaxAutoSizeHint).Returns(new Size(1920, 1080)); windowImpl.Setup(x => x.DesktopScaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); var parentWindowServices = TestServices.StyledWindow.With( windowingPlatform: new MockWindowingPlatform(() => parentWindowImpl.Object)); From 88db585d42ff663fb42ab1d3ef6bda5485298d68 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 20 Jul 2020 17:52:56 -0300 Subject: [PATCH 15/35] fix calculation of working area. --- native/Avalonia.Native/src/OSX/Screens.mm | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/Screens.mm b/native/Avalonia.Native/src/OSX/Screens.mm index 278daf9a18..455cfa2e41 100644 --- a/native/Avalonia.Native/src/OSX/Screens.mm +++ b/native/Avalonia.Native/src/OSX/Screens.mm @@ -4,6 +4,14 @@ class Screens : public ComSingleObject { public: FORWARD_IUNKNOWN() + + private: + CGFloat PrimaryDisplayHeight() + { + return NSMaxY([[[NSScreen screens] firstObject] frame]); + } + +public: virtual HRESULT GetScreenCount (int* ret) override { @autoreleasepool @@ -25,15 +33,15 @@ class Screens : public ComSingleObject auto screen = [[NSScreen screens] objectAtIndex:index]; - ret->Bounds.X = [screen frame].origin.x; - ret->Bounds.Y = [screen frame].origin.y; ret->Bounds.Height = [screen frame].size.height; ret->Bounds.Width = [screen frame].size.width; + ret->Bounds.X = [screen frame].origin.x; + ret->Bounds.Y = PrimaryDisplayHeight() - [screen frame].origin.y - ret->Bounds.Height; - ret->WorkingArea.X = [screen visibleFrame].origin.x; - ret->WorkingArea.Y = [screen visibleFrame].origin.y; ret->WorkingArea.Height = [screen visibleFrame].size.height; ret->WorkingArea.Width = [screen visibleFrame].size.width; + ret->WorkingArea.X = [screen visibleFrame].origin.x; + ret->WorkingArea.Y = ret->Bounds.Height - [screen visibleFrame].origin.y - ret->WorkingArea.Height; ret->PixelDensity = [screen backingScaleFactor]; From 297ed15bb5ae08ab06cbc22b2de004dd8f786bc4 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 20 Jul 2020 20:54:33 -0300 Subject: [PATCH 16/35] fix sizetocontent on win32. --- src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index ee6845e2eb..25a34561fc 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -402,6 +402,8 @@ namespace Avalonia.Win32 case WindowsMessage.WM_GETMINMAXINFO: { MINMAXINFO mmi = Marshal.PtrToStructure(lParam); + + _maxTrackSize = mmi.ptMaxTrackSize; if (_minSize.Width > 0) { From b0bcd2e5e62ce4ea94a094677cdb191adf53c214 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 20 Jul 2020 21:16:35 -0300 Subject: [PATCH 17/35] dont emit excessive Resized events on OSX. --- native/Avalonia.Native/src/OSX/window.mm | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 872269bb26..2d0ffbe4f0 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -1291,10 +1291,15 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent _parent->UpdateCursor(); auto fsize = [self convertSizeToBacking: [self frame].size]; - _lastPixelSize.Width = (int)fsize.width; - _lastPixelSize.Height = (int)fsize.height; - [self updateRenderTarget]; - _parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height}); + + if(_lastPixelSize.Width != (int)fsize.width || _lastPixelSize.Height != (int)fsize.height) + { + _lastPixelSize.Width = (int)fsize.width; + _lastPixelSize.Height = (int)fsize.height; + [self updateRenderTarget]; + + _parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height}); + } } - (void)updateLayer From e5324684a179e4ede5cfb5982f948ac18b3cbf93 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 20 Jul 2020 22:15:12 -0300 Subject: [PATCH 18/35] ensure managed titlebar is attached in window when template is applied. --- src/Avalonia.Controls/Chrome/TitleBar.cs | 8 ++++---- src/Avalonia.Controls/Window.cs | 13 ++++++++++++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Controls/Chrome/TitleBar.cs b/src/Avalonia.Controls/Chrome/TitleBar.cs index 78b49d2a03..91cbc8b497 100644 --- a/src/Avalonia.Controls/Chrome/TitleBar.cs +++ b/src/Avalonia.Controls/Chrome/TitleBar.cs @@ -29,12 +29,12 @@ namespace Avalonia.Controls.Chrome { if (_disposables == null) { - var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); - - layer?.Children.Add(this); - if (_hostWindow != null) { + var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); + + layer?.Children.Add(this); + _disposables = new CompositeDisposable { _hostWindow.GetObservable(Window.WindowDecorationMarginProperty) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 90e5c22c45..874c94a974 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -6,6 +6,7 @@ using System.Reactive.Linq; using System.Threading.Tasks; using Avalonia.Controls.Chrome; using Avalonia.Controls.Platform; +using Avalonia.Controls.Primitives; using Avalonia.Data; using Avalonia.Input; using Avalonia.Interactivity; @@ -75,6 +76,7 @@ namespace Avalonia.Controls private bool _isExtendedIntoWindowDecorations; private Thickness _windowDecorationMargin; private Thickness _offScreenMargin; + private bool _templateApplied; /// /// Defines the property. @@ -553,7 +555,7 @@ namespace Avalonia.Controls WindowDecorationMargin = PlatformImpl.ExtendedMargins; OffScreenMargin = PlatformImpl.OffScreenMargin; - if (PlatformImpl.NeedsManagedDecorations) + if (PlatformImpl.NeedsManagedDecorations && _templateApplied) { if (_managedTitleBar == null) { @@ -568,6 +570,15 @@ namespace Avalonia.Controls } } + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + + _templateApplied = true; + + ExtendClientAreaToDecorationsChanged(PlatformImpl.IsClientAreaExtendedToDecorations); + } + /// /// Hides the window but does not close it. /// From d8604567eec0445e2898a78437b5016b021a2cd4 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 20 Jul 2020 22:38:31 -0300 Subject: [PATCH 19/35] fix window background interfering with titlebar and window dragging. --- src/Avalonia.Themes.Default/Window.xaml | 31 ++++++++++++------------- src/Avalonia.Themes.Fluent/Window.xaml | 19 +++++++-------- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/src/Avalonia.Themes.Default/Window.xaml b/src/Avalonia.Themes.Default/Window.xaml index 739887fb35..2aa62df01f 100644 --- a/src/Avalonia.Themes.Default/Window.xaml +++ b/src/Avalonia.Themes.Default/Window.xaml @@ -1,23 +1,22 @@ + + + + + + + + + + diff --git a/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs b/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs index dc9c4a8f49..9e7ae8b716 100644 --- a/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs @@ -17,6 +17,23 @@ namespace ControlCatalog.ViewModels SaveCommand = ReactiveCommand.Create(Save, Observable.Return(false)); OpenRecentCommand = ReactiveCommand.Create(OpenRecent); + var recentItems = new[] + { + new MenuItemViewModel + { + Header = "File1.txt", + Command = OpenRecentCommand, + CommandParameter = @"c:\foo\File1.txt" + }, + new MenuItemViewModel + { + Header = "File2.txt", + Command = OpenRecentCommand, + CommandParameter = @"c:\foo\File2.txt" + }, + }; + + RecentItems = recentItems; MenuItems = new[] { new MenuItemViewModel @@ -24,27 +41,13 @@ namespace ControlCatalog.ViewModels Header = "_File", Items = new[] { - new MenuItemViewModel { Header = "_Open...", Command = OpenCommand }, + new MenuItemViewModel { Header = "O_pen...", Command = OpenCommand }, new MenuItemViewModel { Header = "Save", Command = SaveCommand }, new MenuItemViewModel { Header = "-" }, new MenuItemViewModel { Header = "Recent", - Items = new[] - { - new MenuItemViewModel - { - Header = "File1.txt", - Command = OpenRecentCommand, - CommandParameter = @"c:\foo\File1.txt" - }, - new MenuItemViewModel - { - Header = "File2.txt", - Command = OpenRecentCommand, - CommandParameter = @"c:\foo\File2.txt" - }, - } + Items = recentItems }, } }, @@ -61,6 +64,7 @@ namespace ControlCatalog.ViewModels } public IReadOnlyList MenuItems { get; set; } + public IReadOnlyList RecentItems { get; set; } public ReactiveCommand OpenCommand { get; } public ReactiveCommand SaveCommand { get; } public ReactiveCommand OpenRecentCommand { get; } From 3c444dc279fe8a030d489315ddc8888b21140a2e Mon Sep 17 00:00:00 2001 From: Maksym Katsydan Date: Tue, 21 Jul 2020 19:49:58 -0400 Subject: [PATCH 30/35] Fix MenuItem:pressed state --- src/Avalonia.Themes.Fluent/MenuItem.xaml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/MenuItem.xaml b/src/Avalonia.Themes.Fluent/MenuItem.xaml index fbb994e90c..4899bf264f 100644 --- a/src/Avalonia.Themes.Fluent/MenuItem.xaml +++ b/src/Avalonia.Themes.Fluent/MenuItem.xaml @@ -183,10 +183,10 @@ - + @@ -212,14 +212,15 @@ - - - From 5e2c641f02f0c6d4382ed1a040c86c5576d825ca Mon Sep 17 00:00:00 2001 From: Maksym Katsydan Date: Tue, 21 Jul 2020 20:21:13 -0400 Subject: [PATCH 31/35] Move Popup in Fluent MenuItem to another parent node and add MenuFlyoutSubItemPopupHorizontalOffset --- src/Avalonia.Themes.Fluent/MenuItem.xaml | 155 ++++++++++++----------- 1 file changed, 79 insertions(+), 76 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/MenuItem.xaml b/src/Avalonia.Themes.Fluent/MenuItem.xaml index 4899bf264f..0442c38025 100644 --- a/src/Avalonia.Themes.Fluent/MenuItem.xaml +++ b/src/Avalonia.Themes.Fluent/MenuItem.xaml @@ -40,6 +40,7 @@ + -4 0,4,0,4 0,0,12,0 24,0,0,0 @@ -54,83 +55,85 @@ - - - - - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + From bb46dee949714829a7515e0637c1f32b5061c61d Mon Sep 17 00:00:00 2001 From: Maksym Katsydan Date: Tue, 21 Jul 2020 20:48:07 -0400 Subject: [PATCH 32/35] MenuBar item header should be centered --- src/Avalonia.Themes.Fluent/Menu.xaml | 8 +++++--- src/Avalonia.Themes.Fluent/MenuItem.xaml | 9 ++++++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Menu.xaml b/src/Avalonia.Themes.Fluent/Menu.xaml index 5f22f77d18..cf647ec64a 100644 --- a/src/Avalonia.Themes.Fluent/Menu.xaml +++ b/src/Avalonia.Themes.Fluent/Menu.xaml @@ -10,11 +10,13 @@ - - 32 + + 32 + 12,0,12,0 + - + @@ -187,7 +189,12 @@ + + - + - +