Browse Source

Merge branch 'master' into fixes/Issue_6263

pull/11418/head
workgroupengineering 3 years ago
committed by GitHub
parent
commit
9f1e807716
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      packages/Avalonia/Avalonia.csproj
  2. 3
      samples/ControlCatalog/Pages/ColorPickerPage.xaml
  3. 18
      src/Avalonia.Base/Diagnostics/AppliedStyle.cs
  4. 4
      src/Avalonia.Base/Diagnostics/StyleDiagnostics.cs
  5. 4
      src/Avalonia.Base/Input/AccessKeyHandler.cs
  6. 56
      src/Avalonia.Base/Input/FocusManager.cs
  7. 36
      src/Avalonia.Base/Input/IFocusManager.cs
  8. 3
      src/Avalonia.Base/Input/IFocusScope.cs
  9. 4
      src/Avalonia.Base/Input/IInputElement.cs
  10. 8
      src/Avalonia.Base/Input/IInputRoot.cs
  11. 8
      src/Avalonia.Base/Input/IKeyboardDevice.cs
  12. 17
      src/Avalonia.Base/Input/InputElement.cs
  13. 4
      src/Avalonia.Base/Input/KeyboardDevice.cs
  14. 4
      src/Avalonia.Base/Input/KeyboardNavigationHandler.cs
  15. 2
      src/Avalonia.Base/Input/MouseDevice.cs
  16. 6
      src/Avalonia.Base/Input/Navigation/TabNavigation.cs
  17. 2
      src/Avalonia.Base/Input/PenDevice.cs
  18. 2
      src/Avalonia.Base/Input/TouchDevice.cs
  19. 32
      src/Avalonia.Base/Media/Color.cs
  20. 78
      src/Avalonia.Base/Media/HslColor.cs
  21. 78
      src/Avalonia.Base/Media/HsvColor.cs
  22. 4
      src/Avalonia.Base/StyledElement.cs
  23. 3
      src/Avalonia.Base/Styling/Activators/IStyleActivator.cs
  24. 3
      src/Avalonia.Base/Styling/Activators/IStyleActivatorSink.cs
  25. 12
      src/Avalonia.Base/Styling/ChildSelector.cs
  26. 12
      src/Avalonia.Base/Styling/DescendentSelector.cs
  27. 24
      src/Avalonia.Base/Styling/ISetter.cs
  28. 2
      src/Avalonia.Base/Styling/ISetterInstance.cs
  29. 4
      src/Avalonia.Base/Styling/ISetterValue.cs
  30. 3
      src/Avalonia.Base/Styling/IStyleInstance.cs
  31. 12
      src/Avalonia.Base/Styling/NestingSelector.cs
  32. 12
      src/Avalonia.Base/Styling/NotSelector.cs
  33. 14
      src/Avalonia.Base/Styling/NthChildSelector.cs
  34. 2
      src/Avalonia.Base/Styling/NthLastChildSelector.cs
  35. 12
      src/Avalonia.Base/Styling/OrSelector.cs
  36. 12
      src/Avalonia.Base/Styling/PropertyEqualsSelector.cs
  37. 14
      src/Avalonia.Base/Styling/Selector.cs
  38. 4
      src/Avalonia.Base/Styling/SelectorMatch.cs
  39. 4
      src/Avalonia.Base/Styling/Setter.cs
  40. 12
      src/Avalonia.Base/Styling/SetterBase.cs
  41. 6
      src/Avalonia.Base/Styling/StyleBase.cs
  42. 12
      src/Avalonia.Base/Styling/TemplateSelector.cs
  43. 12
      src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs
  44. 72
      src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.Properties.cs
  45. 88
      src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs
  46. 91
      src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs
  47. 6
      src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs
  48. 60
      src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs
  49. 2
      src/Avalonia.Controls.ColorPicker/Helpers/Hsv.cs
  50. 2
      src/Avalonia.Controls.ColorPicker/Helpers/Rgb.cs
  51. 25
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml
  52. 13
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml
  53. 25
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml
  54. 25
      src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml
  55. 13
      src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPreviewer.xaml
  56. 25
      src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml
  57. 5
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  58. 2
      src/Avalonia.Controls.ItemsRepeater/Controls/ViewManager.cs
  59. 2
      src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs
  60. 2
      src/Avalonia.Controls/Calendar/Calendar.cs
  61. 2
      src/Avalonia.Controls/Calendar/CalendarItem.cs
  62. 3
      src/Avalonia.Controls/ComboBox.cs
  63. 6
      src/Avalonia.Controls/ContextMenu.cs
  64. 2
      src/Avalonia.Controls/Control.cs
  65. 11
      src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs
  66. 6
      src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs
  67. 4
      src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs
  68. 11
      src/Avalonia.Controls/ItemsControl.cs
  69. 6
      src/Avalonia.Controls/Primitives/Popup.cs
  70. 5
      src/Avalonia.Controls/TopLevel.cs
  71. 3
      src/Avalonia.Controls/TreeView.cs
  72. 2
      src/Avalonia.Controls/TreeViewItem.cs
  73. 2
      src/Avalonia.Controls/VirtualizingStackPanel.cs
  74. 4
      src/Avalonia.Controls/WindowBase.cs
  75. 8
      src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
  76. 2
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
  77. 4
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/StyleViewModel.cs
  78. 2
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
  79. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs
  80. 4
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  81. 6
      src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs
  82. 2
      src/Markup/Avalonia.Markup/Data/TemplateBinding.cs
  83. 2
      src/Windows/Avalonia.Win32.Interop/WinForms/WinFormsAvaloniaControlHost.cs
  84. 2
      src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs
  85. 58
      tests/Avalonia.Base.UnitTests/Input/InputElement_Focus.cs
  86. 4
      tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs
  87. 29
      tests/Avalonia.Base.UnitTests/Media/ColorTests.cs
  88. 36
      tests/Avalonia.Benchmarks/Styling/SelectorBenchmark.cs
  89. 10
      tests/Avalonia.Controls.UnitTests/FlyoutTests.cs
  90. 6
      tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
  91. 8
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  92. 4
      tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs
  93. 11
      tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
  94. 8
      tests/Avalonia.Controls.UnitTests/TabControlTests.cs
  95. 16
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
  96. 34
      tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs
  97. 4
      tests/Avalonia.LeakTests/ControlTests.cs
  98. 4
      tests/Avalonia.UnitTests/StyleHelpers.cs
  99. 1
      tests/Avalonia.UnitTests/TestRoot.cs

2
packages/Avalonia/Avalonia.csproj

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

3
samples/ControlCatalog/Pages/ColorPickerPage.xaml

@ -103,7 +103,8 @@
HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}" />-->
<ColorPreviewer Grid.Row="8"
IsAccentColorsVisible="False"
HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}" />
HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}"
Margin="0,2,0,0" />
</Grid>
</Grid>
</UserControl>

18
src/Avalonia.Base/Diagnostics/AppliedStyle.cs

@ -0,0 +1,18 @@
using Avalonia.Styling;
namespace Avalonia.Diagnostics
{
public class AppliedStyle
{
private readonly IStyleInstance _instance;
internal AppliedStyle(IStyleInstance instance)
{
_instance = instance;
}
public bool HasActivator => _instance.HasActivator;
public bool IsActive => _instance.IsActive;
public StyleBase Style => (StyleBase)_instance.Source;
}
}

4
src/Avalonia.Base/Diagnostics/StyleDiagnostics.cs

@ -11,9 +11,9 @@ namespace Avalonia.Diagnostics
/// <summary>
/// Currently applied styles.
/// </summary>
public IReadOnlyList<IStyleInstance> AppliedStyles { get; }
public IReadOnlyList<AppliedStyle> AppliedStyles { get; }
public StyleDiagnostics(IReadOnlyList<IStyleInstance> appliedStyles)
public StyleDiagnostics(IReadOnlyList<AppliedStyle> appliedStyles)
{
AppliedStyles = appliedStyles;
}

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

@ -141,9 +141,11 @@ namespace Avalonia.Input
if (MainMenu == null || !MainMenu.IsOpen)
{
var focusManager = FocusManager.GetFocusManager(e.Source as IInputElement);
// TODO: Use FocusScopes to store the current element and restore it when context menu is closed.
// Save currently focused input element.
_restoreFocusElement = FocusManager.Instance?.Current;
_restoreFocusElement = focusManager?.GetFocusedElement();
// When Alt is pressed without a main menu, or with a closed main menu, show
// access key markers in the window (i.e. "_File").

56
src/Avalonia.Base/Input/FocusManager.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using Avalonia.Interactivity;
using Avalonia.Metadata;
using Avalonia.VisualTree;
namespace Avalonia.Input
@ -10,6 +11,7 @@ namespace Avalonia.Input
/// <summary>
/// Manages focus for the application.
/// </summary>
[PrivateApi]
public class FocusManager : IFocusManager
{
/// <summary>
@ -29,15 +31,12 @@ namespace Avalonia.Input
RoutingStrategies.Tunnel);
}
/// <summary>
/// Gets the instance of the <see cref="IFocusManager"/>.
/// </summary>
public static IFocusManager? Instance => AvaloniaLocator.Current.GetService<IFocusManager>();
private IInputElement? Current => KeyboardDevice.Instance?.FocusedElement;
/// <summary>
/// Gets the currently focused <see cref="IInputElement"/>.
/// </summary>
public IInputElement? Current => KeyboardDevice.Instance?.FocusedElement;
public IInputElement? GetFocusedElement() => Current;
/// <summary>
/// Gets the current focus scope.
@ -54,7 +53,7 @@ namespace Avalonia.Input
/// <param name="control">The control to focus.</param>
/// <param name="method">The method by which focus was changed.</param>
/// <param name="keyModifiers">Any key modifiers active at the time of focus.</param>
public void Focus(
public bool Focus(
IInputElement? control,
NavigationMethod method = NavigationMethod.Unspecified,
KeyModifiers keyModifiers = KeyModifiers.None)
@ -67,7 +66,7 @@ namespace Avalonia.Input
if (scope != null)
{
Scope = scope;
SetFocusedElement(scope, control, method, keyModifiers);
return SetFocusedElement(scope, control, method, keyModifiers);
}
}
else if (Current != null)
@ -79,28 +78,29 @@ namespace Avalonia.Input
_focusScopes.TryGetValue(scope, out var element) &&
element != null)
{
Focus(element, method);
return;
return Focus(element, method);
}
}
if (Scope is object)
{
// Couldn't find a focus scope, clear focus.
SetFocusedElement(Scope, null);
return SetFocusedElement(Scope, null);
}
}
return false;
}
public IInputElement? GetFocusedElement(IInputElement e)
public void ClearFocus()
{
if (e is IFocusScope scope)
{
_focusScopes.TryGetValue(scope, out var result);
return result;
}
Focus(null);
}
return null;
public IInputElement? GetFocusedElement(IFocusScope scope)
{
_focusScopes.TryGetValue(scope, out var result);
return result;
}
/// <summary>
@ -114,7 +114,7 @@ namespace Avalonia.Input
/// If the specified scope is the current <see cref="Scope"/> then the keyboard focus
/// will change.
/// </remarks>
public void SetFocusedElement(
public bool SetFocusedElement(
IFocusScope scope,
IInputElement? element,
NavigationMethod method = NavigationMethod.Unspecified,
@ -124,7 +124,7 @@ namespace Avalonia.Input
if (element is not null && !CanFocus(element))
{
return;
return false;
}
if (_focusScopes.TryGetValue(scope, out var existingElement))
@ -144,6 +144,8 @@ namespace Avalonia.Input
{
KeyboardDevice.Instance?.SetFocusedElement(element, method, keyModifiers);
}
return true;
}
/// <summary>
@ -185,6 +187,20 @@ namespace Avalonia.Input
public static bool GetIsFocusScope(IInputElement e) => e is IFocusScope;
/// <summary>
/// Public API customers should use TopLevel.GetTopLevel(control).FocusManager.
/// But since we have split projects, we can't access TopLevel from Avalonia.Base.
/// That's why we need this helper method instead.
/// </summary>
internal static FocusManager? GetFocusManager(IInputElement? element)
{
// Element might not be a visual, and not attached to the root.
// But IFocusManager is always expected to be a FocusManager.
return (FocusManager?)((element as Visual)?.VisualRoot as IInputRoot)?.FocusManager
// In our unit tests some elements might not have a root. Remove when we migrate to headless tests.
?? (FocusManager?)AvaloniaLocator.Current.GetService<IFocusManager>();
}
/// <summary>
/// Checks if the specified element can be focused.
/// </summary>
@ -237,7 +253,7 @@ namespace Avalonia.Input
{
if (element is IInputElement inputElement && CanFocus(inputElement))
{
Instance?.Focus(inputElement, NavigationMethod.Pointer, ev.KeyModifiers);
inputElement.Focus(NavigationMethod.Pointer, ev.KeyModifiers);
break;
}

36
src/Avalonia.Base/Input/IFocusManager.cs

@ -11,40 +11,12 @@ namespace Avalonia.Input
/// <summary>
/// Gets the currently focused <see cref="IInputElement"/>.
/// </summary>
IInputElement? Current { get; }
IInputElement? GetFocusedElement();
/// <summary>
/// Gets the current focus scope.
/// Clears currently focused element.
/// </summary>
IFocusScope? Scope { get; }
/// <summary>
/// Focuses a control.
/// </summary>
/// <param name="control">The control to focus.</param>
/// <param name="method">The method by which focus was changed.</param>
/// <param name="keyModifiers">Any key modifiers active at the time of focus.</param>
void Focus(
IInputElement? control,
NavigationMethod method = NavigationMethod.Unspecified,
KeyModifiers keyModifiers = KeyModifiers.None);
/// <summary>
/// Notifies the focus manager of a change in focus scope.
/// </summary>
/// <param name="scope">The new focus scope.</param>
/// <remarks>
/// This should not be called by client code. It is called by an <see cref="IFocusScope"/>
/// when it activates, e.g. when a Window is activated.
/// </remarks>
void SetFocusScope(IFocusScope scope);
/// <summary>
/// Notifies the focus manager that a focus scope has been removed.
/// </summary>
/// <param name="scope">The focus scope to be removed.</param>
/// This should not be called by client code. It is called by an <see cref="IFocusScope"/>
/// when it deactivates or closes, e.g. when a Window is closed.
void RemoveFocusScope(IFocusScope scope);
[Unstable("This API might be removed in 11.x minor updates. Please consider focusing another element instead of removing focus at all for better UX.")]
void ClearFocus();
}
}

