diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index b4cfd9404c..c690726e71 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -531,11 +531,23 @@ 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) + { + if (info.ContainerControl.IsSet(TextSearch.TextProperty)) + { + var searchText = info.ContainerControl.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); + } + } +} diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs index 3749947da9..eed51e6c4a 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,6 +335,13 @@ namespace Avalonia.Input var hit = HitTest(root, p); var source = GetSource(hit); + // 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); + } + if (source is not null) { var e = new PointerWheelEventArgs(source, _pointer, root, p, timestamp, props, inputModifiers, delta); 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