Browse Source

Merge branch 'master' into fixes/Issue_6263

pull/11418/head
workgroupengineering 3 years ago
committed by GitHub
parent
commit
afb7be8dcb
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      packages/Avalonia/Avalonia.csproj
  2. 5
      samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs
  3. 2
      samples/GpuInterop/VulkanDemo/VulkanContext.cs
  4. 3
      samples/IntegrationTestApp/MainWindow.axaml
  5. 6
      samples/VirtualizationDemo/ViewModels/PlaygroundPageViewModel.cs
  6. 4
      src/Avalonia.Base/Animation/Animatable.cs
  7. 1
      src/Avalonia.Base/Avalonia.Base.csproj
  8. 2
      src/Avalonia.Base/AvaloniaPropertyExtensions.cs
  9. 2
      src/Avalonia.Base/Collections/Pooled/ClearMode.cs
  10. 2
      src/Avalonia.Base/Collections/Pooled/IReadOnlyPooledList.cs
  11. 2
      src/Avalonia.Base/Collections/Pooled/PooledList.cs
  12. 2
      src/Avalonia.Base/Collections/Pooled/PooledStack.cs
  13. 2
      src/Avalonia.Base/Controls/ChildNameScope.cs
  14. 2
      src/Avalonia.Base/Data/Core/CommonPropertyNames.cs
  15. 2
      src/Avalonia.Base/Data/IndexerBinding.cs
  16. 2
      src/Avalonia.Base/Diagnostics/IAvaloniaObjectDebug.cs
  17. 2
      src/Avalonia.Base/Diagnostics/INotifyCollectionChangedDebug.cs
  18. 2
      src/Avalonia.Base/EnumExtensions.cs
  19. 2
      src/Avalonia.Base/Input/AccessKeyHandler.cs
  20. 16
      src/Avalonia.Base/Input/KeyboardNavigationHandler.cs
  21. 2
      src/Avalonia.Base/Interactivity/Interactive.cs
  22. 2
      src/Avalonia.Base/Logging/TraceLogSink.cs
  23. 2
      src/Avalonia.Base/Media/ArcSegment.cs
  24. 2
      src/Avalonia.Base/Media/BezierSegment .cs
  25. 2
      src/Avalonia.Base/Media/Imaging/Bitmap.cs
  26. 2
      src/Avalonia.Base/Media/Immutable/ImmutableTileBrush.cs
  27. 2
      src/Avalonia.Base/Media/LineSegment.cs
  28. 4
      src/Avalonia.Base/Media/PathSegment.cs
  29. 2
      src/Avalonia.Base/Media/PolyLineSegment.cs
  30. 2
      src/Avalonia.Base/Media/QuadraticBezierSegment .cs
  31. 1
      src/Avalonia.Base/Media/StreamGeometryContext.cs
  32. 2
      src/Avalonia.Base/Media/Transformation/TransformParser.cs
  33. 6
      src/Avalonia.Base/Platform/Storage/NameCollisionOption.cs
  34. 2
      src/Avalonia.Base/Rendering/ManagedDeferredRendererLock.cs
  35. 50
      src/Avalonia.Base/Rendering/RendererBase.cs
  36. 2
      src/Avalonia.Base/Rendering/Utilities/TileBrushCalculator.cs
  37. 3
      src/Avalonia.Base/Rendering/ZIndexComparer.cs
  38. 2
      src/Avalonia.Base/StyledElement.cs
  39. 2
      src/Avalonia.Base/Utilities/DisposableLock.cs
  40. 2
      src/Avalonia.Base/Utilities/NonPumpingLockHelper.cs
  41. 2
      src/Avalonia.Base/Utilities/SingleOrDictionary.cs
  42. 2
      src/Avalonia.Base/Utilities/SingleOrQueue.cs
  43. 2
      src/Avalonia.Base/Utilities/ValueSingleOrList.cs
  44. 49
      src/Avalonia.Base/Utilities/WeakTimer.cs
  45. 2
      src/Avalonia.Base/Visual.cs
  46. 4
      src/Avalonia.Controls.ItemsRepeater/Controls/RepeaterLayoutContext.cs
  47. 2
      src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs
  48. 2
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  49. 2
      src/Avalonia.Controls/Primitives/AccessText.cs
  50. 2
      src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
  51. 2
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  52. 2
      src/Avalonia.Controls/Primitives/ScrollBar.cs
  53. 2
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  54. 2
      src/Avalonia.Controls/Primitives/VisualLayerManager.cs
  55. 54
      src/Avalonia.Controls/ScrollViewer.cs
  56. 2
      src/Avalonia.Controls/TextBlock.cs
  57. 2
      src/Avalonia.Controls/Viewbox.cs
  58. 12
      src/Avalonia.Controls/VirtualizingStackPanel.cs
  59. 3
      src/Avalonia.Themes.Fluent/Controls/ListBox.xaml
  60. 5
      src/Avalonia.Themes.Fluent/Controls/ScrollViewer.xaml
  61. 3
      src/Avalonia.Themes.Fluent/Controls/TextBox.xaml
  62. 3
      src/Avalonia.Themes.Fluent/Controls/TreeView.xaml
  63. 1
      src/Avalonia.Themes.Simple/Controls/ListBox.xaml
  64. 5
      src/Avalonia.Themes.Simple/Controls/ScrollViewer.xaml
  65. 1
      src/Avalonia.Themes.Simple/Controls/TextBox.xaml
  66. 1
      src/Avalonia.Themes.Simple/Controls/TreeView.xaml
  67. 4
      src/Headless/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs
  68. 33
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  69. 4
      src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs
  70. 1
      src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs
  71. 2
      src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs
  72. 4
      src/Skia/Avalonia.Skia/PictureRenderTarget.cs
  73. 2
      src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs
  74. 8
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
  75. 20
      tests/Avalonia.Base.UnitTests/Input/KeyboardNavigationTests_Tab.cs
  76. 71
      tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs
  77. 60
      tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs
  78. 9
      tests/Avalonia.IntegrationTests.Appium/ButtonTests.cs
  79. 6
      tests/Avalonia.IntegrationTests.Appium/PlatformFactAttribute.cs
  80. 4
      tests/Avalonia.IntegrationTests.Appium/PlatformTheoryAttribute.cs

2
packages/Avalonia/Avalonia.csproj

@ -5,7 +5,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia.BuildServices" Version="0.0.15" /> <PackageReference Include="Avalonia.BuildServices" Version="0.0.16" />
<ProjectReference Include="../../src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj" /> <ProjectReference Include="../../src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj" />
<ProjectReference Include="../../src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj"> <ProjectReference Include="../../src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>

5
samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs

@ -1,5 +1,4 @@
using System; using System;
using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Avalonia.Controls; using Avalonia.Controls;
@ -23,7 +22,7 @@ namespace ControlCatalog.Pages
$"Text was dragged {++textCount} times"), DragDropEffects.Copy | DragDropEffects.Move | DragDropEffects.Link); $"Text was dragged {++textCount} times"), DragDropEffects.Copy | DragDropEffects.Move | DragDropEffects.Link);
SetupDnd("Custom", d => d.Set(CustomFormat, "Test123"), DragDropEffects.Move); 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<DataObject> factory, DragDropEffects effects) void SetupDnd(string suffix, Action<DataObject> factory, DragDropEffects effects)
@ -99,7 +98,7 @@ namespace ControlCatalog.Pages
{ {
if (item is IStorageFile file) 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}"; contentStr += $"File {item.Name}:{Environment.NewLine}{content}{Environment.NewLine}{Environment.NewLine}";
} }
else if (item is IStorageFolder folder) else if (item is IStorageFolder folder)

2
samples/GpuInterop/VulkanDemo/VulkanContext.cs