3
src/Avalonia.Base/Input/IFocusScope.cs

@ -1,5 +1,8 @@
using Avalonia.Metadata;
namespace Avalonia.Input
{
[NotClientImplementable]
public interface IFocusScope
{
}

4
src/Avalonia.Base/Input/IInputElement.cs

@ -119,7 +119,9 @@ namespace Avalonia.Input
/// <summary>
/// Focuses the control.
/// </summary>
void Focus();
/// <param name="method">The method by which focus was changed.</param>
/// <param name="keyModifiers">Any key modifiers active at the time of focus.</param>
bool Focus(NavigationMethod method = NavigationMethod.Unspecified, KeyModifiers keyModifiers = KeyModifiers.None);
/// <summary>
/// Gets the key bindings for the element.

8
src/Avalonia.Base/Input/IInputRoot.cs

@ -18,6 +18,14 @@ namespace Avalonia.Input
/// </summary>
IKeyboardNavigationHandler KeyboardNavigationHandler { get; }
/// <summary>
/// Gets focus manager of the root.
/// </summary>
/// <remarks>
/// Focus manager can be null only if application wasn't initialized yet.
/// </remarks>
IFocusManager? FocusManager { get; }
/// <summary>
/// Gets or sets the input element that the pointer is currently over.
/// </summary>

8
src/Avalonia.Base/Input/IKeyboardDevice.cs

@ -44,13 +44,7 @@ namespace Avalonia.Input
}
[NotClientImplementable]
public interface IKeyboardDevice : IInputDevice, INotifyPropertyChanged
public interface IKeyboardDevice : IInputDevice
{
IInputElement? FocusedElement { get; }
void SetFocusedElement(
IInputElement? element,
NavigationMethod method,
KeyModifiers modifiers);
}
}

17
src/Avalonia.Base/Input/InputElement.cs

@ -458,9 +458,10 @@ namespace Avalonia.Input
SetAndRaise(IsEffectivelyEnabledProperty, ref _isEffectivelyEnabled, value);
PseudoClasses.Set(":disabled", !value);
if (!IsEffectivelyEnabled && FocusManager.Instance?.Current == this)
if (!IsEffectivelyEnabled && FocusManager.GetFocusManager(this) is {} focusManager
&& Equals(focusManager.GetFocusedElement(), this))
{
FocusManager.Instance?.Focus(null);
focusManager.ClearFocus();
}
}
}
@ -491,12 +492,10 @@ namespace Avalonia.Input
public GestureRecognizerCollection GestureRecognizers
=> _gestureRecognizers ?? (_gestureRecognizers = new GestureRecognizerCollection(this));
/// <summary>
/// Focuses the control.
/// </summary>
public void Focus()
/// <inheritdoc />
public bool Focus(NavigationMethod method = NavigationMethod.Unspecified, KeyModifiers keyModifiers = KeyModifiers.None)
{
FocusManager.Instance?.Focus(this);
return FocusManager.GetFocusManager(this)?.Focus(this, method, keyModifiers) ?? false;
}
/// <inheritdoc/>
@ -506,7 +505,7 @@ namespace Avalonia.Input
if (IsFocused)
{
FocusManager.Instance?.Focus(null);
FocusManager.GetFocusManager(this)?.ClearFocus();
}
}
@ -649,7 +648,7 @@ namespace Avalonia.Input
}
else if (change.Property == IsVisibleProperty && !change.GetNewValue<bool>() && IsFocused)
{
FocusManager.Instance?.Focus(null);
FocusManager.GetFocusManager(this)?.ClearFocus();
}
}

4
src/Avalonia.Base/Input/KeyboardDevice.cs

