Skip to content

Commit

Permalink
Merge branch 'master' into feature/ios-voiceover
Browse files Browse the repository at this point in the history
  • Loading branch information
IsaMorphic authored Feb 28, 2025
2 parents 254313c + b815659 commit 836b387
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 35 deletions.
40 changes: 38 additions & 2 deletions src/Avalonia.Controls/Presenters/ItemsPresenter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
using System.Diagnostics;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;

namespace Avalonia.Controls.Presenters
{
Expand Down Expand Up @@ -198,6 +196,44 @@ private void CreateSimplePanelGenerator()
return v.GetRealizedContainers();
return Panel?.Children;
}

internal static bool ControlMatchesTextSearch(Control control, string textSearchTerm)
{
if (control is AvaloniaObject ao && ao.IsSet(TextSearch.TextProperty))
{
var searchText = ao.GetValue(TextSearch.TextProperty);

if (searchText?.StartsWith(textSearchTerm, StringComparison.OrdinalIgnoreCase) == true)
{
return true;
}
}
return control is IContentControl cc &&
cc.Content?.ToString()?.StartsWith(textSearchTerm, StringComparison.OrdinalIgnoreCase) == true;
}

internal int GetIndexFromTextSearch(string textSearch)
{
if (Panel is VirtualizingPanel v)
return v.GetIndexFromTextSearch(textSearch);
return GetIndexFromTextSearch(ItemsControl?.Items, textSearch);
}

internal static int GetIndexFromTextSearch(IReadOnlyList<object?>? items, string textSearchTerm)
{
if (items is null)
return -1;

for (var i = 0; i < items.Count; i++)
{
if (items[i] is Control c && ControlMatchesTextSearch(c, textSearchTerm)
|| items[i]?.ToString()?.StartsWith(textSearchTerm, StringComparison.OrdinalIgnoreCase) == true)
{
return i;
}
}
return -1;
}

internal int IndexFromContainer(Control container)
{
Expand Down
28 changes: 5 additions & 23 deletions src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using System.Linq;
using Avalonia.Controls.Selection;
using Avalonia.Data;
using Avalonia.Data.Core;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Metadata;
Expand Down Expand Up @@ -115,7 +114,7 @@ public class SelectingItemsControl : ItemsControl
/// </summary>
public static readonly StyledProperty<bool> IsTextSearchEnabledProperty =
AvaloniaProperty.Register<SelectingItemsControl, bool>(nameof(IsTextSearchEnabled), false);

/// <summary>
/// Event that should be raised by containers when their selection state changes to notify
/// the parent <see cref="SelectingItemsControl"/> that their selection state has changed.
Expand Down Expand Up @@ -610,29 +609,12 @@ protected override void OnTextInput(TextInputEventArgs e)

_textSearchTerm += e.Text;

bool Match(Control container)
var newIndex = Presenter?.GetIndexFromTextSearch(_textSearchTerm);
if (newIndex >= 0)
{
if (container is AvaloniaObject ao && ao.IsSet(TextSearch.TextProperty))
{
var searchText = ao.GetValue(TextSearch.TextProperty);

if (searchText?.StartsWith(_textSearchTerm, StringComparison.OrdinalIgnoreCase) == true)
{
return true;
}
}

return container is IContentControl control &&
control.Content?.ToString()?.StartsWith(_textSearchTerm, StringComparison.OrdinalIgnoreCase) == true;
SelectedIndex = (int)newIndex;
}

var container = GetRealizedContainers().FirstOrDefault(Match);

if (container != null)
{
SelectedIndex = IndexFromContainer(container);
}


StartTextSearchTimer();

e.Handled = true;
Expand Down
6 changes: 6 additions & 0 deletions src/Avalonia.Controls/VirtualizingPanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Utils;
using Avalonia.Input;
Expand Down Expand Up @@ -192,6 +193,11 @@ protected void RemoveInternalChildRange(int index, int count)
Children.RemoveRange(index, count);
}

internal int GetIndexFromTextSearch(string textSearchTerm)
{
return ItemsPresenter.GetIndexFromTextSearch(Items, textSearchTerm);
}