@ -174,7 +174,7 @@ public unsafe class VulkanContext : IDisposable
for (uint queueFamilyIndex = 0; queueFamilyIndex < queueFamilyCount; queueFamilyIndex++) for (uint queueFamilyIndex = 0; queueFamilyIndex < queueFamilyCount; queueFamilyIndex++)
{ {
var family = familyProperties[queueFamilyIndex]; var family = familyProperties[queueFamilyIndex];
if (!family.QueueFlags.HasAllFlags(QueueFlags.GraphicsBit)) if (!family.QueueFlags.HasFlag(QueueFlags.GraphicsBit))
continue; continue;

3
samples/IntegrationTestApp/MainWindow.axaml

@ -47,6 +47,9 @@
<Button Name="DisabledButton" IsEnabled="False"> <Button Name="DisabledButton" IsEnabled="False">
Disabled Button Disabled Button
</Button> </Button>
<Button Name="EffectivelyDisabledButton" Command="{ReflectionBinding DoesntExist}">
Effectively Disabled Button
</Button>
<Button Name="BasicButton"> <Button Name="BasicButton">
Basic Button Basic Button
</Button> </Button>

6
samples/VirtualizationDemo/ViewModels/PlaygroundPageViewModel.cs

@ -24,19 +24,19 @@ public class PlaygroundPageViewModel : ViewModelBase
public bool Multiple public bool Multiple
{ {
get => _selectionMode.HasAnyFlag(SelectionMode.Multiple); get => _selectionMode.HasFlag(SelectionMode.Multiple);
set => SetSelectionMode(SelectionMode.Multiple, value); set => SetSelectionMode(SelectionMode.Multiple, value);
} }
public bool Toggle public bool Toggle
{ {
get => _selectionMode.HasAnyFlag(SelectionMode.Toggle); get => _selectionMode.HasFlag(SelectionMode.Toggle);
set => SetSelectionMode(SelectionMode.Toggle, value); set => SetSelectionMode(SelectionMode.Toggle, value);
} }
public bool AlwaysSelected public bool AlwaysSelected
{ {
get => _selectionMode.HasAnyFlag(SelectionMode.AlwaysSelected); get => _selectionMode.HasFlag(SelectionMode.AlwaysSelected);
set => SetSelectionMode(SelectionMode.AlwaysSelected, value); set => SetSelectionMode(SelectionMode.AlwaysSelected, value);
} }

4
src/Avalonia.Base/Animation/Animatable.cs

@ -58,7 +58,7 @@ namespace Avalonia.Animation
/// This method should not be called from user code, it will be called automatically by the framework /// This method should not be called from user code, it will be called automatically by the framework
/// when a control is added to the visual tree. /// when a control is added to the visual tree.
/// </remarks> /// </remarks>
protected void EnableTransitions() internal void EnableTransitions()
{ {
if (!_transitionsEnabled) if (!_transitionsEnabled)
{ {
@ -83,7 +83,7 @@ namespace Avalonia.Animation
/// This method should not be called from user code, it will be called automatically by the framework /// This method should not be called from user code, it will be called automatically by the framework
/// when a control is removed from the visual tree. /// when a control is removed from the visual tree.
/// </remarks> /// </remarks>
protected void DisableTransitions() internal void DisableTransitions()
{ {
if (_transitionsEnabled) if (_transitionsEnabled)
{ {

1
src/Avalonia.Base/Avalonia.Base.csproj

@ -63,6 +63,7 @@
<InternalsVisibleTo Include="Avalonia.iOS, PublicKey=$(AvaloniaPublicKey)" /> <InternalsVisibleTo Include="Avalonia.iOS, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Dialogs, PublicKey=$(AvaloniaPublicKey)" /> <InternalsVisibleTo Include="Avalonia.Dialogs, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Diagnostics, PublicKey=$(AvaloniaPublicKey)" /> <InternalsVisibleTo Include="Avalonia.Diagnostics, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.LinuxFramebuffer, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="MiniMvvm, PublicKey=$(AvaloniaPublicKey)" /> <InternalsVisibleTo Include="MiniMvvm, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="ControlCatalog, PublicKey=$(AvaloniaPublicKey)" /> <InternalsVisibleTo Include="ControlCatalog, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7" /> <InternalsVisibleTo Include="DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7" />

2
src/Avalonia.Base/AvaloniaPropertyExtensions.cs

@ -7,7 +7,7 @@ namespace Avalonia
/// <summary> /// <summary>
/// Extensions for <see cref="AvaloniaProperty"/>. /// Extensions for <see cref="AvaloniaProperty"/>.
/// </summary> /// </summary>
public static class AvaloniaPropertyExtensions internal static class AvaloniaPropertyExtensions
{ {
/// <summary> /// <summary>
/// Checks if values of given property can affect rendering (via <see cref="IAffectsRender"/>). /// Checks if values of given property can affect rendering (via <see cref="IAffectsRender"/>).

2
src/Avalonia.Base/Collections/Pooled/ClearMode.cs

@ -9,7 +9,7 @@ namespace Avalonia.Collections.Pooled
/// what each option does before using anything other than the default /// what each option does before using anything other than the default
/// of Auto. /// of Auto.
/// </summary> /// </summary>
public enum ClearMode internal enum ClearMode
{ {
/// <summary> /// <summary>
/// <para><code>Auto</code> has different behavior depending on the host project's target framework.</para> /// <para><code>Auto</code> has different behavior depending on the host project's target framework.</para>

2
src/Avalonia.Base/Collections/Pooled/IReadOnlyPooledList.cs

@ -11,7 +11,7 @@ namespace Avalonia.Collections.Pooled
/// </summary> /// </summary>
/// <typeparam name="T">The type of elements in the read-only pooled list.</typeparam> /// <typeparam name="T">The type of elements in the read-only pooled list.</typeparam>
public interface IReadOnlyPooledList<T> : IReadOnlyList<T> internal interface IReadOnlyPooledList<T> : IReadOnlyList<T>
{ {
#pragma warning disable CS0419 #pragma warning disable CS0419
/// <summary> /// <summary>

2
src/Avalonia.Base/Collections/Pooled/PooledList.cs

@ -29,7 +29,7 @@ namespace Avalonia.Collections.Pooled
[DebuggerDisplay("Count = {Count}")] [DebuggerDisplay("Count = {Count}")]
[DebuggerTypeProxy(typeof(ICollectionDebugView<>))] [DebuggerTypeProxy(typeof(ICollectionDebugView<>))]
[Serializable] [Serializable]
public class PooledList<T> : IList<T>, IReadOnlyPooledList<T>, IList, IDisposable, IDeserializationCallback internal class PooledList<T> : IList<T>, IReadOnlyPooledList<T>, IList, IDisposable, IDeserializationCallback
{ {
// internal constant copied from Array.MaxArrayLength // internal constant copied from Array.MaxArrayLength
private const int MaxArrayLength = 0x7FEFFFFF; private const int MaxArrayLength = 0x7FEFFFFF;

2
src/Avalonia.Base/Collections/Pooled/PooledStack.cs

@ -29,7 +29,7 @@ namespace Avalonia.Collections.Pooled
[DebuggerTypeProxy(typeof(StackDebugView<>))] [DebuggerTypeProxy(typeof(StackDebugView<>))]
[DebuggerDisplay("Count = {Count}")] [DebuggerDisplay("Count = {Count}")]
[Serializable] [Serializable]
public class PooledStack<T> : IEnumerable<T>, ICollection, IReadOnlyCollection<T>, IDisposable, IDeserializationCallback internal class PooledStack<T> : IEnumerable<T>, ICollection, IReadOnlyCollection<T>, IDisposable, IDeserializationCallback
{ {
[NonSerialized] [NonSerialized]
private ArrayPool<T> _pool; private ArrayPool<T> _pool;

2
src/Avalonia.Base/Controls/ChildNameScope.cs

@ -3,7 +3,7 @@ using Avalonia.Utilities;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
public class ChildNameScope : INameScope internal class ChildNameScope : INameScope
{ {
private readonly INameScope _parentScope; private readonly INameScope _parentScope;
private readonly NameScope _inner = new NameScope(); private readonly NameScope _inner = new NameScope();

2
src/Avalonia.Base/Data/Core/CommonPropertyNames.cs

@ -1,6 +1,6 @@
namespace Avalonia.Data.Core namespace Avalonia.Data.Core
{ {
public static class CommonPropertyNames internal static class CommonPropertyNames
{ {
public const string IndexerName = "Item"; public const string IndexerName = "Item";
} }

2
src/Avalonia.Base/Data/IndexerBinding.cs

@ -2,7 +2,7 @@
namespace Avalonia.Data namespace Avalonia.Data
{ {
public class IndexerBinding : IBinding internal class IndexerBinding : IBinding
{ {
public IndexerBinding( public IndexerBinding(
AvaloniaObject source, AvaloniaObject source,

2
src/Avalonia.Base/Diagnostics/IAvaloniaObjectDebug.cs

@ -5,7 +5,7 @@ namespace Avalonia.Diagnostics
/// <summary> /// <summary>
/// Provides a debug interface into <see cref="AvaloniaObject"/>. /// Provides a debug interface into <see cref="AvaloniaObject"/>.
/// </summary> /// </summary>
public interface IAvaloniaObjectDebug internal interface IAvaloniaObjectDebug
{ {
/// <summary> /// <summary>
/// Gets the subscriber list for the <see cref="AvaloniaObject.PropertyChanged"/> /// Gets the subscriber list for the <see cref="AvaloniaObject.PropertyChanged"/>

2
src/Avalonia.Base/Diagnostics/INotifyCollectionChangedDebug.cs

@ -8,7 +8,7 @@ namespace Avalonia.Diagnostics
/// Provides a debug interface into <see cref="INotifyCollectionChanged"/> subscribers on /// Provides a debug interface into <see cref="INotifyCollectionChanged"/> subscribers on
/// <see cref="AvaloniaList{T}"/> /// <see cref="AvaloniaList{T}"/>
/// </summary> /// </summary>
public interface INotifyCollectionChangedDebug internal interface INotifyCollectionChangedDebug
{ {
/// <summary> /// <summary>
/// Gets the subscriber list for the <see cref="INotifyCollectionChanged.CollectionChanged"/> /// Gets the subscriber list for the <see cref="INotifyCollectionChanged.CollectionChanged"/>

2
src/Avalonia.Base/EnumExtensions.cs

@ -6,7 +6,7 @@ namespace Avalonia
/// <summary> /// <summary>
/// Provides extension methods for enums. /// Provides extension methods for enums.
/// </summary> /// </summary>
public static class EnumExtensions internal static class EnumExtensions
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]

2
src/Avalonia.Base/Input/AccessKeyHandler.cs

@ -9,7 +9,7 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Handles access keys for a window. /// Handles access keys for a window.
/// </summary> /// </summary>
public class AccessKeyHandler : IAccessKeyHandler internal class AccessKeyHandler : IAccessKeyHandler
{ {
/// <summary> /// <summary>
/// Defines the AccessKeyPressed attached event. /// Defines the AccessKeyPressed attached event.

16
src/Avalonia.Base/Input/KeyboardNavigationHandler.cs

@ -1,6 +1,7 @@
using System; using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Avalonia.Input.Navigation; using Avalonia.Input.Navigation;
using Avalonia.Metadata;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Input namespace Avalonia.Input
@ -8,6 +9,7 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Handles keyboard navigation for a window. /// Handles keyboard navigation for a window.
/// </summary> /// </summary>
[Unstable]
public class KeyboardNavigationHandler : IKeyboardNavigationHandler public class KeyboardNavigationHandler : IKeyboardNavigationHandler
{ {
/// <summary> /// <summary>
@ -75,13 +77,16 @@ namespace Avalonia.Input
/// <param name="direction">The direction to move.</param> /// <param name="direction">The direction to move.</param>
/// <param name="keyModifiers">Any key modifiers active at the time of focus.</param> /// <param name="keyModifiers">Any key modifiers active at the time of focus.</param>
public void Move( public void Move(
IInputElement element, IInputElement? element,
NavigationDirection direction, NavigationDirection direction,
KeyModifiers keyModifiers = KeyModifiers.None) KeyModifiers keyModifiers = KeyModifiers.None)
{ {
element = element ?? throw new ArgumentNullException(nameof(element)); if (element is null && _owner is null)
{
return;
}
var next = GetNext(element, direction); var next = GetNext(element ?? _owner!, direction);
if (next != null) if (next != null)
{ {
@ -99,10 +104,9 @@ namespace Avalonia.Input
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
protected virtual void OnKeyDown(object? sender, KeyEventArgs e) protected virtual void OnKeyDown(object? sender, KeyEventArgs e)
{ {
var current = FocusManager.GetFocusManager(e.Source as IInputElement)?.GetFocusedElement(); if (e.Key == Key.Tab)
if (current != null && e.Key == Key.Tab)
{ {
var current = FocusManager.GetFocusManager(e.Source as IInputElement)?.GetFocusedElement();
var direction = (e.KeyModifiers & KeyModifiers.Shift) == 0 ? var direction = (e.KeyModifiers & KeyModifiers.Shift) == 0 ?
NavigationDirection.Next : NavigationDirection.Previous; NavigationDirection.Next : NavigationDirection.Previous;
Move(current, direction, e.KeyModifiers); Move(current, direction, e.KeyModifiers);

2
src/Avalonia.Base/Interactivity/Interactive.cs

@ -17,7 +17,7 @@ namespace Avalonia.Interactivity
/// <summary> /// <summary>
/// Gets the interactive parent of the object for bubbling and tunneling events. /// Gets the interactive parent of the object for bubbling and tunneling events.
/// </summary> /// </summary>
protected internal virtual Interactive? InteractiveParent => VisualParent as Interactive; internal virtual Interactive? InteractiveParent => VisualParent as Interactive;
/// <summary> /// <summary>
/// Adds a handler for the specified routed event. /// Adds a handler for the specified routed event.

2
src/Avalonia.Base/Logging/TraceLogSink.cs

@ -6,7 +6,7 @@ using Avalonia.Utilities;
namespace Avalonia.Logging namespace Avalonia.Logging
{ {
public class TraceLogSink : ILogSink internal class TraceLogSink : ILogSink
{ {
private readonly LogEventLevel _level; private readonly LogEventLevel _level;
private readonly IList<string>? _areas; private readonly IList<string>? _areas;

2
src/Avalonia.Base/Media/ArcSegment.cs

@ -95,7 +95,7 @@ namespace Avalonia.Media
set { SetValue(SweepDirectionProperty, value); } set { SetValue(SweepDirectionProperty, value); }
} }
protected internal override void ApplyTo(StreamGeometryContext ctx) internal override void ApplyTo(StreamGeometryContext ctx)
{ {
ctx.ArcTo(Point, Size, RotationAngle, IsLargeArc, SweepDirection); ctx.ArcTo(Point, Size, RotationAngle, IsLargeArc, SweepDirection);
} }

2
src/Avalonia.Base/Media/BezierSegment .cs

@ -56,7 +56,7 @@ namespace Avalonia.Media
set { SetValue(Point3Property, value); } set { SetValue(Point3Property, value); }
} }
protected internal override void ApplyTo(StreamGeometryContext ctx) internal override void ApplyTo(StreamGeometryContext ctx)
{ {
ctx.CubicBezierTo(Point1, Point2, Point3); ctx.CubicBezierTo(Point1, Point2, Point3);
} }

2
src/Avalonia.Base/Media/Imaging/Bitmap.cs

@ -173,7 +173,7 @@ namespace Avalonia.Media.Imaging
public virtual PixelFormat? Format => (PlatformImpl.Item as IReadableBitmapImpl)?.Format; public virtual PixelFormat? Format => (PlatformImpl.Item as IReadableBitmapImpl)?.Format;
protected internal unsafe void CopyPixelsCore(PixelRect sourceRect, IntPtr buffer, int bufferSize, int stride, private protected unsafe void CopyPixelsCore(PixelRect sourceRect, IntPtr buffer, int bufferSize, int stride,
ILockedFramebuffer fb) ILockedFramebuffer fb)
{ {
if ((sourceRect.Width <= 0 || sourceRect.Height <= 0) && (sourceRect.X != 0 || sourceRect.Y != 0)) if ((sourceRect.Width <= 0 || sourceRect.Height <= 0) && (sourceRect.X != 0 || sourceRect.Y != 0))

2
src/Avalonia.Base/Media/Immutable/ImmutableTileBrush.cs

@ -21,7 +21,7 @@ namespace Avalonia.Media.Immutable
/// How the source rectangle will be stretched to fill the destination rect. /// How the source rectangle will be stretched to fill the destination rect.
/// </param> /// </param>
/// <param name="tileMode">The tile mode.</param> /// <param name="tileMode">The tile mode.</param>
protected internal ImmutableTileBrush( private protected ImmutableTileBrush(
AlignmentX alignmentX, AlignmentX alignmentX,
AlignmentY alignmentY, AlignmentY alignmentY,
RelativeRect destinationRect, RelativeRect destinationRect,

2
src/Avalonia.Base/Media/LineSegment.cs

@ -22,7 +22,7 @@ namespace Avalonia.Media
set { SetValue(PointProperty, value); } set { SetValue(PointProperty, value); }
} }
protected internal override void ApplyTo(StreamGeometryContext ctx) internal override void ApplyTo(StreamGeometryContext ctx)
{ {
ctx.LineTo(Point); ctx.LineTo(Point);
} }

4
src/Avalonia.Base/Media/PathSegment.cs

@ -2,6 +2,6 @@ namespace Avalonia.Media
{ {
public abstract class PathSegment : AvaloniaObject public abstract class PathSegment : AvaloniaObject
{ {
protected internal abstract void ApplyTo(StreamGeometryContext ctx); internal abstract void ApplyTo(StreamGeometryContext ctx);
} }
} }

2
src/Avalonia.Base/Media/PolyLineSegment.cs

@ -44,7 +44,7 @@ namespace Avalonia.Media
Points = new Points(points); Points = new Points(points);
} }
protected internal override void ApplyTo(StreamGeometryContext ctx) internal override void ApplyTo(StreamGeometryContext ctx)
{ {
var points = Points; var points = Points;
if (points.Count > 0) if (points.Count > 0)

2
src/Avalonia.Base/Media/QuadraticBezierSegment .cs

@ -40,7 +40,7 @@ namespace Avalonia.Media
set { SetValue(Point2Property, value); } set { SetValue(Point2Property, value); }
} }
protected internal override void ApplyTo(StreamGeometryContext ctx) internal override void ApplyTo(StreamGeometryContext ctx)
{ {
ctx.QuadraticBezierTo(Point1, Point2); ctx.QuadraticBezierTo(Point1, Point2);
} }

1
src/Avalonia.Base/Media/StreamGeometryContext.cs

@ -10,7 +10,6 @@ namespace Avalonia.Media
/// of <see cref="StreamGeometryContext"/> is obtained by calling /// of <see cref="StreamGeometryContext"/> is obtained by calling
/// <see cref="StreamGeometry.Open"/>. /// <see cref="StreamGeometry.Open"/>.
/// </remarks> /// </remarks>
/// TODO: This class is just a wrapper around IStreamGeometryContextImpl: is it needed?
public class StreamGeometryContext : IGeometryContext public class StreamGeometryContext : IGeometryContext
{ {
private readonly IStreamGeometryContextImpl _impl; private readonly IStreamGeometryContextImpl _impl;

2
src/Avalonia.Base/Media/Transformation/TransformParser.cs

@ -4,7 +4,7 @@ using Avalonia.Utilities;
namespace Avalonia.Media.Transformation namespace Avalonia.Media.Transformation
{ {
public static class TransformParser internal static class TransformParser
{ {
private static readonly (string, TransformFunction)[] s_functionMapping = private static readonly (string, TransformFunction)[] s_functionMapping =
{ {

6
src/Avalonia.Base/Platform/Storage/NameCollisionOption.cs

@ -1,6 +0,0 @@
namespace Avalonia.Platform.Storage;
public class NameCollisionOption
{
}

2
src/Avalonia.Base/Rendering/ManagedDeferredRendererLock.cs

@ -4,7 +4,7 @@ using Avalonia.Utilities;
namespace Avalonia.Rendering namespace Avalonia.Rendering
{ {
public class ManagedDeferredRendererLock : DisposableLock, IDeferredRendererLock internal class ManagedDeferredRendererLock : DisposableLock, IDeferredRendererLock
{ {
} }

50
src/Avalonia.Base/Rendering/RendererBase.cs

@ -1,50 +0,0 @@
using System;
using System.Diagnostics;
using System.Globalization;
using Avalonia.Media;
namespace Avalonia.Rendering
{
public class RendererBase
{
private readonly bool _useManualFpsCounting;
private static int s_fontSize = 18;
private readonly Stopwatch _stopwatch = Stopwatch.StartNew();
private int _framesThisSecond;
private int _fps;
private TimeSpan _lastFpsUpdate;
public RendererBase(bool useManualFpsCounting = false)
{
_useManualFpsCounting = useManualFpsCounting;
}
protected void FpsTick() => _framesThisSecond++;
protected void RenderFps(DrawingContext context, Rect clientRect, int? layerCount)
{
var now = _stopwatch.Elapsed;
var elapsed = now - _lastFpsUpdate;
if (!_useManualFpsCounting)
++_framesThisSecond;
if (elapsed.TotalSeconds > 1)
{
_fps = (int)(_framesThisSecond / elapsed.TotalSeconds);
_framesThisSecond = 0;
_lastFpsUpdate = now;
}
var text = layerCount.HasValue ? FormattableString.Invariant($"Layers: {layerCount} FPS: {_fps:000}") : FormattableString.Invariant($"FPS: {_fps:000}");
var formattedText = new FormattedText(text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, Typeface.Default, s_fontSize, Brushes.White);
var rect = new Rect(clientRect.Right - formattedText.Width, 0, formattedText.Width, formattedText.Height);
context.DrawRectangle(Brushes.Black, null, rect);
context.DrawText(formattedText, rect.TopLeft);
}
}
}

2
src/Avalonia.Base/Rendering/Utilities/TileBrushCalculator.cs

@ -2,7 +2,7 @@
namespace Avalonia.Rendering.Utilities namespace Avalonia.Rendering.Utilities
{ {
public class TileBrushCalculator internal class TileBrushCalculator
{ {
private readonly Size _imageSize; private readonly Size _imageSize;
private readonly Rect _drawRect; private readonly Rect _drawRect;

3
src/Avalonia.Base/Rendering/ZIndexComparer.cs

@ -1,10 +1,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.VisualTree;
namespace Avalonia.Rendering namespace Avalonia.Rendering
{ {
public class ZIndexComparer : IComparer<Visual> internal class ZIndexComparer : IComparer<Visual>
{ {
public static readonly ZIndexComparer Instance = new ZIndexComparer(); public static readonly ZIndexComparer Instance = new ZIndexComparer();
public static readonly Comparison<Visual> ComparisonInstance = Instance.Compare; public static readonly Comparison<Visual> ComparisonInstance = Instance.Compare;

2
src/Avalonia.Base/StyledElement.cs

@ -556,7 +556,7 @@ namespace Avalonia
/// Notifies child controls that a change has been made to resources that apply to them. /// Notifies child controls that a change has been made to resources that apply to them.
/// </summary> /// </summary>
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
protected virtual void NotifyChildResourcesChanged(ResourcesChangedEventArgs e) internal virtual void NotifyChildResourcesChanged(ResourcesChangedEventArgs e)
{ {
if (_logicalChildren is object) if (_logicalChildren is object)
{ {

2
src/Avalonia.Base/Utilities/DisposableLock.cs

@ -3,7 +3,7 @@ using System.Threading;
namespace Avalonia.Utilities namespace Avalonia.Utilities
{ {
public class DisposableLock internal class DisposableLock
{ {
private readonly object _lock = new object(); private readonly object _lock = new object();

2
src/Avalonia.Base/Utilities/NonPumpingLockHelper.cs

@ -3,7 +3,7 @@ using Avalonia.Threading;
namespace Avalonia.Utilities namespace Avalonia.Utilities
{ {
public class NonPumpingLockHelper internal class NonPumpingLockHelper
{ {
public interface IHelperImpl public interface IHelperImpl
{ {

2
src/Avalonia.Base/Utilities/SingleOrDictionary.cs

@ -11,7 +11,7 @@ namespace Avalonia.Utilities
/// </summary> /// </summary>
/// <typeparam name="TKey">The type of the key.</typeparam> /// <typeparam name="TKey">The type of the key.</typeparam>
/// <typeparam name="TValue">The type of the value.</typeparam> /// <typeparam name="TValue">The type of the value.</typeparam>
public class SingleOrDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>> internal class SingleOrDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>
where TKey : notnull where TKey : notnull
{ {
private KeyValuePair<TKey, TValue>? _singleValue; private KeyValuePair<TKey, TValue>? _singleValue;

2
src/Avalonia.Base/Utilities/SingleOrQueue.cs

@ -7,7 +7,7 @@ namespace Avalonia.Utilities
/// FIFO Queue optimized for holding zero or one items. /// FIFO Queue optimized for holding zero or one items.
/// </summary> /// </summary>
/// <typeparam name="T">The type of items held in the queue.</typeparam> /// <typeparam name="T">The type of items held in the queue.</typeparam>
public class SingleOrQueue<T> internal class SingleOrQueue<T>
{ {
private T? _head; private T? _head;
private Queue<T>? _tail; private Queue<T>? _tail;

2
src/Avalonia.Base/Utilities/ValueSingleOrList.cs

@ -9,7 +9,7 @@ namespace Avalonia.Utilities
/// <remarks> /// <remarks>
/// Once more than value has been added to this storage it will switch to using <see cref="List"/> internally. /// Once more than value has been added to this storage it will switch to using <see cref="List"/> internally.
/// </remarks> /// </remarks>
public ref struct ValueSingleOrList<T> internal ref struct ValueSingleOrList<T>
{ {
private bool _isSingleSet; private bool _isSingleSet;

49
src/Avalonia.Base/Utilities/WeakTimer.cs

@ -1,49 +0,0 @@
using System;
using Avalonia.Threading;
namespace Avalonia.Utilities
{
public class WeakTimer
{
public interface IWeakTimerSubscriber
{
bool Tick();
}
private readonly WeakReference<IWeakTimerSubscriber> _subscriber;
private DispatcherTimer _timer;
public WeakTimer(IWeakTimerSubscriber subscriber)
{
_subscriber = new WeakReference<IWeakTimerSubscriber>(subscriber);
_timer = new DispatcherTimer();
_timer.Tick += delegate { OnTick(); };
}
private void OnTick()
{
if (!_subscriber.TryGetTarget(out var subscriber) || !subscriber.Tick())
Stop();
}
public TimeSpan Interval
{
get { return _timer.Interval; }
set { _timer.Interval = value; }
}
public void Start() => _timer.Start();
public void Stop() => _timer.Stop();
public static WeakTimer StartWeakTimer(IWeakTimerSubscriber subscriber, TimeSpan interval)
{
var timer = new WeakTimer(subscriber) {Interval = interval};
timer.Start();
return timer;
}
}
}

2
src/Avalonia.Base/Visual.cs

@ -321,7 +321,7 @@ namespace Avalonia
internal RenderOptions RenderOptions { get; set; } internal RenderOptions RenderOptions { get; set; }
public bool HasNonUniformZIndexChildren { get; private set; } internal bool HasNonUniformZIndexChildren { get; private set; }
/// <summary> /// <summary>
/// Gets a value indicating whether this control is attached to a visual root. /// Gets a value indicating whether this control is attached to a visual root.

4
src/Avalonia.Controls.ItemsRepeater/Controls/RepeaterLayoutContext.cs

@ -53,8 +53,8 @@ namespace Avalonia.Controls
{ {
return _owner.GetElementImpl( return _owner.GetElementImpl(
index, index,
options.HasAllFlags(ElementRealizationOptions.ForceCreate), options.HasFlag(ElementRealizationOptions.ForceCreate),
options.HasAllFlags(ElementRealizationOptions.SuppressAutoRecycle)); options.HasFlag(ElementRealizationOptions.SuppressAutoRecycle));
} }
protected override object GetItemAtCore(int index) => _owner.ItemsSourceView!.GetAt(index)!; protected override object GetItemAtCore(int index) => _owner.ItemsSourceView!.GetAt(index)!;

2
src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs

@ -151,7 +151,7 @@ namespace Avalonia.Automation.Peers
protected override bool HasKeyboardFocusCore() => Owner.IsFocused; protected override bool HasKeyboardFocusCore() => Owner.IsFocused;
protected override bool IsContentElementCore() => true; protected override bool IsContentElementCore() => true;
protected override bool IsControlElementCore() => 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 bool IsKeyboardFocusableCore() => Owner.Focusable;
protected override void SetFocusCore() => Owner.Focus(); protected override void SetFocusCore() => Owner.Focus();

2
src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs

@ -302,7 +302,7 @@ namespace Avalonia.Controls.Presenters
/// <remarks> /// <remarks>
/// This method is automatically called when the control is attached to a visual tree. /// This method is automatically called when the control is attached to a visual tree.
/// </remarks> /// </remarks>
protected internal virtual void AttachToScrollViewer() internal void AttachToScrollViewer()
{ {
var owner = this.FindAncestorOfType<ScrollViewer>(); var owner = this.FindAncestorOfType<ScrollViewer>();

2
src/Avalonia.Controls/Primitives/AccessText.cs

@ -60,7 +60,7 @@ namespace Avalonia.Controls.Primitives
/// Renders the <see cref="AccessText"/> to a drawing context. /// Renders the <see cref="AccessText"/> to a drawing context.
/// </summary> /// </summary>
/// <param name="context">The drawing context.</param> /// <param name="context">The drawing context.</param>
protected internal override void RenderCore(DrawingContext context) private protected override void RenderCore(DrawingContext context)
{ {
base.RenderCore(context); base.RenderCore(context);
int underscore = Text?.IndexOf('_') ?? -1; int underscore = Text?.IndexOf('_') ?? -1;

2
src/Avalonia.Controls/Primitives/OverlayPopupHost.cs

@ -51,7 +51,7 @@ namespace Avalonia.Controls.Primitives
} }
/// <inheritdoc /> /// <inheritdoc />
protected internal override Interactive? InteractiveParent => Parent as Interactive; internal override Interactive? InteractiveParent => Parent as Interactive;
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() => Hide(); public void Dispose() => Hide();

2
src/Avalonia.Controls/Primitives/PopupRoot.cs

@ -72,7 +72,7 @@ namespace Avalonia.Controls.Primitives
/// <remarks> /// <remarks>
/// Popup events are passed to their parent window. This facilitates this. /// Popup events are passed to their parent window. This facilitates this.
/// </remarks> /// </remarks>
protected internal override Interactive? InteractiveParent => (Interactive?)Parent; internal override Interactive? InteractiveParent => (Interactive?)Parent;
/// <summary> /// <summary>
/// Gets the control that is hosting the popup root. /// Gets the control that is hosting the popup root.

2
src/Avalonia.Controls/Primitives/ScrollBar.cs

@ -200,7 +200,7 @@ namespace Avalonia.Controls.Primitives
/// <remarks> /// <remarks>
/// This method is automatically called when the control is attached to a visual tree. /// This method is automatically called when the control is attached to a visual tree.
/// </remarks> /// </remarks>
protected internal virtual void AttachToScrollViewer() internal void AttachToScrollViewer()
{ {
var owner = this.FindAncestorOfType<ScrollViewer>(); var owner = this.FindAncestorOfType<ScrollViewer>();

2
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@ -318,7 +318,7 @@ namespace Avalonia.Controls.Primitives
} }
/// <inheritdoc /> /// <inheritdoc />
protected sealed override void NotifyChildResourcesChanged(ResourcesChangedEventArgs e) internal sealed override void NotifyChildResourcesChanged(ResourcesChangedEventArgs e)
{ {
var count = VisualChildren.Count; var count = VisualChildren.Count;

2
src/Avalonia.Controls/Primitives/VisualLayerManager.cs

@ -104,7 +104,7 @@ namespace Avalonia.Controls.Primitives
} }
/// <inheritdoc /> /// <inheritdoc />
protected override void NotifyChildResourcesChanged(ResourcesChangedEventArgs e) internal override void NotifyChildResourcesChanged(ResourcesChangedEventArgs e)
{ {
foreach (var l in _layers) foreach (var l in _layers)
((ILogical)l).NotifyResourcesChanged(e); ((ILogical)l).NotifyResourcesChanged(e);

54
src/Avalonia.Controls/ScrollViewer.cs

@ -16,6 +16,12 @@ namespace Avalonia.Controls
[TemplatePart("PART_VerticalScrollBar", typeof(ScrollBar))] [TemplatePart("PART_VerticalScrollBar", typeof(ScrollBar))]
public class ScrollViewer : ContentControl, IScrollable, IScrollAnchorProvider public class ScrollViewer : ContentControl, IScrollable, IScrollAnchorProvider
{ {
/// <summary>
/// Defines the <see cref="BringIntoViewOnFocusChange "/> property.
/// </summary>
public static readonly AttachedProperty<bool> BringIntoViewOnFocusChangeProperty =
AvaloniaProperty.RegisterAttached<ScrollViewer, Control, bool>(nameof(BringIntoViewOnFocusChange), true);
/// <summary> /// <summary>
/// Defines the <see cref="Extent"/> property. /// Defines the <see cref="Extent"/> property.
/// </summary> /// </summary>
@ -174,6 +180,26 @@ namespace Avalonia.Controls
remove => RemoveHandler(ScrollChangedEvent, value); remove => RemoveHandler(ScrollChangedEvent, value);
} }
/// <summary>
/// Gets or sets a value that determines whether the <see cref="ScrollViewer"/> uses a
/// bring-into-view scroll behavior when an item in the view gets focus.
/// </summary>
/// <value>
/// 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.
/// </value>
/// <remarks>
/// <see cref="BringIntoViewOnFocusChange"/> can either be set explicitly on a
/// <see cref="ScrollViewer"/>, or a the attached
/// <code>ScrollViewer.BringIntoViewOnFocusChange</code> property can be set on an element
/// that hosts a <see cref="ScrollViewer"/>.
/// </remarks>
public bool BringIntoViewOnFocusChange
{
get => GetValue(BringIntoViewOnFocusChangeProperty);
set => SetValue(BringIntoViewOnFocusChangeProperty, value);
}
/// <summary> /// <summary>
/// Gets the extent of the scrollable content. /// Gets the extent of the scrollable content.
/// </summary> /// </summary>
@ -400,6 +426,26 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public void ScrollToEnd() => SetCurrentValue(OffsetProperty, new Vector(double.NegativeInfinity, double.PositiveInfinity)); public void ScrollToEnd() => SetCurrentValue(OffsetProperty, new Vector(double.NegativeInfinity, double.PositiveInfinity));
/// <summary>
/// Gets the value of the <see cref="BringIntoViewOnFocusChange"/> attached property.
/// </summary>
/// <param name="control">The control to read the value from.</param>
/// <returns>The value of the property.</returns>
public static bool GetBringIntoViewOnFocusChange(Control control)
{
return control.GetValue(BringIntoViewOnFocusChangeProperty);
}
/// <summary>
/// Gets the value of the <see cref="BringIntoViewOnFocusChange"/> attached property.
/// </summary>
/// <param name="control">The control to set the value on.</param>
/// <param name="value">The value of the property.</param>
public static void SetBringIntoViewOnFocusChange(Control control, bool value)
{
control.SetValue(BringIntoViewOnFocusChangeProperty, value);
}
/// <summary> /// <summary>
/// Gets the value of the HorizontalScrollBarVisibility attached property. /// Gets the value of the HorizontalScrollBarVisibility attached property.
/// </summary> /// </summary>
@ -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) protected override void OnKeyDown(KeyEventArgs e)
{ {
if (e.Key == Key.PageUp) if (e.Key == Key.PageUp)

2
src/Avalonia.Controls/TextBlock.cs

@ -555,7 +555,7 @@ namespace Avalonia.Controls
} }
// Workaround to seal Render method, we need to make so because AccessText was overriding Render method which is sealed now. // Workaround to seal Render method, we need to make so because AccessText was overriding Render method which is sealed now.
internal protected virtual void RenderCore(DrawingContext context) private protected virtual void RenderCore(DrawingContext context)
{ {
var background = Background; var background = Background;

2
src/Avalonia.Controls/Viewbox.cs

@ -82,7 +82,7 @@ namespace Avalonia.Controls
/// Gets or sets the transform applied to the container visual that /// Gets or sets the transform applied to the container visual that
/// hosts the child of the Viewbox /// hosts the child of the Viewbox
/// </summary> /// </summary>
protected internal ITransform? InternalTransform internal ITransform? InternalTransform
{ {
get => _containerVisual.RenderTransform; get => _containerVisual.RenderTransform;
set => _containerVisual.RenderTransform = value; set => _containerVisual.RenderTransform = value;

12
src/Avalonia.Controls/VirtualizingStackPanel.cs

@ -662,9 +662,12 @@ namespace Avalonia.Controls
_scrollViewer?.UnregisterAnchorCandidate(element); _scrollViewer?.UnregisterAnchorCandidate(element);
var recycleKey = element.GetValue(RecycleKeyProperty); var recycleKey = element.GetValue(RecycleKeyProperty);
Debug.Assert(recycleKey is not null);
if (recycleKey == s_itemIsItsOwnContainer) if (recycleKey is null)
{
RemoveInternalChild(element);
}
else if (recycleKey == s_itemIsItsOwnContainer)
{ {
element.IsVisible = false; element.IsVisible = false;
} }
@ -687,9 +690,8 @@ namespace Avalonia.Controls
Debug.Assert(ItemContainerGenerator is not null); Debug.Assert(ItemContainerGenerator is not null);
var recycleKey = element.GetValue(RecycleKeyProperty); var recycleKey = element.GetValue(RecycleKeyProperty);
Debug.Assert(recycleKey is not null);
if (recycleKey is null || recycleKey == s_itemIsItsOwnContainer)
if (recycleKey == s_itemIsItsOwnContainer)
{ {
RemoveInternalChild(element); RemoveInternalChild(element);
} }

3
src/Avalonia.Themes.Fluent/Controls/ListBox.xaml

@ -36,7 +36,8 @@
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}" VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}" IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}"
IsScrollInertiaEnabled="{TemplateBinding (ScrollViewer.IsScrollInertiaEnabled)}" IsScrollInertiaEnabled="{TemplateBinding (ScrollViewer.IsScrollInertiaEnabled)}"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"> AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"
BringIntoViewOnFocusChange="{TemplateBinding (ScrollViewer.BringIntoViewOnFocusChange)}">
<ItemsPresenter Name="PART_ItemsPresenter" <ItemsPresenter Name="PART_ItemsPresenter"
AreVerticalSnapPointsRegular="{TemplateBinding AreVerticalSnapPointsRegular}" AreVerticalSnapPointsRegular="{TemplateBinding AreVerticalSnapPointsRegular}"
AreHorizontalSnapPointsRegular="{TemplateBinding AreHorizontalSnapPointsRegular}" AreHorizontalSnapPointsRegular="{TemplateBinding AreHorizontalSnapPointsRegular}"

5
src/Avalonia.Themes.Fluent/Controls/ScrollViewer.xaml

@ -33,11 +33,12 @@
VerticalSnapPointsType="{TemplateBinding VerticalSnapPointsType}" VerticalSnapPointsType="{TemplateBinding VerticalSnapPointsType}"
HorizontalSnapPointsAlignment="{TemplateBinding HorizontalSnapPointsAlignment}" HorizontalSnapPointsAlignment="{TemplateBinding HorizontalSnapPointsAlignment}"
VerticalSnapPointsAlignment="{TemplateBinding VerticalSnapPointsAlignment}" VerticalSnapPointsAlignment="{TemplateBinding VerticalSnapPointsAlignment}"
Padding="{TemplateBinding Padding}"> Padding="{TemplateBinding Padding}"
ScrollViewer.IsScrollInertiaEnabled="{TemplateBinding IsScrollInertiaEnabled}">
<ScrollContentPresenter.GestureRecognizers> <ScrollContentPresenter.GestureRecognizers>
<ScrollGestureRecognizer CanHorizontallyScroll="{Binding CanHorizontallyScroll, ElementName=PART_ContentPresenter}" <ScrollGestureRecognizer CanHorizontallyScroll="{Binding CanHorizontallyScroll, ElementName=PART_ContentPresenter}"
CanVerticallyScroll="{Binding CanVerticallyScroll, ElementName=PART_ContentPresenter}" CanVerticallyScroll="{Binding CanVerticallyScroll, ElementName=PART_ContentPresenter}"
IsScrollInertiaEnabled="{Binding IsScrollInertiaEnabled, RelativeSource={RelativeSource TemplatedParent}}" /> IsScrollInertiaEnabled="{Binding (ScrollViewer.IsScrollInertiaEnabled), ElementName=PART_ContentPresenter}"/>
</ScrollContentPresenter.GestureRecognizers> </ScrollContentPresenter.GestureRecognizers>
</ScrollContentPresenter> </ScrollContentPresenter>
<ScrollBar Name="PART_HorizontalScrollBar" <ScrollBar Name="PART_HorizontalScrollBar"

3
src/Avalonia.Themes.Fluent/Controls/TextBox.xaml

@ -136,7 +136,8 @@
<ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}" <ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}" VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}" IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"> AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"
BringIntoViewOnFocusChange="{TemplateBinding (ScrollViewer.BringIntoViewOnFocusChange)}">
<Panel> <Panel>
<TextBlock Name="PART_Watermark" <TextBlock Name="PART_Watermark"
Opacity="0.5" Opacity="0.5"

3
src/Avalonia.Themes.Fluent/Controls/TreeView.xaml

@ -30,7 +30,8 @@
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}" HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}" VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}" IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"> AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"
BringIntoViewOnFocusChange="{TemplateBinding (ScrollViewer.BringIntoViewOnFocusChange)}">
<ItemsPresenter Name="PART_ItemsPresenter" <ItemsPresenter Name="PART_ItemsPresenter"
ItemsPanel="{TemplateBinding ItemsPanel}" ItemsPanel="{TemplateBinding ItemsPanel}"
Margin="{TemplateBinding Padding}" /> Margin="{TemplateBinding Padding}" />

1
src/Avalonia.Themes.Simple/Controls/ListBox.xaml

@ -17,6 +17,7 @@
CornerRadius="{TemplateBinding CornerRadius}"> CornerRadius="{TemplateBinding CornerRadius}">
<ScrollViewer Name="PART_ScrollViewer" <ScrollViewer Name="PART_ScrollViewer"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}" AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"
BringIntoViewOnFocusChange="{TemplateBinding (ScrollViewer.BringIntoViewOnFocusChange)}"
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}" HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}" IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}"

5
src/Avalonia.Themes.Simple/Controls/ScrollViewer.xaml

@ -14,11 +14,12 @@
VerticalSnapPointsType="{TemplateBinding VerticalSnapPointsType}" VerticalSnapPointsType="{TemplateBinding VerticalSnapPointsType}"
HorizontalSnapPointsAlignment="{TemplateBinding HorizontalSnapPointsAlignment}" HorizontalSnapPointsAlignment="{TemplateBinding HorizontalSnapPointsAlignment}"
VerticalSnapPointsAlignment="{TemplateBinding VerticalSnapPointsAlignment}" VerticalSnapPointsAlignment="{TemplateBinding VerticalSnapPointsAlignment}"
Background="{TemplateBinding Background}"> Background="{TemplateBinding Background}"
ScrollViewer.IsScrollInertiaEnabled="{TemplateBinding IsScrollInertiaEnabled}">
<ScrollContentPresenter.GestureRecognizers> <ScrollContentPresenter.GestureRecognizers>
<ScrollGestureRecognizer CanHorizontallyScroll="{Binding CanHorizontallyScroll, ElementName=PART_ContentPresenter}" <ScrollGestureRecognizer CanHorizontallyScroll="{Binding CanHorizontallyScroll, ElementName=PART_ContentPresenter}"
CanVerticallyScroll="{Binding CanVerticallyScroll, ElementName=PART_ContentPresenter}" CanVerticallyScroll="{Binding CanVerticallyScroll, ElementName=PART_ContentPresenter}"
IsScrollInertiaEnabled="{Binding IsScrollInertiaEnabled, RelativeSource={RelativeSource TemplatedParent}}" /> IsScrollInertiaEnabled="{Binding (ScrollViewer.IsScrollInertiaEnabled), ElementName=PART_ContentPresenter}"/>
</ScrollContentPresenter.GestureRecognizers> </ScrollContentPresenter.GestureRecognizers>
</ScrollContentPresenter> </ScrollContentPresenter>
<ScrollBar Name="PART_HorizontalScrollBar" <ScrollBar Name="PART_HorizontalScrollBar"

1
src/Avalonia.Themes.Simple/Controls/TextBox.xaml

@ -126,6 +126,7 @@
<ScrollViewer Grid.Column="1" <ScrollViewer Grid.Column="1"
Grid.ColumnSpan="1" Grid.ColumnSpan="1"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}" AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"
BringIntoViewOnFocusChange="{TemplateBinding (ScrollViewer.BringIntoViewOnFocusChange)}"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}" HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}" IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"> VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">

1
src/Avalonia.Themes.Simple/Controls/TreeView.xaml

@ -15,6 +15,7 @@
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}"> CornerRadius="{TemplateBinding CornerRadius}">
<ScrollViewer AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}" <ScrollViewer AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"
BringIntoViewOnFocusChange="{TemplateBinding (ScrollViewer.BringIntoViewOnFocusChange)}"
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}" HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}" IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}"

4
src/Headless/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs

@ -40,11 +40,11 @@ namespace Avalonia.Headless.Vnc
{ {
Window?.MouseMove(pt); Window?.MouseMove(pt);
foreach (var btn in CheckedButtons) foreach (var btn in CheckedButtons)
if (_previousButtons.HasAllFlags(btn) && !buttons.HasAllFlags(btn)) if (_previousButtons.HasFlag(btn) && !buttons.HasFlag(btn))
Window?.MouseUp(pt, TranslateButton(btn), modifiers); Window?.MouseUp(pt, TranslateButton(btn), modifiers);
foreach (var btn in CheckedButtons) foreach (var btn in CheckedButtons)
if (!_previousButtons.HasAllFlags(btn) && buttons.HasAllFlags(btn)) if (!_previousButtons.HasFlag(btn) && buttons.HasFlag(btn))
Window?.MouseDown(pt, TranslateButton(btn), modifiers); Window?.MouseDown(pt, TranslateButton(btn), modifiers);
_previousButtons = buttons; _previousButtons = buttons;
}, DispatcherPriority.Input); }, DispatcherPriority.Input);

33
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -7,7 +7,6 @@ using Avalonia.Media;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering.Utilities; using Avalonia.Rendering.Utilities;
using Avalonia.Utilities; using Avalonia.Utilities;
using Avalonia.Media.Imaging;
using SkiaSharp; using SkiaSharp;
using ISceneBrush = Avalonia.Media.ISceneBrush; using ISceneBrush = Avalonia.Media.ISceneBrush;
@ -26,7 +25,7 @@ namespace Avalonia.Skia
private readonly Stack<double> _opacityStack = new(); private readonly Stack<double> _opacityStack = new();
private readonly Matrix? _postTransform; private readonly Matrix? _postTransform;
private double _currentOpacity = 1.0f; private double _currentOpacity = 1.0f;
private readonly bool _canTextUseLcdRendering; private readonly bool _disableSubpixelTextRendering;
private Matrix _currentTransform; private Matrix _currentTransform;
private bool _disposed; private bool _disposed;
private GRContext? _grContext; private GRContext? _grContext;
@ -59,11 +58,11 @@ namespace Avalonia.Skia
/// Dpi of drawings. /// Dpi of drawings.
/// </summary> /// </summary>
public Vector Dpi; public Vector Dpi;
/// <summary> /// <summary>
/// Render text without Lcd rendering. /// Render text without subpixel antialiasing.
/// </summary> /// </summary>
public bool DisableTextLcdRendering; public bool DisableSubpixelTextRendering;
/// <summary> /// <summary>
/// GPU-accelerated context (optional) /// GPU-accelerated context (optional)
@ -135,7 +134,7 @@ namespace Avalonia.Skia
_dpi = createInfo.Dpi; _dpi = createInfo.Dpi;
_disposables = disposables; _disposables = disposables;
_canTextUseLcdRendering = !createInfo.DisableTextLcdRendering; _disableSubpixelTextRendering = createInfo.DisableSubpixelTextRendering;
_grContext = createInfo.GrContext; _grContext = createInfo.GrContext;
_gpu = createInfo.Gpu; _gpu = createInfo.Gpu;
if (_grContext != null) if (_grContext != null)
@ -519,7 +518,23 @@ namespace Avalonia.Skia
{ {
var glyphRunImpl = (GlyphRunImpl)glyphRun; 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, Canvas.DrawText(textBlob, (float)glyphRun.BaselineOrigin.X,
(float)glyphRun.BaselineOrigin.Y, paintWrapper.Paint); (float)glyphRun.BaselineOrigin.Y, paintWrapper.Paint);
@ -969,6 +984,7 @@ namespace Avalonia.Skia
using (var ctx = intermediate.CreateDrawingContext()) using (var ctx = intermediate.CreateDrawingContext())
{ {
ctx.RenderOptions = RenderOptions;
ctx.Clear(Colors.Transparent); ctx.Clear(Colors.Transparent);
content.Render(ctx, rect.TopLeft == default ? null : Matrix.CreateTranslation(-rect.X, -rect.Y)); content.Render(ctx, rect.TopLeft == default ? null : Matrix.CreateTranslation(-rect.X, -rect.Y));
} }
@ -997,6 +1013,7 @@ namespace Avalonia.Skia
using var pictureTarget = new PictureRenderTarget(_gpu, _grContext, _dpi); using var pictureTarget = new PictureRenderTarget(_gpu, _grContext, _dpi);
using (var ctx = pictureTarget.CreateDrawingContext(calc.IntermediateSize)) using (var ctx = pictureTarget.CreateDrawingContext(calc.IntermediateSize))
{ {
ctx.RenderOptions = RenderOptions;
ctx.PushClip(calc.IntermediateClip); ctx.PushClip(calc.IntermediateClip);
content.Render(ctx, transform); content.Render(ctx, transform);
ctx.PopClip(); ctx.PopClip();
@ -1283,7 +1300,7 @@ namespace Avalonia.Skia
Height = pixelSize.Height, Height = pixelSize.Height,
Dpi = _dpi, Dpi = _dpi,
Format = format, Format = format,
DisableTextLcdRendering = !_canTextUseLcdRendering, DisableTextLcdRendering = isLayer ? _disableSubpixelTextRendering : true,
GrContext = _grContext, GrContext = _grContext,
Gpu = _gpu, Gpu = _gpu,
Session = _session, Session = _session,

4
src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs

@ -3,7 +3,6 @@ using System.Diagnostics.CodeAnalysis;
using Avalonia.Reactive; using Avalonia.Reactive;
using Avalonia.Controls.Platform.Surfaces; using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering;
using SkiaSharp; using SkiaSharp;
namespace Avalonia.Skia namespace Avalonia.Skia
@ -54,8 +53,7 @@ namespace Avalonia.Skia
var createInfo = new DrawingContextImpl.CreateInfo var createInfo = new DrawingContextImpl.CreateInfo
{ {
Surface = _framebufferSurface, Surface = _framebufferSurface,
Dpi = framebuffer.Dpi, Dpi = framebuffer.Dpi
DisableTextLcdRendering = true
}; };
return new DrawingContextImpl(createInfo, _preFramebufferCopyHandler, canvas, framebuffer); return new DrawingContextImpl(createInfo, _preFramebufferCopyHandler, canvas, framebuffer);

1
src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs

@ -30,7 +30,6 @@ namespace Avalonia.Skia
GrContext = session.GrContext, GrContext = session.GrContext,
Surface = session.SkSurface, Surface = session.SkSurface,
Dpi = SkiaPlatform.DefaultDpi * session.ScaleFactor, Dpi = SkiaPlatform.DefaultDpi * session.ScaleFactor,
DisableTextLcdRendering = true,
Gpu = _skiaGpu, Gpu = _skiaGpu,
CurrentSession = session CurrentSession = session
}; };

2
src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs

@ -20,7 +20,7 @@ namespace Avalonia.Skia.Helpers
{ {
Canvas = canvas, Canvas = canvas,
Dpi = dpi, Dpi = dpi,
DisableTextLcdRendering = true, DisableSubpixelTextRendering = true,
}; };
return new DrawingContextImpl(createInfo); return new DrawingContextImpl(createInfo);

4
src/Skia/Avalonia.Skia/PictureRenderTarget.cs

@ -39,7 +39,7 @@ internal class PictureRenderTarget : IDisposable
{ {
Canvas = canvas, Canvas = canvas,
Dpi = _dpi, Dpi = _dpi,
DisableTextLcdRendering = true, DisableSubpixelTextRendering = true,
GrContext = _grContext, GrContext = _grContext,
Gpu = _gpu, Gpu = _gpu,
}; };
@ -52,4 +52,4 @@ internal class PictureRenderTarget : IDisposable
} }
public void Dispose() => _picture?.Dispose(); public void Dispose() => _picture?.Dispose();
} }

2
src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs

@ -106,7 +106,7 @@ namespace Avalonia.Skia
{ {
Surface = _surface.Surface, Surface = _surface.Surface,
Dpi = Dpi, Dpi = Dpi,
DisableTextLcdRendering = _disableLcdRendering, DisableSubpixelTextRendering = _disableLcdRendering,
GrContext = _grContext, GrContext = _grContext,
Gpu = _gpu, Gpu = _gpu,
}; };

8
src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs

@ -141,13 +141,13 @@ namespace Avalonia.Win32.Interop.Wpf
{ {
var state = Keyboard.Modifiers; var state = Keyboard.Modifiers;
var rv = default(RawInputModifiers); var rv = default(RawInputModifiers);
if (state.HasAllFlags(ModifierKeys.Windows)) if (state.HasFlag(ModifierKeys.Windows))
rv |= RawInputModifiers.Meta; rv |= RawInputModifiers.Meta;
if (state.HasAllFlags(ModifierKeys.Alt)) if (state.HasFlag(ModifierKeys.Alt))
rv |= RawInputModifiers.Alt; rv |= RawInputModifiers.Alt;
if (state.HasAllFlags(ModifierKeys.Control)) if (state.HasFlag(ModifierKeys.Control))
rv |= RawInputModifiers.Control; rv |= RawInputModifiers.Control;
if (state.HasAllFlags(ModifierKeys.Shift)) if (state.HasFlag(ModifierKeys.Shift))
rv |= RawInputModifiers.Shift; rv |= RawInputModifiers.Shift;
if (e != null) if (e != null)
{ {

20
tests/Avalonia.Base.UnitTests/Input/KeyboardNavigationTests_Tab.cs

@ -1,6 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.UnitTests;
using Xunit; using Xunit;
namespace Avalonia.Base.UnitTests.Input namespace Avalonia.Base.UnitTests.Input
@ -1253,5 +1254,24 @@ namespace Avalonia.Base.UnitTests.Input
Assert.Same(expected, result); 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);
}
} }
} }

71
tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs

@ -358,6 +358,77 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(100, thumb.Bounds.Top); 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<ScrollViewer>(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<ScrollViewer>(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) private Point GetRootPoint(Visual control, Point p)
{ {
if (control.GetVisualRoot() is Visual root && if (control.GetVisualRoot() is Visual root &&

60
tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs

@ -646,7 +646,7 @@ namespace Avalonia.Controls.UnitTests
{ {
// Issue #11272 // Issue #11272
using var app = App(); using var app = App();
var (_, _, itemsControl) = CreateUnrootedTarget(); var (_, _, itemsControl) = CreateUnrootedTarget<ItemsControl>();
var container = new Decorator { Margin = new Thickness(100) }; var container = new Decorator { Margin = new Thickness(100) };
var root = new TestRoot(true, container); var root = new TestRoot(true, container);
@ -657,11 +657,49 @@ namespace Avalonia.Controls.UnitTests
root.LayoutManager.ExecuteLayoutPass(); root.LayoutManager.ExecuteLayoutPass();
} }
[Fact]
public void Supports_Null_Recycle_Key_When_Scrolling()
{
using var app = App();
var (_, scroll, itemsControl) = CreateUnrootedTarget<NonRecyclingItemsControl>();
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<NonRecyclingItemsControl>();
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] [Fact]
public void ScrollIntoView_On_Effectively_Invisible_Panel_Does_Not_Create_Ghost_Elements() public void ScrollIntoView_On_Effectively_Invisible_Panel_Does_Not_Create_Ghost_Elements()
{ {
var items = new[] { "foo", "bar", "baz" }; var items = new[] { "foo", "bar", "baz" };
var (target, _, itemsControl) = CreateUnrootedTarget(items: items); var (target, _, itemsControl) = CreateUnrootedTarget<ItemsControl>(items: items);
var container = new Decorator { Margin = new Thickness(100), Child = itemsControl }; var container = new Decorator { Margin = new Thickness(100), Child = itemsControl };
var root = new TestRoot(true, container); var root = new TestRoot(true, container);
@ -738,7 +776,7 @@ namespace Avalonia.Controls.UnitTests
Optional<IDataTemplate?> itemTemplate = default, Optional<IDataTemplate?> itemTemplate = default,
IEnumerable<Style>? styles = null) IEnumerable<Style>? styles = null)
{ {
var (target, scroll, itemsControl) = CreateUnrootedTarget(items, itemTemplate); var (target, scroll, itemsControl) = CreateUnrootedTarget<ItemsControl>(items, itemTemplate);
var root = CreateRoot(itemsControl, styles); var root = CreateRoot(itemsControl, styles);
root.LayoutManager.ExecuteInitialLayoutPass(); root.LayoutManager.ExecuteInitialLayoutPass();
@ -746,9 +784,10 @@ namespace Avalonia.Controls.UnitTests
return (target, scroll, itemsControl); return (target, scroll, itemsControl);
} }
private static (VirtualizingStackPanel, ScrollViewer, ItemsControl) CreateUnrootedTarget( private static (VirtualizingStackPanel, ScrollViewer, T) CreateUnrootedTarget<T>(
IEnumerable<object>? items = null, IEnumerable<object>? items = null,
Optional<IDataTemplate?> itemTemplate = default) Optional<IDataTemplate?> itemTemplate = default)
where T : ItemsControl, new()
{ {
var target = new VirtualizingStackPanel(); var target = new VirtualizingStackPanel();
@ -766,7 +805,7 @@ namespace Avalonia.Controls.UnitTests
Template = ScrollViewerTemplate(), Template = ScrollViewerTemplate(),
}; };
var itemsControl = new ItemsControl var itemsControl = new T
{ {
ItemsSource = items, ItemsSource = items,
Template = new FuncControlTemplate<ItemsControl>((_, ns) => scroll.RegisterInNameScope(ns)), Template = new FuncControlTemplate<ItemsControl>((_, ns) => scroll.RegisterInNameScope(ns)),
@ -840,5 +879,16 @@ namespace Avalonia.Controls.UnitTests
public event NotifyCollectionChangedEventHandler? CollectionChanged; public event NotifyCollectionChangedEventHandler? CollectionChanged;
} }
private class NonRecyclingItemsControl : ItemsControl
{
protected override Type StyleKeyOverride => typeof(ItemsControl);
protected internal override bool NeedsContainerOverride(object? item, int index, out object? recycleKey)
{
recycleKey = null;
return true;
}
}
} }
} }

9
tests/Avalonia.IntegrationTests.Appium/ButtonTests.cs

@ -27,6 +27,15 @@ namespace Avalonia.IntegrationTests.Appium
Assert.False(button.Enabled); Assert.False(button.Enabled);
} }
[Fact]
public void EffectivelyDisabledButton()
{
var button = _session.FindElementByAccessibilityId("EffectivelyDisabledButton");
Assert.Equal("Effectively Disabled Button", button.Text);
Assert.False(button.Enabled);
}
[Fact] [Fact]
public void BasicButton() public void BasicButton()
{ {

6
tests/Avalonia.IntegrationTests.Appium/PlatformFactAttribute.cs

@ -44,11 +44,11 @@ namespace Avalonia
private bool IsSupported() private bool IsSupported()
{ {
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return Platforms.HasAnyFlag(TestPlatforms.Windows); return Platforms.HasFlag(TestPlatforms.Windows);
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
return Platforms.HasAnyFlag(TestPlatforms.MacOS); return Platforms.HasFlag(TestPlatforms.MacOS);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
return Platforms.HasAnyFlag(TestPlatforms.Linux); return Platforms.HasFlag(TestPlatforms.Linux);
return false; return false;
} }
} }

4
tests/Avalonia.IntegrationTests.Appium/PlatformTheoryAttribute.cs

@ -27,9 +27,9 @@ namespace Avalonia.IntegrationTests.Appium
private bool IsSupported() private bool IsSupported()
{ {
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return Platforms.HasAnyFlag(TestPlatforms.Windows); return Platforms.HasFlag(TestPlatforms.Windows);
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
return Platforms.HasAnyFlag(TestPlatforms.MacOS); return Platforms.HasFlag(TestPlatforms.MacOS);
return false; return false;
} }
} }

Loading…
Cancel
Save