@ -3,9 +3,11 @@ using System.Runtime.CompilerServices;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Avalonia.Interactivity;
using Avalonia.Metadata;
namespace Avalonia.Input
{
[PrivateApi]
public class KeyboardDevice : IKeyboardDevice, INotifyPropertyChanged
{
private IInputElement? _focusedElement;
@ -13,7 +15,7 @@ namespace Avalonia.Input
public event PropertyChangedEventHandler? PropertyChanged;
public static IKeyboardDevice? Instance => AvaloniaLocator.Current.GetService<IKeyboardDevice>();
internal static KeyboardDevice? Instance => AvaloniaLocator.Current.GetService<IKeyboardDevice>() as KeyboardDevice;
public IInputManager? InputManager => AvaloniaLocator.Current.GetService<IInputManager>();

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

@ -88,7 +88,7 @@ namespace Avalonia.Input
var method = direction == NavigationDirection.Next ||
direction == NavigationDirection.Previous ?
NavigationMethod.Tab : NavigationMethod.Directional;
FocusManager.Instance?.Focus(next, method, keyModifiers);
next.Focus(method, keyModifiers);
}
}
@ -99,7 +99,7 @@ namespace Avalonia.Input
/// <param name="e">The event args.</param>
protected virtual void OnKeyDown(object? sender, KeyEventArgs e)
{
var current = FocusManager.Instance?.Current;
var current = FocusManager.GetFocusManager(e.Source as IInputElement)?.GetFocusedElement();
if (current != null && e.Key == Key.Tab)
{

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

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using Avalonia.Reactive;
using Avalonia.Input.Raw;
using Avalonia.Metadata;
using Avalonia.Platform;
using Avalonia.Utilities;
#pragma warning disable CS0618
@ -11,6 +12,7 @@ namespace Avalonia.Input
/// <summary>
/// Represents a mouse device.
/// </summary>
[PrivateApi]
public class MouseDevice : IMouseDevice, IDisposable
{
private int _clickCount;

6
src/Avalonia.Base/Input/Navigation/TabNavigation.cs

@ -190,9 +190,11 @@ namespace Avalonia.Input.Navigation
private static IInputElement? FocusedElement(IInputElement? e)
{
// Focus delegation is enabled only if keyboard focus is outside the container
if (e != null && !e.IsKeyboardFocusWithin)
if (e != null && !e.IsKeyboardFocusWithin && e is IFocusScope scope)
{
var focusedElement = (FocusManager.Instance as FocusManager)?.GetFocusedElement(e);
var focusManager = FocusManager.GetFocusManager(e);
var focusedElement = focusManager?.GetFocusedElement(scope);
if (focusedElement != null)
{
if (!IsFocusScope(e))

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

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Avalonia.Input.Raw;
using Avalonia.Metadata;
using Avalonia.Platform;
#pragma warning disable CS0618
@ -11,6 +12,7 @@ namespace Avalonia.Input
/// <summary>
/// Represents a pen/stylus device.
/// </summary>
[PrivateApi]
public class PenDevice : IPenDevice, IDisposable
{
private readonly Dictionary<long, Pointer> _pointers = new();

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

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Avalonia.Input.Raw;
using Avalonia.Metadata;
using Avalonia.Platform;
#pragma warning disable CS0618
@ -14,6 +15,7 @@ namespace Avalonia.Input
/// <remarks>
/// This class is supposed to be used on per-toplevel basis, don't use a shared one
/// </remarks>
[PrivateApi]
public class TouchDevice : IPointerDevice, IDisposable
{
private readonly Dictionary<long, Pointer> _pointers = new Dictionary<long, Pointer>();

32
src/Avalonia.Base/Media/Color.cs

@ -478,7 +478,6 @@ namespace Avalonia.Media
/// <returns>The HSL equivalent color.</returns>
public HslColor ToHsl()
{
// Don't use the HslColor(Color) constructor to avoid an extra HslColor
return Color.ToHsl(R, G, B, A);
}
@ -488,7 +487,6 @@ namespace Avalonia.Media
/// <returns>The HSV equivalent color.</returns>
public HsvColor ToHsv()
{
// Don't use the HsvColor(Color) constructor to avoid an extra HsvColor
return Color.ToHsv(R, G, B, A);
}
@ -517,21 +515,6 @@ namespace Avalonia.Media
}
}
/// <summary>
/// Converts the given RGB color to its HSL color equivalent.
/// </summary>
/// <param name="color">The color in the RGB color model.</param>
/// <returns>A new <see cref="HslColor"/> equivalent to the given RGBA values.</returns>
public static HslColor ToHsl(Color color)
{
// Normalize RGBA components into the 0..1 range
return Color.ToHsl(
(byteToDouble * color.R),
(byteToDouble * color.G),
(byteToDouble * color.B),
(byteToDouble * color.A));
}
/// <summary>
/// Converts the given RGBA color component values to their HSL color equivalent.
/// </summary>
@ -606,21 +589,6 @@ namespace Avalonia.Media
return new HslColor(a, 60 * h1, saturation, lightness, clampValues: false);
}
/// <summary>
/// Converts the given RGB color to its HSV color equivalent.
/// </summary>
/// <param name="color">The color in the RGB color model.</param>
/// <returns>A new <see cref="HsvColor"/> equivalent to the given RGBA values.</returns>
public static HsvColor ToHsv(Color color)
{
// Normalize RGBA components into the 0..1 range
return Color.ToHsv(
(byteToDouble * color.R),
(byteToDouble * color.G),
(byteToDouble * color.B),
(byteToDouble * color.A));
}
/// <summary>
/// Converts the given RGBA color component values to their HSV color equivalent.
/// </summary>

78
src/Avalonia.Base/Media/HslColor.cs

@ -90,7 +90,7 @@ namespace Avalonia.Media
/// <param name="color">The RGB color to convert to HSL.</param>
public HslColor(Color color)
{
var hsl = Color.ToHsl(color);
var hsl = color.ToHsl();
A = hsl.A;
H = hsl.H;
@ -165,10 +165,18 @@ namespace Avalonia.Media
/// <returns>The RGB equivalent color.</returns>
public Color ToRgb()
{
// Use the by-component conversion method directly for performance
return HslColor.ToRgb(H, S, L, A);
}
/// <summary>
/// Returns the HSV color model equivalent of this HSL color.
/// </summary>
/// <returns>The HSV equivalent color.</returns>
public HsvColor ToHsv()
{
return HslColor.ToHsv(H, S, L, A);
}
/// <inheritdoc/>
public override string ToString()
{
@ -349,16 +357,6 @@ namespace Avalonia.Media
return new HslColor(1.0, h, s, l);
}
/// <summary>
/// Converts the given HSL color to its RGB color equivalent.
/// </summary>
/// <param name="hslColor">The color in the HSL color model.</param>
/// <returns>A new RGB <see cref="Color"/> equivalent to the given HSLA values.</returns>
public static Color ToRgb(HslColor hslColor)
{
return HslColor.ToRgb(hslColor.H, hslColor.S, hslColor.L, hslColor.A);
}
/// <summary>
/// Converts the given HSLA color component values to their RGB color equivalent.
/// </summary>
@ -442,13 +440,67 @@ namespace Avalonia.Media
b1 = x;
}
return Color.FromArgb(
return new Color(
(byte)Math.Round(255 * alpha),
(byte)Math.Round(255 * (r1 + m)),
(byte)Math.Round(255 * (g1 + m)),
(byte)Math.Round(255 * (b1 + m)));
}
/// <summary>
/// Converts the given HSLA color component values to their HSV color equivalent.
/// </summary>
/// <param name="hue">The Hue component in the HSL color model in the range from 0..360.</param>
/// <param name="saturation">The Saturation component in the HSL color model in the range from 0..1.</param>
/// <param name="lightness">The Lightness component in the HSL color model in the range from 0..1.</param>
/// <param name="alpha">The Alpha component in the range from 0..1.</param>
/// <returns>A new <see cref="HsvColor"/> equivalent to the given HSLA values.</returns>
public static HsvColor ToHsv(
double hue,
double saturation,
double lightness,
double alpha = 1.0)
{
// We want the hue to be between 0 and 359,
// so we first ensure that that's the case.
while (hue >= 360.0)
{
hue -= 360.0;
}
while (hue < 0.0)
{
hue += 360.0;
}
// We similarly clamp saturation, lightness and alpha between 0 and 1.
saturation = saturation < 0.0 ? 0.0 : saturation;
saturation = saturation > 1.0 ? 1.0 : saturation;
lightness = lightness < 0.0 ? 0.0 : lightness;
lightness = lightness > 1.0 ? 1.0 : lightness;
alpha = alpha < 0.0 ? 0.0 : alpha;
alpha = alpha > 1.0 ? 1.0 : alpha;
// The conversion algorithm is from the below link
// https://en.wikipedia.org/wiki/HSL_and_HSV#Interconversion
double s;
double v = lightness + (saturation * Math.Min(lightness, 1.0 - lightness));
if (v <= 0)
{
s = 0;
}
else
{
s = 2.0 * (1.0 - (lightness / v));
}
return new HsvColor(alpha, hue, s, v);
}
/// <summary>
/// Indicates whether the values of two specified <see cref="HslColor"/> objects are equal.
/// </summary>

78
src/Avalonia.Base/Media/HsvColor.cs

@ -90,7 +90,7 @@ namespace Avalonia.Media
/// <param name="color">The RGB color to convert to HSV.</param>
public HsvColor(Color color)
{
var hsv = Color.ToHsv(color);
var hsv = color.ToHsv();
A = hsv.A;
H = hsv.H;
@ -195,10 +195,18 @@ namespace Avalonia.Media
/// <returns>The RGB equivalent color.</returns>
public Color ToRgb()
{
// Use the by-component conversion method directly for performance
return HsvColor.ToRgb(H, S, V, A);
}
/// <summary>
/// Returns the HSL color model equivalent of this HSV color.
/// </summary>
/// <returns>The HSL equivalent color.</returns>
public HslColor ToHsl()
{
return HsvColor.ToHsl(H, S, V, A);
}
/// <inheritdoc/>
public override string ToString()
{
@ -379,16 +387,6 @@ namespace Avalonia.Media
return new HsvColor(1.0, h, s, v);
}
/// <summary>
/// Converts the given HSV color to its RGB color equivalent.
/// </summary>
/// <param name="hsvColor">The color in the HSV color model.</param>
/// <returns>A new RGB <see cref="Color"/> equivalent to the given HSVA values.</returns>
public static Color ToRgb(HsvColor hsvColor)
{
return HsvColor.ToRgb(hsvColor.H, hsvColor.S, hsvColor.V, hsvColor.A);
}
/// <summary>
/// Converts the given HSVA color component values to their RGB color equivalent.
/// </summary>
@ -520,13 +518,67 @@ namespace Avalonia.Media
break;
}
return Color.FromArgb(
return new Color(
(byte)Math.Round(alpha * 255),
(byte)Math.Round(r * 255),
(byte)Math.Round(g * 255),
(byte)Math.Round(b * 255));
}
/// <summary>
/// Converts the given HSVA color component values to their HSL color equivalent.
/// </summary>
/// <param name="hue">The Hue component in the HSV color model in the range from 0..360.</param>
/// <param name="saturation">The Saturation component in the HSV color model in the range from 0..1.</param>
/// <param name="value">The Value component in the HSV color model in the range from 0..1.</param>
/// <param name="alpha">The Alpha component in the range from 0..1.</param>
/// <returns>A new <see cref="HslColor"/> equivalent to the given HSVA values.</returns>
public static HslColor ToHsl(
double hue,
double saturation,
double value,
double alpha = 1.0)
{
// We want the hue to be between 0 and 359,
// so we first ensure that that's the case.
while (hue >= 360.0)
{
hue -= 360.0;
}
while (hue < 0.0)
{
hue += 360.0;
}
// We similarly clamp saturation, value and alpha between 0 and 1.
saturation = saturation < 0.0 ? 0.0 : saturation;
saturation = saturation > 1.0 ? 1.0 : saturation;
value = value < 0.0 ? 0.0 : value;
value = value > 1.0 ? 1.0 : value;
alpha = alpha < 0.0 ? 0.0 : alpha;
alpha = alpha > 1.0 ? 1.0 : alpha;
// The conversion algorithm is from the below link
// https://en.wikipedia.org/wiki/HSL_and_HSV#Interconversion
double s;
double l = value * (1.0 - (saturation / 2.0));
if (l <= 0 || l >= 1)
{
s = 0.0;
}
else
{
s = (value - l) / Math.Min(l, 1.0 - l);
}
return new HslColor(alpha, hue, s, l);
}
/// <summary>
/// Indicates whether the values of two specified <see cref="HsvColor"/> objects are equal.
/// </summary>

4
src/Avalonia.Base/StyledElement.cs

@ -420,12 +420,12 @@ namespace Avalonia
internal StyleDiagnostics GetStyleDiagnosticsInternal()
{
var styles = new List<IStyleInstance>();
var styles = new List<AppliedStyle>();
foreach (var frame in GetValueStore().Frames)
{
if (frame is IStyleInstance style)
styles.Add(style);
styles.Add(new(style));
}
return new StyleDiagnostics(styles);

3
src/Avalonia.Base/Styling/Activators/IStyleActivator.cs

@ -15,8 +15,7 @@ namespace Avalonia.Styling.Activators
/// - The activation state can be re-evaluated at any time by calling <see cref="GetIsActive"/>
/// - No error or completion messages
/// </remarks>
[Unstable]
public interface IStyleActivator : IDisposable
internal interface IStyleActivator : IDisposable
{
/// <summary>
/// Gets a value indicating whether the style is subscribed.

3
src/Avalonia.Base/Styling/Activators/IStyleActivatorSink.cs

@ -5,8 +5,7 @@ namespace Avalonia.Styling.Activators
/// <summary>
/// Receives notifications from an <see cref="IStyleActivator"/>.
/// </summary>
[Unstable]
public interface IStyleActivatorSink
internal interface IStyleActivatorSink
{
/// <summary>
/// Called when the subscribed activator value changes.

12
src/Avalonia.Base/Styling/ChildSelector.cs

@ -19,13 +19,13 @@ namespace Avalonia.Styling
}
/// <inheritdoc/>
public override bool InTemplate => _parent.InTemplate;
internal override bool InTemplate => _parent.InTemplate;
/// <inheritdoc/>
public override bool IsCombinator => true;
internal override bool IsCombinator => true;
/// <inheritdoc/>
public override Type? TargetType => null;
internal override Type? TargetType => null;
public override string ToString(Style? owner)
{
@ -37,7 +37,7 @@ namespace Avalonia.Styling
return _selectorString;
}
protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
{
var controlParent = ((ILogical)control).LogicalParent;
@ -64,7 +64,7 @@ namespace Avalonia.Styling
}
}
protected override Selector? MovePrevious() => null;
protected override Selector? MovePreviousOrParent() => _parent;
private protected override Selector? MovePrevious() => null;
private protected override Selector? MovePreviousOrParent() => _parent;
}
}

12
src/Avalonia.Base/Styling/DescendentSelector.cs

@ -17,13 +17,13 @@ namespace Avalonia.Styling
}
/// <inheritdoc/>
public override bool IsCombinator => true;
internal override bool IsCombinator => true;
/// <inheritdoc/>
public override bool InTemplate => _parent.InTemplate;
internal override bool InTemplate => _parent.InTemplate;
/// <inheritdoc/>
public override Type? TargetType => null;
internal override Type? TargetType => null;
public override string ToString(Style? owner)
{
@ -35,7 +35,7 @@ namespace Avalonia.Styling
return _selectorString;
}
protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
{
var c = (ILogical)control;
var descendantMatches = new OrActivatorBuilder();
@ -69,7 +69,7 @@ namespace Avalonia.Styling
}
}
protected override Selector? MovePrevious() => null;
protected override Selector? MovePreviousOrParent() => _parent;
private protected override Selector? MovePrevious() => null;
private protected override Selector? MovePreviousOrParent() => _parent;
}
}

24
src/Avalonia.Base/Styling/ISetter.cs

