From e4cf7a6bf75c087769e738bb14047072a2c28a53 Mon Sep 17 00:00:00 2001 From: luthfiampas Date: Thu, 4 Feb 2021 01:52:45 +0700 Subject: [PATCH 01/15] add auto-select functionality to ComboBox control --- src/Avalonia.Controls/ComboBox.cs | 72 +++++++++++++++++++ .../ComboBoxTests.cs | 37 ++++++++++ 2 files changed, 109 insertions(+) diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index 7f2acb58fe..95baf8146d 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -10,6 +10,7 @@ using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.LogicalTree; using Avalonia.Media; +using Avalonia.Threading; using Avalonia.VisualTree; namespace Avalonia.Controls @@ -76,6 +77,14 @@ namespace Avalonia.Controls public static readonly StyledProperty VerticalContentAlignmentProperty = ContentControl.VerticalContentAlignmentProperty.AddOwner(); + /// + /// Defines the property. + /// + public static readonly StyledProperty IsAutoSelectEnabledProperty = + AvaloniaProperty.Register(nameof(IsAutoSelectEnabled)); + + private string _autoSelectTerm = string.Empty; + private DispatcherTimer _autoSelectTimer; private bool _isDropDownOpen; private Popup _popup; private object _selectionBoxItem; @@ -164,6 +173,17 @@ namespace Avalonia.Controls set { SetValue(VerticalContentAlignmentProperty, value); } } + /// + /// Gets or sets value indicating whether auto-select is currently enabled. + /// If true, the control will try to find then select matched + /// based on current keyboard inputs. + /// + public bool IsAutoSelectEnabled + { + get { return GetValue(IsAutoSelectEnabledProperty); } + set { SetValue(IsAutoSelectEnabledProperty, value); } + } + /// protected override IItemContainerGenerator CreateItemContainerGenerator() { @@ -229,6 +249,32 @@ namespace Avalonia.Controls } } + /// + protected override void OnTextInput(TextInputEventArgs e) + { + if (!IsAutoSelectEnabled || e.Handled) + return; + + StopAutoSelectTimer(); + + _autoSelectTerm += e.Text; + + bool match(ItemContainerInfo info) => + info.ContainerControl is IContentControl control && + control.Content?.ToString()?.StartsWith(_autoSelectTerm, StringComparison.OrdinalIgnoreCase) == true; + + var info = ItemContainerGenerator.Containers.FirstOrDefault(match); + + if (info != null) + { + SelectedIndex = info.Index; + } + + StartAutoSelectTimer(); + + e.Handled = true; + } + /// protected override void OnPointerWheelChanged(PointerWheelEventArgs e) { @@ -426,5 +472,31 @@ namespace Avalonia.Controls SelectedIndex = prev; } + + private void StartAutoSelectTimer() + { + _autoSelectTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) }; + _autoSelectTimer.Tick += AutoSelectTimer_Tick; + _autoSelectTimer.Start(); + } + + private void StopAutoSelectTimer() + { + if (_autoSelectTimer == null) + { + return; + } + + _autoSelectTimer.Stop(); + _autoSelectTimer.Tick -= AutoSelectTimer_Tick; + + _autoSelectTimer = null; + } + + private void AutoSelectTimer_Tick(object sender, EventArgs e) + { + _autoSelectTerm = string.Empty; + StopAutoSelectTimer(); + } } } diff --git a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs index c8a30a42e9..218832d988 100644 --- a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs @@ -1,7 +1,9 @@ +using System.Linq; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Shapes; using Avalonia.Controls.Templates; +using Avalonia.Input; using Avalonia.LogicalTree; using Avalonia.Media; using Avalonia.UnitTests; @@ -137,5 +139,40 @@ namespace Avalonia.Controls.UnitTests Assert.True(other.IsFocused); } } + + [Theory] + [InlineData(-1, 2, "c", "A item", "B item", "C item")] + [InlineData(0, 1, "b", "A item", "B item", "C item")] + [InlineData(2, 2, "x", "A item", "B item", "C item")] + public void AutoSelect_Should_Have_Expected_SelectedIndex( + int initialSelectedIndex, + int expectedSelectedIndex, + string searchTerm, + params string[] items) + { + using (UnitTestApplication.Start(TestServices.MockThreadingInterface)) + { + var target = new ComboBox + { + IsAutoSelectEnabled = true, + Template = GetTemplate(), + Items = items.Select(x => new ComboBoxItem { Content = x }) + }; + + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + target.SelectedIndex = initialSelectedIndex; + + var args = new TextInputEventArgs + { + Text = searchTerm, + RoutedEvent = InputElement.TextInputEvent + }; + + target.RaiseEvent(args); + + Assert.Equal(expectedSelectedIndex, target.SelectedIndex); + } + } } } From 079d1d64360a2b0689628ce9cc0967f1a6c93008 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 16 Feb 2021 16:47:05 +0100 Subject: [PATCH 02/15] Turn off light dismiss for non-toplevel menus. #5295 enabled the light dismiss overlay for popups, which in turn broke submenu interactions (#5475). The fix seems to be simple: turn off light dismiss for non-toplevel menus. These menus will be in popups so this means their behavior will be reverted to that of before #5295. Fixes #5475 --- src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml b/src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml index 4950043a99..4c098afe16 100644 --- a/src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml @@ -112,9 +112,8 @@ Date: Tue, 23 Feb 2021 16:34:34 +0100 Subject: [PATCH 03/15] Turn off light dismiss for non-toplevel menus. For default theme. --- src/Avalonia.Themes.Default/MenuItem.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Themes.Default/MenuItem.xaml b/src/Avalonia.Themes.Default/MenuItem.xaml index 9ce19cff6e..b58d2ff808 100644 --- a/src/Avalonia.Themes.Default/MenuItem.xaml +++ b/src/Avalonia.Themes.Default/MenuItem.xaml @@ -59,7 +59,7 @@ Grid.Column="4"/> Date: Tue, 23 Feb 2021 21:21:44 +0100 Subject: [PATCH 04/15] Removed unneeded binding. Co-authored-by: Max Katz --- src/Avalonia.Themes.Default/MenuItem.xaml | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Avalonia.Themes.Default/MenuItem.xaml b/src/Avalonia.Themes.Default/MenuItem.xaml index b58d2ff808..4bfae4c223 100644 --- a/src/Avalonia.Themes.Default/MenuItem.xaml +++ b/src/Avalonia.Themes.Default/MenuItem.xaml @@ -60,7 +60,6 @@ Date: Wed, 24 Feb 2021 14:46:40 +0700 Subject: [PATCH 05/15] rename AutoSelect to TextSearch --- src/Avalonia.Controls/ComboBox.cs | 54 +++++++++---------- .../ComboBoxTests.cs | 4 +- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index 95baf8146d..fa467a83ac 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -78,13 +78,13 @@ namespace Avalonia.Controls ContentControl.VerticalContentAlignmentProperty.AddOwner(); /// - /// Defines the property. + /// Defines the property. /// - public static readonly StyledProperty IsAutoSelectEnabledProperty = - AvaloniaProperty.Register(nameof(IsAutoSelectEnabled)); + public static readonly StyledProperty IsTextSearchEnabledProperty = + AvaloniaProperty.Register(nameof(IsTextSearchEnabled)); - private string _autoSelectTerm = string.Empty; - private DispatcherTimer _autoSelectTimer; + private string _textSearchTerm = string.Empty; + private DispatcherTimer _textSearchTimer; private bool _isDropDownOpen; private Popup _popup; private object _selectionBoxItem; @@ -174,14 +174,12 @@ namespace Avalonia.Controls } /// - /// Gets or sets value indicating whether auto-select is currently enabled. - /// If true, the control will try to find then select matched - /// based on current keyboard inputs. + /// Gets or sets a value that specifies whether a user can jump to a value by typing. /// - public bool IsAutoSelectEnabled + public bool IsTextSearchEnabled { - get { return GetValue(IsAutoSelectEnabledProperty); } - set { SetValue(IsAutoSelectEnabledProperty, value); } + get { return GetValue(IsTextSearchEnabledProperty); } + set { SetValue(IsTextSearchEnabledProperty, value); } } /// @@ -252,16 +250,16 @@ namespace Avalonia.Controls /// protected override void OnTextInput(TextInputEventArgs e) { - if (!IsAutoSelectEnabled || e.Handled) + if (!IsTextSearchEnabled || e.Handled) return; - StopAutoSelectTimer(); + StopTextSearchTimer(); - _autoSelectTerm += e.Text; + _textSearchTerm += e.Text; bool match(ItemContainerInfo info) => info.ContainerControl is IContentControl control && - control.Content?.ToString()?.StartsWith(_autoSelectTerm, StringComparison.OrdinalIgnoreCase) == true; + control.Content?.ToString()?.StartsWith(_textSearchTerm, StringComparison.OrdinalIgnoreCase) == true; var info = ItemContainerGenerator.Containers.FirstOrDefault(match); @@ -270,7 +268,7 @@ namespace Avalonia.Controls SelectedIndex = info.Index; } - StartAutoSelectTimer(); + StartTextSearchTimer(); e.Handled = true; } @@ -473,30 +471,30 @@ namespace Avalonia.Controls SelectedIndex = prev; } - private void StartAutoSelectTimer() + private void StartTextSearchTimer() { - _autoSelectTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) }; - _autoSelectTimer.Tick += AutoSelectTimer_Tick; - _autoSelectTimer.Start(); + _textSearchTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) }; + _textSearchTimer.Tick += TextSearchTimer_Tick; + _textSearchTimer.Start(); } - private void StopAutoSelectTimer() + private void StopTextSearchTimer() { - if (_autoSelectTimer == null) + if (_textSearchTimer == null) { return; } - _autoSelectTimer.Stop(); - _autoSelectTimer.Tick -= AutoSelectTimer_Tick; + _textSearchTimer.Stop(); + _textSearchTimer.Tick -= TextSearchTimer_Tick; - _autoSelectTimer = null; + _textSearchTimer = null; } - private void AutoSelectTimer_Tick(object sender, EventArgs e) + private void TextSearchTimer_Tick(object sender, EventArgs e) { - _autoSelectTerm = string.Empty; - StopAutoSelectTimer(); + _textSearchTerm = string.Empty; + StopTextSearchTimer(); } } } diff --git a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs index 218832d988..7cd7e74976 100644 --- a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs @@ -144,7 +144,7 @@ namespace Avalonia.Controls.UnitTests [InlineData(-1, 2, "c", "A item", "B item", "C item")] [InlineData(0, 1, "b", "A item", "B item", "C item")] [InlineData(2, 2, "x", "A item", "B item", "C item")] - public void AutoSelect_Should_Have_Expected_SelectedIndex( + public void TextSearch_Should_Have_Expected_SelectedIndex( int initialSelectedIndex, int expectedSelectedIndex, string searchTerm, @@ -154,7 +154,7 @@ namespace Avalonia.Controls.UnitTests { var target = new ComboBox { - IsAutoSelectEnabled = true, + IsTextSearchEnabled = true, Template = GetTemplate(), Items = items.Select(x => new ComboBoxItem { Content = x }) }; From a4477e7de62870fe6082c4e306cd1d953c36b4e5 Mon Sep 17 00:00:00 2001 From: luthfiampas Date: Wed, 24 Feb 2021 15:08:30 +0700 Subject: [PATCH 06/15] set default value of IsTextSearchEnabled to true --- src/Avalonia.Controls/ComboBox.cs | 2 +- tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index fa467a83ac..8e7f01cad9 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -81,7 +81,7 @@ namespace Avalonia.Controls /// Defines the property. /// public static readonly StyledProperty IsTextSearchEnabledProperty = - AvaloniaProperty.Register(nameof(IsTextSearchEnabled)); + AvaloniaProperty.Register(nameof(IsTextSearchEnabled), true); private string _textSearchTerm = string.Empty; private DispatcherTimer _textSearchTimer; diff --git a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs index 7cd7e74976..4ea838358c 100644 --- a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs @@ -154,7 +154,6 @@ namespace Avalonia.Controls.UnitTests { var target = new ComboBox { - IsTextSearchEnabled = true, Template = GetTemplate(), Items = items.Select(x => new ComboBoxItem { Content = x }) }; From 3d4c17b387e8ec958f3779c91893785c0526b964 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 26 Feb 2021 16:41:15 +0100 Subject: [PATCH 07/15] Added failing leak test for TabControl/TabItem. Removing `TabItem`s causes a leak. --- tests/Avalonia.LeakTests/ControlTests.cs | 38 +++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs index b03ae00cfe..087d42370e 100644 --- a/tests/Avalonia.LeakTests/ControlTests.cs +++ b/tests/Avalonia.LeakTests/ControlTests.cs @@ -313,7 +313,6 @@ namespace Avalonia.LeakTests } } - [Fact] public void Slider_Is_Freed() { @@ -347,6 +346,43 @@ namespace Avalonia.LeakTests } } + [Fact] + public void TabItem_Is_Freed() + { + using (Start()) + { + Func run = () => + { + var window = new Window + { + Content = new TabControl + { + Items = new[] { new TabItem() } + } + }; + + window.Show(); + + // Do a layout and make sure that TabControl and TabItem gets added to visual tree. + window.LayoutManager.ExecuteInitialLayoutPass(); + var tabControl = Assert.IsType(window.Presenter.Child); + Assert.IsType(tabControl.Presenter.Panel.Children[0]); + + // Clear the items and ensure the TabItem is removed. + tabControl.Items = null; + window.LayoutManager.ExecuteLayoutPass(); + Assert.Empty(tabControl.Presenter.Panel.Children); + + return window; + }; + + var result = run(); + + dotMemory.Check(memory => + Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + } + } + [Fact] public void RendererIsDisposed() { From 1837548b3b595207c3ae814a7a50daa92089e2fd Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 26 Feb 2021 17:08:13 +0100 Subject: [PATCH 08/15] Fix TabItems leaking. When a `TabItem` was created a binding was being set up to the owner `TabControl` but that binding was never being freed. Ideally we'd be setting these properties in XAML rather than hardcoding them in the generator but that would be a breaking change for everyone who re-templated `TabControl`. As a second-best option what we'd do is set up a `$parent` binding in the generator but this isn't available in Avalonia.Controls so had to implement a quick observable which watches for a parent `TabControl` and subscribes to it. --- .../Generators/TabItemContainerGenerator.cs | 55 ++++++++++++++++++- .../TabControlTests.cs | 1 + 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs index d2e05ee136..840a1f66f1 100644 --- a/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs @@ -1,4 +1,10 @@ +using System; +using System.Collections.Generic; using Avalonia.Controls.Primitives; +using Avalonia.Controls.Templates; +using Avalonia.LogicalTree; +using Avalonia.Reactive; +using Avalonia.VisualTree; namespace Avalonia.Controls.Generators { @@ -16,11 +22,15 @@ namespace Avalonia.Controls.Generators { var tabItem = (TabItem)base.CreateContainer(item); - tabItem[~TabControl.TabStripPlacementProperty] = Owner[~TabControl.TabStripPlacementProperty]; + tabItem.Bind(TabItem.TabStripPlacementProperty, new OwnerBinding( + tabItem, + TabControl.TabStripPlacementProperty)); if (tabItem.HeaderTemplate == null) { - tabItem[~HeaderedContentControl.HeaderTemplateProperty] = Owner[~ItemsControl.ItemTemplateProperty]; + tabItem.Bind(TabItem.HeaderTemplateProperty, new OwnerBinding( + tabItem, + TabControl.ItemTemplateProperty)); } if (tabItem.Header == null) @@ -40,10 +50,49 @@ namespace Avalonia.Controls.Generators if (!(tabItem.Content is IControl)) { - tabItem[~ContentControl.ContentTemplateProperty] = Owner[~TabControl.ContentTemplateProperty]; + tabItem.Bind(TabItem.ContentTemplateProperty, new OwnerBinding( + tabItem, + TabControl.ContentTemplateProperty)); } return tabItem; } + + private class OwnerBinding : SingleSubscriberObservableBase + { + private readonly TabItem _item; + private readonly StyledProperty _ownerProperty; + private IDisposable _ownerSubscription; + private IDisposable _propertySubscription; + + public OwnerBinding(TabItem item, StyledProperty ownerProperty) + { + _item = item; + _ownerProperty = ownerProperty; + } + + protected override void Subscribed() + { + _ownerSubscription = ControlLocator.Track(_item, 0, typeof(TabControl)).Subscribe(OwnerChanged); + } + + protected override void Unsubscribed() + { + _ownerSubscription?.Dispose(); + _ownerSubscription = null; + } + + private void OwnerChanged(ILogical c) + { + _propertySubscription?.Dispose(); + _propertySubscription = null; + + if (c is TabControl tabControl) + { + _propertySubscription = tabControl.GetObservable(_ownerProperty) + .Subscribe(x => PublishNext(x)); + } + } + } } } diff --git a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs index e6f7ac601f..c54d7efe61 100644 --- a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs @@ -374,6 +374,7 @@ namespace Avalonia.Controls.UnitTests new TextBlock { Tag = "bar", Text = x }), Items = new[] { "Foo" }, }; + var root = new TestRoot(target); ApplyTemplate(target); ((ContentPresenter)target.ContentPart).UpdateChild(); From 0a54da63ecf2b53d8bb7a8c9c1e401f47859ac1c Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 1 Mar 2021 16:31:33 +0100 Subject: [PATCH 09/15] fixes invalid name of DirectProperty in Avalonia.Animation.Animation --- src/Avalonia.Animation/Animation.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index ca1d97290e..05142532e9 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -22,7 +22,7 @@ namespace Avalonia.Animation /// public static readonly DirectProperty DurationProperty = AvaloniaProperty.RegisterDirect( - nameof(_duration), + nameof(Duration), o => o._duration, (o, v) => o._duration = v); @@ -31,7 +31,7 @@ namespace Avalonia.Animation /// public static readonly DirectProperty IterationCountProperty = AvaloniaProperty.RegisterDirect( - nameof(_iterationCount), + nameof(IterationCount), o => o._iterationCount, (o, v) => o._iterationCount = v); @@ -40,7 +40,7 @@ namespace Avalonia.Animation /// public static readonly DirectProperty PlaybackDirectionProperty = AvaloniaProperty.RegisterDirect( - nameof(_playbackDirection), + nameof(PlaybackDirection), o => o._playbackDirection, (o, v) => o._playbackDirection = v); @@ -49,7 +49,7 @@ namespace Avalonia.Animation /// public static readonly DirectProperty FillModeProperty = AvaloniaProperty.RegisterDirect( - nameof(_fillMode), + nameof(FillMode), o => o._fillMode, (o, v) => o._fillMode = v); @@ -58,7 +58,7 @@ namespace Avalonia.Animation /// public static readonly DirectProperty EasingProperty = AvaloniaProperty.RegisterDirect( - nameof(_easing), + nameof(Easing), o => o._easing, (o, v) => o._easing = v); @@ -67,7 +67,7 @@ namespace Avalonia.Animation /// public static readonly DirectProperty DelayProperty = AvaloniaProperty.RegisterDirect( - nameof(_delay), + nameof(Delay), o => o._delay, (o, v) => o._delay = v); @@ -76,7 +76,7 @@ namespace Avalonia.Animation /// public static readonly DirectProperty DelayBetweenIterationsProperty = AvaloniaProperty.RegisterDirect( - nameof(_delayBetweenIterations), + nameof(DelayBetweenIterations), o => o._delayBetweenIterations, (o, v) => o._delayBetweenIterations = v); @@ -85,7 +85,7 @@ namespace Avalonia.Animation /// public static readonly DirectProperty SpeedRatioProperty = AvaloniaProperty.RegisterDirect( - nameof(_speedRatio), + nameof(SpeedRatio), o => o._speedRatio, (o, v) => o._speedRatio = v, defaultBindingMode: BindingMode.TwoWay); From d8d6a47809c0b492c1ebcf66e545db85969153ea Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 1 Mar 2021 15:43:39 +0000 Subject: [PATCH 10/15] make cell template the content of DataGridTemplateColumn saves a lot of bloat when decalring TemplatedColumns. --- src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs index e513a7b678..a93f5314c6 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs @@ -22,6 +22,7 @@ namespace Avalonia.Controls o => o.CellTemplate, (o, v) => o.CellTemplate = v); + [Content] public IDataTemplate CellTemplate { get { return _cellTemplate; } From 30a0be6ef51c24294bbd911b22a2df6e4879ee57 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 1 Mar 2021 18:26:29 +0000 Subject: [PATCH 11/15] missing using. --- src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs index a93f5314c6..7e95dd100c 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs @@ -8,6 +8,7 @@ using Avalonia.Controls.Utils; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Media; +using Avalonia.Metadata; using Avalonia.Utilities; namespace Avalonia.Controls From 8f330b0b82334276f10ab97e935b280fb0329913 Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Tue, 2 Mar 2021 15:22:38 +0100 Subject: [PATCH 12/15] Return desired size as is without recreating it --- src/Avalonia.Layout/UniformGridLayout.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Layout/UniformGridLayout.cs b/src/Avalonia.Layout/UniformGridLayout.cs index 68f08d7cbb..6b147590ab 100644 --- a/src/Avalonia.Layout/UniformGridLayout.cs +++ b/src/Avalonia.Layout/UniformGridLayout.cs @@ -447,7 +447,7 @@ namespace Avalonia.Layout // and only use the layout when to clear it when it's done. gridState.EnsureFirstElementOwnership(context); - return new Size(desiredSize.Width, desiredSize.Height); + return desiredSize; } protected internal override Size ArrangeOverride(VirtualizingLayoutContext context, Size finalSize) From 111a9c80abc9f83bd267a1ef425394abf2eb605b Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 2 Mar 2021 22:00:27 +0000 Subject: [PATCH 13/15] allow user to disable generation of default appmenu items on osx. --- native/Avalonia.Native/src/OSX/common.h | 2 + native/Avalonia.Native/src/OSX/main.mm | 17 ++++ native/Avalonia.Native/src/OSX/menu.mm | 85 ++++++++++--------- src/Avalonia.Native/AvaloniaNativePlatform.cs | 2 + .../AvaloniaNativePlatformExtensions.cs | 2 + src/Avalonia.Native/avn.idl | 1 + 6 files changed, 68 insertions(+), 41 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h index ea48a19874..05b2d762ae 100644 --- a/native/Avalonia.Native/src/OSX/common.h +++ b/native/Avalonia.Native/src/OSX/common.h @@ -28,6 +28,8 @@ extern IAvnNativeControlHost* CreateNativeControlHost(NSView* parent); extern void SetAppMenu (NSString* appName, IAvnMenu* appMenu); extern IAvnMenu* GetAppMenu (); extern NSMenuItem* GetAppMenuItem (); +extern void SetAutoGenerateDefaultAppMenuItems (bool enabled); +extern bool GetAutoGenerateDefaultAppMenuItems (); extern void InitializeAvnApp(); extern NSApplicationActivationPolicy AvnDesiredActivationPolicy; diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm index 72f5aa0a7d..17746e1d1d 100644 --- a/native/Avalonia.Native/src/OSX/main.mm +++ b/native/Avalonia.Native/src/OSX/main.mm @@ -2,6 +2,7 @@ #define COM_GUIDS_MATERIALIZE #include "common.h" +static bool s_generateDefaultAppMenuItems = true; static NSString* s_appTitle = @"Avalonia"; // Copyright (c) 2011 The Chromium Authors. All rights reserved. @@ -122,6 +123,12 @@ public: ? NSApplicationActivationPolicyRegular : NSApplicationActivationPolicyAccessory; return S_OK; } + + virtual HRESULT SetDisableDefaultApplicationMenuItems (bool enabled) override + { + SetAutoGenerateDefaultAppMenuItems(!enabled); + return S_OK; + } }; /// See "Using POSIX Threads in a Cocoa Application" section here: @@ -310,3 +317,13 @@ CGFloat PrimaryDisplayHeight() { return NSMaxY([[[NSScreen screens] firstObject] frame]); } + +void SetAutoGenerateDefaultAppMenuItems (bool enabled) +{ + s_generateDefaultAppMenuItems = enabled; +} + +bool GetAutoGenerateDefaultAppMenuItems () +{ + return s_generateDefaultAppMenuItems; +} diff --git a/native/Avalonia.Native/src/OSX/menu.mm b/native/Avalonia.Native/src/OSX/menu.mm index 198b01714f..ea5cca9ce8 100644 --- a/native/Avalonia.Native/src/OSX/menu.mm +++ b/native/Avalonia.Native/src/OSX/menu.mm @@ -445,47 +445,50 @@ extern void SetAppMenu (NSString* appName, IAvnMenu* menu) auto appMenu = [s_appMenuItem submenu]; - [appMenu addItem:[NSMenuItem separatorItem]]; - - // Services item and menu - auto servicesItem = [[NSMenuItem alloc] init]; - servicesItem.title = @"Services"; - NSMenu *servicesMenu = [[NSMenu alloc] initWithTitle:@"Services"]; - servicesItem.submenu = servicesMenu; - [NSApplication sharedApplication].servicesMenu = servicesMenu; - [appMenu addItem:servicesItem]; - - [appMenu addItem:[NSMenuItem separatorItem]]; - - // Hide Application - auto hideItem = [[NSMenuItem alloc] initWithTitle:[@"Hide " stringByAppendingString:appName] action:@selector(hide:) keyEquivalent:@"h"]; - - [appMenu addItem:hideItem]; - - // Hide Others - auto hideAllOthersItem = [[NSMenuItem alloc] initWithTitle:@"Hide Others" - action:@selector(hideOtherApplications:) - keyEquivalent:@"h"]; - - hideAllOthersItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagOption; - [appMenu addItem:hideAllOthersItem]; - - // Show All - auto showAllItem = [[NSMenuItem alloc] initWithTitle:@"Show All" - action:@selector(unhideAllApplications:) - keyEquivalent:@""]; - - [appMenu addItem:showAllItem]; - - [appMenu addItem:[NSMenuItem separatorItem]]; - - // Quit Application - auto quitItem = [[NSMenuItem alloc] init]; - quitItem.title = [@"Quit " stringByAppendingString:appName]; - quitItem.keyEquivalent = @"q"; - quitItem.target = [AvnWindow class]; - quitItem.action = @selector(closeAll); - [appMenu addItem:quitItem]; + if(GetAutoGenerateDefaultAppMenuItems()) + { + [appMenu addItem:[NSMenuItem separatorItem]]; + + // Services item and menu + auto servicesItem = [[NSMenuItem alloc] init]; + servicesItem.title = @"Services"; + NSMenu *servicesMenu = [[NSMenu alloc] initWithTitle:@"Services"]; + servicesItem.submenu = servicesMenu; + [NSApplication sharedApplication].servicesMenu = servicesMenu; + [appMenu addItem:servicesItem]; + + [appMenu addItem:[NSMenuItem separatorItem]]; + + // Hide Application + auto hideItem = [[NSMenuItem alloc] initWithTitle:[@"Hide " stringByAppendingString:appName] action:@selector(hide:) keyEquivalent:@"h"]; + + [appMenu addItem:hideItem]; + + // Hide Others + auto hideAllOthersItem = [[NSMenuItem alloc] initWithTitle:@"Hide Others" + action:@selector(hideOtherApplications:) + keyEquivalent:@"h"]; + + hideAllOthersItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagOption; + [appMenu addItem:hideAllOthersItem]; + + // Show All + auto showAllItem = [[NSMenuItem alloc] initWithTitle:@"Show All" + action:@selector(unhideAllApplications:) + keyEquivalent:@""]; + + [appMenu addItem:showAllItem]; + + [appMenu addItem:[NSMenuItem separatorItem]]; + + // Quit Application + auto quitItem = [[NSMenuItem alloc] init]; + quitItem.title = [@"Quit " stringByAppendingString:appName]; + quitItem.keyEquivalent = @"q"; + quitItem.target = [AvnWindow class]; + quitItem.action = @selector(closeAll); + [appMenu addItem:quitItem]; + } } else { diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index edcbf90ebc..35d21a75d3 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -92,6 +92,8 @@ namespace Avalonia.Native var macOpts = AvaloniaLocator.Current.GetService(); _factory.MacOptions.SetShowInDock(macOpts?.ShowInDock != false ? 1 : 0); + _factory.MacOptions.SetDisableDefaultApplicationMenuItems( + macOpts?.DisableDefaultApplicationMenuItems == true ? 1 : 0); } AvaloniaLocator.CurrentMutable diff --git a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs index 545659f813..76cb7a8057 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs @@ -38,5 +38,7 @@ namespace Avalonia public class MacOSPlatformOptions { public bool ShowInDock { get; set; } = true; + + public bool DisableDefaultApplicationMenuItems { get; set; } } } diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index 3627ff6894..57a0c32067 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -528,6 +528,7 @@ interface IAvnMacOptions : IUnknown { HRESULT SetShowInDock(int show); HRESULT SetApplicationTitle(char* utf8string); + HRESULT SetDisableDefaultApplicationMenuItems(bool enabled); } [uuid(04c1b049-1f43-418a-9159-cae627ec1367)] From f831b5d346897500ba1d03bb3a46249416ff7d0c Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Wed, 3 Mar 2021 18:19:20 +0100 Subject: [PATCH 14/15] Fixed parameter name in uniform grid state --- src/Avalonia.Layout/UniformGridLayoutState.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Layout/UniformGridLayoutState.cs b/src/Avalonia.Layout/UniformGridLayoutState.cs index 62c5174775..282bbab1a8 100644 --- a/src/Avalonia.Layout/UniformGridLayoutState.cs +++ b/src/Avalonia.Layout/UniformGridLayoutState.cs @@ -44,7 +44,7 @@ namespace Avalonia.Layout Size availableSize, VirtualizingLayoutContext context, double layoutItemWidth, - double LayoutItemHeight, + double layoutItemHeight, UniformGridLayoutItemsStretch stretch, Orientation orientation, double minRowSpacing, @@ -63,7 +63,7 @@ namespace Avalonia.Layout if (realizedElement != null) { realizedElement.Measure(availableSize); - SetSize(realizedElement, layoutItemWidth, LayoutItemHeight, availableSize, stretch, orientation, minRowSpacing, minColumnSpacing, maxItemsPerLine); + SetSize(realizedElement, layoutItemWidth, layoutItemHeight, availableSize, stretch, orientation, minRowSpacing, minColumnSpacing, maxItemsPerLine); _cachedFirstElement = null; } else @@ -78,7 +78,7 @@ namespace Avalonia.Layout _cachedFirstElement.Measure(availableSize); - SetSize(_cachedFirstElement, layoutItemWidth, LayoutItemHeight, availableSize, stretch, orientation, minRowSpacing, minColumnSpacing, maxItemsPerLine); + SetSize(_cachedFirstElement, layoutItemWidth, layoutItemHeight, availableSize, stretch, orientation, minRowSpacing, minColumnSpacing, maxItemsPerLine); // See if we can move ownership to the flow algorithm. If we can, we do not need a local cache. bool added = FlowAlgorithm.TryAddElement0(_cachedFirstElement); @@ -93,7 +93,7 @@ namespace Avalonia.Layout private void SetSize( ILayoutable element, double layoutItemWidth, - double LayoutItemHeight, + double layoutItemHeight, Size availableSize, UniformGridLayoutItemsStretch stretch, Orientation orientation, @@ -107,7 +107,7 @@ namespace Avalonia.Layout } EffectiveItemWidth = (double.IsNaN(layoutItemWidth) ? element.DesiredSize.Width : layoutItemWidth); - EffectiveItemHeight = (double.IsNaN(LayoutItemHeight) ? element.DesiredSize.Height : LayoutItemHeight); + EffectiveItemHeight = (double.IsNaN(layoutItemHeight) ? element.DesiredSize.Height : layoutItemHeight); var availableSizeMinor = orientation == Orientation.Horizontal ? availableSize.Width : availableSize.Height; var minorItemSpacing = orientation == Orientation.Vertical ? minRowSpacing : minColumnSpacing; From 7475354709e597bf21ea0cd6c0145016eefe45ac Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 4 Mar 2021 10:37:41 +0100 Subject: [PATCH 15/15] Add more mono trickery --- .../ExpressionObserverBuilderTests_AttachedProperty.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_AttachedProperty.cs b/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_AttachedProperty.cs index 7c48a975ef..e5e63b24d0 100644 --- a/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_AttachedProperty.cs +++ b/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_AttachedProperty.cs @@ -117,9 +117,11 @@ namespace Avalonia.Markup.UnitTests.Parsers var result = run(); result.Item1.Subscribe(x => { }); - GC.Collect(); + // Mono trickery + GC.Collect(2); GC.WaitForPendingFinalizers(); - GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(2); Assert.Null(result.Item2.Target); }