diff --git a/.ncrunch/AppWithoutLifetime.v3.ncrunchproject b/.ncrunch/AppWithoutLifetime.v3.ncrunchproject
new file mode 100644
index 0000000000..319cd523ce
--- /dev/null
+++ b/.ncrunch/AppWithoutLifetime.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ True
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Headless.NUnit.netstandard2.0.v3.ncrunchproject b/.ncrunch/Avalonia.Headless.NUnit.netstandard2.0.v3.ncrunchproject
new file mode 100644
index 0000000000..319cd523ce
--- /dev/null
+++ b/.ncrunch/Avalonia.Headless.NUnit.netstandard2.0.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ True
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Headless.XUnit.netstandard2.0.v3.ncrunchproject b/.ncrunch/Avalonia.Headless.XUnit.netstandard2.0.v3.ncrunchproject
new file mode 100644
index 0000000000..319cd523ce
--- /dev/null
+++ b/.ncrunch/Avalonia.Headless.XUnit.netstandard2.0.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ True
+
+
\ No newline at end of file
diff --git a/Avalonia.Desktop.slnf b/Avalonia.Desktop.slnf
index 92c924f107..5e770a0170 100644
--- a/Avalonia.Desktop.slnf
+++ b/Avalonia.Desktop.slnf
@@ -39,14 +39,13 @@
"src\\Markup\\Avalonia.Markup.Xaml\\Avalonia.Markup.Xaml.csproj",
"src\\Markup\\Avalonia.Markup\\Avalonia.Markup.csproj",
"src\\Skia\\Avalonia.Skia\\Avalonia.Skia.csproj",
- "src\\tools\\Avalonia.Generators\\Avalonia.Generators.csproj",
- "src\\tools\\Avalonia.Generators\\Avalonia.Generators.csproj",
- "src\\tools\\DevAnalyzers\\DevAnalyzers.csproj",
- "src\\tools\\DevGenerators\\DevGenerators.csproj",
- "src\\tools\\PublicAnalyzers\\Avalonia.Analyzers.csproj",
"src\\Windows\\Avalonia.Direct2D1\\Avalonia.Direct2D1.csproj",
"src\\Windows\\Avalonia.Win32.Interop\\Avalonia.Win32.Interop.csproj",
"src\\Windows\\Avalonia.Win32\\Avalonia.Win32.csproj",
+ "src\\tools\\Avalonia.Analyzers\\Avalonia.Analyzers.csproj",
+ "src\\tools\\Avalonia.Generators\\Avalonia.Generators.csproj",
+ "src\\tools\\DevAnalyzers\\DevAnalyzers.csproj",
+ "src\\tools\\DevGenerators\\DevGenerators.csproj",
"tests\\Avalonia.Base.UnitTests\\Avalonia.Base.UnitTests.csproj",
"tests\\Avalonia.Benchmarks\\Avalonia.Benchmarks.csproj",
"tests\\Avalonia.Controls.DataGrid.UnitTests\\Avalonia.Controls.DataGrid.UnitTests.csproj",
@@ -66,4 +65,4 @@
"tests\\Avalonia.UnitTests\\Avalonia.UnitTests.csproj"
]
}
-}
+}
\ No newline at end of file
diff --git a/packages/Avalonia/Avalonia.csproj b/packages/Avalonia/Avalonia.csproj
index 412251bc9c..8f1b39ae12 100644
--- a/packages/Avalonia/Avalonia.csproj
+++ b/packages/Avalonia/Avalonia.csproj
@@ -5,7 +5,7 @@
-
+
all
diff --git a/samples/ControlCatalog/Pages/ColorPickerPage.xaml b/samples/ControlCatalog/Pages/ColorPickerPage.xaml
index b759720cf2..25fffabfd2 100644
--- a/samples/ControlCatalog/Pages/ColorPickerPage.xaml
+++ b/samples/ControlCatalog/Pages/ColorPickerPage.xaml
@@ -103,7 +103,8 @@
HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}" />-->
+ HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}"
+ Margin="0,2,0,0" />
diff --git a/samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs b/samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs
index 7fb5bec589..aef82768c8 100644
--- a/samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs
@@ -1,5 +1,4 @@
using System;
-using System.IO;
using System.Linq;
using System.Reflection;
using Avalonia.Controls;
@@ -23,7 +22,7 @@ namespace ControlCatalog.Pages
$"Text was dragged {++textCount} times"), DragDropEffects.Copy | DragDropEffects.Move | DragDropEffects.Link);
SetupDnd("Custom", d => d.Set(CustomFormat, "Test123"), DragDropEffects.Move);
- SetupDnd("Files", d => d.Set(DataFormats.Files, new[] { Assembly.GetEntryAssembly()?.GetModules().FirstOrDefault()?.FullyQualifiedName }), DragDropEffects.Copy);
+ SetupDnd("Files", async d => d.Set(DataFormats.Files, new[] { await (VisualRoot as TopLevel)!.StorageProvider.TryGetFileFromPathAsync(Assembly.GetEntryAssembly()?.GetModules().FirstOrDefault()?.FullyQualifiedName) }), DragDropEffects.Copy);
}
void SetupDnd(string suffix, Action factory, DragDropEffects effects)
@@ -99,7 +98,7 @@ namespace ControlCatalog.Pages
{
if (item is IStorageFile file)
{
- var content = await DialogsPage.ReadTextFromFile(file, 1000);
+ var content = await DialogsPage.ReadTextFromFile(file, 500);
contentStr += $"File {item.Name}:{Environment.NewLine}{content}{Environment.NewLine}{Environment.NewLine}";
}
else if (item is IStorageFolder folder)
diff --git a/samples/ControlCatalog/Pages/TabControlPage.xaml b/samples/ControlCatalog/Pages/TabControlPage.xaml
index 3a2464e9fd..1faa74c1ce 100644
--- a/samples/ControlCatalog/Pages/TabControlPage.xaml
+++ b/samples/ControlCatalog/Pages/TabControlPage.xaml
@@ -4,7 +4,27 @@
xmlns="https://github.com/avaloniaui"
xmlns:viewModels="using:ControlCatalog.ViewModels"
x:DataType="viewModels:TabControlPageViewModel">
-
+
+
+
+
+
+
-
+
-
+
+
+
+
+
+ -->
+
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml
index e05fa5a907..fabc5d0349 100644
--- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml
@@ -8,7 +8,9 @@
-
+
+
+
@@ -21,7 +23,6 @@
Height="{StaticResource ColorPreviewerAccentSectionHeight}"
Width="{StaticResource ColorPreviewerAccentSectionWidth}"
ColumnDefinitions="*,*"
- Margin="0,0,-10,0"
VerticalAlignment="Center">
+ CornerRadius="{TemplateBinding CornerRadius}">
@@ -82,8 +80,7 @@
+ VerticalAlignment="Stretch">
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml
index acd2c7ff15..8793467b36 100644
--- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml
@@ -360,8 +360,8 @@
-
+
+
+
+
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml
index ff4e1d93a8..d9ba8bb9d2 100644
--- a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml
@@ -112,8 +112,8 @@
-
+
@@ -501,6 +501,23 @@
+
+
+
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPreviewer.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPreviewer.xaml
index a39dd91f52..9e123b2a1f 100644
--- a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPreviewer.xaml
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPreviewer.xaml
@@ -8,7 +8,9 @@
-
+
+
+
@@ -21,7 +23,6 @@
Height="{StaticResource ColorPreviewerAccentSectionHeight}"
Width="{StaticResource ColorPreviewerAccentSectionWidth}"
ColumnDefinitions="*,*"
- Margin="0,0,-10,0"
VerticalAlignment="Center">
+ CornerRadius="{TemplateBinding CornerRadius}">
@@ -82,8 +80,7 @@
+ VerticalAlignment="Stretch">
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml
index a26d3179b5..d4f02933f2 100644
--- a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml
@@ -322,8 +322,8 @@
-
+
+
+
+
diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs
index a55a47fa53..bfcd4750e3 100644
--- a/src/Avalonia.Controls.DataGrid/DataGrid.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs
@@ -3958,7 +3958,7 @@ namespace Avalonia.Controls
{
bool focusLeftDataGrid = true;
bool dataGridWillReceiveRoutedEvent = true;
- Visual focusedObject = FocusManager.Instance.Current as Visual;
+ Visual focusedObject = FocusManager.GetFocusManager(this)?.GetFocusedElement() as Visual;
DataGridColumn editingColumn = null;
while (focusedObject != null)
@@ -4865,7 +4865,8 @@ namespace Avalonia.Controls
if (!ctrl)
{
// If Enter was used by a TextBox, we shouldn't handle the key
- if (FocusManager.Instance.Current is TextBox focusedTextBox && focusedTextBox.AcceptsReturn)
+ if (FocusManager.GetFocusManager(this)?.GetFocusedElement() is TextBox focusedTextBox
+ && focusedTextBox.AcceptsReturn)
{
return false;
}
diff --git a/src/Avalonia.Controls.ItemsRepeater/Controls/RepeaterLayoutContext.cs b/src/Avalonia.Controls.ItemsRepeater/Controls/RepeaterLayoutContext.cs
index 49c8b9ff92..d03949c1c2 100644
--- a/src/Avalonia.Controls.ItemsRepeater/Controls/RepeaterLayoutContext.cs
+++ b/src/Avalonia.Controls.ItemsRepeater/Controls/RepeaterLayoutContext.cs
@@ -53,8 +53,8 @@ namespace Avalonia.Controls
{
return _owner.GetElementImpl(
index,
- options.HasAllFlags(ElementRealizationOptions.ForceCreate),
- options.HasAllFlags(ElementRealizationOptions.SuppressAutoRecycle));
+ options.HasFlag(ElementRealizationOptions.ForceCreate),
+ options.HasFlag(ElementRealizationOptions.SuppressAutoRecycle));
}
protected override object GetItemAtCore(int index) => _owner.ItemsSourceView!.GetAt(index)!;
diff --git a/src/Avalonia.Controls.ItemsRepeater/Controls/ViewManager.cs b/src/Avalonia.Controls.ItemsRepeater/Controls/ViewManager.cs
index 6b9d7934bf..674389d77c 100644
--- a/src/Avalonia.Controls.ItemsRepeater/Controls/ViewManager.cs
+++ b/src/Avalonia.Controls.ItemsRepeater/Controls/ViewManager.cs
@@ -695,7 +695,7 @@ namespace Avalonia.Controls
{
Control? focusedElement = null;
- if (FocusManager.Instance?.Current is Visual child)
+ if (TopLevel.GetTopLevel(_owner)?.FocusManager?.GetFocusedElement() is Visual child)
{
var parent = child.GetVisualParent();
var owner = _owner;
diff --git a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs
index 20711eecbc..d0b894101f 100644
--- a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs
+++ b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs
@@ -762,7 +762,7 @@ namespace Avalonia.Controls
/// otherwise, false.
protected bool HasFocus()
{
- Visual? focused = FocusManager.Instance?.Current as Visual;
+ Visual? focused = FocusManager.GetFocusManager(this)?.GetFocusedElement() as Visual;
while (focused != null)
{
diff --git a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs
index d04dfec3e8..c55bd0f3e5 100644
--- a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs
+++ b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs
@@ -151,7 +151,7 @@ namespace Avalonia.Automation.Peers
protected override bool HasKeyboardFocusCore() => Owner.IsFocused;
protected override bool IsContentElementCore() => true;
protected override bool IsControlElementCore() => true;
- protected override bool IsEnabledCore() => Owner.IsEnabled;
+ protected override bool IsEnabledCore() => Owner.IsEffectivelyEnabled;
protected override bool IsKeyboardFocusableCore() => Owner.Focusable;
protected override void SetFocusCore() => Owner.Focus();
diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj
index 48761ca8b8..3f1753a4d3 100644
--- a/src/Avalonia.Controls/Avalonia.Controls.csproj
+++ b/src/Avalonia.Controls/Avalonia.Controls.csproj
@@ -14,6 +14,7 @@
+
@@ -22,5 +23,6 @@
+
diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs
index f48d7a7cc1..d8c31fc477 100644
--- a/src/Avalonia.Controls/Button.cs
+++ b/src/Avalonia.Controls/Button.cs
@@ -34,8 +34,8 @@ namespace Avalonia.Controls
[PseudoClasses(pcFlyoutOpen, pcPressed)]
public class Button : ContentControl, ICommandSource, IClickableControl
{
- protected const string pcPressed = ":pressed";
- protected const string pcFlyoutOpen = ":flyout-open";
+ private const string pcPressed = ":pressed";
+ private const string pcFlyoutOpen = ":flyout-open";
///
/// Defines the property.
diff --git a/src/Avalonia.Controls/Calendar/Calendar.cs b/src/Avalonia.Controls/Calendar/Calendar.cs
index 10aadfa759..6468d0b4e8 100644
--- a/src/Avalonia.Controls/Calendar/Calendar.cs
+++ b/src/Avalonia.Controls/Calendar/Calendar.cs
@@ -1567,7 +1567,7 @@ namespace Avalonia.Controls
base.OnPointerReleased(e);
if (!HasFocusInternal && e.InitialPressMouseButton == MouseButton.Left)
{
- FocusManager.Instance?.Focus(this);
+ Focus();
}
}
diff --git a/src/Avalonia.Controls/Calendar/CalendarButton.cs b/src/Avalonia.Controls/Calendar/CalendarButton.cs
index d8672cbf18..b35298b101 100644
--- a/src/Avalonia.Controls/Calendar/CalendarButton.cs
+++ b/src/Avalonia.Controls/Calendar/CalendarButton.cs
@@ -39,7 +39,7 @@ namespace Avalonia.Controls.Primitives
public CalendarButton()
: base()
{
- Content = DateTimeHelper.GetCurrentDateFormat().AbbreviatedMonthNames[0];
+ SetCurrentValue(ContentProperty, DateTimeHelper.GetCurrentDateFormat().AbbreviatedMonthNames[0]);
}
///
diff --git a/src/Avalonia.Controls/Calendar/CalendarDayButton.cs b/src/Avalonia.Controls/Calendar/CalendarDayButton.cs
index 3d0befdba7..ea3ca8e8fc 100644
--- a/src/Avalonia.Controls/Calendar/CalendarDayButton.cs
+++ b/src/Avalonia.Controls/Calendar/CalendarDayButton.cs
@@ -34,7 +34,7 @@ namespace Avalonia.Controls.Primitives
: base()
{
//Focusable = false;
- Content = DefaultContent.ToString(CultureInfo.CurrentCulture);
+ SetCurrentValue(ContentProperty, DefaultContent.ToString(CultureInfo.CurrentCulture));
}
///
diff --git a/src/Avalonia.Controls/Calendar/CalendarItem.cs b/src/Avalonia.Controls/Calendar/CalendarItem.cs
index 2e3f1f96ce..e81ed8072e 100644
--- a/src/Avalonia.Controls/Calendar/CalendarItem.cs
+++ b/src/Avalonia.Controls/Calendar/CalendarItem.cs
@@ -171,7 +171,7 @@ namespace Avalonia.Controls.Primitives
var childCount = Calendar.RowsPerMonth + Calendar.RowsPerMonth * Calendar.ColumnsPerMonth;
using var children = new PooledList(childCount);
- for (int i = 0; i < Calendar.RowsPerMonth; i++)
+ for (int i = 0; i < Calendar.ColumnsPerMonth; i++)
{
if (DayTitleTemplate?.Build() is Control cell)
{
diff --git a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs
index 90153d3293..679ac4a853 100644
--- a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs
+++ b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs
@@ -26,8 +26,8 @@ namespace Avalonia.Controls
[PseudoClasses(pcFlyoutOpen, pcPressed)]
public partial class CalendarDatePicker : TemplatedControl
{
- protected const string pcPressed = ":pressed";
- protected const string pcFlyoutOpen = ":flyout-open";
+ private const string pcPressed = ":pressed";
+ private const string pcFlyoutOpen = ":flyout-open";
private const string ElementTextBox = "PART_TextBox";
private const string ElementButton = "PART_Button";
diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs
index f41c00662d..926cd5d58e 100644
--- a/src/Avalonia.Controls/ComboBox.cs
+++ b/src/Avalonia.Controls/ComboBox.cs
@@ -21,8 +21,9 @@ namespace Avalonia.Controls
[PseudoClasses(pcDropdownOpen, pcPressed)]
public class ComboBox : SelectingItemsControl
{
- public const string pcDropdownOpen = ":dropdownopen";
- public const string pcPressed = ":pressed";
+ internal const string pcDropdownOpen = ":dropdownopen";
+ internal const string pcPressed = ":pressed";
+
///
/// The default value for the property.
///
@@ -164,7 +165,7 @@ namespace Avalonia.Controls
UpdateSelectionBoxItem(SelectedItem);
}
- public override void InvalidateMirrorTransform()
+ protected internal override void InvalidateMirrorTransform()
{
base.InvalidateMirrorTransform();
UpdateFlowDirection();
@@ -230,8 +231,7 @@ namespace Avalonia.Controls
var firstChild = Presenter?.Panel?.Children.FirstOrDefault(c => CanFocus(c));
if (firstChild != null)
{
- FocusManager.Instance?.Focus(firstChild, NavigationMethod.Directional);
- e.Handled = true;
+ e.Handled = firstChild.Focus(NavigationMethod.Directional);
}
}
}
diff --git a/src/Avalonia.Controls/ContentControl.cs b/src/Avalonia.Controls/ContentControl.cs
index f30920ed2e..867dca2be1 100644
--- a/src/Avalonia.Controls/ContentControl.cs
+++ b/src/Avalonia.Controls/ContentControl.cs
@@ -13,7 +13,7 @@ namespace Avalonia.Controls
///
/// Displays according to an .
///
- [TemplatePart("PART_ContentPresenter", typeof(IContentPresenter))]
+ [TemplatePart("PART_ContentPresenter", typeof(ContentPresenter))]
public class ContentControl : TemplatedControl, IContentControl, IContentPresenterHost
{
///
@@ -68,7 +68,7 @@ namespace Avalonia.Controls
///
/// Gets the presenter from the control's template.
///
- public IContentPresenter? Presenter
+ public ContentPresenter? Presenter
{
get;
private set;
@@ -96,16 +96,16 @@ namespace Avalonia.Controls
IAvaloniaList IContentPresenterHost.LogicalChildren => LogicalChildren;
///
- bool IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter)
+ bool IContentPresenterHost.RegisterContentPresenter(ContentPresenter presenter)
{
return RegisterContentPresenter(presenter);
}
///
- /// Called when an is registered with the control.
+ /// Called when an is registered with the control.
///
/// The presenter.
- protected virtual bool RegisterContentPresenter(IContentPresenter presenter)
+ protected virtual bool RegisterContentPresenter(ContentPresenter presenter)
{
if (presenter.Name == "PART_ContentPresenter")
{
@@ -116,19 +116,14 @@ namespace Avalonia.Controls
return false;
}
- protected virtual void ContentChanged(AvaloniaPropertyChangedEventArgs e)
+ private void ContentChanged(AvaloniaPropertyChangedEventArgs e)
{
- UpdateLogicalTree(e.OldValue, e.NewValue);
- }
-
- protected void UpdateLogicalTree(object? toRemove, object? toAdd)
- {
- if (toRemove is ILogical oldChild)
+ if (e.OldValue is ILogical oldChild)
{
LogicalChildren.Remove(oldChild);
}
- if (toAdd is ILogical newChild)
+ if (e.NewValue is ILogical newChild)
{
LogicalChildren.Add(newChild);
}
diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs
index 97a8c6fe97..a9d8e4c9c7 100644
--- a/src/Avalonia.Controls/ContextMenu.cs
+++ b/src/Avalonia.Controls/ContextMenu.cs
@@ -285,7 +285,7 @@ namespace Avalonia.Controls
}
}
- void ISetterValue.Initialize(ISetter setter)
+ void ISetterValue.Initialize(SetterBase setter)
{
// ContextMenu can be assigned to the ContextMenu property in a setter. This overrides
// the behavior defined in Control which requires controls to be wrapped in a .
@@ -360,7 +360,7 @@ namespace Avalonia.Controls
private void PopupOpened(object? sender, EventArgs e)
{
- _previousFocus = FocusManager.Instance?.Current;
+ _previousFocus = FocusManager.GetFocusManager(this)?.GetFocusedElement();
Focus();
_popupHostChangedHandler?.Invoke(_popup!.Host);
@@ -390,7 +390,7 @@ namespace Avalonia.Controls
}
// HACK: Reset the focus when the popup is closed. We need to fix this so it's automatic.
- FocusManager.Instance?.Focus(_previousFocus);
+ _previousFocus?.Focus();
RaiseEvent(new RoutedEventArgs
{
diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs
index fcd607a707..13e3978ca6 100644
--- a/src/Avalonia.Controls/Control.cs
+++ b/src/Avalonia.Controls/Control.cs
@@ -213,7 +213,7 @@ namespace Avalonia.Controls
bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null;
///
- void ISetterValue.Initialize(ISetter setter)
+ void ISetterValue.Initialize(SetterBase setter)
{
if (setter is Setter s && s.Property == ContextFlyoutProperty)
{
diff --git a/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs b/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs
index 0ae743f30a..fb61ea679c 100644
--- a/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs
+++ b/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs
@@ -323,10 +323,11 @@ namespace Avalonia.Controls
e.Handled = true;
break;
case Key.Tab:
- if (FocusManager.Instance?.Current is IInputElement focus)
+ var focusManager = FocusManager.GetFocusManager(this);
+ if (focusManager?.GetFocusedElement() is { } focus)
{
var nextFocus = KeyboardNavigationHandler.GetNext(focus, NavigationDirection.Next);
- KeyboardDevice.Instance?.SetFocusedElement(nextFocus, NavigationMethod.Tab, KeyModifiers.None);
+ nextFocus?.Focus(NavigationMethod.Tab);
e.Handled = true;
}
break;
@@ -449,15 +450,15 @@ namespace Avalonia.Controls
if (monthCol < dayCol && monthCol < yearCol)
{
- KeyboardDevice.Instance?.SetFocusedElement(_monthSelector, NavigationMethod.Pointer, KeyModifiers.None);
+ _monthSelector?.Focus(NavigationMethod.Pointer);
}
else if (dayCol < monthCol && dayCol < yearCol)
{
- KeyboardDevice.Instance?.SetFocusedElement(_daySelector, NavigationMethod.Pointer, KeyModifiers.None);
+ _monthSelector?.Focus(NavigationMethod.Pointer);
}
else if (yearCol < monthCol && yearCol < dayCol)
{
- KeyboardDevice.Instance?.SetFocusedElement(_yearSelector, NavigationMethod.Pointer, KeyModifiers.None);
+ _yearSelector?.Focus(NavigationMethod.Pointer);
}
}
diff --git a/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs b/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs
index ba06e1b5e6..929ad68c24 100644
--- a/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs
+++ b/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs
@@ -161,10 +161,10 @@ namespace Avalonia.Controls
e.Handled = true;
break;
case Key.Tab:
- if (FocusManager.Instance?.Current is IInputElement focus)
+ if (FocusManager.GetFocusManager(this)?.GetFocusedElement() is { } focus)
{
var nextFocus = KeyboardNavigationHandler.GetNext(focus, NavigationDirection.Next);
- KeyboardDevice.Instance?.SetFocusedElement(nextFocus, NavigationMethod.Tab, KeyModifiers.None);
+ nextFocus?.Focus(NavigationMethod.Tab);
e.Handled = true;
}
break;
@@ -216,7 +216,7 @@ namespace Avalonia.Controls
_periodSelector.SelectedValue = hr >= 12 ? 1 : 0;
SetGrid();
- KeyboardDevice.Instance?.SetFocusedElement(_hourSelector, NavigationMethod.Pointer, KeyModifiers.None);
+ _hourSelector?.Focus(NavigationMethod.Pointer);
}
private void SetGrid()
diff --git a/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs b/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs
index 5b23b5030f..7fd9fad605 100644
--- a/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs
+++ b/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs
@@ -250,14 +250,14 @@ namespace Avalonia.Controls.Primitives
// Try and focus content inside Flyout
if (Popup.Child.Focusable)
{
- FocusManager.Instance?.Focus(Popup.Child);
+ Popup.Child.Focus();
}
else
{
var nextFocus = KeyboardNavigationHandler.GetNext(Popup.Child, NavigationDirection.Next);
if (nextFocus != null)
{
- FocusManager.Instance?.Focus(nextFocus);
+ nextFocus.Focus();
}
}
}
diff --git a/src/Avalonia.Controls/GridSplitter.cs b/src/Avalonia.Controls/GridSplitter.cs
index 4684304725..234c1ff27a 100644
--- a/src/Avalonia.Controls/GridSplitter.cs
+++ b/src/Avalonia.Controls/GridSplitter.cs
@@ -695,7 +695,8 @@ namespace Avalonia.Controls
{
private readonly TranslateTransform _translation;
private readonly Decorator _decorator;
-
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1012", Justification = "Private object")]
public PreviewAdorner(Control? previewControl)
{
// Add a decorator to perform translations.
diff --git a/src/Avalonia.Controls/IContentControl.cs b/src/Avalonia.Controls/IContentControl.cs
index b2828c26e8..1f975d5fa6 100644
--- a/src/Avalonia.Controls/IContentControl.cs
+++ b/src/Avalonia.Controls/IContentControl.cs
@@ -8,8 +8,7 @@ namespace Avalonia.Controls
/// Defines a control that displays according to a
/// .
///
- [NotClientImplementable]
- public interface IContentControl
+ internal interface IContentControl
{
///
/// Gets or sets the content to display.
diff --git a/src/Avalonia.Controls/IHeadered.cs b/src/Avalonia.Controls/IHeadered.cs
index 8c1b5cfb1b..c59ddafe73 100644
--- a/src/Avalonia.Controls/IHeadered.cs
+++ b/src/Avalonia.Controls/IHeadered.cs
@@ -3,7 +3,7 @@ namespace Avalonia.Controls
///
/// Defines a headered object.
///
- public interface IHeadered
+ internal interface IHeadered
{
///
/// Gets or set the header.
diff --git a/src/Avalonia.Controls/IMenu.cs b/src/Avalonia.Controls/IMenu.cs
index ddf2997014..b3ec77b108 100644
--- a/src/Avalonia.Controls/IMenu.cs
+++ b/src/Avalonia.Controls/IMenu.cs
@@ -8,8 +8,7 @@ namespace Avalonia.Controls
///
/// Represents a or .
///
- [NotClientImplementable]
- public interface IMenu : IMenuElement, IInputElement
+ internal interface IMenu : IMenuElement, IInputElement
{
///
/// Gets the menu interaction handler.
diff --git a/src/Avalonia.Controls/IMenuElement.cs b/src/Avalonia.Controls/IMenuElement.cs
index 2a81d20bd9..a474f335f2 100644
--- a/src/Avalonia.Controls/IMenuElement.cs
+++ b/src/Avalonia.Controls/IMenuElement.cs
@@ -8,8 +8,7 @@ namespace Avalonia.Controls
///
/// Represents an or .
///
- [NotClientImplementable]
- public interface IMenuElement : IInputElement, ILogical
+ internal interface IMenuElement : IInputElement, ILogical
{
///
/// Gets or sets the currently selected submenu item.
diff --git a/src/Avalonia.Controls/IMenuItem.cs b/src/Avalonia.Controls/IMenuItem.cs
index 1257d33684..3a9c5373a6 100644
--- a/src/Avalonia.Controls/IMenuItem.cs
+++ b/src/Avalonia.Controls/IMenuItem.cs
@@ -5,8 +5,7 @@ namespace Avalonia.Controls
///
/// Represents a .
///
- [NotClientImplementable]
- public interface IMenuItem : IMenuElement
+ internal interface IMenuItem : IMenuElement
{
///
/// Gets or sets a value that indicates whether the item has a submenu.
diff --git a/src/Avalonia.Controls/INativeMenuExporterEventsImplBridge.cs b/src/Avalonia.Controls/INativeMenuExporterEventsImplBridge.cs
index 29963e4821..6a22676a69 100644
--- a/src/Avalonia.Controls/INativeMenuExporterEventsImplBridge.cs
+++ b/src/Avalonia.Controls/INativeMenuExporterEventsImplBridge.cs
@@ -2,7 +2,7 @@ using Avalonia.Metadata;
namespace Avalonia.Controls
{
- [Unstable]
+ [PrivateApi]
public interface INativeMenuExporterEventsImplBridge
{
void RaiseNeedsUpdate ();
diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs
index 1613bda45b..06427991f8 100644
--- a/src/Avalonia.Controls/ItemsControl.cs
+++ b/src/Avalonia.Controls/ItemsControl.cs
@@ -365,6 +365,30 @@ namespace Avalonia.Controls
///
public IEnumerable GetRealizedContainers() => Presenter?.GetRealizedContainers() ?? Array.Empty();
+ ///
+ /// Returns the that owns the specified container control.
+ ///
+ /// The container.
+ ///
+ /// The owning or null if the control is not an items container.
+ ///
+ public static ItemsControl? ItemsControlFromItemContaner(Control container)
+ {
+ var c = container.Parent as Control;
+
+ while (c is not null)
+ {
+ if (c is ItemsControl itemsControl)
+ {
+ return itemsControl.IndexFromContainer(container) >= 0 ? itemsControl : null;
+ }
+
+ c = c.Parent as Control;
+ }
+
+ return null;
+ }
+
///
/// Creates or a container that can be used to display an item.
///
@@ -566,19 +590,20 @@ namespace Avalonia.Controls
{
if (!e.Handled)
{
- var focus = FocusManager.Instance;
+ var focus = FocusManager.GetFocusManager(this);
var direction = e.Key.ToNavigationDirection();
var container = Presenter?.Panel as INavigableContainer;
- if (container == null ||
- focus?.Current == null ||
+ if (focus == null ||
+ container == null ||
+ focus.GetFocusedElement() == null ||
direction == null ||
direction.Value.IsTab())
{
return;
}
- Visual? current = focus.Current as Visual;
+ Visual? current = focus.GetFocusedElement() as Visual;
while (current != null)
{
@@ -588,7 +613,7 @@ namespace Avalonia.Controls
if (next != null)
{
- focus.Focus(next, NavigationMethod.Directional, e.KeyModifiers);
+ next.Focus(NavigationMethod.Directional, e.KeyModifiers);
e.Handled = true;
}
diff --git a/src/Avalonia.Controls/ListBox.cs b/src/Avalonia.Controls/ListBox.cs
index 2586333f20..f3f0b135b8 100644
--- a/src/Avalonia.Controls/ListBox.cs
+++ b/src/Avalonia.Controls/ListBox.cs
@@ -1,4 +1,8 @@
+using System;
using System.Collections;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Selection;
@@ -47,7 +51,7 @@ namespace Avalonia.Controls
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1010",
Justification = "This property is owned by SelectingItemsControl, but protected there. ListBox changes its visibility.")]
- public static readonly new StyledProperty SelectionModeProperty =
+ public static readonly new StyledProperty SelectionModeProperty =
SelectingItemsControl.SelectionModeProperty;
private IScrollable? _scroll;
@@ -121,41 +125,36 @@ namespace Avalonia.Controls
return NeedsContainer(item, out recycleKey);
}
- ///
- protected override void OnGotFocus(GotFocusEventArgs e)
+ protected override void OnKeyDown(KeyEventArgs e)
{
- base.OnGotFocus(e);
+ var hotkeys = AvaloniaLocator.Current.GetService();
+ var ctrl = hotkeys is not null && e.KeyModifiers.HasAllFlags(hotkeys.CommandModifiers);
- if (e.NavigationMethod == NavigationMethod.Directional)
+ if (!ctrl &&
+ e.Key.ToNavigationDirection() is { } direction &&
+ direction.IsDirectional())
+ {
+ e.Handled |= MoveSelection(
+ direction,
+ WrapSelection,
+ e.KeyModifiers.HasAllFlags(KeyModifiers.Shift));
+ }
+ else if (SelectionMode.HasAllFlags(SelectionMode.Multiple) &&
+ hotkeys is not null && hotkeys.SelectAll.Any(x => x.Matches(e)))
{
- e.Handled = UpdateSelectionFromEventSource(
+ Selection.SelectAll();
+ e.Handled = true;
+ }
+ else if (e.Key == Key.Space || e.Key == Key.Enter)
+ {
+ e.Handled |= UpdateSelectionFromEventSource(
e.Source,
true,
- e.KeyModifiers.HasAllFlags(KeyModifiers.Shift),
- e.KeyModifiers.HasAllFlags(KeyModifiers.Control),
- fromFocus: true);
+ e.KeyModifiers.HasFlag(KeyModifiers.Shift),
+ ctrl);
}
- }
-
- ///
- protected override void OnPointerPressed(PointerPressedEventArgs e)
- {
- base.OnPointerPressed(e);
- if (e.Source is Visual source)
- {
- var point = e.GetCurrentPoint(source);
-
- if (point.Properties.IsLeftButtonPressed || point.Properties.IsRightButtonPressed)
- {
- e.Handled = UpdateSelectionFromEventSource(
- e.Source,
- true,
- e.KeyModifiers.HasAllFlags(KeyModifiers.Shift),
- e.KeyModifiers.HasAllFlags(AvaloniaLocator.Current.GetRequiredService().CommandModifiers),
- point.Properties.IsRightButtonPressed);
- }
- }
+ base.OnKeyDown(e);
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
@@ -163,5 +162,18 @@ namespace Avalonia.Controls
base.OnApplyTemplate(e);
Scroll = e.NameScope.Find("PART_ScrollViewer");
}
+
+ internal bool UpdateSelectionFromPointerEvent(Control source, PointerEventArgs e)
+ {
+ var hotkeys = AvaloniaLocator.Current.GetService();
+ var toggle = hotkeys is not null && e.KeyModifiers.HasAllFlags(hotkeys.CommandModifiers);
+
+ return UpdateSelectionFromEventSource(
+ source,
+ true,
+ e.KeyModifiers.HasAllFlags(KeyModifiers.Shift),
+ toggle,
+ e.GetCurrentPoint(source).Properties.IsRightButtonPressed);
+ }
}
}
diff --git a/src/Avalonia.Controls/ListBoxItem.cs b/src/Avalonia.Controls/ListBoxItem.cs
index 0a6873cd59..c2044661e2 100644
--- a/src/Avalonia.Controls/ListBoxItem.cs
+++ b/src/Avalonia.Controls/ListBoxItem.cs
@@ -2,6 +2,8 @@ using Avalonia.Automation.Peers;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Mixins;
using Avalonia.Controls.Primitives;
+using Avalonia.Input;
+using Avalonia.Platform;
namespace Avalonia.Controls
{
@@ -17,6 +19,9 @@ namespace Avalonia.Controls
public static readonly StyledProperty IsSelectedProperty =
SelectingItemsControl.IsSelectedProperty.AddOwner();
+ private static readonly Point s_invalidPoint = new Point(double.NaN, double.NaN);
+ private Point _pointerDownPoint = s_invalidPoint;
+
///
/// Initializes static members of the class.
///
@@ -40,5 +45,67 @@ namespace Avalonia.Controls
{
return new ListItemAutomationPeer(this);
}
+
+ protected override void OnPointerPressed(PointerPressedEventArgs e)
+ {
+ base.OnPointerPressed(e);
+
+ _pointerDownPoint = s_invalidPoint;
+
+ if (e.Handled)
+ return;
+
+ if (!e.Handled && ItemsControl.ItemsControlFromItemContaner(this) is ListBox owner)
+ {
+ var p = e.GetCurrentPoint(this);
+
+ if (p.Properties.PointerUpdateKind is PointerUpdateKind.LeftButtonPressed or
+ PointerUpdateKind.RightButtonPressed)
+ {
+ if (p.Pointer.Type == PointerType.Mouse)
+ {
+ // If the pressed point comes from a mouse, perform the selection immediately.
+ e.Handled = owner.UpdateSelectionFromPointerEvent(this, e);
+ }
+ else
+ {
+ // Otherwise perform the selection when the pointer is released as to not
+ // interfere with gestures.
+ _pointerDownPoint = p.Position;
+
+ // Ideally we'd set handled here, but that would prevent the scroll gesture
+ // recognizer from working.
+ ////e.Handled = true;
+ }
+ }
+ }
+ }
+
+ protected override void OnPointerReleased(PointerReleasedEventArgs e)
+ {
+ base.OnPointerReleased(e);
+
+ if (!e.Handled &&
+ !double.IsNaN(_pointerDownPoint.X) &&
+ e.InitialPressMouseButton is MouseButton.Left or MouseButton.Right)
+ {
+ var point = e.GetCurrentPoint(this);
+ var settings = AvaloniaLocator.Current.GetService();
+ var tapSize = settings?.GetTapSize(point.Pointer.Type) ?? new Size(4, 4);
+ var tapRect = new Rect(_pointerDownPoint, new Size())
+ .Inflate(new Thickness(tapSize.Width, tapSize.Height));
+
+ if (new Rect(Bounds.Size).ContainsExclusive(point.Position) &&
+ tapRect.ContainsExclusive(point.Position) &&
+ ItemsControl.ItemsControlFromItemContaner(this) is ListBox owner)
+ {
+ if (owner.UpdateSelectionFromPointerEvent(this, e))
+ e.Handled = true;
+ }
+ }
+
+ _pointerDownPoint = s_invalidPoint;
+ }
+
}
}
diff --git a/src/Avalonia.Controls/Menu.cs b/src/Avalonia.Controls/Menu.cs
index 4df423dee8..fe4d42c603 100644
--- a/src/Avalonia.Controls/Menu.cs
+++ b/src/Avalonia.Controls/Menu.cs
@@ -87,7 +87,7 @@ namespace Avalonia.Controls
{
base.OnAttachedToVisualTree(e);
- var inputRoot = e.Root as IInputRoot;
+ var inputRoot = e.Root as TopLevel;
if (inputRoot?.AccessKeyHandler != null)
{
diff --git a/src/Avalonia.Controls/MenuItemAccessKeyHandler.cs b/src/Avalonia.Controls/MenuItemAccessKeyHandler.cs
index 381f1799d4..e1e7779f17 100644
--- a/src/Avalonia.Controls/MenuItemAccessKeyHandler.cs
+++ b/src/Avalonia.Controls/MenuItemAccessKeyHandler.cs
@@ -9,7 +9,7 @@ namespace Avalonia.Controls
///
/// Handles access keys within a
///
- public class MenuItemAccessKeyHandler : IAccessKeyHandler
+ internal class MenuItemAccessKeyHandler : IAccessKeyHandler
{
///
/// The registered access keys.
diff --git a/src/Avalonia.Controls/NativeMenuItemSeparator.cs b/src/Avalonia.Controls/NativeMenuItemSeparator.cs
index f55d714884..55b3fd08de 100644
--- a/src/Avalonia.Controls/NativeMenuItemSeparator.cs
+++ b/src/Avalonia.Controls/NativeMenuItemSeparator.cs
@@ -4,7 +4,7 @@
{
public NativeMenuItemSeparator()
{
- Header = "-";
+ SetCurrentValue(HeaderProperty, "-");
}
}
}
diff --git a/src/Avalonia.Controls/Panel.cs b/src/Avalonia.Controls/Panel.cs
index eff6603727..4705b0e9a5 100644
--- a/src/Avalonia.Controls/Panel.cs
+++ b/src/Avalonia.Controls/Panel.cs
@@ -213,7 +213,7 @@ namespace Avalonia.Controls
}
///
- public bool TryGetTotalCount(out int count)
+ bool IChildIndexProvider.TryGetTotalCount(out int count)
{
count = Children.Count;
return true;
diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
index d2b23a7ac3..79a3038edf 100644
--- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
+++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
@@ -39,91 +39,20 @@ namespace Avalonia.Controls.Platform
DelayRun = delayRun;
}
- public virtual void Attach(IMenu menu)
- {
- if (Menu != null)
- {
- throw new NotSupportedException("DefaultMenuInteractionHandler is already attached.");
- }
-
- Menu = menu;
- Menu.GotFocus += GotFocus;
- Menu.LostFocus += LostFocus;
- Menu.KeyDown += KeyDown;
- Menu.PointerPressed += PointerPressed;
- Menu.PointerReleased += PointerReleased;
- Menu.AddHandler(AccessKeyHandler.AccessKeyPressedEvent, AccessKeyPressed);
- Menu.AddHandler(MenuBase.MenuOpenedEvent, MenuOpened);
- Menu.AddHandler(MenuItem.PointerEnteredItemEvent, PointerEntered);
- Menu.AddHandler(MenuItem.PointerExitedItemEvent, PointerExited);
- Menu.AddHandler(InputElement.PointerMovedEvent, PointerMoved);
-
- _root = Menu.VisualRoot;
-
- if (_root is InputElement inputRoot)
- {
- inputRoot.AddHandler(InputElement.PointerPressedEvent, RootPointerPressed, RoutingStrategies.Tunnel);
- }
-
- if (_root is WindowBase window)
- {
- window.Deactivated += WindowDeactivated;
- }
-
- if (_root is TopLevel tl && tl.PlatformImpl is ITopLevelImpl pimpl)
- pimpl.LostFocus += TopLevelLostPlatformFocus;
-
- _inputManagerSubscription = InputManager?.Process.Subscribe(RawInput);
- }
-
- public virtual void Detach(IMenu menu)
- {
- if (Menu != menu)
- {
- throw new NotSupportedException("DefaultMenuInteractionHandler is not attached to the menu.");
- }
-
- Menu.GotFocus -= GotFocus;
- Menu.LostFocus -= LostFocus;
- Menu.KeyDown -= KeyDown;
- Menu.PointerPressed -= PointerPressed;
- Menu.PointerReleased -= PointerReleased;
- Menu.RemoveHandler(AccessKeyHandler.AccessKeyPressedEvent, AccessKeyPressed);
- Menu.RemoveHandler(MenuBase.MenuOpenedEvent, MenuOpened);
- Menu.RemoveHandler(MenuItem.PointerEnteredItemEvent, PointerEntered);
- Menu.RemoveHandler(MenuItem.PointerExitedItemEvent, PointerExited);
- Menu.RemoveHandler(InputElement.PointerMovedEvent, PointerMoved);
-
- if (_root is InputElement inputRoot)
- {
- inputRoot.RemoveHandler(InputElement.PointerPressedEvent, RootPointerPressed);
- }
-
- if (_root is WindowBase root)
- {
- root.Deactivated -= WindowDeactivated;
- }
-
- if (_root is TopLevel tl && tl.PlatformImpl != null)
- tl.PlatformImpl.LostFocus -= TopLevelLostPlatformFocus;
-
- _inputManagerSubscription?.Dispose();
-
- Menu = null;
- _root = null;
- }
+ public void Attach(MenuBase menu) => AttachCore(menu);
+ public void Detach(MenuBase menu) => DetachCore(menu);
protected Action DelayRun { get; }
protected IInputManager? InputManager { get; }
- protected IMenu? Menu { get; private set; }
+ internal IMenu? Menu { get; private set; }
protected static TimeSpan MenuShowDelay { get; } = TimeSpan.FromMilliseconds(400);
protected internal virtual void GotFocus(object? sender, GotFocusEventArgs e)
{
- var item = GetMenuItem(e.Source as Control);
+ var item = GetMenuItemCore(e.Source as Control);
if (item?.Parent != null)
{
@@ -133,7 +62,7 @@ namespace Avalonia.Controls.Platform
protected internal virtual void LostFocus(object? sender, RoutedEventArgs e)
{
- var item = GetMenuItem(e.Source as Control);
+ var item = GetMenuItemCore(e.Source as Control);
if (item != null)
{
@@ -143,146 +72,12 @@ namespace Avalonia.Controls.Platform
protected internal virtual void KeyDown(object? sender, KeyEventArgs e)
{
- KeyDown(GetMenuItem(e.Source as Control), e);
- }
-
- protected internal virtual void KeyDown(IMenuItem? item, KeyEventArgs e)
- {
- switch (e.Key)
- {
- case Key.Up:
- case Key.Down:
- {
- if (item?.IsTopLevel == true && item.HasSubMenu)
- {
- if (!item.IsSubMenuOpen)
- {
- Open(item, true);
- }
- else
- {
- item.MoveSelection(NavigationDirection.First, true);
- }
-
- e.Handled = true;
- }
- else
- {
- goto default;
- }
- break;
- }
-
- case Key.Left:
- {
- if (item is { IsSubMenuOpen: true, SelectedItem: null })
- {
- item.Close();
- }
- else if (item?.Parent is IMenuItem { IsTopLevel: false, IsSubMenuOpen: true } parent)
- {
- parent.Close();
- parent.Focus();
- e.Handled = true;
- }
- else
- {
- goto default;
- }
- break;
- }
-
- case Key.Right:
- {
- if (item != null && !item.IsTopLevel && item.HasSubMenu)
- {
- Open(item, true);
- e.Handled = true;
- }
- else
- {
- goto default;
- }
- break;
- }
-
- case Key.Enter:
- {
- if (item != null)
- {
- if (!item.HasSubMenu)
- {
- Click(item);
- }
- else
- {
- Open(item, true);
- }
-
- e.Handled = true;
- }
- break;
- }
-
- case Key.Escape:
- {
- if (item?.Parent is IMenuElement parent)
- {
- parent.Close();
- parent.Focus();
- }
- else
- {
- Menu!.Close();
- }
-
- e.Handled = true;
- break;
- }
-
- default:
- {
- var direction = e.Key.ToNavigationDirection();
-
- if (direction?.IsDirectional() == true)
- {
- if (item == null && _isContextMenu)
- {
- if (Menu!.MoveSelection(direction.Value, true) == true)
- {
- e.Handled = true;
- }
- }
- else if (item?.Parent?.MoveSelection(direction.Value, true) == true)
- {
- // If the the parent is an IMenu which successfully moved its selection,
- // and the current menu is open then close the current menu and open the
- // new menu.
- if (item.IsSubMenuOpen &&
- item.Parent is IMenu &&
- item.Parent.SelectedItem is object &&
- item.Parent.SelectedItem != item)
- {
- item.Close();
- Open(item.Parent.SelectedItem, true);
- }
- e.Handled = true;
- }
- }
-
- break;
- }
- }
-
- if (!e.Handled && item?.Parent is IMenuItem parentItem)
- {
- KeyDown(parentItem, e);
- }
+ KeyDown(GetMenuItemCore(e.Source as Control), e);
}
protected internal virtual void AccessKeyPressed(object? sender, RoutedEventArgs e)
{
- var item = GetMenuItem(e.Source as Control);
+ var item = GetMenuItemCore(e.Source as Control);
if (item == null)
{
@@ -303,7 +98,7 @@ namespace Avalonia.Controls.Platform
protected internal virtual void PointerEntered(object? sender, RoutedEventArgs e)
{
- var item = GetMenuItem(e.Source as Control);
+ var item = GetMenuItemCore(e.Source as Control);
if (item?.Parent == null)
{
@@ -349,7 +144,7 @@ namespace Avalonia.Controls.Platform
protected internal virtual void PointerMoved(object? sender, PointerEventArgs e)
{
// HACK: #8179 needs to be addressed to correctly implement it in the PointerPressed method.
- var item = GetMenuItem(e.Source as Control) as MenuItem;
+ var item = GetMenuItemCore(e.Source as Control) as MenuItem;
if (item == null)
return;
@@ -370,7 +165,7 @@ namespace Avalonia.Controls.Platform
protected internal virtual void PointerExited(object? sender, RoutedEventArgs e)
{
- var item = GetMenuItem(e.Source as Control);
+ var item = GetMenuItemCore(e.Source as Control);
if (item?.Parent == null)
{
@@ -405,7 +200,7 @@ namespace Avalonia.Controls.Platform
protected internal virtual void PointerPressed(object? sender, PointerPressedEventArgs e)
{
- var item = GetMenuItem(e.Source as Control);
+ var item = GetMenuItemCore(e.Source as Control);
if (sender is Visual visual &&
e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed && item?.HasSubMenu == true)
@@ -436,7 +231,7 @@ namespace Avalonia.Controls.Platform
protected internal virtual void PointerReleased(object? sender, PointerReleasedEventArgs e)
{
- var item = GetMenuItem(e.Source as Control);
+ var item = GetMenuItemCore(e.Source as Control);
if (e.InitialPressMouseButton == MouseButton.Left && item?.HasSubMenu == false)
{
@@ -478,13 +273,84 @@ namespace Avalonia.Controls.Platform
{
Menu?.Close();
}
-
- private void TopLevelLostPlatformFocus()
+
+ internal static MenuItem? GetMenuItem(StyledElement? item) => (MenuItem?)GetMenuItemCore(item);
+
+ internal void AttachCore(IMenu menu)
{
- Menu?.Close();
+ if (Menu != null)
+ {
+ throw new NotSupportedException("DefaultMenuInteractionHandler is already attached.");
+ }
+
+ Menu = menu;
+ Menu.GotFocus += GotFocus;
+ Menu.LostFocus += LostFocus;
+ Menu.KeyDown += KeyDown;
+ Menu.PointerPressed += PointerPressed;
+ Menu.PointerReleased += PointerReleased;
+ Menu.AddHandler(AccessKeyHandler.AccessKeyPressedEvent, AccessKeyPressed);
+ Menu.AddHandler(MenuBase.MenuOpenedEvent, MenuOpened);
+ Menu.AddHandler(MenuItem.PointerEnteredItemEvent, PointerEntered);
+ Menu.AddHandler(MenuItem.PointerExitedItemEvent, PointerExited);
+ Menu.AddHandler(InputElement.PointerMovedEvent, PointerMoved);
+
+ _root = Menu.VisualRoot;
+
+ if (_root is InputElement inputRoot)
+ {
+ inputRoot.AddHandler(InputElement.PointerPressedEvent, RootPointerPressed, RoutingStrategies.Tunnel);
+ }
+
+ if (_root is WindowBase window)
+ {
+ window.Deactivated += WindowDeactivated;
+ }
+
+ if (_root is TopLevel tl && tl.PlatformImpl is ITopLevelImpl pimpl)
+ pimpl.LostFocus += TopLevelLostPlatformFocus;
+
+ _inputManagerSubscription = InputManager?.Process.Subscribe(RawInput);
}
- protected void Click(IMenuItem item)
+ internal void DetachCore(IMenu menu)
+ {
+ if (Menu != menu)
+ {
+ throw new NotSupportedException("DefaultMenuInteractionHandler is not attached to the menu.");
+ }
+
+ Menu.GotFocus -= GotFocus;
+ Menu.LostFocus -= LostFocus;
+ Menu.KeyDown -= KeyDown;
+ Menu.PointerPressed -= PointerPressed;
+ Menu.PointerReleased -= PointerReleased;
+ Menu.RemoveHandler(AccessKeyHandler.AccessKeyPressedEvent, AccessKeyPressed);
+ Menu.RemoveHandler(MenuBase.MenuOpenedEvent, MenuOpened);
+ Menu.RemoveHandler(MenuItem.PointerEnteredItemEvent, PointerEntered);
+ Menu.RemoveHandler(MenuItem.PointerExitedItemEvent, PointerExited);
+ Menu.RemoveHandler(InputElement.PointerMovedEvent, PointerMoved);
+
+ if (_root is InputElement inputRoot)
+ {
+ inputRoot.RemoveHandler(InputElement.PointerPressedEvent, RootPointerPressed);
+ }
+
+ if (_root is WindowBase root)
+ {
+ root.Deactivated -= WindowDeactivated;
+ }
+
+ if (_root is TopLevel tl && tl.PlatformImpl != null)
+ tl.PlatformImpl.LostFocus -= TopLevelLostPlatformFocus;
+
+ _inputManagerSubscription?.Dispose();
+
+ Menu = null;
+ _root = null;
+ }
+
+ internal void Click(IMenuItem item)
{
item.RaiseClick();
@@ -494,7 +360,7 @@ namespace Avalonia.Controls.Platform
}
}
- protected void CloseMenu(IMenuItem item)
+ internal void CloseMenu(IMenuItem item)
{
var current = (IMenuElement?)item;
@@ -506,7 +372,7 @@ namespace Avalonia.Controls.Platform
current?.Close();
}
- protected void CloseWithDelay(IMenuItem item)
+ internal void CloseWithDelay(IMenuItem item)
{
void Execute()
{
@@ -519,7 +385,141 @@ namespace Avalonia.Controls.Platform
DelayRun(Execute, MenuShowDelay);
}
- protected void Open(IMenuItem item, bool selectFirst)
+ internal void KeyDown(IMenuItem? item, KeyEventArgs e)
+ {
+ switch (e.Key)
+ {
+ case Key.Up:
+ case Key.Down:
+ {
+ if (item?.IsTopLevel == true && item.HasSubMenu)
+ {
+ if (!item.IsSubMenuOpen)
+ {
+ Open(item, true);
+ }
+ else
+ {
+ item.MoveSelection(NavigationDirection.First, true);
+ }
+
+ e.Handled = true;
+ }
+ else
+ {
+ goto default;
+ }
+ break;
+ }
+
+ case Key.Left:
+ {
+ if (item is { IsSubMenuOpen: true, SelectedItem: null })
+ {
+ item.Close();
+ }
+ else if (item?.Parent is IMenuItem { IsTopLevel: false, IsSubMenuOpen: true } parent)
+ {
+ parent.Close();
+ parent.Focus();
+ e.Handled = true;
+ }
+ else
+ {
+ goto default;
+ }
+ break;
+ }
+
+ case Key.Right:
+ {
+ if (item != null && !item.IsTopLevel && item.HasSubMenu)
+ {
+ Open(item, true);
+ e.Handled = true;
+ }
+ else
+ {
+ goto default;
+ }
+ break;
+ }
+
+ case Key.Enter:
+ {
+ if (item != null)
+ {
+ if (!item.HasSubMenu)
+ {
+ Click(item);
+ }
+ else
+ {
+ Open(item, true);
+ }
+
+ e.Handled = true;
+ }
+ break;
+ }
+
+ case Key.Escape:
+ {
+ if (item?.Parent is IMenuElement parent)
+ {
+ parent.Close();
+ parent.Focus();
+ }
+ else
+ {
+ Menu!.Close();
+ }
+
+ e.Handled = true;
+ break;
+ }
+
+ default:
+ {
+ var direction = e.Key.ToNavigationDirection();
+
+ if (direction?.IsDirectional() == true)
+ {
+ if (item == null && _isContextMenu)
+ {
+ if (Menu!.MoveSelection(direction.Value, true) == true)
+ {
+ e.Handled = true;
+ }
+ }
+ else if (item?.Parent?.MoveSelection(direction.Value, true) == true)
+ {
+ // If the the parent is an IMenu which successfully moved its selection,
+ // and the current menu is open then close the current menu and open the
+ // new menu.
+ if (item.IsSubMenuOpen &&
+ item.Parent is IMenu &&
+ item.Parent.SelectedItem is object &&
+ item.Parent.SelectedItem != item)
+ {
+ item.Close();
+ Open(item.Parent.SelectedItem, true);
+ }
+ e.Handled = true;
+ }
+ }
+
+ break;
+ }
+ }
+
+ if (!e.Handled && item?.Parent is IMenuItem parentItem)
+ {
+ KeyDown(parentItem, e);
+ }
+ }
+
+ internal void Open(IMenuItem item, bool selectFirst)
{
item.Open();
@@ -529,7 +529,7 @@ namespace Avalonia.Controls.Platform
}
}
- protected void OpenWithDelay(IMenuItem item)
+ internal void OpenWithDelay(IMenuItem item)
{
void Execute()
{
@@ -542,7 +542,7 @@ namespace Avalonia.Controls.Platform
DelayRun(Execute, MenuShowDelay);
}
- protected void SelectItemAndAncestors(IMenuItem item)
+ internal void SelectItemAndAncestors(IMenuItem item)
{
var current = (IMenuItem?)item;
@@ -553,7 +553,7 @@ namespace Avalonia.Controls.Platform
}
}
- protected static IMenuItem? GetMenuItem(StyledElement? item)
+ internal static IMenuItem? GetMenuItemCore(StyledElement? item)
{
while (true)
{
@@ -565,6 +565,11 @@ namespace Avalonia.Controls.Platform
}
}
+ private void TopLevelLostPlatformFocus()
+ {
+ Menu?.Close();
+ }
+
private static void DefaultDelayRun(Action action, TimeSpan timeSpan)
{
DispatcherTimer.RunOnce(action, timeSpan);
diff --git a/src/Avalonia.Controls/Platform/ExportAvaloniaModuleAttribute.cs b/src/Avalonia.Controls/Platform/ExportAvaloniaModuleAttribute.cs
deleted file mode 100644
index f271abb59a..0000000000
--- a/src/Avalonia.Controls/Platform/ExportAvaloniaModuleAttribute.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-using System;
-
-namespace Avalonia.Platform
-{
- ///
- /// Defines an "Avalonia Module", a 3rd party extension to Avalonia that can be automatically initialized by an AppBuilder instance.
- ///
- ///
- /// Avalonia Modules can either be platform independent (ex default control styles provider) or dependent on a
- /// specific windowing or rendering subsystem being used (ex native rendering speedup, subsystem-specific interop backends).
- /// In the case of a subsystem-specific module, you can specify multiple module implementations, and also a fallback
- /// platform-independent module if you so choose. Additionally, these different implementations can be in different assemblies.
- /// They just need to all share the same module name.
- ///
- /// For example, if I had a module Foo that has a special back-end for Skia and a less performant/less user friendly back-end for
- /// any other rendering subsystem, I would do the following:
- ///
- /// // In assembly FooModuleSkia.dll
- /// [assembly:ExportAvaloniaModule("Foo", typeof(FooModuleSkia), ForRenderingSubsystem="Skia")]
- ///
- /// class FooModuleSkia
- /// {
- /// public FooModuleSkia()
- /// {
- /// InitializeModule();
- /// }
- /// }
- ///
- /// // In assembly FooModuleFallback.dll
- /// [assembly:ExportAvaloniaModule("Foo", typeof(FooModuleFallback))]
- ///
- /// class FooModuleFallback
- /// {
- /// public FooModuleFallback()
- /// {
- /// InitializeModule();
- /// }
- /// }
- ///
- ///
- /// The fallback module will only be initialized if the Skia-specific module is not applicable.
- ///
- [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
- public sealed class ExportAvaloniaModuleAttribute : Attribute
- {
- public ExportAvaloniaModuleAttribute(string name, Type moduleType)
- {
- Name = name;
- ModuleType = moduleType;
- }
-
- public string Name { get; private set; }
- public Type ModuleType { get; private set; }
-
- public string ForWindowingSubsystem { get; set; } = "";
- public string ForRenderingSubsystem { get; set; } = "";
- }
-}
diff --git a/src/Avalonia.Controls/Platform/IMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/IMenuInteractionHandler.cs
index 47b5a048b0..d1d2267f05 100644
--- a/src/Avalonia.Controls/Platform/IMenuInteractionHandler.cs
+++ b/src/Avalonia.Controls/Platform/IMenuInteractionHandler.cs
@@ -12,11 +12,11 @@ namespace Avalonia.Controls.Platform
/// Attaches the interaction handler to a menu.
///
/// The menu.
- void Attach(IMenu menu);
+ void Attach(MenuBase menu);
///
/// Detaches the interaction handler from the attached menu.
///
- void Detach(IMenu menu);
+ void Detach(MenuBase menu);
}
}
diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs
index 736c338c10..7a81454bb0 100644
--- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs
@@ -17,7 +17,7 @@ namespace Avalonia.Controls.Presenters
/// Presents a single item of data inside a template.
///
[PseudoClasses(":empty")]
- public class ContentPresenter : Control, IContentPresenter
+ public class ContentPresenter : Control
{
///
/// Defines the property.
@@ -540,17 +540,6 @@ namespace Avalonia.Controls.Presenters
BoxShadow);
}
- ///
- /// Creates the child control.
- ///
- /// The child control or null.
- protected virtual Control? CreateChild()
- {
- var content = Content;
- var oldChild = Child;
- return CreateChild(content, oldChild, ContentTemplate);
- }
-
private Control? CreateChild(object? content, Control? oldChild, IDataTemplate? template)
{
var newChild = content as Control;
diff --git a/src/Avalonia.Controls/Presenters/IContentPresenter.cs b/src/Avalonia.Controls/Presenters/IContentPresenter.cs
deleted file mode 100644
index 01aec3855f..0000000000
--- a/src/Avalonia.Controls/Presenters/IContentPresenter.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using Avalonia.Controls.Primitives;
-using Avalonia.Metadata;
-
-namespace Avalonia.Controls.Presenters
-{
- ///
- /// Interface for controls that present a single item of data inside a
- /// template.
- ///
- [NotClientImplementable]
- public interface IContentPresenter : IPresenter
- {
- ///
- /// Gets the control displayed by the presenter.
- ///
- Control? Child { get; }
-
- ///
- /// Gets or sets the content to be displayed by the presenter.
- ///
- object? Content { get; set; }
- }
-}
diff --git a/src/Avalonia.Controls/Presenters/IContentPresenterHost.cs b/src/Avalonia.Controls/Presenters/IContentPresenterHost.cs
index 4c1e16ef72..6b1c733f37 100644
--- a/src/Avalonia.Controls/Presenters/IContentPresenterHost.cs
+++ b/src/Avalonia.Controls/Presenters/IContentPresenterHost.cs
@@ -1,7 +1,5 @@
using Avalonia.Collections;
using Avalonia.LogicalTree;
-using Avalonia.Metadata;
-using Avalonia.Styling;
namespace Avalonia.Controls.Presenters
{
@@ -16,8 +14,7 @@ namespace Avalonia.Controls.Presenters
/// parent control's template is instantiated so they register themselves using this
/// interface.
///
- [NotClientImplementable]
- public interface IContentPresenterHost
+ internal interface IContentPresenterHost
{
///
/// Gets a collection describing the logical children of the host control.
@@ -25,13 +22,13 @@ namespace Avalonia.Controls.Presenters
IAvaloniaList LogicalChildren { get; }
///
- /// Registers an with a host control.
+ /// Registers an with a host control.
///
/// The content presenter.
///
/// True if the content presenter should add its child to the logical children of the
/// host; otherwise false.
///
- bool RegisterContentPresenter(IContentPresenter presenter);
+ bool RegisterContentPresenter(ContentPresenter presenter);
}
}
diff --git a/src/Avalonia.Controls/Presenters/IPresenter.cs b/src/Avalonia.Controls/Presenters/IPresenter.cs
deleted file mode 100644
index dc1d40cdcd..0000000000
--- a/src/Avalonia.Controls/Presenters/IPresenter.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using Avalonia.Controls.Primitives;
-using Avalonia.Metadata;
-
-namespace Avalonia.Controls.Presenters
-{
- ///
- /// Interface for presenters such as and
- /// .
- ///
- ///
- /// A presenter is the gateway between a templated control and its content. When
- /// a control which implements is found in the template
- /// of a then that signals that the visual child
- /// of the presenter is not a part of the template.
- ///
- [NotClientImplementable]
- public interface IPresenter : INamed
- {
- }
-}
diff --git a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
index 1704965fd7..1c9dd79b13 100644
--- a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
@@ -13,7 +13,7 @@ namespace Avalonia.Controls.Presenters
///
/// Presents a scrolling view of content inside a .
///
- public class ScrollContentPresenter : ContentPresenter, IPresenter, IScrollable, IScrollAnchorProvider
+ public class ScrollContentPresenter : ContentPresenter, IScrollable, IScrollAnchorProvider
{
private const double EdgeDetectionTolerance = 0.1;
@@ -302,7 +302,7 @@ namespace Avalonia.Controls.Presenters
///
/// This method is automatically called when the control is attached to a visual tree.
///
- protected internal virtual void AttachToScrollViewer()
+ internal void AttachToScrollViewer()
{
var owner = this.FindAncestorOfType();
diff --git a/src/Avalonia.Controls/Primitives/AccessText.cs b/src/Avalonia.Controls/Primitives/AccessText.cs
index ed3412bb45..2e570e25c7 100644
--- a/src/Avalonia.Controls/Primitives/AccessText.cs
+++ b/src/Avalonia.Controls/Primitives/AccessText.cs
@@ -60,7 +60,7 @@ namespace Avalonia.Controls.Primitives
/// Renders the to a drawing context.
///
/// The drawing context.
- protected internal override void RenderCore(DrawingContext context)
+ private protected override void RenderCore(DrawingContext context)
{
base.RenderCore(context);
int underscore = Text?.IndexOf('_') ?? -1;
@@ -86,7 +86,7 @@ namespace Avalonia.Controls.Primitives
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
- _accessKeys = (e.Root as IInputRoot)?.AccessKeyHandler;
+ _accessKeys = (e.Root as TopLevel)?.AccessKeyHandler;
if (_accessKeys != null && AccessKey != 0)
{
diff --git a/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs b/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs
index d44ef5666c..d989550222 100644
--- a/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs
+++ b/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs
@@ -41,7 +41,7 @@ namespace Avalonia.Controls.Primitives
///
/// Gets the header presenter from the control's template.
///
- public IContentPresenter? HeaderPresenter
+ public ContentPresenter? HeaderPresenter
{
get;
private set;
@@ -57,7 +57,7 @@ namespace Avalonia.Controls.Primitives
}
///
- protected override bool RegisterContentPresenter(IContentPresenter presenter)
+ protected override bool RegisterContentPresenter(ContentPresenter presenter)
{
var result = base.RegisterContentPresenter(presenter);
diff --git a/src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs b/src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs
index d71439ece6..2236dc8fe4 100644
--- a/src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs
+++ b/src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs
@@ -56,7 +56,7 @@ namespace Avalonia.Controls.Primitives
///
/// Gets the header presenter from the control's template.
///
- public IContentPresenter? HeaderPresenter
+ public ContentPresenter? HeaderPresenter
{
get;
private set;
@@ -77,16 +77,16 @@ namespace Avalonia.Controls.Primitives
}
///
- bool IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter)
+ bool IContentPresenterHost.RegisterContentPresenter(ContentPresenter presenter)
{
return RegisterContentPresenter(presenter);
}
///
- /// Called when an is registered with the control.
+ /// Called when an is registered with the control.
///
/// The presenter.
- protected virtual bool RegisterContentPresenter(IContentPresenter presenter)
+ protected virtual bool RegisterContentPresenter(ContentPresenter presenter)
{
if (presenter.Name == "PART_HeaderPresenter")
{
diff --git a/src/Avalonia.Controls/Primitives/HeaderedSelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/HeaderedSelectingItemsControl.cs
index 88ca1f1fe1..c0075cb389 100644
--- a/src/Avalonia.Controls/Primitives/HeaderedSelectingItemsControl.cs
+++ b/src/Avalonia.Controls/Primitives/HeaderedSelectingItemsControl.cs
@@ -56,7 +56,7 @@ namespace Avalonia.Controls.Primitives
///
/// Gets the header presenter from the control's template.
///
- public IContentPresenter? HeaderPresenter
+ public ContentPresenter? HeaderPresenter
{
get;
private set;
@@ -66,7 +66,7 @@ namespace Avalonia.Controls.Primitives
IAvaloniaList IContentPresenterHost.LogicalChildren => LogicalChildren;
///
- bool IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter)
+ bool IContentPresenterHost.RegisterContentPresenter(ContentPresenter presenter)
{
return RegisterContentPresenter(presenter);
}
@@ -83,10 +83,10 @@ namespace Avalonia.Controls.Primitives
}
///
- /// Called when an is registered with the control.
+ /// Called when an is registered with the control.
///
/// The presenter.
- protected virtual bool RegisterContentPresenter(IContentPresenter presenter)
+ protected virtual bool RegisterContentPresenter(ContentPresenter presenter)
{
if (presenter.Name == "PART_HeaderPresenter")
{
diff --git a/src/Avalonia.Controls/Primitives/IPopupHost.cs b/src/Avalonia.Controls/Primitives/IPopupHost.cs
index 90fbdcdbec..0aad838b0f 100644
--- a/src/Avalonia.Controls/Primitives/IPopupHost.cs
+++ b/src/Avalonia.Controls/Primitives/IPopupHost.cs
@@ -52,7 +52,7 @@ namespace Avalonia.Controls.Primitives
///
/// Gets the presenter from the control's template.
///
- IContentPresenter? Presenter { get; }
+ ContentPresenter? Presenter { get; }
///
/// Gets or sets whether the popup appears on top of all other windows.
diff --git a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
index 7ed055f2e5..d02d20ea91 100644
--- a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
+++ b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
@@ -29,6 +29,7 @@ namespace Avalonia.Controls.Primitives
}
///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1012", Justification = "Explicit set")]
public void SetChild(Control? control)
{
Content = control;
@@ -51,7 +52,7 @@ namespace Avalonia.Controls.Primitives
}
///
- protected internal override Interactive? InteractiveParent => Parent as Interactive;
+ internal override Interactive? InteractiveParent => Parent as Interactive;
///
public void Dispose() => Hide();
diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs
index 80b7841fc7..7ac219aa83 100644
--- a/src/Avalonia.Controls/Primitives/Popup.cs
+++ b/src/Avalonia.Controls/Primitives/Popup.cs
@@ -727,7 +727,7 @@ namespace Avalonia.Controls.Primitives
Closed?.Invoke(this, EventArgs.Empty);
- var focusCheck = FocusManager.Instance?.Current;
+ var focusCheck = FocusManager.GetFocusManager(this)?.GetFocusedElement();
// Focus is set to null as part of popup closing, so we only want to
// set focus to PlacementTarget if this is the case
@@ -744,7 +744,7 @@ namespace Avalonia.Controls.Primitives
if (e is object)
{
- FocusManager.Instance?.Focus(e);
+ e.Focus();
}
}
else
@@ -752,7 +752,7 @@ namespace Avalonia.Controls.Primitives
var anc = this.FindLogicalAncestorOfType();
if (anc != null)
{
- FocusManager.Instance?.Focus(anc);
+ anc.Focus();
}
}
}
diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs
index a2034a2dbb..aef97bce36 100644
--- a/src/Avalonia.Controls/Primitives/PopupRoot.cs
+++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs
@@ -72,7 +72,7 @@ namespace Avalonia.Controls.Primitives
///
/// Popup events are passed to their parent window. This facilitates this.
///
- protected internal override Interactive? InteractiveParent => (Interactive?)Parent;
+ internal override Interactive? InteractiveParent => (Interactive?)Parent;
///
/// Gets the control that is hosting the popup root.
diff --git a/src/Avalonia.Controls/Primitives/RangeBase.cs b/src/Avalonia.Controls/Primitives/RangeBase.cs
index fd9de47236..ebf7879412 100644
--- a/src/Avalonia.Controls/Primitives/RangeBase.cs
+++ b/src/Avalonia.Controls/Primitives/RangeBase.cs
@@ -1,5 +1,6 @@
using System;
using Avalonia.Data;
+using Avalonia.Interactivity;
using Avalonia.Utilities;
namespace Avalonia.Controls.Primitives
@@ -42,7 +43,23 @@ namespace Avalonia.Controls.Primitives
AvaloniaProperty.Register(nameof(LargeChange), 10);
///
- /// Gets or sets the minimum value.
+ /// Defines the event.
+ ///
+ public static readonly RoutedEvent> ValueChangedEvent =
+ RoutedEvent.Register>(
+ nameof(ValueChanged), RoutingStrategies.Bubble);
+
+ ///
+ /// Occurs when the property changes.
+ ///
+ public event EventHandler>? ValueChanged
+ {
+ add => AddHandler(ValueChangedEvent, value);
+ remove => RemoveHandler(ValueChangedEvent, value);
+ }
+
+ ///
+ /// Gets or sets the minimum possible value.
///
public double Minimum
{
@@ -65,7 +82,7 @@ namespace Avalonia.Controls.Primitives
}
///
- /// Gets or sets the maximum value.
+ /// Gets or sets the maximum possible value.
///
public double Maximum
{
@@ -104,18 +121,25 @@ namespace Avalonia.Controls.Primitives
: sender.GetValue(ValueProperty);
}
+ ///
+ /// Gets or sets the small increment value added or subtracted from the .
+ ///
public double SmallChange
{
get => GetValue(SmallChangeProperty);
set => SetValue(SmallChangeProperty, value);
}
+ ///
+ /// Gets or sets the large increment value added or subtracted from the .
+ ///
public double LargeChange
{
get => GetValue(LargeChangeProperty);
set => SetValue(LargeChangeProperty, value);
}
+ ///
protected override void OnInitialized()
{
base.OnInitialized();
@@ -124,6 +148,7 @@ namespace Avalonia.Controls.Primitives
CoerceValue(ValueProperty);
}
+ ///
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
@@ -136,6 +161,14 @@ namespace Avalonia.Controls.Primitives
{
OnMaximumChanged();
}
+ else if (change.Property == ValueProperty)
+ {
+ var valueChangedEventArgs = new RoutedPropertyChangedEventArgs(
+ change.GetOldValue(),
+ change.GetNewValue(),
+ ValueChangedEvent);
+ RaiseEvent(valueChangedEventArgs);
+ }
}
///
diff --git a/src/Avalonia.Controls/Primitives/ScrollBar.cs b/src/Avalonia.Controls/Primitives/ScrollBar.cs
index 37aa1ebffd..86bea8daa5 100644
--- a/src/Avalonia.Controls/Primitives/ScrollBar.cs
+++ b/src/Avalonia.Controls/Primitives/ScrollBar.cs
@@ -200,7 +200,7 @@ namespace Avalonia.Controls.Primitives
///
/// This method is automatically called when the control is attached to a visual tree.
///
- protected internal virtual void AttachToScrollViewer()
+ internal void AttachToScrollViewer()
{
var owner = this.FindAncestorOfType();
diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
index af82a89517..0e627c2a37 100644
--- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
+++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
@@ -618,38 +618,6 @@ namespace Avalonia.Controls.Primitives
base.OnTextInput(e);
}
- ///
- protected override void OnKeyDown(KeyEventArgs e)
- {
- base.OnKeyDown(e);
-
- if (!e.Handled)
- {
- var keymap = AvaloniaLocator.Current.GetService();
-
- if (keymap is null)
- return;
-
- bool Match(List gestures) => gestures.Any(g => g.Matches(e));
-
- if (ItemCount > 0 &&
- Match(keymap.SelectAll) &&
- SelectionMode.HasAllFlags(SelectionMode.Multiple))
- {
- Selection.SelectAll();
- e.Handled = true;
- }
- else if (e.Key == Key.Space || e.Key == Key.Enter)
- {
- UpdateSelectionFromEventSource(
- e.Source,
- true,
- e.KeyModifiers.HasFlag(KeyModifiers.Shift),
- e.KeyModifiers.HasFlag(KeyModifiers.Control));
- }
- }
- }
-
///
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
@@ -729,11 +697,16 @@ namespace Avalonia.Controls.Primitives
///
/// The direction to move.
/// Whether to wrap when the selection reaches the first or last item.
+ /// Whether the range modifier is enabled (i.e. shift key).
/// True if the selection was moved; otherwise false.
- protected bool MoveSelection(NavigationDirection direction, bool wrap)
+ protected bool MoveSelection(
+ NavigationDirection direction,
+ bool wrap = false,
+ bool rangeModifier = false)
{
- var from = SelectedIndex != -1 ? ContainerFromIndex(SelectedIndex) : null;
- return MoveSelection(from, direction, wrap);
+ var focused = FocusManager.GetFocusManager(this)?.GetFocusedElement();
+ var from = GetContainerFromEventSource(focused) ?? ContainerFromIndex(Selection.AnchorIndex);
+ return MoveSelection(from, direction, wrap, rangeModifier);
}
///
@@ -742,17 +715,37 @@ namespace Avalonia.Controls.Primitives
/// The container which serves as a starting point for the movement.
/// The direction to move.
/// Whether to wrap when the selection reaches the first or last item.
+ /// Whether the range modifier is enabled (i.e. shift key).
/// True if the selection was moved; otherwise false.
- protected bool MoveSelection(Control? from, NavigationDirection direction, bool wrap)
+ protected bool MoveSelection(
+ Control? from,
+ NavigationDirection direction,
+ bool wrap = false,
+ bool rangeModifier = false)
{
- if (Presenter?.Panel is INavigableContainer container &&
- GetNextControl(container, direction, from, wrap) is Control next)
+ if (Presenter?.Panel is not INavigableContainer container)
+ return false;
+
+ if (from is null)
+ {
+ direction = direction switch
+ {
+ NavigationDirection.Down => NavigationDirection.First,
+ NavigationDirection.Up => NavigationDirection.Last,
+ NavigationDirection.Right => NavigationDirection.First,
+ NavigationDirection.Left => NavigationDirection.Last,
+ _ => direction,
+ };
+ }
+
+ if (GetNextControl(container, direction, from, wrap) is Control next)
{
var index = IndexFromContainer(next);
if (index != -1)
{
- SelectedIndex = index;
+ UpdateSelection(index, true, rangeModifier);
+ next.Focus();
return true;
}
}
diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs
index 44d68fcf30..bef02567bc 100644
--- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs
+++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs
@@ -318,7 +318,7 @@ namespace Avalonia.Controls.Primitives
}
///
- protected sealed override void NotifyChildResourcesChanged(ResourcesChangedEventArgs e)
+ internal sealed override void NotifyChildResourcesChanged(ResourcesChangedEventArgs e)
{
var count = VisualChildren.Count;
diff --git a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs
index 35676474dd..bc47462b56 100644
--- a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs
+++ b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs
@@ -104,7 +104,7 @@ namespace Avalonia.Controls.Primitives
}
///
- protected override void NotifyChildResourcesChanged(ResourcesChangedEventArgs e)
+ internal override void NotifyChildResourcesChanged(ResourcesChangedEventArgs e)
{
foreach (var l in _layers)
((ILogical)l).NotifyResourcesChanged(e);
diff --git a/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs b/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs
index ed419ad89b..5c7071a161 100644
--- a/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs
+++ b/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs
@@ -193,7 +193,7 @@ namespace Avalonia.Controls
UpdateContent();
};
- Content = _content;
+ SetCurrentValue(ContentProperty, _content);
}
else
{
diff --git a/src/Avalonia.Controls/ScrollViewer.cs b/src/Avalonia.Controls/ScrollViewer.cs
index a7188c6226..31c4a7e399 100644
--- a/src/Avalonia.Controls/ScrollViewer.cs
+++ b/src/Avalonia.Controls/ScrollViewer.cs
@@ -16,6 +16,12 @@ namespace Avalonia.Controls
[TemplatePart("PART_VerticalScrollBar", typeof(ScrollBar))]
public class ScrollViewer : ContentControl, IScrollable, IScrollAnchorProvider
{
+ ///
+ /// Defines the property.
+ ///
+ public static readonly AttachedProperty BringIntoViewOnFocusChangeProperty =
+ AvaloniaProperty.RegisterAttached(nameof(BringIntoViewOnFocusChange), true);
+
///
/// Defines the property.
///
@@ -174,6 +180,26 @@ namespace Avalonia.Controls
remove => RemoveHandler(ScrollChangedEvent, value);
}
+ ///
+ /// Gets or sets a value that determines whether the uses a
+ /// bring-into-view scroll behavior when an item in the view gets focus.
+ ///
+ ///
+ /// true to use a behavior that brings focused items into view. false to use a behavior
+ /// that focused items do not automatically scroll into view. The default is true.
+ ///
+ ///
+ /// can either be set explicitly on a
+ /// , or a the attached
+ /// ScrollViewer.BringIntoViewOnFocusChange property can be set on an element
+ /// that hosts a .
+ ///
+ public bool BringIntoViewOnFocusChange
+ {
+ get => GetValue(BringIntoViewOnFocusChangeProperty);
+ set => SetValue(BringIntoViewOnFocusChangeProperty, value);
+ }
+
///
/// Gets the extent of the scrollable content.
///
@@ -400,6 +426,26 @@ namespace Avalonia.Controls
///
public void ScrollToEnd() => SetCurrentValue(OffsetProperty, new Vector(double.NegativeInfinity, double.PositiveInfinity));
+ ///
+ /// Gets the value of the attached property.
+ ///
+ /// The control to read the value from.
+ /// The value of the property.
+ public static bool GetBringIntoViewOnFocusChange(Control control)
+ {
+ return control.GetValue(BringIntoViewOnFocusChangeProperty);
+ }
+
+ ///
+ /// Gets the value of the attached property.
+ ///
+ /// The control to set the value on.
+ /// The value of the property.
+ public static void SetBringIntoViewOnFocusChange(Control control, bool value)
+ {
+ control.SetValue(BringIntoViewOnFocusChangeProperty, value);
+ }
+
///
/// Gets the value of the HorizontalScrollBarVisibility attached property.
///
@@ -598,7 +644,7 @@ namespace Avalonia.Controls
(Presenter as IScrollAnchorProvider)?.UnregisterAnchorCandidate(element);
}
- protected override bool RegisterContentPresenter(IContentPresenter presenter)
+ protected override bool RegisterContentPresenter(ContentPresenter presenter)
{
_childSubscription?.Dispose();
_childSubscription = null;
@@ -696,6 +742,14 @@ namespace Avalonia.Controls
}
}
+ protected override void OnGotFocus(GotFocusEventArgs e)
+ {
+ base.OnGotFocus(e);
+
+ if (e.Source != this && e.Source is Control c && BringIntoViewOnFocusChange)
+ c.BringIntoView();
+ }
+
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.Key == Key.PageUp)
diff --git a/src/Avalonia.Controls/Slider.cs b/src/Avalonia.Controls/Slider.cs
index b0dff5be79..25fff93f85 100644
--- a/src/Avalonia.Controls/Slider.cs
+++ b/src/Avalonia.Controls/Slider.cs
@@ -86,10 +86,10 @@ namespace Avalonia.Controls
TickBar.TicksProperty.AddOwner();
// Slider required parts
- protected bool _isDragging;
- protected Track? _track;
- protected Button? _decreaseButton;
- protected Button? _increaseButton;
+ private bool _isDragging;
+ private Track? _track;
+ private Button? _decreaseButton;
+ private Button? _increaseButton;
private IDisposable? _decreaseButtonPressDispose;
private IDisposable? _decreaseButtonReleaseDispose;
private IDisposable? _increaseButtonSubscription;
@@ -181,6 +181,16 @@ namespace Avalonia.Controls
set { SetValue(TickPlacementProperty, value); }
}
+ ///
+ /// Gets a value indicating whether the is currently being dragged.
+ ///
+ protected bool IsDragging => _isDragging;
+
+ ///
+ /// Gets the part of the .
+ ///
+ protected Track? Track => _track;
+
///
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
diff --git a/src/Avalonia.Controls/SplitButton/SplitButton.cs b/src/Avalonia.Controls/SplitButton/SplitButton.cs
index 7f10c632c5..bc46e549eb 100644
--- a/src/Avalonia.Controls/SplitButton/SplitButton.cs
+++ b/src/Avalonia.Controls/SplitButton/SplitButton.cs
@@ -18,9 +18,9 @@ namespace Avalonia.Controls
[PseudoClasses(pcFlyoutOpen, pcPressed)]
public class SplitButton : ContentControl, ICommandSource
{
- protected const string pcChecked = ":checked";
- protected const string pcPressed = ":pressed";
- protected const string pcFlyoutOpen = ":flyout-open";
+ internal const string pcChecked = ":checked";
+ internal const string pcPressed = ":pressed";
+ internal const string pcFlyoutOpen = ":flyout-open";
///
/// Raised when the user presses the primary part of the .
diff --git a/src/Avalonia.Controls/SplitView/SplitView.cs b/src/Avalonia.Controls/SplitView/SplitView.cs
index 3ad656ee3c..fed0367d12 100644
--- a/src/Avalonia.Controls/SplitView/SplitView.cs
+++ b/src/Avalonia.Controls/SplitView/SplitView.cs
@@ -22,15 +22,15 @@ namespace Avalonia.Controls
[PseudoClasses(pcLightDismiss)]
public class SplitView : ContentControl
{
- protected const string pcOpen = ":open";
- protected const string pcClosed = ":closed";
- protected const string pcCompactOverlay = ":compactoverlay";
- protected const string pcCompactInline = ":compactinline";
- protected const string pcOverlay = ":overlay";
- protected const string pcInline = ":inline";
- protected const string pcLeft = ":left";
- protected const string pcRight = ":right";
- protected const string pcLightDismiss = ":lightDismiss";
+ private const string pcOpen = ":open";
+ private const string pcClosed = ":closed";
+ private const string pcCompactOverlay = ":compactoverlay";
+ private const string pcCompactInline = ":compactinline";
+ private const string pcOverlay = ":overlay";
+ private const string pcInline = ":inline";
+ private const string pcLeft = ":left";
+ private const string pcRight = ":right";
+ private const string pcLightDismiss = ":lightDismiss";
///
/// Defines the property
@@ -279,7 +279,7 @@ namespace Avalonia.Controls
remove => RemoveHandler(PaneOpeningEvent, value);
}
- protected override bool RegisterContentPresenter(IContentPresenter presenter)
+ protected override bool RegisterContentPresenter(ContentPresenter presenter)
{
var result = base.RegisterContentPresenter(presenter);
diff --git a/src/Avalonia.Controls/TabControl.cs b/src/Avalonia.Controls/TabControl.cs
index b86624ead8..2b3a00cf3e 100644
--- a/src/Avalonia.Controls/TabControl.cs
+++ b/src/Avalonia.Controls/TabControl.cs
@@ -10,6 +10,7 @@ using Avalonia.LogicalTree;
using Avalonia.VisualTree;
using Avalonia.Automation;
using Avalonia.Controls.Metadata;
+using Avalonia.Reactive;
namespace Avalonia.Controls
{
@@ -19,6 +20,10 @@ namespace Avalonia.Controls
[TemplatePart("PART_ItemsPresenter", typeof(ItemsPresenter))]
public class TabControl : SelectingItemsControl, IContentPresenterHost
{
+ private object? _selectedContent;
+ private IDataTemplate? _selectedContentTemplate;
+ private CompositeDisposable? _selectedItemSubscriptions;
+
///
/// Defines the property.
///
@@ -46,14 +51,14 @@ namespace Avalonia.Controls
///
/// The selected content property
///
- public static readonly StyledProperty
@@ -189,7 +189,6 @@
-
@@ -197,6 +196,7 @@
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
@@ -239,16 +238,17 @@
+
-
-
-
-
+
+
+
+
diff --git a/src/Avalonia.Themes.Fluent/Controls/ScrollViewer.xaml b/src/Avalonia.Themes.Fluent/Controls/ScrollViewer.xaml
index 1d9815713c..6218fe94ca 100644
--- a/src/Avalonia.Themes.Fluent/Controls/ScrollViewer.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/ScrollViewer.xaml
@@ -33,11 +33,12 @@
VerticalSnapPointsType="{TemplateBinding VerticalSnapPointsType}"
HorizontalSnapPointsAlignment="{TemplateBinding HorizontalSnapPointsAlignment}"
VerticalSnapPointsAlignment="{TemplateBinding VerticalSnapPointsAlignment}"
- Padding="{TemplateBinding Padding}">
+ Padding="{TemplateBinding Padding}"
+ ScrollViewer.IsScrollInertiaEnabled="{TemplateBinding IsScrollInertiaEnabled}">
+ IsScrollInertiaEnabled="{Binding (ScrollViewer.IsScrollInertiaEnabled), ElementName=PART_ContentPresenter}"/>
-
+ AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"
+ BringIntoViewOnFocusChange="{TemplateBinding (ScrollViewer.BringIntoViewOnFocusChange)}">
+ AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"
+ BringIntoViewOnFocusChange="{TemplateBinding (ScrollViewer.BringIntoViewOnFocusChange)}">
diff --git a/src/Avalonia.Themes.Simple/Controls/ListBox.xaml b/src/Avalonia.Themes.Simple/Controls/ListBox.xaml
index eaa1f914ca..a51fcbf72e 100644
--- a/src/Avalonia.Themes.Simple/Controls/ListBox.xaml
+++ b/src/Avalonia.Themes.Simple/Controls/ListBox.xaml
@@ -17,6 +17,7 @@
CornerRadius="{TemplateBinding CornerRadius}">
-
+
-
-
+
+
@@ -136,7 +136,6 @@
DockPanel.Dock="Top">
-
@@ -144,19 +143,45 @@
+
-
-
-
-
+
+
-
-
+
+
+
-
-
+
+
+
+
+
-
@@ -177,19 +201,20 @@
+
-
-
-
-
+
diff --git a/src/Avalonia.Themes.Simple/Controls/ScrollViewer.xaml b/src/Avalonia.Themes.Simple/Controls/ScrollViewer.xaml
index 724adbddd8..4f6fc5f322 100644
--- a/src/Avalonia.Themes.Simple/Controls/ScrollViewer.xaml
+++ b/src/Avalonia.Themes.Simple/Controls/ScrollViewer.xaml
@@ -14,11 +14,12 @@
VerticalSnapPointsType="{TemplateBinding VerticalSnapPointsType}"
HorizontalSnapPointsAlignment="{TemplateBinding HorizontalSnapPointsAlignment}"
VerticalSnapPointsAlignment="{TemplateBinding VerticalSnapPointsAlignment}"
- Background="{TemplateBinding Background}">
+ Background="{TemplateBinding Background}"
+ ScrollViewer.IsScrollInertiaEnabled="{TemplateBinding IsScrollInertiaEnabled}">
+ IsScrollInertiaEnabled="{Binding (ScrollViewer.IsScrollInertiaEnabled), ElementName=PART_ContentPresenter}"/>
-
diff --git a/src/Avalonia.Themes.Simple/Controls/TreeView.xaml b/src/Avalonia.Themes.Simple/Controls/TreeView.xaml
index 8630c14aa2..f2f451fe6f 100644
--- a/src/Avalonia.Themes.Simple/Controls/TreeView.xaml
+++ b/src/Avalonia.Themes.Simple/Controls/TreeView.xaml
@@ -15,6 +15,7 @@
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
().First().Type.GetClrType();
// When using self bindings with setters we need to change target type to resolved selector type.
- if (context.GetAvaloniaTypes().ISetter.IsAssignableFrom(selfType))
+ if (context.GetAvaloniaTypes().SetterBase.IsAssignableFrom(selfType))
{
selfType = context.ParentNodes().OfType().First().TargetType.GetClrType();
}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
index c5c3cdd123..d160d4a277 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
@@ -104,7 +104,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType TextDecorationCollection { get; }
public IXamlType TextDecorations { get; }
public IXamlType TextTrimming { get; }
- public IXamlType ISetter { get; }
+ public IXamlType SetterBase { get; }
public IXamlType IStyle { get; }
public IXamlType StyleInclude { get; }
public IXamlType ResourceInclude { get; }
@@ -244,7 +244,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
TextDecorationCollection = cfg.TypeSystem.GetType("Avalonia.Media.TextDecorationCollection");
TextDecorations = cfg.TypeSystem.GetType("Avalonia.Media.TextDecorations");
TextTrimming = cfg.TypeSystem.GetType("Avalonia.Media.TextTrimming");
- ISetter = cfg.TypeSystem.GetType("Avalonia.Styling.ISetter");
+ SetterBase = cfg.TypeSystem.GetType("Avalonia.Styling.SetterBase");
IStyle = cfg.TypeSystem.GetType("Avalonia.Styling.IStyle");
StyleInclude = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Styling.StyleInclude");
ResourceInclude = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Styling.ResourceInclude");
diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
index 05e937fc05..618379757b 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
+++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
@@ -71,6 +71,8 @@
+
+
diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
index 834fa84b53..3d952a7ee9 100644
--- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
@@ -9,7 +9,7 @@ namespace Avalonia.Markup.Xaml
///
public static class AvaloniaXamlLoader
{
- public interface IRuntimeXamlLoader
+ internal interface IRuntimeXamlLoader
{
object Load(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration);
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs
index 49349b047c..73da7a7fe6 100644
--- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs
@@ -173,7 +173,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
public CompiledBindingPath Build() => new CompiledBindingPath(_elements.ToArray(), _rawSource);
}
- public interface ICompiledBindingPathElement
+ internal interface ICompiledBindingPathElement
{
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs
index f8eab5b654..be6b37bb02 100644
--- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs
@@ -154,7 +154,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
var namespaces = _nsInfo.XmlNamespaces;
if (!namespaces.TryGetValue(ns, out var lst))
throw new ArgumentException("Unable to resolve namespace for type " + qualifiedTypeName);
- foreach (var entry in lst)
+ var resolvable = lst.Where(static e => e.ClrAssemblyName is { Length: > 0 });
+ foreach (var entry in resolvable)
{
var asm = Assembly.Load(new AssemblyName(entry.ClrAssemblyName));
var resolved = asm.GetType(entry.ClrNamespace + "." + name);
@@ -164,7 +165,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
throw new ArgumentException(
$"Unable to resolve type {qualifiedTypeName} from any of the following locations: " +
- string.Join(",", lst.Select(e => $"`{e.ClrAssemblyName}:{e.ClrNamespace}.{name}`")));
+ string.Join(",", resolvable.Select(e => $"`clr-namespace:{e.ClrNamespace};assembly={e.ClrAssemblyName}`")))
+ { HelpLink = "https://docs.avaloniaui.net/guides/basics/introduction-to-xaml#valid-xaml-namespaces" };
}
}
diff --git a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
index ec44eeb38f..d041e7d2e6 100644
--- a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
+++ b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
@@ -19,7 +19,13 @@
+
+
+
+
+
+
diff --git a/src/Markup/Avalonia.Markup/Data/BindingBase.cs b/src/Markup/Avalonia.Markup/Data/BindingBase.cs
index 17f44a4b54..1d82725e57 100644
--- a/src/Markup/Avalonia.Markup/Data/BindingBase.cs
+++ b/src/Markup/Avalonia.Markup/Data/BindingBase.cs
@@ -76,7 +76,7 @@ namespace Avalonia.Data
bool enableDataValidation);
///
- [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.TypeConvertionSupressWarningMessage)]
+ [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.TypeConversionSupressWarningMessage)]
public InstancedBinding? Initiate(
AvaloniaObject target,
AvaloniaProperty? targetProperty,
@@ -234,7 +234,7 @@ namespace Avalonia.Data
return result;
}
- protected IObservable GetParentDataContext(AvaloniaObject target)
+ private IObservable GetParentDataContext(AvaloniaObject target)
{
// The DataContext is based on the visual parent and not the logical parent: this may
// seem counter intuitive considering the fact that property inheritance works on the logical
diff --git a/src/Markup/Avalonia.Markup/Markup/Data/DelayedBinding.cs b/src/Markup/Avalonia.Markup/Markup/Data/DelayedBinding.cs
index 551e017a61..6c70a81298 100644
--- a/src/Markup/Avalonia.Markup/Markup/Data/DelayedBinding.cs
+++ b/src/Markup/Avalonia.Markup/Markup/Data/DelayedBinding.cs
@@ -18,7 +18,7 @@ namespace Avalonia.Markup.Data
/// is applied to the property before the properties on the `Binding` object are set. Looking
/// at WPF it uses a similar mechanism for bindings that come from XAML.
///
- public static class DelayedBinding
+ internal static class DelayedBinding
{
private static readonly ConditionalWeakTable> _entries = new();
diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/ArgumentListParser.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/ArgumentListParser.cs
index e858bacb74..c3651594e9 100644
--- a/src/Markup/Avalonia.Markup/Markup/Parsers/ArgumentListParser.cs
+++ b/src/Markup/Avalonia.Markup/Markup/Parsers/ArgumentListParser.cs
@@ -4,10 +4,7 @@ using Avalonia.Utilities;
namespace Avalonia.Markup.Parsers
{
-#if !BUILDTASK
- public
-#endif
- static class ArgumentListParser
+ internal static class ArgumentListParser
{
public static IList ParseArguments(this ref CharacterReader r, char open, char close, char delimiter = ',')
{
diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs
index bf11a02fee..74d2ee3153 100644
--- a/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs
+++ b/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs
@@ -7,10 +7,7 @@ using Avalonia.Utilities;
namespace Avalonia.Markup.Parsers
{
-#if !BUILDTASK
- public
-#endif
- class PropertyPathGrammar
+ internal class PropertyPathGrammar
{
private enum State
{
diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs
index 557045d469..350b07742f 100644
--- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs
+++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs
@@ -11,7 +11,7 @@ namespace Avalonia.Markup.Parsers
///
/// Parses a from text.
///
- public class SelectorParser
+ internal class SelectorParser
{
private readonly Func _typeResolver;
diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
index 56e0877708..fbff4ab4e7 100644
--- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
+++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
@@ -25,7 +25,7 @@ namespace Avalonia.Skia
private readonly Stack _opacityStack = new();
private readonly Matrix? _postTransform;
private double _currentOpacity = 1.0f;
- private readonly bool _canTextUseLcdRendering;
+ private readonly bool _disableSubpixelTextRendering;
private Matrix _currentTransform;
private bool _disposed;
private GRContext? _grContext;
@@ -58,11 +58,11 @@ namespace Avalonia.Skia
/// Dpi of drawings.
///
public Vector Dpi;
-
+
///
- /// Render text without Lcd rendering.
+ /// Render text without subpixel antialiasing.
///
- public bool DisableTextLcdRendering;
+ public bool DisableSubpixelTextRendering;
///
/// GPU-accelerated context (optional)
@@ -134,7 +134,7 @@ namespace Avalonia.Skia
_dpi = createInfo.Dpi;
_disposables = disposables;
- _canTextUseLcdRendering = !createInfo.DisableTextLcdRendering;
+ _disableSubpixelTextRendering = createInfo.DisableSubpixelTextRendering;
_grContext = createInfo.GrContext;
_gpu = createInfo.Gpu;
if (_grContext != null)
@@ -518,7 +518,23 @@ namespace Avalonia.Skia
{
var glyphRunImpl = (GlyphRunImpl)glyphRun;
- var textBlob = glyphRunImpl.GetTextBlob(RenderOptions);
+ var textRenderOptions = RenderOptions;
+
+ if (_disableSubpixelTextRendering)
+ {
+ switch (textRenderOptions.TextRenderingMode)
+ {
+ case TextRenderingMode.Unspecified
+ when textRenderOptions.EdgeMode == EdgeMode.Antialias || textRenderOptions.EdgeMode == EdgeMode.Unspecified:
+ case TextRenderingMode.SubpixelAntialias:
+ {
+ textRenderOptions = textRenderOptions with { TextRenderingMode = TextRenderingMode.Antialias };
+ break;
+ }
+ }
+ }
+
+ var textBlob = glyphRunImpl.GetTextBlob(textRenderOptions);
Canvas.DrawText(textBlob, (float)glyphRun.BaselineOrigin.X,
(float)glyphRun.BaselineOrigin.Y, paintWrapper.Paint);
@@ -975,6 +991,7 @@ namespace Avalonia.Skia
using (var ctx = intermediate.CreateDrawingContext())
{
+ ctx.RenderOptions = RenderOptions;
ctx.Clear(Colors.Transparent);
content.Render(ctx, rect.TopLeft == default ? null : Matrix.CreateTranslation(-rect.X, -rect.Y));
}
@@ -1003,6 +1020,7 @@ namespace Avalonia.Skia
using var pictureTarget = new PictureRenderTarget(_gpu, _grContext, _dpi);
using (var ctx = pictureTarget.CreateDrawingContext(calc.IntermediateSize))
{
+ ctx.RenderOptions = RenderOptions;
ctx.PushClip(calc.IntermediateClip);
content.Render(ctx, transform);
ctx.PopClip();
@@ -1289,7 +1307,7 @@ namespace Avalonia.Skia
Height = pixelSize.Height,
Dpi = _dpi,
Format = format,
- DisableTextLcdRendering = !_canTextUseLcdRendering,
+ DisableTextLcdRendering = isLayer ? _disableSubpixelTextRendering : true,
GrContext = _grContext,
Gpu = _gpu,
Session = _session,
diff --git a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs
index a22b67e09e..64afaa6817 100644
--- a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs
+++ b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs
@@ -3,7 +3,6 @@ using System.Diagnostics.CodeAnalysis;
using Avalonia.Reactive;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Platform;
-using Avalonia.Rendering;
using SkiaSharp;
namespace Avalonia.Skia
@@ -54,8 +53,7 @@ namespace Avalonia.Skia
var createInfo = new DrawingContextImpl.CreateInfo
{
Surface = _framebufferSurface,
- Dpi = framebuffer.Dpi,
- DisableTextLcdRendering = true
+ Dpi = framebuffer.Dpi
};
return new DrawingContextImpl(createInfo, _preFramebufferCopyHandler, canvas, framebuffer);
diff --git a/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs b/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs
index ce292eb826..6db6083ba7 100644
--- a/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs
+++ b/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs
@@ -30,7 +30,6 @@ namespace Avalonia.Skia
GrContext = session.GrContext,
Surface = session.SkSurface,
Dpi = SkiaPlatform.DefaultDpi * session.ScaleFactor,
- DisableTextLcdRendering = true,
Gpu = _skiaGpu,
CurrentSession = session
};
diff --git a/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs b/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs
index b774ddf411..6a726dc9dc 100644
--- a/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs
+++ b/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs
@@ -20,7 +20,7 @@ namespace Avalonia.Skia.Helpers
{
Canvas = canvas,
Dpi = dpi,
- DisableTextLcdRendering = true,
+ DisableSubpixelTextRendering = true,
};
return new DrawingContextImpl(createInfo);
diff --git a/src/Skia/Avalonia.Skia/PictureRenderTarget.cs b/src/Skia/Avalonia.Skia/PictureRenderTarget.cs
index 280b7c27cd..02cc1c0676 100644
--- a/src/Skia/Avalonia.Skia/PictureRenderTarget.cs
+++ b/src/Skia/Avalonia.Skia/PictureRenderTarget.cs
@@ -39,7 +39,7 @@ internal class PictureRenderTarget : IDisposable
{
Canvas = canvas,
Dpi = _dpi,
- DisableTextLcdRendering = true,
+ DisableSubpixelTextRendering = true,
GrContext = _grContext,
Gpu = _gpu,
};
@@ -52,4 +52,4 @@ internal class PictureRenderTarget : IDisposable
}
public void Dispose() => _picture?.Dispose();
-}
\ No newline at end of file
+}
diff --git a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs
index 92210c30e2..c695e8ba41 100644
--- a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs
+++ b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs
@@ -106,7 +106,7 @@ namespace Avalonia.Skia
{
Surface = _surface.Surface,
Dpi = Dpi,
- DisableTextLcdRendering = _disableLcdRendering,
+ DisableSubpixelTextRendering = _disableLcdRendering,
GrContext = _grContext,
Gpu = _gpu,
};
diff --git a/src/Windows/Avalonia.Win32.Interop/WinForms/WinFormsAvaloniaControlHost.cs b/src/Windows/Avalonia.Win32.Interop/WinForms/WinFormsAvaloniaControlHost.cs
index a8060d3fbf..8d73bde919 100644
--- a/src/Windows/Avalonia.Win32.Interop/WinForms/WinFormsAvaloniaControlHost.cs
+++ b/src/Windows/Avalonia.Win32.Interop/WinForms/WinFormsAvaloniaControlHost.cs
@@ -22,7 +22,7 @@ namespace Avalonia.Win32.Embedding
UnmanagedMethods.SetParent(WindowHandle, Handle);
_root.Prepare();
if (_root.IsFocused)
- FocusManager.Instance.Focus(null);
+ _root.FocusManager.ClearFocus();
_root.GotFocus += RootGotFocus;
FixPosition();
diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
index 24fd7e3933..8025779c90 100644
--- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
+++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
@@ -141,13 +141,13 @@ namespace Avalonia.Win32.Interop.Wpf
{
var state = Keyboard.Modifiers;
var rv = default(RawInputModifiers);
- if (state.HasAllFlags(ModifierKeys.Windows))
+ if (state.HasFlag(ModifierKeys.Windows))
rv |= RawInputModifiers.Meta;
- if (state.HasAllFlags(ModifierKeys.Alt))
+ if (state.HasFlag(ModifierKeys.Alt))
rv |= RawInputModifiers.Alt;
- if (state.HasAllFlags(ModifierKeys.Control))
+ if (state.HasFlag(ModifierKeys.Control))
rv |= RawInputModifiers.Control;
- if (state.HasAllFlags(ModifierKeys.Shift))
+ if (state.HasFlag(ModifierKeys.Shift))
rv |= RawInputModifiers.Shift;
if (e != null)
{
diff --git a/src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs b/src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs
index 7e1e22579b..4f9b6c54d3 100644
--- a/src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs
+++ b/src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs
@@ -5,7 +5,7 @@ using Avalonia.Win32.Interop;
namespace Avalonia.Win32.Input
{
- class WindowsKeyboardDevice : KeyboardDevice
+ internal class WindowsKeyboardDevice : KeyboardDevice
{
private readonly byte[] _keyStates = new byte[256];
diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs
index 0d0456dbda..42720cbb4c 100644
--- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs
+++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs
@@ -124,6 +124,19 @@ namespace Avalonia.Base.UnitTests
Assert.Equal(2, target.CoreChanges.Count);
}
+ [Fact]
+ public void CoerceValue_Calls_Coerce_Callback_Only_Once()
+ {
+ var target = new Class1 { Foo = 99 };
+
+ target.MaxFoo = 50;
+
+ target.CoerceFooInvocations.Clear();
+ target.CoerceValue(Class1.FooProperty);
+
+ Assert.Equal(new[] { 99 }, target.CoerceFooInvocations);
+ }
+
[Fact]
public void Coerced_Value_Can_Be_Restored_If_Limit_Changed()
{
@@ -218,6 +231,18 @@ namespace Avalonia.Base.UnitTests
Assert.Equal(1, raised);
}
+ [Fact]
+ public void Default_Value_Is_Coerced_Only_Once()
+ {
+ var target = new Class1();
+
+ target.MinFoo = 20;
+ target.CoerceFooInvocations.Clear();
+ target.CoerceValue(Class1.FooProperty);
+
+ Assert.Equal(new[] { 11 }, target.CoerceFooInvocations);
+ }
+
[Fact]
public void ClearValue_Respects_Coerced_Default_Value()
{
@@ -338,10 +363,12 @@ namespace Avalonia.Base.UnitTests
public int MinFoo { get; set; } = 0;
public int MaxFoo { get; set; } = 100;
+ public List CoerceFooInvocations { get; } = new();
public List CoreChanges { get; } = new();
public static int CoerceFoo(AvaloniaObject instance, int value)
{
+ (instance as Class1)?.CoerceFooInvocations.Add(value);
return instance is Class1 o ?
Math.Clamp(value, o.MinFoo, o.MaxFoo) :
Math.Clamp(value, 0, 100);
diff --git a/tests/Avalonia.Base.UnitTests/Input/InputElement_Focus.cs b/tests/Avalonia.Base.UnitTests/Input/InputElement_Focus.cs
index ac1547d09f..aa2e47de2d 100644
--- a/tests/Avalonia.Base.UnitTests/Input/InputElement_Focus.cs
+++ b/tests/Avalonia.Base.UnitTests/Input/InputElement_Focus.cs
@@ -23,7 +23,7 @@ namespace Avalonia.Base.UnitTests.Input
target.Focus();
- Assert.Same(target, FocusManager.Instance.Current);
+ Assert.Same(target, root.FocusManager.GetFocusedElement());
}
}
@@ -39,14 +39,14 @@ namespace Avalonia.Base.UnitTests.Input
Child = target = new Button() { IsVisible = false}
};
- Assert.Null(FocusManager.Instance.Current);
+ Assert.Null(root.FocusManager.GetFocusedElement());
target.Focus();
Assert.False(target.IsFocused);
Assert.False(target.IsKeyboardFocusWithin);
- Assert.Null(FocusManager.Instance.Current);
+ Assert.Null(root.FocusManager.GetFocusedElement());
}
}
@@ -67,14 +67,14 @@ namespace Avalonia.Base.UnitTests.Input
}
};
- Assert.Null(FocusManager.Instance.Current);
+ Assert.Null(root.FocusManager.GetFocusedElement());
target.Focus();
Assert.False(target.IsFocused);
Assert.False(target.IsKeyboardFocusWithin);
- Assert.Null(FocusManager.Instance.Current);
+ Assert.Null(root.FocusManager.GetFocusedElement());
}
}
@@ -100,11 +100,11 @@ namespace Avalonia.Base.UnitTests.Input
first.Focus();
- Assert.Same(first, FocusManager.Instance.Current);
+ Assert.Same(first, root.FocusManager.GetFocusedElement());
second.Focus();
- Assert.Same(first, FocusManager.Instance.Current);
+ Assert.Same(first, root.FocusManager.GetFocusedElement());
}
}
@@ -120,14 +120,14 @@ namespace Avalonia.Base.UnitTests.Input
Child = target = new Button() { IsEnabled = false }
};
- Assert.Null(FocusManager.Instance.Current);
+ Assert.Null(root.FocusManager.GetFocusedElement());
target.Focus();
Assert.False(target.IsFocused);
Assert.False(target.IsKeyboardFocusWithin);
- Assert.Null(FocusManager.Instance.Current);
+ Assert.Null(root.FocusManager.GetFocusedElement());
}
}
@@ -148,14 +148,14 @@ namespace Avalonia.Base.UnitTests.Input
}
};
- Assert.Null(FocusManager.Instance.Current);
+ Assert.Null(root.FocusManager.GetFocusedElement());
target.Focus();
Assert.False(target.IsFocused);
Assert.False(target.IsKeyboardFocusWithin);
- Assert.Null(FocusManager.Instance.Current);
+ Assert.Null(root.FocusManager.GetFocusedElement());
}
}
@@ -201,7 +201,7 @@ namespace Avalonia.Base.UnitTests.Input
target.Focus();
target.IsVisible = false;
- Assert.Null(FocusManager.Instance.Current);
+ Assert.Null(root.FocusManager.GetFocusedElement());
}
}
@@ -224,7 +224,7 @@ namespace Avalonia.Base.UnitTests.Input
target.Focus();
container.IsVisible = false;
- Assert.Null(FocusManager.Instance.Current);
+ Assert.Null(root.FocusManager.GetFocusedElement());
}
}
@@ -243,7 +243,7 @@ namespace Avalonia.Base.UnitTests.Input
target.Focus();
target.IsEnabled = false;
- Assert.Null(FocusManager.Instance.Current);
+ Assert.Null(root.FocusManager.GetFocusedElement());
}
}
@@ -266,7 +266,7 @@ namespace Avalonia.Base.UnitTests.Input
target.Focus();
container.IsEnabled = false;
- Assert.Null(FocusManager.Instance.Current);
+ Assert.Null(root.FocusManager.GetFocusedElement());
}
}
@@ -285,7 +285,7 @@ namespace Avalonia.Base.UnitTests.Input
target.Focus();
root.Child = null;
- Assert.Null(FocusManager.Instance.Current);
+ Assert.Null(root.FocusManager.GetFocusedElement());
}
}
@@ -312,13 +312,13 @@ namespace Avalonia.Base.UnitTests.Input
target2.ApplyTemplate();
- FocusManager.Instance?.Focus(target1);
+ target1.Focus();
Assert.True(target1.IsFocused);
Assert.True(target1.Classes.Contains(":focus"));
Assert.False(target2.IsFocused);
Assert.False(target2.Classes.Contains(":focus"));
- FocusManager.Instance?.Focus(target2, NavigationMethod.Tab);
+ target2.Focus(NavigationMethod.Tab);
Assert.False(target1.IsFocused);
Assert.False(target1.Classes.Contains(":focus"));
Assert.True(target2.IsFocused);
@@ -348,19 +348,19 @@ namespace Avalonia.Base.UnitTests.Input
target1.ApplyTemplate();
target2.ApplyTemplate();
- FocusManager.Instance?.Focus(target1);
+ target1.Focus();
Assert.True(target1.IsFocused);
Assert.False(target1.Classes.Contains(":focus-visible"));
Assert.False(target2.IsFocused);
Assert.False(target2.Classes.Contains(":focus-visible"));
- FocusManager.Instance?.Focus(target2, NavigationMethod.Tab);
+ target2.Focus(NavigationMethod.Tab);
Assert.False(target1.IsFocused);
Assert.False(target1.Classes.Contains(":focus-visible"));
Assert.True(target2.IsFocused);
Assert.True(target2.Classes.Contains(":focus-visible"));
- FocusManager.Instance?.Focus(target1, NavigationMethod.Directional);
+ target1.Focus(NavigationMethod.Directional);
Assert.True(target1.IsFocused);
Assert.True(target1.Classes.Contains(":focus-visible"));
Assert.False(target2.IsFocused);
@@ -390,7 +390,7 @@ namespace Avalonia.Base.UnitTests.Input
target1.ApplyTemplate();
target2.ApplyTemplate();
- FocusManager.Instance?.Focus(target1);
+ target1.Focus();
Assert.True(target1.IsFocused);
Assert.True(target1.Classes.Contains(":focus-within"));
Assert.True(target1.IsKeyboardFocusWithin);
@@ -425,7 +425,7 @@ namespace Avalonia.Base.UnitTests.Input
target1.ApplyTemplate();
target2.ApplyTemplate();
- FocusManager.Instance?.Focus(target1);
+ target1.Focus();
Assert.True(target1.IsFocused);
Assert.True(target1.Classes.Contains(":focus-within"));
Assert.True(target1.IsKeyboardFocusWithin);
@@ -436,7 +436,7 @@ namespace Avalonia.Base.UnitTests.Input
Assert.True(root.Classes.Contains(":focus-within"));
Assert.True(root.IsKeyboardFocusWithin);
- FocusManager.Instance?.Focus(target2);
+ target2.Focus();
Assert.False(target1.IsFocused);
Assert.False(target1.Classes.Contains(":focus-within"));
@@ -478,7 +478,7 @@ namespace Avalonia.Base.UnitTests.Input
target1.ApplyTemplate();
target2.ApplyTemplate();
- FocusManager.Instance?.Focus(target1);
+ target1.Focus();
Assert.True(target1.IsFocused);
Assert.True(target1.Classes.Contains(":focus-within"));
Assert.True(target1.IsKeyboardFocusWithin);
@@ -534,7 +534,7 @@ namespace Avalonia.Base.UnitTests.Input
target1.ApplyTemplate();
target2.ApplyTemplate();
- FocusManager.Instance?.Focus(target1);
+ target1.Focus();
Assert.True(target1.IsFocused);
Assert.True(target1.Classes.Contains(":focus-within"));
Assert.True(target1.IsKeyboardFocusWithin);
@@ -545,7 +545,7 @@ namespace Avalonia.Base.UnitTests.Input
Assert.Equal(KeyboardDevice.Instance.FocusedElement, target1);
- FocusManager.Instance?.Focus(target2);
+ target2.Focus();
Assert.False(target1.IsFocused);
Assert.False(target1.Classes.Contains(":focus-within"));
@@ -578,9 +578,9 @@ namespace Avalonia.Base.UnitTests.Input
};
target.Focus();
- FocusManager.Instance.Focus(null);
+ root.FocusManager.ClearFocus();
- Assert.Null(FocusManager.Instance.Current);
+ Assert.Null(root.FocusManager.GetFocusedElement());
}
}
}
diff --git a/tests/Avalonia.Base.UnitTests/Input/KeyboardNavigationTests_Tab.cs b/tests/Avalonia.Base.UnitTests/Input/KeyboardNavigationTests_Tab.cs
index 34a9947d28..0b3d1a275b 100644
--- a/tests/Avalonia.Base.UnitTests/Input/KeyboardNavigationTests_Tab.cs
+++ b/tests/Avalonia.Base.UnitTests/Input/KeyboardNavigationTests_Tab.cs
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Input;
+using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Base.UnitTests.Input
@@ -1253,5 +1254,24 @@ namespace Avalonia.Base.UnitTests.Input
Assert.Same(expected, result);
}
+
+ [Fact]
+ public void Focuses_First_Child_From_No_Focus()
+ {
+ using var app = UnitTestApplication.Start(TestServices.RealFocus);
+ var button = new Button();
+ var root = new TestRoot(button);
+ var target = new KeyboardNavigationHandler();
+
+ target.SetOwner(root);
+
+ root.RaiseEvent(new KeyEventArgs
+ {
+ RoutedEvent = InputElement.KeyDownEvent,
+ Key = Key.Tab,
+ });
+
+ Assert.True(button.IsFocused);
+ }
}
}
diff --git a/tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs b/tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs
index 629188800a..ae6b601896 100644
--- a/tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs
@@ -20,7 +20,9 @@ namespace Avalonia.Base.UnitTests.Input
[Fact]
public void Close_Should_Remove_PointerOver()
{
- using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager()));
+ using var app = UnitTestApplication.Start(new TestServices(
+ inputManager: new InputManager(),
+ focusManager: new FocusManager()));
var renderer = RendererMocks.CreateRenderer();
var device = CreatePointerDeviceMock().Object;
diff --git a/tests/Avalonia.Base.UnitTests/Media/ColorTests.cs b/tests/Avalonia.Base.UnitTests/Media/ColorTests.cs
index 36929d5e95..1ed3ea50b9 100644
--- a/tests/Avalonia.Base.UnitTests/Media/ColorTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Media/ColorTests.cs
@@ -335,5 +335,34 @@ namespace Avalonia.Base.UnitTests.Media
Assert.True(dataPoint.Item2 == parsedColor);
}
}
+
+ [Fact]
+ public void Hsv_To_From_Hsl_Conversion()
+ {
+ // Note that conversion of values more representative of actual colors is not done due to rounding error
+ // It would be necessary to introduce a different equality comparison that accounts for rounding differences in values
+ // This is a result of the math in the conversion itself
+ // RGB doesn't have this problem because it uses whole numbers
+ var data = new Tuple[]
+ {
+ Tuple.Create(new HsvColor(1.0, 0.0, 0.0, 0.0), new HslColor(1.0, 0.0, 0.0, 0.0)),
+ Tuple.Create(new HsvColor(1.0, 359.0, 1.0, 1.0), new HslColor(1.0, 359.0, 1.0, 0.5)),
+
+ Tuple.Create(new HsvColor(1.0, 128.0, 0.0, 0.0), new HslColor(1.0, 128.0, 0.0, 0.0)),
+ Tuple.Create(new HsvColor(1.0, 128.0, 0.0, 1.0), new HslColor(1.0, 128.0, 0.0, 1.0)),
+ Tuple.Create(new HsvColor(1.0, 128.0, 1.0, 1.0), new HslColor(1.0, 128.0, 1.0, 0.5)),
+
+ Tuple.Create(new HsvColor(0.23, 0.5, 1.0, 1.0), new HslColor(0.23, 0.5, 1.0, 0.5)),
+ };
+
+ foreach (var dataPoint in data)
+ {
+ var convertedHsl = dataPoint.Item1.ToHsl();
+ var convertedHsv = dataPoint.Item2.ToHsv();
+
+ Assert.Equal(convertedHsv, dataPoint.Item1);
+ Assert.Equal(convertedHsl, dataPoint.Item2);
+ }
+ }
}
}
diff --git a/tests/Avalonia.Benchmarks/Styling/SelectorBenchmark.cs b/tests/Avalonia.Benchmarks/Styling/SelectorBenchmark.cs
index 11bc5ce35f..e4799d46b8 100644
--- a/tests/Avalonia.Benchmarks/Styling/SelectorBenchmark.cs
+++ b/tests/Avalonia.Benchmarks/Styling/SelectorBenchmark.cs
@@ -37,62 +37,62 @@ namespace Avalonia.Benchmarks.Styling
}
[Benchmark]
- public SelectorMatch IsSelector_NoMatch()
+ public void IsSelector_NoMatch()
{
- return _isCalendarSelector.Match(_notMatchingControl);
+ _isCalendarSelector.Match(_notMatchingControl);
}
[Benchmark]
- public SelectorMatch IsSelector_Match()
+ public void IsSelector_Match()
{
- return _isCalendarSelector.Match(_matchingControl);
+ _isCalendarSelector.Match(_matchingControl);
}
[Benchmark]
- public SelectorMatch ClassSelector_NoMatch()
+ public void ClassSelector_NoMatch()
{
- return _classSelector.Match(_notMatchingControl);
+ _classSelector.Match(_notMatchingControl);
}
[Benchmark]
- public SelectorMatch ClassSelector_Match()
+ public void ClassSelector_Match()
{
- return _classSelector.Match(_matchingControl);
+ _classSelector.Match(_matchingControl);
}
[Benchmark]
- public SelectorMatch OrSelector_One_Match()
+ public void OrSelector_One_Match()
{
- return _orSelectorTwo.Match(_matchingControl);
+ _orSelectorTwo.Match(_matchingControl);
}
[Benchmark]
- public SelectorMatch OrSelector_Five_Match()
+ public void OrSelector_Five_Match()
{
- return _orSelectorFive.Match(_matchingControl);
+ _orSelectorFive.Match(_matchingControl);
}
}
internal class AlwaysMatchSelector : Selector
{
- public override bool InTemplate => false;
+ internal override bool InTemplate => false;
- public override bool IsCombinator => false;
+ internal override bool IsCombinator => false;
- public override Type TargetType => null;
+ internal override Type TargetType => null;
public override string ToString(Style owner)
{
return "Always";
}
- protected override SelectorMatch Evaluate(StyledElement control, IStyle parent, bool subscribe)
+ private protected override SelectorMatch Evaluate(StyledElement control, IStyle parent, bool subscribe)
{
return SelectorMatch.AlwaysThisType;
}
- protected override Selector MovePrevious() => null;
+ private protected override Selector MovePrevious() => null;
- protected override Selector MovePreviousOrParent() => null;
+ private protected override Selector MovePreviousOrParent() => null;
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs b/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs
index d5e4693666..02b6592097 100644
--- a/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs
@@ -24,9 +24,9 @@ namespace Avalonia.Controls.UnitTests
target.Content = "Foo";
target.Template = GetTemplate();
target.ApplyTemplate();
- ((ContentPresenter)target.Presenter).UpdateChild();
+ target.Presenter.UpdateChild();
- var child = ((Visual)target).VisualChildren.Single();
+ var child = target.VisualChildren.Single();
Assert.IsType(child);
child = child.VisualChildren.Single();
Assert.IsType(child);
@@ -55,7 +55,7 @@ namespace Avalonia.Controls.UnitTests
root.Child = target;
target.ApplyTemplate();
- ((Control)target.Presenter).ApplyTemplate();
+ target.Presenter.ApplyTemplate();
foreach (Control child in target.GetTemplateChildren())
Assert.Equal("foo", child.Tag);
@@ -70,7 +70,7 @@ namespace Avalonia.Controls.UnitTests
target.Template = GetTemplate();
target.Content = child;
target.ApplyTemplate();
- ((ContentPresenter)target.Presenter).UpdateChild();
+ target.Presenter.UpdateChild();
var contentPresenter = child.GetVisualParent();
Assert.Equal(target, contentPresenter.TemplatedParent);
@@ -85,7 +85,7 @@ namespace Avalonia.Controls.UnitTests
target.Template = GetTemplate();
target.Content = child;
target.ApplyTemplate();
- ((ContentPresenter)target.Presenter).UpdateChild();
+ target.Presenter.UpdateChild();
Assert.Null(child.TemplatedParent);
}
@@ -117,7 +117,7 @@ namespace Avalonia.Controls.UnitTests
var child = new Control();
target.Content = child;
target.ApplyTemplate();
- ((ContentPresenter)target.Presenter).UpdateChild();
+ target.Presenter.UpdateChild();
Assert.Equal(child.Parent, target);
Assert.Equal(child.GetLogicalParent(), target);
@@ -135,7 +135,7 @@ namespace Avalonia.Controls.UnitTests
target.Content = "Foo";
target.ApplyTemplate();
- ((ContentPresenter)target.Presenter).UpdateChild();
+ target.Presenter.UpdateChild();
var child = target.Presenter.Child;
@@ -152,7 +152,7 @@ namespace Avalonia.Controls.UnitTests
target.Content = "Foo";
target.ApplyTemplate();
- ((ContentPresenter)target.Presenter).UpdateChild();
+ target.Presenter.UpdateChild();
var child = target.Presenter.Child;
@@ -192,7 +192,7 @@ namespace Avalonia.Controls.UnitTests
target.Template = GetTemplate();
target.Content = child;
target.ApplyTemplate();
- ((ContentPresenter)target.Presenter).UpdateChild();
+ target.Presenter.UpdateChild();
Assert.True(called);
}
@@ -207,12 +207,12 @@ namespace Avalonia.Controls.UnitTests
target.Template = GetTemplate();
target.Content = child;
target.ApplyTemplate();
- ((ContentPresenter)target.Presenter).UpdateChild();
+ target.Presenter.UpdateChild();
((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => called = true;
target.Content = null;
- ((ContentPresenter)target.Presenter).UpdateChild();
+ target.Presenter.UpdateChild();
Assert.True(called);
}
@@ -228,12 +228,12 @@ namespace Avalonia.Controls.UnitTests
target.Template = GetTemplate();
target.Content = child1;
target.ApplyTemplate();
- ((ContentPresenter)target.Presenter).UpdateChild();
+ target.Presenter.UpdateChild();
((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => called = true;
target.Content = child2;
- ((Control)target.Presenter).ApplyTemplate();
+ target.Presenter.ApplyTemplate();
Assert.True(called);
}
@@ -245,13 +245,13 @@ namespace Avalonia.Controls.UnitTests
target.Template = GetTemplate();
target.ApplyTemplate();
- ((ContentPresenter)target.Presenter).UpdateChild();
+ target.Presenter.UpdateChild();
target.Content = "Foo";
- ((ContentPresenter)target.Presenter).UpdateChild();
+ target.Presenter.UpdateChild();
Assert.Equal("Foo", ((TextBlock)target.Presenter.Child).Text);
target.Content = "Bar";
- ((ContentPresenter)target.Presenter).UpdateChild();
+ target.Presenter.UpdateChild();
Assert.Equal("Bar", ((TextBlock)target.Presenter.Child).Text);
}
@@ -263,7 +263,7 @@ namespace Avalonia.Controls.UnitTests
target.Template = GetTemplate();
target.Content = "Foo";
target.ApplyTemplate();
- ((ContentPresenter)target.Presenter).UpdateChild();
+ target.Presenter.UpdateChild();
Assert.Equal("Foo", target.Presenter.Child.DataContext);
}
@@ -276,7 +276,7 @@ namespace Avalonia.Controls.UnitTests
target.Template = GetTemplate();
target.Content = new TextBlock();
target.ApplyTemplate();
- ((ContentPresenter)target.Presenter).UpdateChild();
+ target.Presenter.UpdateChild();
Assert.Null(target.Presenter.Child.DataContext);
}
diff --git a/tests/Avalonia.Controls.UnitTests/FlyoutTests.cs b/tests/Avalonia.Controls.UnitTests/FlyoutTests.cs
index 7767de11c7..db12de9db9 100644
--- a/tests/Avalonia.Controls.UnitTests/FlyoutTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/FlyoutTests.cs
@@ -288,10 +288,10 @@ namespace Avalonia.Controls.UnitTests
window.Show();
button.Focus();
- Assert.True(FocusManager.Instance?.Current == button);
+ Assert.True(window.FocusManager.GetFocusedElement() == button);
button.Flyout.ShowAt(button);
Assert.False(button.IsFocused);
- Assert.True(FocusManager.Instance?.Current == flyoutTextBox);
+ Assert.True(window.FocusManager.GetFocusedElement() == flyoutTextBox);
}
}
@@ -322,10 +322,10 @@ namespace Avalonia.Controls.UnitTests
window.Content = button;
window.Show();
- FocusManager.Instance?.Focus(button);
- Assert.True(FocusManager.Instance?.Current == button);
+ button.Focus();
+ Assert.True(window.FocusManager.GetFocusedElement() == button);
button.Flyout.ShowAt(button);
- Assert.True(FocusManager.Instance?.Current == button);
+ Assert.True(window.FocusManager.GetFocusedElement() == button);
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/HeaderedItemsControlTests .cs b/tests/Avalonia.Controls.UnitTests/HeaderedItemsControlTests .cs
index 2cf4148fe3..78012a4d25 100644
--- a/tests/Avalonia.Controls.UnitTests/HeaderedItemsControlTests .cs
+++ b/tests/Avalonia.Controls.UnitTests/HeaderedItemsControlTests .cs
@@ -34,7 +34,7 @@ namespace Avalonia.Controls.UnitTests
target.Header = "Foo";
target.ApplyTemplate();
- ((ContentPresenter)target.HeaderPresenter).UpdateChild();
+ target.HeaderPresenter.UpdateChild();
var child = target.HeaderPresenter.Child;
diff --git a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
index 86249c66ff..58e4ec1b75 100644
--- a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
@@ -576,8 +576,9 @@ namespace Avalonia.Controls.UnitTests
});
var panel = Assert.IsAssignableFrom(target.ItemsPanelRoot);
+ var focusManager = ((IInputRoot)target.VisualRoot!).FocusManager;
- Assert.Equal(panel.Children[1], FocusManager.Instance!.Current);
+ Assert.Equal(panel.Children[1], focusManager?.GetFocusedElement());
}
[Fact]
@@ -601,8 +602,9 @@ namespace Avalonia.Controls.UnitTests
});
var panel = Assert.IsAssignableFrom(target.ItemsPanelRoot);
+ var focusManager = ((IInputRoot)target.VisualRoot!).FocusManager;
- Assert.Equal(panel.Children[2], FocusManager.Instance!.Current);
+ Assert.Equal(panel.Children[2], focusManager?.GetFocusedElement());
}
[Fact]
diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
index 72f476a3b0..6d0b543ede 100644
--- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
@@ -12,6 +12,7 @@ using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.LogicalTree;
+using Avalonia.Markup.Xaml.Templates;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
@@ -298,11 +299,11 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(false, item.IsSelected);
- RaisePressedEvent(target, item, MouseButton.Left);
+ RaisePressedEvent(item, MouseButton.Left);
Assert.Equal(true, item.IsSelected);
- RaisePressedEvent(target, item, MouseButton.Left);
+ RaisePressedEvent(item, MouseButton.Left);
Assert.Equal(false, item.IsSelected);
}
@@ -328,9 +329,9 @@ namespace Avalonia.Controls.UnitTests
}
}
- private void RaisePressedEvent(ListBox listBox, ListBoxItem item, MouseButton mouseButton)
+ private void RaisePressedEvent(ListBoxItem item, MouseButton mouseButton)
{
- _mouse.Click(listBox, item, mouseButton);
+ _mouse.Click(item, item, mouseButton);
}
[Fact]
@@ -752,6 +753,177 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(Enumerable.Range(0, 10).Select(x => $"Item{10 - x}"), realized);
}
+ [Fact]
+ public void Arrow_Keys_Should_Move_Selection_Vertical()
+ {
+ using var app = UnitTestApplication.Start(TestServices.RealFocus);
+ var items = Enumerable.Range(0, 10).Select(x => $"Item {x}").ToArray();
+ var target = new ListBox
+ {
+ Template = ListBoxTemplate(),
+ ItemsSource = items,
+ ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Height = 10 }),
+ SelectedIndex = 0,
+ };
+
+ Prepare(target);
+
+ RaiseKeyEvent(target, Key.Down);
+ Assert.Equal(1, target.SelectedIndex);
+
+ RaiseKeyEvent(target, Key.Down);
+ Assert.Equal(2, target.SelectedIndex);
+
+ RaiseKeyEvent(target, Key.Up);
+ Assert.Equal(1, target.SelectedIndex);
+ }
+
+ [Fact]
+ public void Arrow_Keys_Should_Move_Selection_Horizontal()
+ {
+ using var app = UnitTestApplication.Start(TestServices.RealFocus);
+ var items = Enumerable.Range(0, 10).Select(x => $"Item {x}").ToArray();
+ var target = new ListBox
+ {
+ Template = ListBoxTemplate(),
+ ItemsSource = items,
+ ItemsPanel = new FuncTemplate(() => new VirtualizingStackPanel
+ {
+ Orientation = Orientation.Horizontal
+ }),
+ ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Height = 10 }),
+ SelectedIndex = 0,
+ };
+
+ Prepare(target);
+
+ RaiseKeyEvent(target, Key.Right);
+ Assert.Equal(1, target.SelectedIndex);
+
+ RaiseKeyEvent(target, Key.Right);
+ Assert.Equal(2, target.SelectedIndex);
+
+ RaiseKeyEvent(target, Key.Left);
+ Assert.Equal(1, target.SelectedIndex);
+ }
+
+ [Fact]
+ public void Arrow_Keys_Should_Focus_Selection()
+ {
+ using var app = UnitTestApplication.Start(TestServices.RealFocus);
+ var items = Enumerable.Range(0, 10).Select(x => $"Item {x}").ToArray();
+ var target = new ListBox
+ {
+ Template = ListBoxTemplate(),
+ ItemsSource = items,
+ ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Height = 10 }),
+ SelectedIndex = 0,
+ };
+
+ Prepare(target);
+
+ RaiseKeyEvent(target, Key.Down);
+ Assert.True(target.ContainerFromIndex(1).IsFocused);
+
+ RaiseKeyEvent(target, Key.Down);
+ Assert.True(target.ContainerFromIndex(2).IsFocused);
+
+ RaiseKeyEvent(target, Key.Up);
+ Assert.True(target.ContainerFromIndex(1).IsFocused);
+ }
+
+ [Fact]
+ public void Down_Key_Selecting_From_No_Selection_And_No_Focus_Selects_From_Start()
+ {
+ using var app = UnitTestApplication.Start(TestServices.RealFocus);
+ var target = new ListBox
+ {
+ Template = ListBoxTemplate(),
+ ItemsSource = new[] { "Foo", "Bar", "Baz" },
+ Width = 100,
+ Height = 100,
+ };
+
+ Prepare(target);
+
+ RaiseKeyEvent(target, Key.Down);
+
+ Assert.Equal(0, target.SelectedIndex);
+ }
+
+ [Fact]
+ public void Down_Key_Selecting_From_No_Selection_Selects_From_Focus()
+ {
+ using var app = UnitTestApplication.Start(TestServices.RealFocus);
+ var target = new ListBox
+ {
+ Template = ListBoxTemplate(),
+ ItemsSource = new[] { "Foo", "Bar", "Baz" },
+ Width = 100,
+ Height = 100,
+ };
+
+ Prepare(target);
+
+ target.ContainerFromIndex(1)!.Focus();
+ RaiseKeyEvent(target, Key.Down);
+
+ Assert.Equal(2, target.SelectedIndex);
+ }
+
+ [Fact]
+ public void Ctrl_Down_Key_Moves_Focus_But_Not_Selection()
+ {
+ using var app = UnitTestApplication.Start(TestServices.RealFocus);
+ var target = new ListBox
+ {
+ Template = ListBoxTemplate(),
+ ItemsSource = new[] { "Foo", "Bar", "Baz" },
+ Width = 100,
+ Height = 100,
+ SelectedIndex = 0,
+ };
+
+ Prepare(target);
+
+ target.ContainerFromIndex(0)!.Focus();
+ RaiseKeyEvent(target, Key.Down, KeyModifiers.Control);
+
+ Assert.Equal(0, target.SelectedIndex);
+ Assert.True(target.ContainerFromIndex(1).IsFocused);
+ }
+
+ [Fact]
+ public void Down_Key_Brings_Unrealized_Selection_Into_View()
+ {
+ using var app = UnitTestApplication.Start(TestServices.RealFocus);
+ var items = Enumerable.Range(0, 100).Select(x => $"Item {x}").ToArray();
+ var target = new ListBox
+ {
+ Template = ListBoxTemplate(),
+ ItemsSource = items,
+ ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Width = 20, Height = 10 }),
+ Width = 100,
+ Height = 100,
+ SelectedIndex = 0,
+ };
+
+ Prepare(target);
+
+ target.ContainerFromIndex(0)!.Focus();
+ target.Scroll.Offset = new Vector(0, 100);
+ Layout(target);
+
+ var panel = (VirtualizingStackPanel)target.ItemsPanelRoot;
+ Assert.Equal(10, panel.FirstRealizedIndex);
+
+ RaiseKeyEvent(target, Key.Down);
+
+ Assert.Equal(1, target.SelectedIndex);
+ Assert.True(target.ContainerFromIndex(1).IsFocused);
+ Assert.Equal(new Vector(0, 10), target.Scroll.Offset);
+ }
+
[Fact]
public void WrapSelection_Should_Wrap()
{
@@ -776,7 +948,7 @@ namespace Avalonia.Controls.UnitTests
first.Focus();
- RaisePressedEvent(target, first, MouseButton.Left);
+ RaisePressedEvent(first, MouseButton.Left);
Assert.Equal(true, first.IsSelected);
RaiseKeyEvent(target, Key.Up);
@@ -979,7 +1151,7 @@ namespace Avalonia.Controls.UnitTests
RaiseKeyEvent(button, Key.Tab);
var item = target.ContainerFromIndex(0);
- Assert.Same(item, FocusManager.Instance.Current);
+ Assert.Same(item, root.FocusManager.GetFocusedElement());
}
[Fact]
@@ -1026,17 +1198,17 @@ namespace Avalonia.Controls.UnitTests
RaiseKeyEvent(button, Key.Tab);
var item = target.ContainerFromIndex(1);
- Assert.Same(item, FocusManager.Instance.Current);
+ Assert.Same(item, root.FocusManager.GetFocusedElement());
RaiseKeyEvent(item, Key.Tab);
- Assert.Same(button, FocusManager.Instance.Current);
+ Assert.Same(button, root.FocusManager.GetFocusedElement());
target.Selection.AnchorIndex = 2;
RaiseKeyEvent(button, Key.Tab);
item = target.ContainerFromIndex(2);
- Assert.Same(item, FocusManager.Instance.Current);
+ Assert.Same(item, root.FocusManager.GetFocusedElement());
}
private static void RaiseKeyEvent(Control target, Key key, KeyModifiers inputModifiers = 0)
diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs
index e77a3529b8..ed19d1dc97 100644
--- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs
+++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs
@@ -17,76 +17,6 @@ namespace Avalonia.Controls.UnitTests
{
private MouseTestHelper _helper = new MouseTestHelper();
- [Fact]
- public void Focusing_Item_With_Shift_And_Arrow_Key_Should_Add_To_Selection()
- {
- var target = new ListBox
- {
- Template = new FuncControlTemplate(CreateListBoxTemplate),
- ItemsSource = new[] { "Foo", "Bar", "Baz " },
- SelectionMode = SelectionMode.Multiple
- };
-
- ApplyTemplate(target);
-
- target.SelectedItem = "Foo";
-
- target.Presenter.Panel.Children[1].RaiseEvent(new GotFocusEventArgs
- {
- NavigationMethod = NavigationMethod.Directional,
- KeyModifiers = KeyModifiers.Shift
- });
-
- Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems);
- }
-
- [Fact]
- public void Focusing_Item_With_Ctrl_And_Arrow_Key_Should_Not_Add_To_Selection()
- {
- var target = new ListBox
- {
- Template = new FuncControlTemplate(CreateListBoxTemplate),
- ItemsSource = new[] { "Foo", "Bar", "Baz " },
- SelectionMode = SelectionMode.Multiple
- };
-
- ApplyTemplate(target);
-
- target.SelectedItem = "Foo";
-
- target.Presenter.Panel.Children[1].RaiseEvent(new GotFocusEventArgs
- {
- NavigationMethod = NavigationMethod.Directional,
- KeyModifiers = KeyModifiers.Control
- });
-
- Assert.Equal(new[] { "Foo" }, target.SelectedItems);
- }
-
- [Fact]
- public void Focusing_Selected_Item_With_Ctrl_And_Arrow_Key_Should_Not_Remove_From_Selection()
- {
- var target = new ListBox
- {
- Template = new FuncControlTemplate(CreateListBoxTemplate),
- ItemsSource = new[] { "Foo", "Bar", "Baz " },
- SelectionMode = SelectionMode.Multiple
- };
-
- ApplyTemplate(target);
-
- target.SelectedItems.Add("Foo");
- target.SelectedItems.Add("Bar");
-
- target.Presenter.Panel.Children[0].RaiseEvent(new GotFocusEventArgs
- {
- NavigationMethod = NavigationMethod.Directional,
- KeyModifiers = KeyModifiers.Control
- });
-
- Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems);
- }
-
[Fact]
public void Shift_Selecting_From_No_Selection_Selects_From_Start()
{
@@ -549,6 +479,104 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(1, target.SelectedItems.Count);
}
}
+
+ [Fact]
+ public void Shift_Arrow_Key_Selects_Range()
+ {
+ using var app = UnitTestApplication.Start(TestServices.RealFocus);
+ var target = new ListBox
+ {
+ Template = new FuncControlTemplate(CreateListBoxTemplate),
+ ItemsSource = new[] { "Foo", "Bar", "Baz" },
+ SelectionMode = SelectionMode.Multiple,
+ Width = 100,
+ Height = 100,
+ SelectedIndex = 0,
+ };
+
+ var root = new TestRoot(target);
+ root.LayoutManager.ExecuteInitialLayoutPass();
+
+ RaiseKeyEvent(target, Key.Down, KeyModifiers.Shift);
+
+ Assert.Equal(new[] { "Foo", "Bar", }, target.SelectedItems);
+ Assert.Equal(new[] { 0, 1 }, SelectedContainers(target));
+ Assert.True(target.ContainerFromIndex(1).IsFocused);
+
+ RaiseKeyEvent(target, Key.Down, KeyModifiers.Shift);
+
+ Assert.Equal(new[] { "Foo", "Bar", "Baz", }, target.SelectedItems);
+ Assert.Equal(new[] { 0, 1, 2 }, SelectedContainers(target));
+ Assert.True(target.ContainerFromIndex(2).IsFocused);
+
+ RaiseKeyEvent(target, Key.Up, KeyModifiers.Shift);
+
+ Assert.Equal(new[] { "Foo", "Bar", }, target.SelectedItems);
+ Assert.Equal(new[] { 0, 1 }, SelectedContainers(target));
+ Assert.True(target.ContainerFromIndex(1).IsFocused);
+ }
+
+ [Fact]
+ public void Shift_Down_Key_Selecting_Selects_Range_End_From_Focus()
+ {
+ using var app = UnitTestApplication.Start(TestServices.RealFocus);
+ var target = new ListBox
+ {
+ Template = new FuncControlTemplate(CreateListBoxTemplate),
+ ItemsSource = new[] { "Foo", "Bar", "Baz" },
+ SelectionMode = SelectionMode.Multiple,
+ Width = 100,
+ Height = 100,
+ SelectedIndex = 0,
+ };
+
+ var root = new TestRoot(target);
+ root.LayoutManager.ExecuteInitialLayoutPass();
+
+ target.ContainerFromIndex(1)!.Focus();
+ RaiseKeyEvent(target, Key.Down, KeyModifiers.Shift);
+
+ Assert.Equal(new[] { "Foo", "Bar", "Baz" }, target.SelectedItems);
+ Assert.Equal(new[] { 0, 1, 2 }, SelectedContainers(target));
+ Assert.True(target.ContainerFromIndex(2).IsFocused);
+ }
+
+ [Fact]
+ public void Shift_Down_Key_Selecting_Selects_Range_End_From_Focus_Moved_With_Ctrl_Key()
+ {
+ using var app = UnitTestApplication.Start(TestServices.RealFocus);
+ var target = new ListBox
+ {
+ Template = new FuncControlTemplate(CreateListBoxTemplate),
+ ItemsSource = new[] { "Foo", "Bar", "Baz", "Qux" },
+ SelectionMode = SelectionMode.Multiple,
+ Width = 100,
+ Height = 100,
+ SelectedIndex = 0,
+ };
+
+ var root = new TestRoot(target);
+ root.LayoutManager.ExecuteInitialLayoutPass();
+
+ RaiseKeyEvent(target, Key.Down, KeyModifiers.Shift);
+
+ Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems);
+ Assert.Equal(new[] { 0, 1 }, SelectedContainers(target));
+ Assert.True(target.ContainerFromIndex(1).IsFocused);
+
+ RaiseKeyEvent(target, Key.Down, KeyModifiers.Control);
+
+ Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems);
+ Assert.Equal(new[] { 0, 1 }, SelectedContainers(target));
+ Assert.True(target.ContainerFromIndex(2).IsFocused);
+
+ RaiseKeyEvent(target, Key.Down, KeyModifiers.Shift);
+
+ Assert.Equal(new[] { "Foo", "Bar", "Baz", "Qux" }, target.SelectedItems);
+ Assert.Equal(new[] { 0, 1, 2, 3 }, SelectedContainers(target));
+ Assert.True(target.ContainerFromIndex(3).IsFocused);
+ }
+
private Control CreateListBoxTemplate(TemplatedControl parent, INameScope scope)
{
return new ScrollViewer
@@ -571,20 +599,14 @@ namespace Avalonia.Controls.UnitTests
}.RegisterInNameScope(scope);
}
- private static void ApplyTemplate(ListBox target)
+ private static void RaiseKeyEvent(Control target, Key key, KeyModifiers inputModifiers = 0)
{
- // Apply the template to the ListBox itself.
- target.ApplyTemplate();
-
- // Then to its inner ScrollViewer.
- var scrollViewer = (ScrollViewer)target.GetVisualChildren().Single();
- scrollViewer.ApplyTemplate();
-
- // Then make the ScrollViewer create its child.
- ((ContentPresenter)scrollViewer.Presenter).UpdateChild();
-
- // Now the ItemsPresenter should be reigstered, so apply its template.
- ((Control)target.Presenter).ApplyTemplate();
+ target.RaiseEvent(new KeyEventArgs
+ {
+ RoutedEvent = InputElement.KeyDownEvent,
+ KeyModifiers = inputModifiers,
+ Key = key
+ });
}
private static IEnumerable SelectedContainers(SelectingItemsControl target)
diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs
index 5551d1c7df..92ea2df2a9 100644
--- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs
+++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs
@@ -39,45 +39,6 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(-1, target.SelectedIndex);
}
- [Fact]
- public void Focusing_Item_With_Arrow_Key_Should_Select_It()
- {
- var target = new ListBox
- {
- Template = new FuncControlTemplate(CreateListBoxTemplate),
- ItemsSource = new[] { "Foo", "Bar", "Baz " },
- };
-
- ApplyTemplate(target);
-
- target.Presenter.Panel.Children[0].RaiseEvent(new GotFocusEventArgs
- {
- NavigationMethod = NavigationMethod.Directional,
- });
-
- Assert.Equal(0, target.SelectedIndex);
- }
-
- [Fact]
- public void Focusing_Item_With_Arrow_Key_And_Ctrl_Pressed_Should_Not_Select_It()
- {
- var target = new ListBox
- {
- Template = new FuncControlTemplate(CreateListBoxTemplate),
- ItemsSource = new[] { "Foo", "Bar", "Baz " },
- };
-
- ApplyTemplate(target);
-
- target.Presenter.Panel.Children[0].RaiseEvent(new GotFocusEventArgs
- {
- NavigationMethod = NavigationMethod.Directional,
- KeyModifiers = KeyModifiers.Control
- });
-
- Assert.Equal(-1, target.SelectedIndex);
- }
-
[Fact]
public void Pressing_Space_On_Focused_Item_With_Ctrl_Pressed_Should_Select_It()
{
@@ -344,7 +305,7 @@ namespace Avalonia.Controls.UnitTests
scrollViewer.ApplyTemplate();
// Then make the ScrollViewer create its child.
- ((ContentPresenter)scrollViewer.Presenter).UpdateChild();
+ scrollViewer.Presenter.UpdateChild();
// Now the ItemsPresenter should be reigstered, so apply its template.
target.Presenter.ApplyTemplate();
diff --git a/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs
index 04acd1c36d..dead810dbb 100644
--- a/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs
@@ -1004,7 +1004,7 @@ namespace Avalonia.Controls.UnitTests
_layoutManager = layoutManager ?? new LayoutManager(this);
}
- protected override ILayoutManager CreateLayoutManager() => _layoutManager;
+ private protected override ILayoutManager CreateLayoutManager() => _layoutManager;
}
private static Mock CreateMockTopLevelImpl()
diff --git a/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs b/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs
index e5c96dcab6..506a62525c 100644
--- a/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs
@@ -267,7 +267,7 @@ namespace Avalonia.Controls.UnitTests.Platform
target.KeyDown(item.Object, e);
parentItem.Verify(x => x.Close());
- parentItem.Verify(x => x.Focus());
+ parentItem.Verify(x => x.Focus(It.IsAny(), It.IsAny()));
Assert.True(e.Handled);
}
@@ -351,7 +351,7 @@ namespace Avalonia.Controls.UnitTests.Platform
target.KeyDown(item.Object, e);
parentItem.Verify(x => x.Close());
- parentItem.Verify(x => x.Focus());
+ parentItem.Verify(x => x.Focus(It.IsAny(), It.IsAny()));
Assert.True(e.Handled);
}
@@ -554,7 +554,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var contextMenu = Mock.Of(x => x.MoveSelection(NavigationDirection.Down, true) == true);
var e = new KeyEventArgs { Key = Key.Down, Source = contextMenu };
- target.Attach(contextMenu);
+ target.AttachCore(contextMenu);
target.KeyDown(contextMenu, e);
Mock.Get(contextMenu).Verify(x => x.MoveSelection(NavigationDirection.Down, true));
diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs
index 9042e84fa1..34a2776bcd 100644
--- a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs
+++ b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs
@@ -391,7 +391,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
templatedParent.ApplyTemplate();
- return ((ContentPresenter)templatedParent.Presenter, templatedParent);
+ return (templatedParent.Presenter, templatedParent);
}
private class TestContentControl : ContentControl, IContentPresenterHost
diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
index 765f2d1c19..51399d1202 100644
--- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
@@ -642,10 +642,11 @@ namespace Avalonia.Controls.UnitTests.Primitives
tb.Focus();
- Assert.True(FocusManager.Instance?.Current == tb);
+ var focusManager = TopLevel.GetTopLevel(tb)!.FocusManager;
+ tb = Assert.IsType(focusManager.GetFocusedElement());
//Ensure focus remains in the popup
- var nextFocus = KeyboardNavigationHandler.GetNext(FocusManager.Instance.Current, NavigationDirection.Next);
+ var nextFocus = KeyboardNavigationHandler.GetNext(tb, NavigationDirection.Next);
Assert.True(nextFocus == b);
@@ -684,7 +685,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
p.Close();
- var focus = FocusManager.Instance?.Current;
+ var focusManager = window.FocusManager;
+ var focus = focusManager.GetFocusedElement();
Assert.True(focus == window);
}
}
@@ -723,7 +725,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
windowTB.Focus();
- var focus = FocusManager.Instance?.Current;
+ var focusManager = window.FocusManager;
+ var focus = focusManager.GetFocusedElement();
Assert.True(focus == windowTB);
diff --git a/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs b/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs
index 82c4b5f8f0..94e24e484f 100644
--- a/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs
@@ -358,6 +358,77 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(100, thumb.Bounds.Top);
}
+ [Fact]
+ public void BringIntoViewOnFocusChange_Scrolls_Child_Control_Into_View_When_Focused()
+ {
+ using var app = UnitTestApplication.Start(TestServices.RealFocus);
+ var content = new StackPanel
+ {
+ Children =
+ {
+ new Button
+ {
+ Width = 100,
+ Height = 900,
+ },
+ new Button
+ {
+ Width = 100,
+ Height = 900,
+ },
+ }
+ };
+
+ var target = new ScrollViewer
+ {
+ Template = new FuncControlTemplate(CreateTemplate),
+ Content = content,
+ };
+ var root = new TestRoot(target);
+
+ root.LayoutManager.ExecuteInitialLayoutPass();
+
+ var button = (Button)content.Children[1];
+ button.Focus();
+
+ Assert.Equal(new Vector(0, 800), target.Offset);
+ }
+
+ [Fact]
+ public void BringIntoViewOnFocusChange_False_Does_Not_Scroll_Child_Control_Into_View_When_Focused()
+ {
+ var content = new StackPanel
+ {
+ Children =
+ {
+ new Button
+ {
+ Width = 100,
+ Height = 900,
+ },
+ new Button
+ {
+ Width = 100,
+ Height = 900,
+ },
+ }
+ };
+
+ var target = new ScrollViewer
+ {
+ Template = new FuncControlTemplate(CreateTemplate),
+ Content = content,
+ };
+ var root = new TestRoot(target);
+
+ root.LayoutManager.ExecuteInitialLayoutPass();
+
+ var button = (Button)content.Children[1];
+ button.Focus();
+
+ Assert.Equal(new Vector(0, 0), target.Offset);
+ }
+
private Point GetRootPoint(Visual control, Point p)
{
if (control.GetVisualRoot() is Visual root &&
@@ -369,7 +440,7 @@ namespace Avalonia.Controls.UnitTests
throw new InvalidOperationException("Could not get the point in root coordinates.");
}
- private Control CreateTemplate(ScrollViewer control, INameScope scope)
+ internal static Control CreateTemplate(ScrollViewer control, INameScope scope)
{
return new Grid
{
@@ -409,7 +480,7 @@ namespace Avalonia.Controls.UnitTests
};
}
- private Control CreateScrollBarTemplate(ScrollBar scrollBar, INameScope scope)
+ private static Control CreateScrollBarTemplate(ScrollBar scrollBar, INameScope scope)
{
return new Border
{
diff --git a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs
index 0d3eb80ae7..7721018453 100644
--- a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs
@@ -272,28 +272,28 @@ namespace Avalonia.Controls.UnitTests
ApplyTemplate(target);
- ((ContentPresenter)target.ContentPart).UpdateChild();
+ target.ContentPart.UpdateChild();
var dataContext = ((TextBlock)target.ContentPart.Child).DataContext;
Assert.Equal(items[0], dataContext);
target.SelectedIndex = 1;
- ((ContentPresenter)target.ContentPart).UpdateChild();
+ target.ContentPart.UpdateChild();
dataContext = ((Button)target.ContentPart.Child).DataContext;
Assert.Equal(items[1], dataContext);
target.SelectedIndex = 2;
- ((ContentPresenter)target.ContentPart).UpdateChild();
+ target.ContentPart.UpdateChild();
dataContext = ((TextBlock)target.ContentPart.Child).DataContext;
Assert.Equal("Base", dataContext);
target.SelectedIndex = 3;
- ((ContentPresenter)target.ContentPart).UpdateChild();
+ target.ContentPart.UpdateChild();
dataContext = ((TextBlock)target.ContentPart.Child).DataContext;
Assert.Equal("Qux", dataContext);
target.SelectedIndex = 4;
- ((ContentPresenter)target.ContentPart).UpdateChild();
- dataContext = ((Control)target.ContentPart).DataContext;
+ target.ContentPart.UpdateChild();
+ dataContext = target.ContentPart.DataContext;
Assert.Equal("Base", dataContext);
}
@@ -367,7 +367,7 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot(target);
ApplyTemplate(target);
- ((ContentPresenter)target.ContentPart).UpdateChild();
+ target.ContentPart.UpdateChild();
var content = Assert.IsType(target.ContentPart.Child);
Assert.Equal("bar", content.Tag);
@@ -375,6 +375,27 @@ namespace Avalonia.Controls.UnitTests
Assert.Single(target.GetLogicalChildren(), content);
}
+ [Fact]
+ public void SelectedContentTemplate_Updates_After_New_ContentTemplate()
+ {
+ TabControl target = new TabControl
+ {
+ Template = TabControlTemplate(),
+ ItemsSource = new[] { "Foo" },
+ };
+ var root = new TestRoot(target);
+
+ ApplyTemplate(target);
+ ((ContentPresenter)target.ContentPart).UpdateChild();
+
+ Assert.Equal(null, Assert.IsType(target.ContentPart.Child).Tag);
+
+ target.ContentTemplate = new FuncDataTemplate((x, _) =>
+ new TextBlock { Tag = "bar", Text = x });
+
+ Assert.Equal("bar", Assert.IsType(target.ContentPart.Child).Tag);
+ }
+
[Fact]
public void Should_Not_Propagate_DataContext_To_TabItem_Content()
{
@@ -461,7 +482,7 @@ namespace Avalonia.Controls.UnitTests
RaiseKeyEvent(button, Key.Tab);
var item = target.ContainerFromIndex(0);
- Assert.Same(item, FocusManager.Instance.Current);
+ Assert.Same(item, root.FocusManager.GetFocusedElement());
}
[Fact]
@@ -513,17 +534,17 @@ namespace Avalonia.Controls.UnitTests
RaiseKeyEvent(button, Key.Tab);
var item = target.ContainerFromIndex(1);
- Assert.Same(item, FocusManager.Instance.Current);
+ Assert.Same(item, root.FocusManager.GetFocusedElement());
RaiseKeyEvent(item, Key.Tab);
- Assert.Same(button, FocusManager.Instance.Current);
+ Assert.Same(button, root.FocusManager.GetFocusedElement());
target.Selection.AnchorIndex = 2;
RaiseKeyEvent(button, Key.Tab);
item = target.ContainerFromIndex(2);
- Assert.Same(item, FocusManager.Instance.Current);
+ Assert.Same(item, root.FocusManager.GetFocusedElement());
}
private static IControlTemplate TabControlTemplate()
@@ -587,7 +608,7 @@ namespace Avalonia.Controls.UnitTests
tabItem.ApplyTemplate();
- ((ContentPresenter)tabItem.Presenter).UpdateChild();
+ tabItem.Presenter.UpdateChild();
}
target.ContentPart.ApplyTemplate();
diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
index ca1986d293..109c233a56 100644
--- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
@@ -884,7 +884,7 @@ namespace Avalonia.Controls.UnitTests
Template = CreateTemplate(),
Text = "ABC",
MaxLines = 1,
- AcceptsReturn= true
+ AcceptsReturn = true
};
var impl = CreateMockTopLevelImpl();
@@ -896,8 +896,11 @@ namespace Avalonia.Controls.UnitTests
topLevel.ApplyTemplate();
topLevel.LayoutManager.ExecuteInitialLayoutPass();
+ target.ApplyTemplate();
target.Measure(Size.Infinity);
+ var initialHeight = target.DesiredSize.Height;
+
topLevel.Clipboard?.SetTextAsync(Environment.NewLine).GetAwaiter().GetResult();
RaiseKeyEvent(target, Key.V, KeyModifiers.Control);
@@ -905,7 +908,10 @@ namespace Avalonia.Controls.UnitTests
RaiseTextEvent(target, Environment.NewLine);
- Assert.Equal("ABC", target.Text);
+ target.InvalidateMeasure();
+ target.Measure(Size.Infinity);
+
+ Assert.Equal(initialHeight, target.DesiredSize.Height);
}
}
@@ -1116,7 +1122,11 @@ namespace Avalonia.Controls.UnitTests
private IControlTemplate CreateTemplate()
{
return new FuncControlTemplate((control, scope) =>
- new TextPresenter
+ new ScrollViewer
+ {
+ Name = "Part_ScrollViewer",
+ Template = new FuncControlTemplate(ScrollViewerTests.CreateTemplate),
+ Content = new TextPresenter
{
Name = "PART_TextPresenter",
[!!TextPresenter.TextProperty] = new Binding
@@ -1133,7 +1143,8 @@ namespace Avalonia.Controls.UnitTests
Priority = BindingPriority.Template,
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
}
- }.RegisterInNameScope(scope));
+ }.RegisterInNameScope(scope)
+ }.RegisterInNameScope(scope));
}
private static void RaiseKeyEvent(TextBox textBox, Key key, KeyModifiers inputModifiers)
@@ -1208,7 +1219,7 @@ namespace Avalonia.Controls.UnitTests
_layoutManager = layoutManager ?? new LayoutManager(this);
}
- protected override ILayoutManager CreateLayoutManager() => _layoutManager;
+ private protected override ILayoutManager CreateLayoutManager() => _layoutManager;
}
private static Mock CreateMockTopLevelImpl()
diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
index 0884dd306a..1c7935e167 100644
--- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
@@ -339,7 +339,7 @@ namespace Avalonia.Controls.UnitTests
_layoutManager = layoutManager ?? new LayoutManager(this);
}
- protected override ILayoutManager CreateLayoutManager() => _layoutManager;
+ private protected override ILayoutManager CreateLayoutManager() => _layoutManager;
}
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
index 51300f343a..604ff1c715 100644
--- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
@@ -969,7 +969,6 @@ namespace Avalonia.Controls.UnitTests
public void Keyboard_Navigation_Should_Move_To_Last_Selected_Node()
{
using var app = Start();
- var focus = FocusManager.Instance!;
var navigation = AvaloniaLocator.Current.GetRequiredService();
var data = CreateTestTreeData();
@@ -984,6 +983,7 @@ namespace Avalonia.Controls.UnitTests
{
Children = { target, button },
});
+ var focus = root.FocusManager;
root.LayoutManager.ExecuteInitialLayoutPass();
ExpandAll(target);
@@ -994,20 +994,19 @@ namespace Avalonia.Controls.UnitTests
target.SelectedItem = item;
node.Focus();
- Assert.Same(node, focus.Current);
+ Assert.Same(node, focus.GetFocusedElement());
- navigation.Move(focus.Current!, NavigationDirection.Next);
- Assert.Same(button, focus.Current);
+ navigation.Move(focus.GetFocusedElement()!, NavigationDirection.Next);
+ Assert.Same(button, focus.GetFocusedElement());
- navigation.Move(focus.Current!, NavigationDirection.Next);
- Assert.Same(node, focus.Current);
+ navigation.Move(focus.GetFocusedElement()!, NavigationDirection.Next);
+ Assert.Same(node, focus.GetFocusedElement());
}
[Fact]
public void Keyboard_Navigation_Should_Not_Crash_If_Selected_Item_Is_not_In_Tree()
{
using var app = Start();
- var focus = FocusManager.Instance!;
var data = CreateTestTreeData();
var selectedNode = new Node { Value = "Out of Tree Selected Item" };
@@ -1025,6 +1024,7 @@ namespace Avalonia.Controls.UnitTests
{
Children = { target, button },
});
+ var focus = root.FocusManager;
root.LayoutManager.ExecuteInitialLayoutPass();
ExpandAll(target);
@@ -1035,7 +1035,7 @@ namespace Avalonia.Controls.UnitTests
target.SelectedItem = selectedNode;
node.Focus();
- Assert.Same(node, focus.Current);
+ Assert.Same(node, focus.GetFocusedElement());
}
[Fact]
diff --git a/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs b/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs
index aa03a77d70..c5a6ecc376 100644
--- a/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs
@@ -646,7 +646,7 @@ namespace Avalonia.Controls.UnitTests
{
// Issue #11272
using var app = App();
- var (_, _, itemsControl) = CreateUnrootedTarget();
+ var (_, _, itemsControl) = CreateUnrootedTarget();
var container = new Decorator { Margin = new Thickness(100) };
var root = new TestRoot(true, container);
@@ -657,6 +657,78 @@ namespace Avalonia.Controls.UnitTests
root.LayoutManager.ExecuteLayoutPass();
}
+ [Fact]
+ public void Supports_Null_Recycle_Key_When_Scrolling()
+ {
+ using var app = App();
+ var (_, scroll, itemsControl) = CreateUnrootedTarget();
+ var root = CreateRoot(itemsControl);
+
+ root.LayoutManager.ExecuteInitialLayoutPass();
+
+ var firstItem = itemsControl.ContainerFromIndex(0)!;
+ scroll.Offset = new(0, 20);
+
+ Layout(itemsControl);
+
+ Assert.Null(firstItem.Parent);
+ Assert.Null(firstItem.VisualParent);
+ Assert.DoesNotContain(firstItem, itemsControl.ItemsPanelRoot!.Children);
+ }
+
+ [Fact]
+ public void Supports_Null_Recycle_Key_When_Clearing_Items()
+ {
+ using var app = App();
+ var (_, _, itemsControl) = CreateUnrootedTarget();
+ var root = CreateRoot(itemsControl);
+
+ root.LayoutManager.ExecuteInitialLayoutPass();
+
+ var firstItem = itemsControl.ContainerFromIndex(0)!;
+ itemsControl.ItemsSource = null;
+
+ Layout(itemsControl);
+
+ Assert.Null(firstItem.Parent);
+ Assert.Null(firstItem.VisualParent);
+ Assert.Empty(itemsControl.ItemsPanelRoot!.Children);
+ }
+
+ [Fact]
+ public void ScrollIntoView_On_Effectively_Invisible_Panel_Does_Not_Create_Ghost_Elements()
+ {
+ var items = new[] { "foo", "bar", "baz" };
+ var (target, _, itemsControl) = CreateUnrootedTarget(items: items);
+ var container = new Decorator { Margin = new Thickness(100), Child = itemsControl };
+ var root = new TestRoot(true, container);
+
+ root.LayoutManager.ExecuteInitialLayoutPass();
+
+ // Clear the items and do a layout to recycle all elements.
+ itemsControl.ItemsSource = null;
+ root.LayoutManager.ExecuteLayoutPass();
+
+ // Should have no realized elements and 3 unrealized elements.
+ Assert.Equal(0, target.GetRealizedElements().Count);
+ Assert.Equal(3, target.Children.Count);
+
+ // Make the panel effectively invisible and set items.
+ container.IsVisible = false;
+ itemsControl.ItemsSource = items;
+
+ // Try to scroll into view while effectively invisible.
+ target.ScrollIntoView(0);
+
+ // Make the panel visible and layout.
+ container.IsVisible = true;
+ root.LayoutManager.ExecuteLayoutPass();
+
+ // Should have 3 realized elements and no unrealized elements.
+ Assert.Equal(3, target.GetRealizedElements().Count);
+ Assert.Equal(3, target.Children.Count);
+ }
+
private static IReadOnlyList GetRealizedIndexes(VirtualizingStackPanel target, ItemsControl itemsControl)
{
return target.GetRealizedElements()
@@ -704,7 +776,7 @@ namespace Avalonia.Controls.UnitTests
Optional itemTemplate = default,
IEnumerable