@ -1,24 +0,0 @@
using System;
using Avalonia.Metadata;
namespace Avalonia.Styling
{
/// <summary>
/// Represents a setter for a <see cref="Style"/>.
/// </summary>
[NotClientImplementable]
public interface ISetter
{
/// <summary>
/// Instances a setter on a control.
/// </summary>
/// <param name="styleInstance">The style which contains the setter.</param>
/// <param name="target">The control.</param>
/// <returns>An <see cref="ISetterInstance"/>.</returns>
/// <remarks>
/// This method should return an <see cref="ISetterInstance"/> which can be used to apply
/// the setter to the specified control.
/// </remarks>
ISetterInstance Instance(IStyleInstance styleInstance, StyledElement target);
}
}

2
src/Avalonia.Base/Styling/ISetterInstance.cs

@ -3,7 +3,7 @@
namespace Avalonia.Styling
{
/// <summary>
/// Represents an <see cref="ISetter"/> that has been instanced on a control.
/// Represents a <see cref="Setter"/> that has been instanced on a control.
/// </summary>
[Unstable]
public interface ISetterInstance

4
src/Avalonia.Base/Styling/ISetterValue.cs

@ -3,13 +3,13 @@
namespace Avalonia.Styling
{
/// <summary>
/// Customizes the behavior of a class when added as a value to an <see cref="ISetter"/>.
/// Customizes the behavior of a class when added as a value to a <see cref="SetterBase"/>.
/// </summary>
public interface ISetterValue
{
/// <summary>
/// Notifies that the object has been added as a setter value.
/// </summary>
void Initialize(ISetter setter);
void Initialize(SetterBase setter);
}
}

3
src/Avalonia.Base/Styling/IStyleInstance.cs

@ -5,8 +5,7 @@ namespace Avalonia.Styling
/// <summary>
/// Represents a <see cref="Style"/> that has been instanced on a control.
/// </summary>
[Unstable]
public interface IStyleInstance
internal interface IStyleInstance
{
/// <summary>
/// Gets the source style.

12
src/Avalonia.Base/Styling/NestingSelector.cs

@ -7,13 +7,13 @@ namespace Avalonia.Styling
/// </summary>
internal class NestingSelector : Selector
{
public override bool InTemplate => false;
public override bool IsCombinator => false;
public override Type? TargetType => null;
internal override bool InTemplate => false;
internal override bool IsCombinator => false;
internal override Type? TargetType => null;
public override string ToString(Style? owner) => owner?.Parent?.ToString() ?? "^";
protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
{
if (parent is Style s && s.Selector is not null)
{
@ -32,7 +32,7 @@ namespace Avalonia.Styling
"Nesting selector was specified but cannot determine parent selector.");
}
protected override Selector? MovePrevious() => null;
protected override Selector? MovePreviousOrParent() => null;
private protected override Selector? MovePrevious() => null;
private protected override Selector? MovePreviousOrParent() => null;
}
}

12
src/Avalonia.Base/Styling/NotSelector.cs

@ -26,13 +26,13 @@ namespace Avalonia.Styling
}
/// <inheritdoc/>
public override bool InTemplate => _argument.InTemplate;
internal override bool InTemplate => _argument.InTemplate;
/// <inheritdoc/>
public override bool IsCombinator => false;
internal override bool IsCombinator => false;
/// <inheritdoc/>
public override Type? TargetType => _previous?.TargetType;
internal override Type? TargetType => _previous?.TargetType;
/// <inheritdoc/>
public override string ToString(Style? owner)
@ -45,7 +45,7 @@ namespace Avalonia.Styling
return _selectorString;
}
protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
{
var innerResult = _argument.Match(control, parent, subscribe);
@ -66,7 +66,7 @@ namespace Avalonia.Styling
}
}
protected override Selector? MovePrevious() => _previous;
protected override Selector? MovePreviousOrParent() => _previous;
private protected override Selector? MovePrevious() => _previous;
private protected override Selector? MovePreviousOrParent() => _previous;
}
}

14
src/Avalonia.Base/Styling/NthChildSelector.cs

@ -12,7 +12,7 @@ namespace Avalonia.Styling
/// <remarks>
/// Element indices are 1-based.
/// </remarks>
public class NthChildSelector : Selector
internal class NthChildSelector : Selector
{
private const string NthChildSelectorName = "nth-child";
private const string NthLastChildSelectorName = "nth-last-child";
@ -39,16 +39,16 @@ namespace Avalonia.Styling
}
public override bool InTemplate => _previous?.InTemplate ?? false;
internal override bool InTemplate => _previous?.InTemplate ?? false;
public override bool IsCombinator => false;
internal override bool IsCombinator => false;
public override Type? TargetType => _previous?.TargetType;
internal override Type? TargetType => _previous?.TargetType;
public int Step { get; }
public int Offset { get; }
protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
{
if (!(control is ILogical logical))
{
@ -103,8 +103,8 @@ namespace Avalonia.Styling
return match ? SelectorMatch.AlwaysThisInstance : SelectorMatch.NeverThisInstance;
}
protected override Selector? MovePrevious() => _previous;
protected override Selector? MovePreviousOrParent() => _previous;
private protected override Selector? MovePrevious() => _previous;
private protected override Selector? MovePreviousOrParent() => _previous;
public override string ToString(Style? owner)
{

2
src/Avalonia.Base/Styling/NthLastChildSelector.cs

@ -8,7 +8,7 @@ namespace Avalonia.Styling
/// <remarks>
/// Element indices are 1-based.
/// </remarks>
public class NthLastChildSelector : NthChildSelector
internal class NthLastChildSelector : NthChildSelector
{
/// <summary>
/// Creates an instance of <see cref="NthLastChildSelector"/>

12
src/Avalonia.Base/Styling/OrSelector.cs

@ -36,13 +36,13 @@ namespace Avalonia.Styling
}
/// <inheritdoc/>
public override bool InTemplate => false;
internal override bool InTemplate => false;
/// <inheritdoc/>
public override bool IsCombinator => false;
internal override bool IsCombinator => false;
/// <inheritdoc/>
public override Type? TargetType => _targetType ??= EvaluateTargetType();
internal override Type? TargetType => _targetType ??= EvaluateTargetType();
/// <inheritdoc/>
public override string ToString(Style? owner)
@ -55,7 +55,7 @@ namespace Avalonia.Styling
return _selectorString;
}
protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
{
var activators = new OrActivatorBuilder();
var neverThisInstance = false;
@ -94,8 +94,8 @@ namespace Avalonia.Styling
}
}
protected override Selector? MovePrevious() => null;
protected override Selector? MovePreviousOrParent() => null;
private protected override Selector? MovePrevious() => null;
private protected override Selector? MovePreviousOrParent() => null;
internal override void ValidateNestingSelector(bool inControlTheme)
{

12
src/Avalonia.Base/Styling/PropertyEqualsSelector.cs

@ -28,13 +28,13 @@ namespace Avalonia.Styling
}
/// <inheritdoc/>
public override bool InTemplate => _previous?.InTemplate ?? false;
internal override bool InTemplate => _previous?.InTemplate ?? false;
/// <inheritdoc/>
public override bool IsCombinator => false;
internal override bool IsCombinator => false;
/// <inheritdoc/>
public override Type? TargetType => _previous?.TargetType;
internal override Type? TargetType => _previous?.TargetType;
/// <inheritdoc/>
public override string ToString(Style? owner)
@ -73,7 +73,7 @@ namespace Avalonia.Styling
}
/// <inheritdoc/>
protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
{
if (subscribe)
{
@ -88,8 +88,8 @@ namespace Avalonia.Styling
}
protected override Selector? MovePrevious() => _previous;
protected override Selector? MovePreviousOrParent() => _previous;
private protected override Selector? MovePrevious() => _previous;
private protected override Selector? MovePreviousOrParent() => _previous;
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.TypeConvertionSupressWarningMessage)]
[UnconditionalSuppressMessage("Trimming", "IL2067", Justification = TrimmingMessages.TypeConvertionSupressWarningMessage)]

14
src/Avalonia.Base/Styling/Selector.cs

@ -14,7 +14,7 @@ namespace Avalonia.Styling
/// Gets a value indicating whether either this selector or a previous selector has moved
/// into a template.
/// </summary>
public abstract bool InTemplate { get; }
internal abstract bool InTemplate { get; }
/// <summary>
/// Gets a value indicating whether this selector is a combinator.
@ -22,12 +22,12 @@ namespace Avalonia.Styling
/// <remarks>
/// A combinator is a selector such as Child or Descendent which links simple selectors.
/// </remarks>
public abstract bool IsCombinator { get; }
internal abstract bool IsCombinator { get; }
/// <summary>
/// Gets the target type of the selector, if available.
/// </summary>
public abstract Type? TargetType { get; }
internal abstract Type? TargetType { get; }
/// <summary>
/// Tries to match the selector with a control.
@ -41,7 +41,7 @@ namespace Avalonia.Styling
/// or simply return an immediate result.
/// </param>
/// <returns>A <see cref="SelectorMatch"/>.</returns>
public SelectorMatch Match(StyledElement control, IStyle? parent = null, bool subscribe = true)
internal SelectorMatch Match(StyledElement control, IStyle? parent = null, bool subscribe = true)
{
// First match the selector until a combinator is found. Selectors are stored from
// right-to-left, so MatchUntilCombinator reverses this order because the type selector
@ -88,17 +88,17 @@ namespace Avalonia.Styling
/// or simply return an immediate result.
/// </param>
/// <returns>A <see cref="SelectorMatch"/>.</returns>
protected abstract SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe);
private protected abstract SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe);
/// <summary>
/// Moves to the previous selector.
/// </summary>
protected abstract Selector? MovePrevious();
private protected abstract Selector? MovePrevious();
/// <summary>
/// Moves to the previous selector or the parent selector.
/// </summary>
protected abstract Selector? MovePreviousOrParent();
private protected abstract Selector? MovePreviousOrParent();
internal virtual void ValidateNestingSelector(bool inControlTheme)
{

4
src/Avalonia.Base/Styling/SelectorMatch.cs

@ -8,7 +8,7 @@ namespace Avalonia.Styling
/// <summary>
/// Describes how a <see cref="SelectorMatch"/> matches a control and its type.
/// </summary>
public enum SelectorMatchResult
internal enum SelectorMatchResult
{
/// <summary>
/// The selector never matches this type.
@ -43,7 +43,7 @@ namespace Avalonia.Styling
/// A selector match describes whether and how a <see cref="Selector"/> matches a control, and
/// in addition whether the selector can ever match a control of the same type.
/// </remarks>
public readonly record struct SelectorMatch
internal readonly record struct SelectorMatch
{
/// <summary>
/// A selector match with the result of <see cref="SelectorMatchResult.NeverThisType"/>.

4
src/Avalonia.Base/Styling/Setter.cs

@ -14,7 +14,7 @@ namespace Avalonia.Styling
/// A <see cref="Setter"/> is used to set a <see cref="AvaloniaProperty"/> value on a
/// <see cref="AvaloniaObject"/> depending on a condition.
/// </remarks>
public class Setter : ISetter, IValueEntry, ISetterInstance, IAnimationSetter
public class Setter : SetterBase, IValueEntry, ISetterInstance, IAnimationSetter
{
private object? _value;
private DirectPropertySetterInstance? _direct;
@ -66,7 +66,7 @@ namespace Avalonia.Styling
void IValueEntry.Unsubscribe() { }
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)]
ISetterInstance ISetter.Instance(IStyleInstance instance, StyledElement target)
internal override ISetterInstance Instance(IStyleInstance instance, StyledElement target)
{
if (target is not AvaloniaObject ao)
throw new InvalidOperationException("Don't know how to instance a style on this type.");

12
src/Avalonia.Base/Styling/SetterBase.cs

@ -0,0 +1,12 @@
namespace Avalonia.Styling
{
/// <summary>
/// Represents the base class for value setters.
/// </summary>
public abstract class SetterBase
{
internal abstract ISetterInstance Instance(
IStyleInstance styleInstance,
StyledElement target);
}
}

6
src/Avalonia.Base/Styling/StyleBase.cs

@ -16,7 +16,7 @@ namespace Avalonia.Styling
private IResourceHost? _owner;
private StyleChildren? _children;
private IResourceDictionary? _resources;
private List<ISetter>? _setters;
private List<SetterBase>? _setters;
private List<IAnimation>? _animations;
private StyleInstance? _sharedInstance;
@ -60,7 +60,7 @@ namespace Avalonia.Styling
}
}
public IList<ISetter> Setters => _setters ??= new List<ISetter>();
public IList<SetterBase> Setters => _setters ??= new();
public IList<IAnimation> Animations => _animations ??= new List<IAnimation>();
bool IResourceNode.HasResources => _resources?.Count > 0;
@ -69,7 +69,7 @@ namespace Avalonia.Styling
internal bool HasChildren => _children?.Count > 0;
internal bool HasSettersOrAnimations => _setters?.Count > 0 || _animations?.Count > 0;
public void Add(ISetter setter) => Setters.Add(setter);
public void Add(SetterBase setter) => Setters.Add(setter);
public void Add(IStyle style) => Children.Add(style);
public event EventHandler? OwnerChanged;

12
src/Avalonia.Base/Styling/TemplateSelector.cs

@ -18,13 +18,13 @@ namespace Avalonia.Styling
}
/// <inheritdoc/>
public override bool InTemplate => true;
internal override bool InTemplate => true;
/// <inheritdoc/>
public override bool IsCombinator => true;
internal override bool IsCombinator => true;
/// <inheritdoc/>
public override Type? TargetType => null;
internal override Type? TargetType => null;
public override string ToString(Style? owner)
{
@ -36,7 +36,7 @@ namespace Avalonia.Styling
return _selectorString;
}
protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
{
var templatedParent = control.TemplatedParent as StyledElement;
@ -48,7 +48,7 @@ namespace Avalonia.Styling
return _parent.Match(templatedParent, parent, subscribe);
}
protected override Selector? MovePrevious() => null;
protected override Selector? MovePreviousOrParent() => _parent;
private protected override Selector? MovePrevious() => null;
private protected override Selector? MovePreviousOrParent() => _parent;
}
}