private protected override void InvalidateMeasureOnChildrenChanged()
{
// Don't invalidate measure when children are added or removed: the panel is responsible
Expand Down
15 changes: 10 additions & 5 deletions src/Avalonia.Controls/Window.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1057,8 +1057,13 @@ protected override Size MeasureOverride(Size availableSize)
{
var sizeToContent = SizeToContent;
var clientSize = ClientSize;
var constraint = clientSize;
var maxAutoSize = PlatformImpl?.MaxAutoSizeHint ?? Size.Infinity;
var useAutoWidth = sizeToContent.HasAllFlags(SizeToContent.Width);
var useAutoHeight = sizeToContent.HasAllFlags(SizeToContent.Height);

var constraint = new Size(
useAutoWidth || double.IsInfinity(availableSize.Width) ? clientSize.Width : availableSize.Width,
useAutoHeight || double.IsInfinity(availableSize.Height) ? clientSize.Height : availableSize.Height);

if (MaxWidth > 0 && MaxWidth < maxAutoSize.Width)
{
Expand All @@ -1069,19 +1074,19 @@ protected override Size MeasureOverride(Size availableSize)
maxAutoSize = maxAutoSize.WithHeight(MaxHeight);
}

if (sizeToContent.HasAllFlags(SizeToContent.Width))
if (useAutoWidth)
{
constraint = constraint.WithWidth(maxAutoSize.Width);
}

if (sizeToContent.HasAllFlags(SizeToContent.Height))
if (useAutoHeight)
{
constraint = constraint.WithHeight(maxAutoSize.Height);
}

var result = base.MeasureOverride(constraint);

if (!sizeToContent.HasAllFlags(SizeToContent.Width))
if (!useAutoWidth)
{
if (!double.IsInfinity(availableSize.Width))
{
Expand All @@ -1093,7 +1098,7 @@ protected override Size MeasureOverride(Size availableSize)
}
}

if (!sizeToContent.HasAllFlags(SizeToContent.Height))
if (!useAutoHeight)
{
if (!double.IsInfinity(availableSize.Height))
{
Expand Down
21 changes: 16 additions & 5 deletions tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,14 @@ private static FuncControlTemplate GetTemplate()
new Popup
{
Name = "PART_Popup",
Child = new ItemsPresenter
Child = new ScrollViewer
{
Name = "PART_ItemsPresenter",
Name = "PART_ScrollViewer",
Content = new ItemsPresenter
{
Name = "PART_ItemsPresenter",
ItemsPanel = new FuncTemplate<Panel>(() => new VirtualizingStackPanel()),
}.RegisterInNameScope(scope)
}.RegisterInNameScope(scope)
}.RegisterInNameScope(scope)
}
Expand Down Expand Up @@ -243,23 +248,30 @@ public void Detaching_Closed_ComboBox_Keeps_Current_Focus()
[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")]
[InlineData(0, 34, "y", "0 item", "1 item", "2 item", "3 item", "4 item", "5 item", "6 item", "7 item", "8 item", "9 item", "A item", "B item", "C item", "D item", "E item", "F item", "G item", "H item", "I item", "J item", "K item", "L item", "M item", "N item", "O item", "P item", "Q item", "R item", "S item", "T item", "U item", "V item", "W item", "X item", "Y item", "Z item")]
public void TextSearch_Should_Have_Expected_SelectedIndex(
int initialSelectedIndex,
int expectedSelectedIndex,
string searchTerm,
params string[] items)
{
using (UnitTestApplication.Start(TestServices.MockThreadingInterface))
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var target = new ComboBox
{
Template = GetTemplate(),
ItemsSource = items.Select(x => new ComboBoxItem { Content = x })
ItemsSource = items.Select(x => new ComboBoxItem { Content = x }),
};

TestRoot root = new(target)
{
ClientSize = new(500,500),
};

target.ApplyTemplate();
target.Presenter.ApplyTemplate();
target.SelectedIndex = initialSelectedIndex;
root.LayoutManager.ExecuteInitialLayoutPass();

var args = new TextInputEventArgs
{
Expand Down Expand Up @@ -293,7 +305,6 @@ public void SelectedItem_Validation()

Assert.True(DataValidationErrors.GetHasErrors(target));
Assert.True(DataValidationErrors.GetErrors(target).SequenceEqual(new[] { exception }));

}

}
Expand Down
14 changes: 14 additions & 0 deletions tests/Avalonia.Controls.UnitTests/WindowTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Avalonia.Threading;
using Avalonia.UnitTests;
using Moq;
using Xunit;
Expand Down Expand Up @@ -686,10 +687,23 @@ public void Child_Should_Be_Measured_With_Width_And_Height_If_SizeToContent_Is_M
Content = child
};

// Verify that the child is initially measured with our Width/Height.
Show(target);

Assert.Equal(1, child.MeasureSizes.Count);
Assert.Equal(new Size(100, 50), child.MeasureSizes[0]);

// Now change the bounds: verify that we are using the new Width/Height, and not the old ClientSize.
child.MeasureSizes.Clear();
child.InvalidateMeasure();

target.Width = 120;
target.Height = 70;

Dispatcher.UIThread.RunJobs();

Assert.Equal(1, child.MeasureSizes.Count);
Assert.Equal(new Size(120, 70), child.MeasureSizes[0]);
}
}

Expand Down

0 comments on commit 836b387

Please sign in to comment.