From dc937485e92e2e2eae79d24bed29a7ffdca372f5 Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 28 Jan 2022 20:52:23 +0100 Subject: [PATCH 1/6] Invert delta if [SHIFT] is pressed --- src/Avalonia.Input/MouseDevice.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs index 34d2038d66..0e1e460427 100644 --- a/src/Avalonia.Input/MouseDevice.cs +++ b/src/Avalonia.Input/MouseDevice.cs @@ -334,6 +334,11 @@ namespace Avalonia.Input var hit = HitTest(root, p); var source = GetSource(hit); + if (inputModifiers == KeyModifiers.Shift) + { + delta = new Vector(delta.Y, delta.X); + } + if (source is not null) { var e = new PointerWheelEventArgs(source, _pointer, root, p, timestamp, props, inputModifiers, delta); From c1e101f491b6153bddfd76920563fe160ef00c9f Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 3 Feb 2022 19:15:46 +0100 Subject: [PATCH 2/6] Perform check if the scroll should be changed on shift key. This may variy by platform, so we need an additional check. --- src/Avalonia.Input/MouseDevice.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs index 0e1e460427..a17f31498e 100644 --- a/src/Avalonia.Input/MouseDevice.cs +++ b/src/Avalonia.Input/MouseDevice.cs @@ -5,6 +5,7 @@ using System.Reactive.Linq; using Avalonia.Input.Raw; using Avalonia.Interactivity; using Avalonia.Platform; +using Avalonia.Utilities; using Avalonia.VisualTree; namespace Avalonia.Input @@ -334,7 +335,9 @@ namespace Avalonia.Input var hit = HitTest(root, p); var source = GetSource(hit); - if (inputModifiers == KeyModifiers.Shift) + // KeyModifiers.Shift should scroll in horizontal direction. This does not work on every platform. + // If Shift-Key is pressed and X is close to 0 we swap the Vector. + if (inputModifiers == KeyModifiers.Shift && MathUtilities.IsZero(delta.X)) { delta = new Vector(delta.Y, delta.X); } From 5de79b1c3d07ee0c19a5b5f66dbf633be528c722 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Sun, 13 Feb 2022 15:31:01 +0100 Subject: [PATCH 3/6] Implement TextSearch attached property --- .../Primitives/SelectingItemsControl.cs | 25 +++++++++++-- .../Primitives/TextSearch.cs | 37 +++++++++++++++++++ 2 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 src/Avalonia.Controls/Primitives/TextSearch.cs diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index b4cfd9404c..6fba33a88c 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -531,11 +531,28 @@ namespace Avalonia.Controls.Primitives _textSearchTerm += e.Text; - bool match(ItemContainerInfo info) => - info.ContainerControl is IContentControl control && - control.Content?.ToString()?.StartsWith(_textSearchTerm, StringComparison.OrdinalIgnoreCase) == true; + bool Match(ItemContainerInfo info) + { + foreach (var child in info.ContainerControl.GetVisualDescendants().OfType()) + { + if (!child.IsSet(TextSearch.TextProperty)) + { + continue; + } + + var searchText = child.GetValue(TextSearch.TextProperty); - var info = ItemContainerGenerator?.Containers.FirstOrDefault(match); + if (searchText?.StartsWith(_textSearchTerm, StringComparison.OrdinalIgnoreCase) == true) + { + return true; + } + } + + return info.ContainerControl is IContentControl control && + control.Content?.ToString()?.StartsWith(_textSearchTerm, StringComparison.OrdinalIgnoreCase) == true; + } + + var info = ItemContainerGenerator?.Containers.FirstOrDefault(Match); if (info != null) { diff --git a/src/Avalonia.Controls/Primitives/TextSearch.cs b/src/Avalonia.Controls/Primitives/TextSearch.cs new file mode 100644 index 0000000000..949532cb16 --- /dev/null +++ b/src/Avalonia.Controls/Primitives/TextSearch.cs @@ -0,0 +1,37 @@ +using Avalonia.Interactivity; + +namespace Avalonia.Controls.Primitives +{ + /// + /// Allows to customize text searching in . + /// + public static class TextSearch + { + /// + /// Defines the Text attached property. + /// This text will be considered during text search in (such as ) + /// + public static readonly AttachedProperty TextProperty + = AvaloniaProperty.RegisterAttached("Text", typeof(TextSearch)); + + /// + /// Sets the for a control. + /// + /// The control + /// The search text to set + public static void SetText(Control control, string text) + { + control.SetValue(TextProperty, text); + } + + /// + /// Gets the of a control. + /// + /// The control + /// The property value + public static string GetText(Control control) + { + return control.GetValue(TextProperty); + } + } +} From 23c38a27ba667c98157faa27ea441a6020391519 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Sun, 13 Feb 2022 19:32:24 +0100 Subject: [PATCH 4/6] Include container itself in resolving search property --- src/Avalonia.Controls/Primitives/SelectingItemsControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 6fba33a88c..badc7eab9d 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -533,7 +533,7 @@ namespace Avalonia.Controls.Primitives bool Match(ItemContainerInfo info) { - foreach (var child in info.ContainerControl.GetVisualDescendants().OfType()) + foreach (var child in info.ContainerControl.GetSelfAndVisualDescendants().OfType()) { if (!child.IsSet(TextSearch.TextProperty)) { From 843676d876cd99816aa4a0dbcd10b7a534fbfd0e Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Sun, 13 Feb 2022 19:32:48 +0100 Subject: [PATCH 5/6] Add UT --- .../Primitives/SelectingItemsControlTests.cs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index 514d3b5475..0e0ca7cd25 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -1,9 +1,11 @@ +using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; +using System.Reactive.Disposables; using Avalonia.Collections; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; @@ -13,7 +15,9 @@ using Avalonia.Data; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Markup.Data; +using Avalonia.Platform; using Avalonia.Styling; +using Avalonia.Threading; using Avalonia.UnitTests; using Moq; using Xunit; @@ -1895,6 +1899,54 @@ namespace Avalonia.Controls.UnitTests.Primitives Assert.Equal(1, carouselRaised); } + [Fact] + public void Setting_IsTextSearchEnabled_Enables_Or_Disables_Text_Search() + { + var pti = Mock.Of(x => x.CurrentThreadIsLoopThread == true); + + Mock.Get(pti) + .Setup(v => v.StartTimer(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Disposable.Empty); + + using (UnitTestApplication.Start(TestServices.StyledWindow.With(threadingInterface: pti))) + { + var items = new[] + { + new Item { [TextSearch.TextProperty] = "Foo" }, + new Item { [TextSearch.TextProperty] = "Bar" } + }; + + var target = new SelectingItemsControl + { + Items = items, + Template = Template(), + IsTextSearchEnabled = false + }; + + Prepare(target); + + target.RaiseEvent(new TextInputEventArgs + { + RoutedEvent = InputElement.TextInputEvent, + Device = KeyboardDevice.Instance, + Text = "Foo" + }); + + Assert.Null(target.SelectedItem); + + target.IsTextSearchEnabled = true; + + target.RaiseEvent(new TextInputEventArgs + { + RoutedEvent = InputElement.TextInputEvent, + Device = KeyboardDevice.Instance, + Text = "Foo" + }); + + Assert.Equal(items[0], target.SelectedItem); + } + } + private static void Prepare(SelectingItemsControl target) { var root = new TestRoot From 9f748827970e0e6c79333c1fc819787a8cba85fd Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Tue, 15 Feb 2022 16:30:38 +0100 Subject: [PATCH 6/6] Don't check child visuals --- .../Primitives/SelectingItemsControl.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index badc7eab9d..c690726e71 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -533,14 +533,9 @@ namespace Avalonia.Controls.Primitives bool Match(ItemContainerInfo info) { - foreach (var child in info.ContainerControl.GetSelfAndVisualDescendants().OfType()) + if (info.ContainerControl.IsSet(TextSearch.TextProperty)) { - if (!child.IsSet(TextSearch.TextProperty)) - { - continue; - } - - var searchText = child.GetValue(TextSearch.TextProperty); + var searchText = info.ContainerControl.GetValue(TextSearch.TextProperty); if (searchText?.StartsWith(_textSearchTerm, StringComparison.OrdinalIgnoreCase) == true) {