12
src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs

@ -58,7 +58,7 @@ namespace Avalonia.Styling
}
/// <inheritdoc/>
public override bool InTemplate => _previous?.InTemplate ?? false;
internal override bool InTemplate => _previous?.InTemplate ?? false;
/// <summary>
/// Gets the name of the control to match.
@ -66,10 +66,10 @@ namespace Avalonia.Styling
public string? Name { get; set; }
/// <inheritdoc/>
public override Type? TargetType => _targetType ?? _previous?.TargetType;
internal override Type? TargetType => _targetType ?? _previous?.TargetType;
/// <inheritdoc/>
public override bool IsCombinator => false;
internal override bool IsCombinator => false;
/// <summary>
/// Whether the selector matches the concrete <see cref="TargetType"/> or any object which
@ -89,7 +89,7 @@ namespace Avalonia.Styling
}
/// <inheritdoc/>
protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
{
if (TargetType != null)
{
@ -134,8 +134,8 @@ namespace Avalonia.Styling
return Name == null ? SelectorMatch.AlwaysThisType : SelectorMatch.AlwaysThisInstance;
}
protected override Selector? MovePrevious() => _previous;
protected override Selector? MovePreviousOrParent() => _previous;
private protected override Selector? MovePrevious() => _previous;
private protected override Selector? MovePreviousOrParent() => _previous;
private string BuildSelectorString(Style? owner)
{

72
src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.Properties.cs

@ -41,11 +41,19 @@ namespace Avalonia.Controls.Primitives
defaultBindingMode: BindingMode.TwoWay);
/// <summary>
/// Defines the <see cref="IsAlphaMaxForced"/> property.
/// Defines the <see cref="IsAlphaVisible"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsAlphaMaxForcedProperty =
public static readonly StyledProperty<bool> IsAlphaVisibleProperty =
AvaloniaProperty.Register<ColorSlider, bool>(
nameof(IsAlphaMaxForced),
nameof(IsAlphaVisible),
false);
/// <summary>
/// Defines the <see cref="IsPerceptive"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsPerceptiveProperty =
AvaloniaProperty.Register<ColorSlider, bool>(
nameof(IsPerceptive),
true);
/// <summary>
@ -56,14 +64,6 @@ namespace Avalonia.Controls.Primitives
nameof(IsRoundingEnabled),
false);
/// <summary>
/// Defines the <see cref="IsSaturationValueMaxForced"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsSaturationValueMaxForcedProperty =
AvaloniaProperty.Register<ColorSlider, bool>(
nameof(IsSaturationValueMaxForced),
true);
/// <summary>
/// Gets or sets the currently selected color in the RGB color model.
/// </summary>
@ -109,14 +109,41 @@ namespace Avalonia.Controls.Primitives
}
/// <summary>
/// Gets or sets a value indicating whether the alpha component is always forced to maximum for components
/// other than <see cref="ColorComponent"/>.
/// This ensures that the background is always visible and never transparent regardless of the actual color.
/// Gets or sets a value indicating whether the alpha component is visible and rendered.
/// When false, this ensures that the gradient is always visible and never transparent regardless of
/// the actual color. This property is ignored when the alpha component itself is being displayed.
/// </summary>
/// <remarks>
/// Setting to false means the alpha component is always forced to maximum for components other than
/// <see cref="ColorComponent"/> during rendering. This doesn't change the value of the alpha component
/// in the color – it is only for display.
/// </remarks>
public bool IsAlphaVisible
{
get => GetValue(IsAlphaVisibleProperty);
set => SetValue(IsAlphaVisibleProperty, value);
}
/// <summary>
/// Gets or sets a value indicating whether the slider adapts rendering to improve user-perception
/// over exactness.
/// </summary>
public bool IsAlphaMaxForced
/// <remarks>
/// When true in the HSVA color model, this ensures that the gradient is always visible and
/// never washed out regardless of the actual color. When true in the RGBA color model, this ensures
/// the gradient always appears as red, green or blue.
/// <br/><br/>
/// For example, with Hue in the HSVA color model, the Saturation and Value components are always forced
/// to maximum values during rendering. In the RGBA color model, all components other than
/// <see cref="ColorComponent"/> are forced to minimum values during rendering.
/// <br/><br/>
/// Note this property will only adjust components other than <see cref="ColorComponent"/> during rendering.
/// This also doesn't change the values of any components in the actual color – it is only for display.
/// </remarks>
public bool IsPerceptive
{
get => GetValue(IsAlphaMaxForcedProperty);
set => SetValue(IsAlphaMaxForcedProperty, value);
get => GetValue(IsPerceptiveProperty);
set => SetValue(IsPerceptiveProperty, value);
}
/// <summary>
@ -131,16 +158,5 @@ namespace Avalonia.Controls.Primitives
get => GetValue(IsRoundingEnabledProperty);
set => SetValue(IsRoundingEnabledProperty, value);
}
/// <summary>
/// Gets or sets a value indicating whether the saturation and value components are always forced to maximum values
/// when using the HSVA color model. Only component values other than <see cref="ColorComponent"/> will be changed.
/// This ensures, for example, that the Hue background is always visible and never washed out regardless of the actual color.
/// </summary>
public bool IsSaturationValueMaxForced
{
get => GetValue(IsSaturationValueMaxForcedProperty);
set => SetValue(IsSaturationValueMaxForcedProperty, value);
}
}
}

88
src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs

@ -52,8 +52,7 @@ namespace Avalonia.Controls.Primitives
// This means under a certain alpha threshold, neither a white or black selector thumb
// should be shown and instead the default slider thumb color should be used instead.
if (Color.A < 128 &&
(IsAlphaMaxForced == false ||
ColorComponent == ColorComponent.Alpha))
(IsAlphaVisible || ColorComponent == ColorComponent.Alpha))
{
PseudoClasses.Set(pcDarkSelector, false);
PseudoClasses.Set(pcLightSelector, false);
@ -64,11 +63,11 @@ namespace Avalonia.Controls.Primitives
if (ColorModel == ColorModel.Hsva)
{
perceivedColor = GetEquivalentBackgroundColor(HsvColor).ToRgb();
perceivedColor = GetPerceptiveBackgroundColor(HsvColor).ToRgb();
}
else
{
perceivedColor = GetEquivalentBackgroundColor(Color);
perceivedColor = GetPerceptiveBackgroundColor(Color);
}
if (ColorHelper.GetRelativeLuminance(perceivedColor) <= 0.5)
@ -108,7 +107,7 @@ namespace Avalonia.Controls.Primitives
{
// As a fallback, attempt to calculate using the overall control size
// This shouldn't happen as a track is a required template part of a slider
// However, if it does, the spectrum will still be shown
// However, if it does, the spectrum gradient will still be shown
pixelWidth = Convert.ToInt32(Bounds.Width * scale);
pixelHeight = Convert.ToInt32(Bounds.Height * scale);
}
@ -122,8 +121,8 @@ namespace Avalonia.Controls.Primitives
ColorModel,
ColorComponent,
HsvColor,
IsAlphaMaxForced,
IsSaturationValueMaxForced);
IsAlphaVisible,
IsPerceptive);
if (_backgroundBitmap != null)
{
@ -316,40 +315,35 @@ namespace Avalonia.Controls.Primitives
/// </summary>
/// <param name="hsvColor">The actual color to get the equivalent background color for.</param>
/// <returns>The equivalent, perceived background color.</returns>
private HsvColor GetEquivalentBackgroundColor(HsvColor hsvColor)
private HsvColor GetPerceptiveBackgroundColor(HsvColor hsvColor)
{
var component = ColorComponent;
var isAlphaMaxForced = IsAlphaMaxForced;
var isSaturationValueMaxForced = IsSaturationValueMaxForced;
var isAlphaVisible = IsAlphaVisible;
var isPerceptive = IsPerceptive;
if (isAlphaMaxForced &&
if (isAlphaVisible == false &&
component != ColorComponent.Alpha)
{
hsvColor = new HsvColor(1.0, hsvColor.H, hsvColor.S, hsvColor.V);
}
switch (component)
if (isPerceptive)
{
case ColorComponent.Component1:
return new HsvColor(
hsvColor.A,
hsvColor.H,
isSaturationValueMaxForced ? 1.0 : hsvColor.S,
isSaturationValueMaxForced ? 1.0 : hsvColor.V);
case ColorComponent.Component2:
return new HsvColor(
hsvColor.A,
hsvColor.H,
hsvColor.S,
isSaturationValueMaxForced ? 1.0 : hsvColor.V);
case ColorComponent.Component3:
return new HsvColor(
hsvColor.A,
hsvColor.H,
isSaturationValueMaxForced ? 1.0 : hsvColor.S,
hsvColor.V);
default:
return hsvColor;
switch (component)
{
case ColorComponent.Component1:
return new HsvColor(hsvColor.A, hsvColor.H, 1.0, 1.0);
case ColorComponent.Component2:
return new HsvColor(hsvColor.A, hsvColor.H, hsvColor.S, 1.0);
case ColorComponent.Component3:
return new HsvColor(hsvColor.A, hsvColor.H, 1.0, hsvColor.V);
default:
return hsvColor;
}
}
else
{
return hsvColor;
}
}
@ -359,18 +353,36 @@ namespace Avalonia.Controls.Primitives
/// </summary>
/// <param name="rgbColor">The actual color to get the equivalent background color for.</param>
/// <returns>The equivalent, perceived background color.</returns>
private Color GetEquivalentBackgroundColor(Color rgbColor)
private Color GetPerceptiveBackgroundColor(Color rgbColor)
{
var component = ColorComponent;
var isAlphaMaxForced = IsAlphaMaxForced;
var isAlphaVisible = IsAlphaVisible;
var isPerceptive = IsPerceptive;
if (isAlphaMaxForced &&
if (isAlphaVisible == false &&
component != ColorComponent.Alpha)
{
rgbColor = new Color(255, rgbColor.R, rgbColor.G, rgbColor.B);
}
return rgbColor;
if (isPerceptive)
{
switch (component)
{
case ColorComponent.Component1:
return new Color(rgbColor.A, rgbColor.R, 0, 0);
case ColorComponent.Component2:
return new Color(rgbColor.A, 0, rgbColor.G, 0);
case ColorComponent.Component3:
return new Color(rgbColor.A, 0, 0, rgbColor.B);
default:
return rgbColor;
}
}
else
{
return rgbColor;
}
}
/// <inheritdoc/>
@ -401,8 +413,8 @@ namespace Avalonia.Controls.Primitives
}
else if (change.Property == ColorComponentProperty ||
change.Property == ColorModelProperty ||
change.Property == IsAlphaMaxForcedProperty ||
change.Property == IsSaturationValueMaxForcedProperty)
change.Property == IsAlphaVisibleProperty ||
change.Property == IsPerceptiveProperty)
{
ignorePropertyChanged = true;

91
src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs

@ -45,6 +45,7 @@ namespace Avalonia.Controls.Primitives
private bool _updatingColor = false;
private bool _updatingHsvColor = false;
private bool _coercedInitialColor = false;
private bool _isPointerPressed = false;
private bool _shouldShowLargeSelection = false;
private List<Hsv> _hsvValues = new List<Hsv>();
@ -601,14 +602,102 @@ namespace Avalonia.Controls.Primitives
}
}
/// <summary>
/// Changes the currently selected color (always in HSV) and applies all necessary updates.
/// </summary>
/// <remarks>
/// Some additional logic is applied in certain situations to coerce and sync color values.
/// Use this method instead of update the <see cref="Color"/> or <see cref="HsvColor"/> directly.
/// </remarks>
/// <param name="newHsv">The new HSV color to change to.</param>
private void UpdateColor(Hsv newHsv)
{
_updatingColor = true;
_updatingHsvColor = true;
Rgb newRgb = newHsv.ToRgb();
double alpha = HsvColor.A;
// It is common for the ColorPicker (and therefore the Spectrum) to be initialized
// with a #00000000 color value in some use cases. This is usually used to indicate
// that no color has been selected by the user. Note that #00000000 is different than
// #00FFFFFF (Transparent).
//
// In this situation, the first time the user clicks on the spectrum the third
// component and alpha component will remain zero. This is because the spectrum only
// controls two components at any given time.
//
// This is very unintuitive from a user-standpoint as after the user clicks on the
// spectrum they must then increase the alpha and then the third component sliders
// to the desired value. In fact, until they increase these slider values no color
// will show at all since it is fully transparent and black. In almost all cases
// though the desired value is simply full color.
//
// To work around this usability issue with an initial #00000000 color, the selected
// color is coerced (only the first time) into a color with maximum third component
// value and maximum alpha. This can only happen once and only if those two components
// are already zero.
//
// Also note this is NOT currently done for #00FFFFFF (Transparent) but based on
// further usability study that case may need to be handled here as well. Right now
// Transparent is treated as a normal color value with the alpha intentionally set
// to zero so the alpha slider must still be adjusted after the spectrum.
if (!_coercedInitialColor &&
IsLoaded)
{
bool isAlphaComponentZero = (alpha == 0.0);
bool isThirdComponentZero = false;
switch (Components)
{
case ColorSpectrumComponents.HueValue:
case ColorSpectrumComponents.ValueHue:
isThirdComponentZero = (newHsv.S == 0.0);
break;
case ColorSpectrumComponents.HueSaturation:
case ColorSpectrumComponents.SaturationHue:
isThirdComponentZero = (newHsv.V == 0.0);
break;
case ColorSpectrumComponents.ValueSaturation:
case ColorSpectrumComponents.SaturationValue:
isThirdComponentZero = (newHsv.H == 0.0);
break;
}
if (isAlphaComponentZero && isThirdComponentZero)
{
alpha = 1.0;
switch (Components)
{
case ColorSpectrumComponents.HueValue:
case ColorSpectrumComponents.ValueHue:
newHsv.S = 1.0;
break;
case ColorSpectrumComponents.HueSaturation:
case ColorSpectrumComponents.SaturationHue:
newHsv.V = 1.0;
break;
case ColorSpectrumComponents.ValueSaturation:
case ColorSpectrumComponents.SaturationValue:
// Hue is mathematically NOT a special case; however, is one conceptually.
// It doesn't make sense to change the selected Hue value, so why is it set here?
// Setting to 360.0 is equivalent to the max set for other components and is
// internally wrapped back to 0.0 (since 360 degrees = 0 degrees).
// This means effectively there is no change to the hue component value.
newHsv.H = 360.0;
break;
}
_coercedInitialColor = true;
}
}
Rgb newRgb = newHsv.ToRgb();
SetCurrentValue(ColorProperty, newRgb.ToColor(alpha));
SetCurrentValue(HsvColorProperty, newHsv.ToHsvColor(alpha));

6
src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs

@ -504,6 +504,12 @@ namespace Avalonia.Controls
/// <summary>
/// Gets or sets the index of the selected tab/panel/page (subview).
/// </summary>
/// <remarks>
/// When using the default control theme, this property is designed to be used with the
/// <see cref="ColorViewTab"/> enum. The <see cref="ColorViewTab"/> enum defines the
/// index values of each of the three standard tabs.
/// Use like `SelectedIndex = (int)ColorViewTab.Palette`.
/// </remarks>
public int SelectedIndex
{
get => GetValue(SelectedIndexProperty);

60
src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs

@ -29,11 +29,10 @@ namespace Avalonia.Controls.Primitives
/// <param name="colorModel">The color model being used: RGBA or HSVA.</param>
/// <param name="component">The specific color component to sweep.</param>
/// <param name="baseHsvColor">The base HSV color used for components not being changed.</param>
/// <param name="isAlphaMaxForced">Fix the alpha component value to maximum during calculation.
/// This will remove any alpha/transparency from the other component backgrounds.</param>
/// <param name="isSaturationValueMaxForced">Fix the saturation and value components to maximum
/// during calculation with the HSVA color model.
/// This will ensure colors are always discernible regardless of saturation/value.</param>
/// <param name="isAlphaVisible">Whether the alpha component is visible and rendered in the bitmap.
/// This property is ignored when the alpha component itself is being rendered.</param>
/// <param name="isPerceptive">Whether the slider adapts rendering to improve user-perception over exactness.
/// This will ensure colors are always discernible.</param>
/// <returns>A new bitmap representing a gradient of color component values.</returns>
public static async Task<ArrayList<byte>> CreateComponentBitmapAsync(
int width,
@ -42,8 +41,8 @@ namespace Avalonia.Controls.Primitives
ColorModel colorModel,
ColorComponent component,
HsvColor baseHsvColor,
bool isAlphaMaxForced,
bool isSaturationValueMaxForced)
bool isAlphaVisible,
bool isPerceptive)
{
if (width == 0 || height == 0)
{
@ -67,7 +66,7 @@ namespace Avalonia.Controls.Primitives
bgraPixelDataWidth = width * 4;
// Maximize alpha component value
if (isAlphaMaxForced &&
if (isAlphaVisible == false &&
component != ColorComponent.Alpha)
{
baseHsvColor = new HsvColor(1.0, baseHsvColor.H, baseHsvColor.S, baseHsvColor.V);
@ -79,22 +78,41 @@ namespace Avalonia.Controls.Primitives
baseRgbColor = baseHsvColor.ToRgb();
}
// Maximize Saturation and Value components when in HSVA mode
if (isSaturationValueMaxForced &&
colorModel == ColorModel.Hsva &&
// Apply any perceptive adjustments to the color
if (isPerceptive &&
component != ColorComponent.Alpha)
{
switch (component)
if (colorModel == ColorModel.Hsva)
{
case ColorComponent.Component1:
baseHsvColor = new HsvColor(baseHsvColor.A, baseHsvColor.H, 1.0, 1.0);
break;
case ColorComponent.Component2:
baseHsvColor = new HsvColor(baseHsvColor.A, baseHsvColor.H, baseHsvColor.S, 1.0);
break;
case ColorComponent.Component3:
baseHsvColor = new HsvColor(baseHsvColor.A, baseHsvColor.H, 1.0, baseHsvColor.V);
break;
// Maximize Saturation and Value components
switch (component)
{
case ColorComponent.Component1: // Hue
baseHsvColor = new HsvColor(baseHsvColor.A, baseHsvColor.H, 1.0, 1.0);
break;
case ColorComponent.Component2: // Saturation
baseHsvColor = new HsvColor(baseHsvColor.A, baseHsvColor.H, baseHsvColor.S, 1.0);
break;
case ColorComponent.Component3: // Value
baseHsvColor = new HsvColor(baseHsvColor.A, baseHsvColor.H, 1.0, baseHsvColor.V);
break;
}
}
else
{
// Minimize component values other than the current one
switch (component)
{
case ColorComponent.Component1: // Red
baseRgbColor = new Color(baseRgbColor.A, baseRgbColor.R, 0, 0);
break;
case ColorComponent.Component2: // Green
baseRgbColor = new Color(baseRgbColor.A, 0, baseRgbColor.G, 0);
break;
case ColorComponent.Component3: // Blue
baseRgbColor = new Color(baseRgbColor.A, 0, 0, baseRgbColor.B);
break;
}
}
}

2
src/Avalonia.Controls.ColorPicker/Helpers/Hsv.cs

@ -11,7 +11,7 @@ namespace Avalonia.Controls.Primitives
/// Contains and allows modification of Hue, Saturation and Value components.
/// </summary>
/// <remarks>
/// The is a specialized struct optimized for permanence and memory:
/// The is a specialized struct optimized for performance and memory:
/// <list type="bullet">
/// <item>This is not a read-only struct like <see cref="HsvColor"/> and allows editing the fields</item>
/// <item>Removes the alpha component unnecessary in core calculations</item>

2
src/Avalonia.Controls.ColorPicker/Helpers/Rgb.cs

@ -12,7 +12,7 @@ namespace Avalonia.Controls.Primitives
/// Contains and allows modification of Red, Green and Blue components.
/// </summary>
/// <remarks>
/// The is a specialized struct optimized for permanence and memory:
/// The is a specialized struct optimized for performance and memory:
/// <list type="bullet">
/// <item>This is not a read-only struct like <see cref="Color"/> and allows editing the fields</item>
/// <item>Removes the alpha component unnecessary in core calculations</item>

25
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml

@ -113,8 +113,8 @@
<primitives:ColorSlider x:Name="ColorSpectrumThirdComponentSlider"
AutomationProperties.Name="Third Component"
Grid.Column="0"
IsAlphaMaxForced="True"
IsSaturationValueMaxForced="False"
IsAlphaVisible="False"
IsPerceptive="True"
Orientation="Vertical"
ColorModel="Hsva"
ColorComponent="{Binding ThirdComponent, ElementName=ColorSpectrum}"
@ -490,11 +490,11 @@
</TabItem>
</TabControl>
<!-- Previewer -->
<!-- Note that top/bottom margins have -5 to remove for drop shadow padding -->
<!-- Note that the drop shadow is allowed to extend past the control bounds -->
<primitives:ColorPreviewer Grid.Row="1"
HsvColor="{Binding HsvColor, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
IsAccentColorsVisible="{TemplateBinding IsAccentColorsVisible}"
Margin="12,-5,12,7"
Margin="12,0,12,12"
IsVisible="{TemplateBinding IsColorPreviewVisible}" />
</Grid>
</Flyout>
@ -502,6 +502,23 @@
</DropDownButton>
</ControlTemplate>
</Setter>
<!--
<Style Selector="^ /template/ primitives|ColorSlider#ColorSpectrumThirdComponentSlider[ColorComponent=Component1]">
<Setter Property="IsPerceptive" Value="True" />
</Style>
<Style Selector="^ /template/ primitives|ColorSlider#Component1Slider[ColorModel=Rgba]">
<Setter Property="IsPerceptive" Value="False" />
</Style>
<Style Selector="^ /template/ primitives|ColorSlider#Component2Slider[ColorModel=Rgba]">
<Setter Property="IsPerceptive" Value="False" />
</Style>
<Style Selector="^ /template/ primitives|ColorSlider#Component3Slider[ColorModel=Rgba]">
<Setter Property="IsPerceptive" Value="False" />
</Style>
-->
</ControlTheme>
</ResourceDictionary>

13
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml

@ -8,7 +8,9 @@
<ControlTheme x:Key="{x:Type ColorPreviewer}"
TargetType="ColorPreviewer">
<Setter Property="Height" Value="70" />
<Setter Property="Height" Value="50" />
<!-- The preview color drop shadow is allowed to extend outside the control bounds -->
<Setter Property="ClipToBounds" Value="False" />
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<Setter Property="Template">
<ControlTemplate TargetType="{x:Type ColorPreviewer}">
@ -21,7 +23,6 @@
Height="{StaticResource ColorPreviewerAccentSectionHeight}"
Width="{StaticResource ColorPreviewerAccentSectionWidth}"
ColumnDefinitions="*,*"
Margin="0,0,-10,0"
VerticalAlignment="Center">
<Border Grid.Column="0"
Grid.ColumnSpan="2"
@ -43,7 +44,6 @@
Height="{StaticResource ColorPreviewerAccentSectionHeight}"
Width="{StaticResource ColorPreviewerAccentSectionWidth}"
ColumnDefinitions="*,*"
Margin="-10,0,0,0"
VerticalAlignment="Center">
<Border Grid.Column="0"
Grid.ColumnSpan="2"
@ -64,10 +64,8 @@
<Border Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="Transparent"
BoxShadow="0 0 10 2 #BF000000"
CornerRadius="{TemplateBinding CornerRadius}"
Margin="10">
CornerRadius="{TemplateBinding CornerRadius}">
<Panel>
<Border Background="{StaticResource ColorControlCheckeredBackgroundBrush}"
CornerRadius="{TemplateBinding CornerRadius}" />
@ -82,8 +80,7 @@
<Border CornerRadius="{TemplateBinding CornerRadius}"
IsVisible="{TemplateBinding IsAccentColorsVisible, Converter={x:Static BoolConverters.Not}}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Margin="0,10,0,10">
VerticalAlignment="Stretch">
<Panel>
<Border Background="{StaticResource ColorControlCheckeredBackgroundBrush}"
CornerRadius="{TemplateBinding CornerRadius}" />

25
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml

@ -360,8 +360,8 @@
<primitives:ColorSlider x:Name="ColorSpectrumThirdComponentSlider"
AutomationProperties.Name="Third Component"
Grid.Column="0"
IsAlphaMaxForced="True"
IsSaturationValueMaxForced="False"
IsAlphaVisible="False"
IsPerceptive="True"
Orientation="Vertical"
ColorModel="Hsva"
ColorComponent="{Binding ThirdComponent, ElementName=ColorSpectrum}"
@ -737,15 +737,32 @@
</TabItem>
</TabControl>
<!-- Previewer -->
<!-- Note that top/bottom margins have -5 to remove for drop shadow padding -->
<!-- Note that the drop shadow is allowed to extend past the control bounds -->
<primitives:ColorPreviewer Grid.Row="1"
HsvColor="{Binding HsvColor, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
IsAccentColorsVisible="{TemplateBinding IsAccentColorsVisible}"
Margin="12,-5,12,7"
Margin="12,0,12,12"
IsVisible="{TemplateBinding IsColorPreviewVisible}" />
</Grid>
</ControlTemplate>
</Setter>
<!--
<Style Selector="^ /template/ primitives|ColorSlider#ColorSpectrumThirdComponentSlider[ColorComponent=Component1]">
<Setter Property="IsPerceptive" Value="True" />
</Style>
<Style Selector="^ /template/ primitives|ColorSlider#Component1Slider[ColorModel=Rgba]">
<Setter Property="IsPerceptive" Value="False" />
</Style>
<Style Selector="^ /template/ primitives|ColorSlider#Component2Slider[ColorModel=Rgba]">
<Setter Property="IsPerceptive" Value="False" />
</Style>
<Style Selector="^ /template/ primitives|ColorSlider#Component3Slider[ColorModel=Rgba]">
<Setter Property="IsPerceptive" Value="False" />
</Style>
-->
</ControlTheme>
</ResourceDictionary>

25
src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml

@ -112,8 +112,8 @@
<primitives:ColorSlider x:Name="ColorSpectrumThirdComponentSlider"
AutomationProperties.Name="Third Component"
Grid.Column="0"
IsAlphaMaxForced="True"
IsSaturationValueMaxForced="False"
IsAlphaVisible="False"
IsPerceptive="True"
Orientation="Vertical"
ColorModel="Hsva"
ColorComponent="{Binding ThirdComponent, ElementName=ColorSpectrum}"
@ -489,11 +489,11 @@
</TabItem>
</TabControl>
<!-- Previewer -->
<!-- Note that top/bottom margins have -5 to remove for drop shadow padding -->
<!-- Note that the drop shadow is allowed to extend past the control bounds -->
<primitives:ColorPreviewer Grid.Row="1"
HsvColor="{Binding HsvColor, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
IsAccentColorsVisible="{TemplateBinding IsAccentColorsVisible}"
Margin="12,-5,12,7"
Margin="12,0,12,12"
IsVisible="{TemplateBinding IsColorPreviewVisible}" />
</Grid>
</Flyout>
@ -501,6 +501,23 @@
</DropDownButton>
</ControlTemplate>
</Setter>
<!--
<Style Selector="^ /template/ primitives|ColorSlider#ColorSpectrumThirdComponentSlider[ColorComponent=Component1]">
<Setter Property="IsPerceptive" Value="True" />
</Style>
<Style Selector="^ /template/ primitives|ColorSlider#Component1Slider[ColorModel=Rgba]">
<Setter Property="IsPerceptive" Value="False" />
</Style>
<Style Selector="^ /template/ primitives|ColorSlider#Component2Slider[ColorModel=Rgba]">
<Setter Property="IsPerceptive" Value="False" />
</Style>
<Style Selector="^ /template/ primitives|ColorSlider#Component3Slider[ColorModel=Rgba]">
<Setter Property="IsPerceptive" Value="False" />
</Style>
-->
</ControlTheme>
</ResourceDictionary>

13
src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPreviewer.xaml

@ -8,7 +8,9 @@
<ControlTheme x:Key="{x:Type ColorPreviewer}"
TargetType="ColorPreviewer">
<Setter Property="Height" Value="70" />
<Setter Property="Height" Value="50" />
<!-- The preview color drop shadow is allowed to extend outside the control bounds -->
<Setter Property="ClipToBounds" Value="False" />
<Setter Property="CornerRadius" Value="0" />
<Setter Property="Template">
<ControlTemplate TargetType="{x:Type ColorPreviewer}">
@ -21,7 +23,6 @@
Height="{StaticResource ColorPreviewerAccentSectionHeight}"
Width="{StaticResource ColorPreviewerAccentSectionWidth}"
ColumnDefinitions="*,*"
Margin="0,0,-10,0"
VerticalAlignment="Center">
<Border Grid.Column="0"
Grid.ColumnSpan="2"
@ -43,7 +44,6 @@
Height="{StaticResource ColorPreviewerAccentSectionHeight}"
Width="{StaticResource ColorPreviewerAccentSectionWidth}"
ColumnDefinitions="*,*"
Margin="-10,0,0,0"
VerticalAlignment="Center">
<Border Grid.Column="0"
Grid.ColumnSpan="2"
@ -64,10 +64,8 @@
<Border Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="Transparent"
BoxShadow="0 0 10 2 #BF000000"
CornerRadius="{TemplateBinding CornerRadius}"
Margin="10">
CornerRadius="{TemplateBinding CornerRadius}">
<Panel>
<Border Background="{StaticResource ColorControlCheckeredBackgroundBrush}"
CornerRadius="{TemplateBinding CornerRadius}" />
@ -82,8 +80,7 @@
<Border CornerRadius="{TemplateBinding CornerRadius}"
IsVisible="{TemplateBinding IsAccentColorsVisible, Converter={x:Static BoolConverters.Not}}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Margin="0,10,0,10">
VerticalAlignment="Stretch">
<Panel>
<Border Background="{StaticResource ColorControlCheckeredBackgroundBrush}"
CornerRadius="{TemplateBinding CornerRadius}" />

25
src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml

@ -322,8 +322,8 @@
<primitives:ColorSlider x:Name="ColorSpectrumThirdComponentSlider"
AutomationProperties.Name="Third Component"
Grid.Column="0"
IsAlphaMaxForced="True"
IsSaturationValueMaxForced="False"
IsAlphaVisible="False"
IsPerceptive="True"
Orientation="Vertical"
ColorModel="Hsva"
ColorComponent="{Binding ThirdComponent, ElementName=ColorSpectrum}"
@ -699,15 +699,32 @@
</TabItem>
</TabControl>
<!-- Previewer -->
<!-- Note that top/bottom margins have -5 to remove for drop shadow padding -->
<!-- Note that the drop shadow is allowed to extend past the control bounds -->
<primitives:ColorPreviewer Grid.Row="1"
HsvColor="{Binding HsvColor, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
IsAccentColorsVisible="{TemplateBinding IsAccentColorsVisible}"
Margin="12,-5,12,7"
Margin="12,0,12,12"
IsVisible="{TemplateBinding IsColorPreviewVisible}" />
</Grid>
</ControlTemplate>
</Setter>
<!--
<Style Selector="^ /template/ primitives|ColorSlider#ColorSpectrumThirdComponentSlider[ColorComponent=Component1]">
<Setter Property="IsPerceptive" Value="True" />
</Style>
<Style Selector="^ /template/ primitives|ColorSlider#Component1Slider[ColorModel=Rgba]">
<Setter Property="IsPerceptive" Value="False" />
</Style>
<Style Selector="^ /template/ primitives|ColorSlider#Component2Slider[ColorModel=Rgba]">
<Setter Property="IsPerceptive" Value="False" />
</Style>
<Style Selector="^ /template/ primitives|ColorSlider#Component3Slider[ColorModel=Rgba]">
<Setter Property="IsPerceptive" Value="False" />
</Style>
-->
</ControlTheme>
</ResourceDictionary>

5
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;
}

2
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;

2
src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs

@ -762,7 +762,7 @@ namespace Avalonia.Controls
/// otherwise, false.</returns>
protected bool HasFocus()
{
Visual? focused = FocusManager.Instance?.Current as Visual;
Visual? focused = FocusManager.GetFocusManager(this)?.GetFocusedElement() as Visual;
while (focused != null)
{

2
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();
}
}

2
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<Control>(childCount);
for (int i = 0; i < Calendar.RowsPerMonth; i++)
for (int i = 0; i < Calendar.ColumnsPerMonth; i++)
{
if (DayTitleTemplate?.Build() is Control cell)
{

3
src/Avalonia.Controls/ComboBox.cs

@ -230,8 +230,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);
}
}
}

6
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 <template>.
@ -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
{

2
src/Avalonia.Controls/Control.cs

@ -213,7 +213,7 @@ namespace Avalonia.Controls
bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null;
/// <inheritdoc/>
void ISetterValue.Initialize(ISetter setter)
void ISetterValue.Initialize(SetterBase setter)
{
if (setter is Setter s && s.Property == ContextFlyoutProperty)
{

11
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);
}
}

6
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()

4
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();
}
}
}

11
src/Avalonia.Controls/ItemsControl.cs

@ -566,19 +566,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 +589,7 @@ namespace Avalonia.Controls
if (next != null)
{
focus.Focus(next, NavigationMethod.Directional, e.KeyModifiers);
next.Focus(NavigationMethod.Directional, e.KeyModifiers);
e.Handled = true;
}

6
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<Control>();
if (anc != null)
{
FocusManager.Instance?.Focus(anc);
anc.Focus();
}
}
}

5
src/Avalonia.Controls/TopLevel.cs

@ -452,6 +452,9 @@ namespace Avalonia.Controls
/// </summary>
public IClipboard? Clipboard => PlatformImpl?.TryGetFeature<IClipboard>();
/// <inheritdoc />
public IFocusManager? FocusManager => AvaloniaLocator.Current.GetService<IFocusManager>();
/// <inheritdoc/>
Point IRenderRoot.PointToClient(PixelPoint p)
{
@ -725,7 +728,7 @@ namespace Avalonia.Controls
void PlatformImpl_LostFocus()
{
var focused = (Visual?)FocusManager.Instance?.Current;
var focused = (Visual?)FocusManager?.GetFocusedElement();
if (focused == null)
return;
while (focused.VisualParent != null)

3
src/Avalonia.Controls/TreeView.cs

@ -560,8 +560,7 @@ namespace Avalonia.Controls
if (next != null)
{
FocusManager.Instance?.Focus(next, NavigationMethod.Directional);
e.Handled = true;
e.Handled = next.Focus(NavigationMethod.Directional);
}
}
else

2
src/Avalonia.Controls/TreeViewItem.cs

@ -238,7 +238,7 @@ namespace Avalonia.Controls
}
else
{
FocusManager.Instance?.Focus(treeViewItem, NavigationMethod.Directional);
treeViewItem.Focus(NavigationMethod.Directional);
}
return true;

2
src/Avalonia.Controls/VirtualizingStackPanel.cs

@ -343,7 +343,7 @@ namespace Avalonia.Controls
{
var items = Items;
if (_isInLayout || index < 0 || index >= items.Count || _realizedElements is null)
if (_isInLayout || index < 0 || index >= items.Count || _realizedElements is null || !IsEffectivelyVisible)
return null;
if (GetRealizedElement(index) is Control element)

4
src/Avalonia.Controls/WindowBase.cs

@ -234,7 +234,7 @@ namespace Avalonia.Controls
if (this is IFocusScope scope)
{
FocusManager.Instance?.RemoveFocusScope(scope);
((FocusManager?)FocusManager)?.RemoveFocusScope(scope);
}
base.HandleClosed();
@ -326,7 +326,7 @@ namespace Avalonia.Controls
if (scope != null)
{
FocusManager.Instance?.SetFocusScope(scope);
((FocusManager?)FocusManager)?.SetFocusScope(scope);
}
IsActive = true;

8
src/Avalonia.Diagnostics/Diagnostics/DevTools.cs

@ -86,7 +86,7 @@ namespace Avalonia.Diagnostics
private static IDisposable Open(IDevToolsTopLevelGroup topLevelGroup, DevToolsOptions options,
Window? owner, Application? app)
{
var focussedControl = KeyboardDevice.Instance?.FocusedElement as Control;
var focusedControl = owner?.FocusManager?.GetFocusedElement() as Control;
AvaloniaObject root = topLevelGroup switch
{
ClassicDesktopStyleApplicationLifetimeTopLevelGroup gr => new Controls.Application(gr, app ?? Application.Current!),
@ -98,7 +98,7 @@ namespace Avalonia.Diagnostics
if (s_open.TryGetValue(topLevelGroup, out var mainWindow))
{
mainWindow.Activate();
mainWindow.SelectedControl(focussedControl);
mainWindow.SelectedControl(focusedControl);
return Disposable.Empty;
}
if (topLevelGroup.Items.Count == 1 && topLevelGroup.Items is not INotifyCollectionChanged)
@ -110,7 +110,7 @@ namespace Avalonia.Diagnostics
if (group.Key.Items.Contains(singleTopLevel))
{
group.Value.Activate();
group.Value.SelectedControl(focussedControl);
group.Value.SelectedControl(focusedControl);
return Disposable.Empty;
}
}
@ -124,7 +124,7 @@ namespace Avalonia.Diagnostics
Tag = topLevelGroup
};
window.SetOptions(options);
window.SelectedControl(focussedControl);
window.SelectedControl(focusedControl);
window.Closed += DevToolsClosed;
s_open.Add(topLevelGroup, window);
if (options.ShowAsChildWindow && owner is not null)

2
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs

@ -64,7 +64,7 @@ namespace Avalonia.Diagnostics.ViewModels
// We need to place styles without activator first, such styles will be overwritten by ones with activators.
foreach (var appliedStyle in styleDiagnostics.AppliedStyles.OrderBy(s => s.HasActivator))
{
var styleSource = appliedStyle.Source;
var styleSource = appliedStyle.Style;
var setters = new List<SetterViewModel>();

4
src/Avalonia.Diagnostics/Diagnostics/ViewModels/StyleViewModel.cs

@ -5,11 +5,11 @@ namespace Avalonia.Diagnostics.ViewModels
{
internal class StyleViewModel : ViewModelBase
{
private readonly IStyleInstance _styleInstance;
private readonly AppliedStyle _styleInstance;
private bool _isActive;
private bool _isVisible;
public StyleViewModel(IStyleInstance styleInstance, string name, List<SetterViewModel> setters)
public StyleViewModel(AppliedStyle styleInstance, string name, List<SetterViewModel> setters)
{
_styleInstance = styleInstance;
IsVisible = true;

2
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs

@ -107,7 +107,7 @@ namespace Avalonia.LinuxFramebuffer
if (_topLevel is IFocusScope scope)
{
FocusManager.Instance?.SetFocusScope(scope);
((FocusManager)_topLevel.FocusManager).SetFocusScope(scope);
}
}

2
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs

@ -125,7 +125,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
var selfType = context.ParentNodes().OfType<XamlAstConstructableObjectNode>().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<AvaloniaXamlIlTargetTypeMetadataNode>().First().TargetType.GetClrType();
}

4
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");

6
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" };
}
}

2
src/Markup/Avalonia.Markup/Data/TemplateBinding.cs

@ -109,7 +109,7 @@ namespace Avalonia.Data
}
/// <inheritdoc/>
void ISetterValue.Initialize(ISetter setter) => _isSetterValue = true;
void ISetterValue.Initialize(SetterBase setter) => _isSetterValue = true;
protected override void Subscribed()
{

2
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();

2
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];

58
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());
}
}
}

4
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;

29
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<HsvColor, HslColor>[]
{
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);
}
}
}
}

36
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;
}
}

10
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);
}
}

6
tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs

@ -576,8 +576,9 @@ namespace Avalonia.Controls.UnitTests
});
var panel = Assert.IsAssignableFrom<Panel>(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<Panel>(target.ItemsPanelRoot);
var focusManager = ((IInputRoot)target.VisualRoot!).FocusManager;
Assert.Equal(panel.Children[2], FocusManager.Instance!.Current);
Assert.Equal(panel.Children[2], focusManager?.GetFocusedElement());
}
[Fact]

8
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@ -991,7 +991,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]
@ -1038,17 +1038,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)

4
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<NavigationMethod>(), It.IsAny<KeyModifiers>()));
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<NavigationMethod>(), It.IsAny<KeyModifiers>()));
Assert.True(e.Handled);
}

11
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<TextBox>(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);

8
tests/Avalonia.Controls.UnitTests/TabControlTests.cs

@ -461,7 +461,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 +513,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()

16
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<IKeyboardNavigationHandler>();
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]

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

@ -657,6 +657,40 @@ namespace Avalonia.Controls.UnitTests
root.LayoutManager.ExecuteLayoutPass();
}
[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<int> GetRealizedIndexes(VirtualizingStackPanel target, ItemsControl itemsControl)
{
return target.GetRealizedElements()

4
tests/Avalonia.LeakTests/ControlTests.cs

@ -561,7 +561,7 @@ namespace Avalonia.LeakTests
var window = new Window { Focusable = true };
window.Show();
Assert.Same(window, FocusManager.Instance.Current);
Assert.Same(window, window.FocusManager.GetFocusedElement());
// Context menu in resources means the baseline may not be 0.
var initialMenuCount = 0;
@ -608,7 +608,7 @@ namespace Avalonia.LeakTests
var window = new Window { Focusable = true };
window.Show();
Assert.Same(window, FocusManager.Instance.Current);
Assert.Same(window, window.FocusManager.GetFocusedElement());
// Context menu in resources means the baseline may not be 0.
var initialMenuCount = 0;

4
tests/Avalonia.UnitTests/StyleHelpers.cs

@ -6,9 +6,9 @@ namespace Avalonia.UnitTests
{
public static class StyleHelpers
{
public static SelectorMatchResult TryAttach(Style style, StyledElement element, object? host = null)
public static void TryAttach(Style style, StyledElement element, object? host = null)
{
return style.TryAttach(element, host ?? element, PropertyStore.FrameType.Style);
style.TryAttach(element, host ?? element, PropertyStore.FrameType.Style);
}
}
}

1
tests/Avalonia.UnitTests/TestRoot.cs

@ -54,6 +54,7 @@ namespace Avalonia.UnitTests
public IAccessKeyHandler AccessKeyHandler => null;
public IKeyboardNavigationHandler KeyboardNavigationHandler => null;
public IFocusManager FocusManager => AvaloniaLocator.Current.GetService<IFocusManager>();
public IInputElement PointerOverElement { get; set; }

Loading…
Cancel
Save