Browse Source

Merge branch 'rndr4' of github.com:AvaloniaUI/Avalonia into rndr4

pull/8105/head
Nikita Tsukanov 4 years ago
parent
commit
2f56bf7fc0
  1. 34
      native/Avalonia.Native/src/OSX/AvnWindow.mm
  2. 21
      native/Avalonia.Native/src/OSX/PopupImpl.mm
  3. 40
      native/Avalonia.Native/src/OSX/WindowBaseImpl.h
  4. 25
      native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
  5. 5
      native/Avalonia.Native/src/OSX/WindowImpl.h
  6. 30
      native/Avalonia.Native/src/OSX/WindowImpl.mm
  7. 4
      native/Avalonia.Native/src/OSX/WindowProtocol.h
  8. 2
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  9. 46
      src/Avalonia.Base/Data/Core/ExpressionNode.cs
  10. 5
      src/Avalonia.Base/Styling/ChildSelector.cs
  11. 5
      src/Avalonia.Base/Styling/DescendentSelector.cs
  12. 30
      src/Avalonia.Base/Styling/NestingSelector.cs
  13. 5
      src/Avalonia.Base/Styling/NotSelector.cs
  14. 3
      src/Avalonia.Base/Styling/NthChildSelector.cs
  15. 17
      src/Avalonia.Base/Styling/OrSelector.cs
  16. 3
      src/Avalonia.Base/Styling/PropertyEqualsSelector.cs
  17. 24
      src/Avalonia.Base/Styling/Selector.cs
  18. 5
      src/Avalonia.Base/Styling/Selectors.cs
  19. 46
      src/Avalonia.Base/Styling/Style.cs
  20. 58
      src/Avalonia.Base/Styling/StyleCache.cs
  21. 35
      src/Avalonia.Base/Styling/StyleChildren.cs
  22. 41
      src/Avalonia.Base/Styling/Styles.cs
  23. 5
      src/Avalonia.Base/Styling/TemplateSelector.cs
  24. 3
      src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs
  25. 141
      src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml
  26. 4
      src/Avalonia.Controls/MenuBase.cs
  27. 4
      src/Avalonia.Controls/MenuItem.cs
  28. 4
      src/Avalonia.Controls/NativeMenuItemBase.cs
  29. 18
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  30. 4
      src/Avalonia.Controls/RepeatButton.cs
  31. 80
      src/Avalonia.Themes.Fluent/Controls/Button.xaml
  32. 603
      src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml
  33. 24
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
  34. 30
      src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs
  35. 275
      tests/Avalonia.Base.UnitTests/Styling/SelectorTests_Nesting.cs
  36. 42
      tests/Avalonia.Base.UnitTests/Styling/StyleTests.cs
  37. 138
      tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs
  38. 32
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs

34
native/Avalonia.Native/src/OSX/AvnWindow.mm

@ -31,6 +31,7 @@
ComPtr<WindowBaseImpl> _parent;
bool _closed;
bool _isEnabled;
bool _canBecomeKeyWindow;
bool _isExtended;
AvnMenu* _menu;
}
@ -216,29 +217,38 @@
-(BOOL)canBecomeKeyWindow
{
// If the window has a child window being shown as a dialog then don't allow it to become the key window.
for(NSWindow* uch in [self childWindows])
if(_canBecomeKeyWindow)
{
if (![uch conformsToProtocol:@protocol(AvnWindowProtocol)])
// If the window has a child window being shown as a dialog then don't allow it to become the key window.
for(NSWindow* uch in [self childWindows])
{
continue;
}
if (![uch conformsToProtocol:@protocol(AvnWindowProtocol)])
{
continue;
}
id <AvnWindowProtocol> ch = (id <AvnWindowProtocol>) uch;
id <AvnWindowProtocol> ch = (id <AvnWindowProtocol>) uch;
return !ch.isDialog;
}
if(ch.isDialog)
return false;
}
return true;
return true;
}
return false;
}
#ifndef IS_NSPANEL
-(BOOL)canBecomeMainWindow
{
#ifdef IS_NSPANEL
return false;
#else
return true;
}
#endif
-(void)setCanBecomeKeyWindow:(bool)value
{
_canBecomeKeyWindow = value;
}
-(bool)shouldTryToHandleEvents

21
native/Avalonia.Native/src/OSX/PopupImpl.mm

@ -26,29 +26,16 @@ private:
PopupImpl(IAvnWindowEvents* events, IAvnGlContext* gl) : WindowBaseImpl(events, gl)
{
WindowEvents = events;
[Window setLevel:NSPopUpMenuWindowLevel];
}
protected:
virtual NSWindowStyleMask GetStyle() override
{
return NSWindowStyleMaskBorderless;
}
virtual HRESULT Resize(double x, double y, AvnPlatformResizeReason reason) override
virtual void OnInitialiseNSWindow () override
{
START_COM_CALL;
@autoreleasepool
{
if (Window != nullptr)
{
[Window setContentSize:NSSize{x, y}];
[Window setFrameTopLeftPoint:ToNSPoint(ConvertPointY(lastPositionSet))];
}
return S_OK;
}
[Window setLevel:NSPopUpMenuWindowLevel];
}
public:
@ -71,4 +58,4 @@ extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events, IAvnGlContext* gl)
IAvnPopup* ptr = dynamic_cast<IAvnPopup*>(new PopupImpl(events, gl));
return ptr;
}
}
}

40
native/Avalonia.Native/src/OSX/WindowBaseImpl.h

@ -16,8 +16,6 @@
class WindowBaseImpl : public virtual ComObject,
public virtual IAvnWindowBase,
public INSWindowHolder {
private:
NSCursor *cursor;
public:
FORWARD_IUNKNOWN()
@ -28,22 +26,6 @@ BEGIN_INTERFACE_MAP()
virtual ~WindowBaseImpl();
AutoFitContentView *StandardContainer;
AvnView *View;
NSWindow * Window;
ComPtr<IAvnWindowBaseEvents> BaseEvents;
ComPtr<IAvnGlContext> _glContext;
NSObject <IRenderTarget> *renderTarget;
AvnPoint lastPositionSet;
NSSize lastSize;
NSSize lastMinSize;
NSSize lastMaxSize;
AvnMenu* lastMenu;
NSString *_lastTitle;
bool _shown;
bool _inResize;
WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl);
virtual HRESULT ObtainNSWindowHandle(void **ret) override;
@ -122,11 +104,33 @@ protected:
virtual NSWindowStyleMask GetStyle();
void UpdateStyle();
virtual void OnInitialiseNSWindow ();
private:
void CreateNSWindow (bool isDialog);
void CleanNSWindow ();
void InitialiseNSWindow ();
NSCursor *cursor;
ComPtr<IAvnGlContext> _glContext;
bool hasPosition;
NSSize lastSize;
NSSize lastMinSize;
NSSize lastMaxSize;
AvnMenu* lastMenu;
bool _inResize;
protected:
AvnPoint lastPositionSet;
AutoFitContentView *StandardContainer;
bool _shown;
public:
NSObject <IRenderTarget> *renderTarget;
NSWindow * Window;
ComPtr<IAvnWindowBaseEvents> BaseEvents;
AvnView *View;
};
#endif //AVALONIA_NATIVE_OSX_WINDOWBASEIMPL_H

25
native/Avalonia.Native/src/OSX/WindowBaseImpl.mm

@ -30,12 +30,11 @@ WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl)
View = [[AvnView alloc] initWithParent:this];
StandardContainer = [[AutoFitContentView new] initWithContent:View];
lastPositionSet.X = -1;
lastPositionSet.Y = -1;
lastPositionSet = { 0, 0 };
hasPosition = false;
lastSize = NSSize { 100, 100 };
lastMaxSize = NSSize { CGFLOAT_MAX, CGFLOAT_MAX};
lastMinSize = NSSize { 0, 0 };
_lastTitle = @"";
Window = nullptr;
lastMenu = nullptr;
@ -92,15 +91,16 @@ HRESULT WindowBaseImpl::Show(bool activate, bool isDialog) {
CreateNSWindow(isDialog);
InitialiseNSWindow();
if(lastPositionSet.X >= 0 && lastPositionSet.Y >= 0)
if(hasPosition)
{
SetPosition(lastPositionSet);
} else
{
[Window center];
}
UpdateStyle();
[Window setTitle:_lastTitle];
if (ShouldTakeFocusOnShow() && activate) {
[Window orderFront:Window];
[Window makeKeyAndOrderFront:Window];
@ -288,12 +288,12 @@ HRESULT WindowBaseImpl::Resize(double x, double y, AvnPlatformResizeReason reaso
}
@try {
lastSize = NSSize {x, y};
if (!_shown) {
BaseEvents->Resized(AvnSize{x, y}, reason);
}
lastSize = NSSize {x, y};
if(Window != nullptr) {
[Window setContentSize:lastSize];
}
@ -385,6 +385,7 @@ HRESULT WindowBaseImpl::SetPosition(AvnPoint point) {
@autoreleasepool {
lastPositionSet = point;
hasPosition = true;
if(Window != nullptr) {
[Window setFrameTopLeftPoint:ToNSPoint(ConvertPointY(point))];
@ -566,6 +567,11 @@ void WindowBaseImpl::CreateNSWindow(bool isDialog) {
}
}
void WindowBaseImpl::OnInitialiseNSWindow()
{
}
void WindowBaseImpl::InitialiseNSWindow() {
if(Window != nullptr) {
[Window setContentView:StandardContainer];
@ -577,7 +583,6 @@ void WindowBaseImpl::InitialiseNSWindow() {
[Window setContentMaxSize:lastMaxSize];
[Window setOpaque:false];
[Window center];
if (lastMenu != nullptr) {
[GetWindowProtocol() applyMenu:lastMenu];
@ -586,6 +591,8 @@ void WindowBaseImpl::InitialiseNSWindow() {
[GetWindowProtocol() showWindowMenuWithAppMenu];
}
}
OnInitialiseNSWindow();
}
}

5
native/Avalonia.Native/src/OSX/WindowImpl.h

@ -88,9 +88,14 @@ BEGIN_INTERFACE_MAP()
virtual HRESULT SetWindowState (AvnWindowState state) override;
virtual bool IsDialog() override;
virtual void OnInitialiseNSWindow() override;
protected:
virtual NSWindowStyleMask GetStyle() override;
private:
NSString *_lastTitle;
};
#endif //AVALONIA_NATIVE_OSX_WINDOWIMPL_H

30
native/Avalonia.Native/src/OSX/WindowImpl.mm

@ -19,10 +19,8 @@ WindowImpl::WindowImpl(IAvnWindowEvents *events, IAvnGlContext *gl) : WindowBase
_inSetWindowState = false;
_lastWindowState = Normal;
_actualWindowState = Normal;
_lastTitle = @"";
WindowEvents = events;
[Window disableCursorRects];
[Window setTabbingMode:NSWindowTabbingModeDisallowed];
[Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
}
void WindowImpl::HideOrShowTrafficLights() {
@ -50,25 +48,29 @@ void WindowImpl::HideOrShowTrafficLights() {
}
}
void WindowImpl::OnInitialiseNSWindow(){
[GetWindowProtocol() setCanBecomeKeyWindow:true];
[Window disableCursorRects];
[Window setTabbingMode:NSWindowTabbingModeDisallowed];
[Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
[Window setTitle:_lastTitle];
if(_isClientAreaExtended)
{
[GetWindowProtocol() setIsExtended:true];
SetExtendClientArea(true);
}
}
HRESULT WindowImpl::Show(bool activate, bool isDialog) {
START_COM_CALL;
@autoreleasepool {
_isDialog = isDialog;
bool created = Window == nullptr;
WindowBaseImpl::Show(activate, isDialog);
if(created)
{
if(_isClientAreaExtended)
{
[GetWindowProtocol() setIsExtended:true];
SetExtendClientArea(true);
}
}
HideOrShowTrafficLights();
return SetWindowState(_lastWindowState);

4
native/Avalonia.Native/src/OSX/WindowProtocol.h

@ -22,4 +22,6 @@
-(void) setIsExtended:(bool)value;
-(void) disconnectParent;
-(bool) isDialog;
@end
-(void) setCanBecomeKeyWindow:(bool)value;
@end

2
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -40,7 +40,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_gl = GlPlatformSurface.TryCreate(this);
_framebuffer = new FramebufferManager(this);
RenderScaling = (int)_view.Scaling;
RenderScaling = _view.Scaling;
MaxClientSize = new PixelSize(_view.Resources.DisplayMetrics.WidthPixels,
_view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling);

46
src/Avalonia.Base/Data/Core/ExpressionNode.cs

@ -98,36 +98,36 @@ namespace Avalonia.Data.Core
private void ValueChanged(object? value, bool notify)
{
if (_subscriber is null)
return;
var notification = value as BindingNotification;
if (notification == null)
if (_subscriber is { } subscriber)
{
LastValue = value != null ? new WeakReference<object?>(value) : NullReference;
var notification = value as BindingNotification;
var next = Next;
if (Next != null)
if (notification == null)
{
Next.Target = LastValue;
LastValue = value != null ? new WeakReference<object?>(value) : NullReference;
if (next != null)
{
next.Target = LastValue;
}
else if (notify)
{
subscriber(value);
}
}
else if (notify)
else
{
_subscriber(value);
}
}
else
{
LastValue = notification.Value != null ? new WeakReference<object?>(notification.Value) : NullReference;
LastValue = notification.Value != null ? new WeakReference<object?>(notification.Value) : NullReference;
if (Next != null)
{
Next.Target = LastValue;
}
if (next != null)
{
next.Target = LastValue;
}
if (Next == null || notification.Error != null)
{
_subscriber(value);
if (next == null || notification.Error != null)
{
subscriber(value);
}
}
}
}

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

@ -37,13 +37,13 @@ namespace Avalonia.Styling
return _selectorString;
}
protected override SelectorMatch Evaluate(IStyleable control, bool subscribe)
protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe)
{
var controlParent = ((ILogical)control).LogicalParent;
if (controlParent != null)
{
var parentMatch = _parent.Match((IStyleable)controlParent, subscribe);
var parentMatch = _parent.Match((IStyleable)controlParent, parent, subscribe);
if (parentMatch.Result == SelectorMatchResult.Sometimes)
{
@ -65,5 +65,6 @@ namespace Avalonia.Styling
}
protected override Selector? MovePrevious() => null;
internal override bool HasValidNestingSelector() => _parent.HasValidNestingSelector();
}
}

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

@ -35,7 +35,7 @@ namespace Avalonia.Styling
return _selectorString;
}
protected override SelectorMatch Evaluate(IStyleable control, bool subscribe)
protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe)
{
var c = (ILogical)control;
var descendantMatches = new OrActivatorBuilder();
@ -46,7 +46,7 @@ namespace Avalonia.Styling
if (c is IStyleable)
{
var match = _parent.Match((IStyleable)c, subscribe);
var match = _parent.Match((IStyleable)c, parent, subscribe);
if (match.Result == SelectorMatchResult.Sometimes)
{
@ -70,5 +70,6 @@ namespace Avalonia.Styling
}
protected override Selector? MovePrevious() => null;
internal override bool HasValidNestingSelector() => _parent.HasValidNestingSelector();
}
}

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

@ -0,0 +1,30 @@
using System;
namespace Avalonia.Styling
{
/// <summary>
/// The `^` nesting style selector.
/// </summary>
internal class NestingSelector : Selector
{
public override bool InTemplate => false;
public override bool IsCombinator => false;
public override Type? TargetType => null;
public override string ToString() => "^";
protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe)
{
if (parent is Style s && s.Selector is Selector selector)
{
return selector.Match(control, (parent as Style)?.Parent, subscribe);
}
throw new InvalidOperationException(
"Nesting selector was specified but cannot determine parent selector.");
}
protected override Selector? MovePrevious() => null;
internal override bool HasValidNestingSelector() => true;
}
}

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

@ -45,9 +45,9 @@ namespace Avalonia.Styling
return _selectorString;
}
protected override SelectorMatch Evaluate(IStyleable control, bool subscribe)
protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe)
{
var innerResult = _argument.Match(control, subscribe);
var innerResult = _argument.Match(control, parent, subscribe);
switch (innerResult.Result)
{
@ -67,5 +67,6 @@ namespace Avalonia.Styling
}
protected override Selector? MovePrevious() => _previous;
internal override bool HasValidNestingSelector() => _argument.HasValidNestingSelector();
}
}

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

@ -48,7 +48,7 @@ namespace Avalonia.Styling
public int Step { get; }
public int Offset { get; }
protected override SelectorMatch Evaluate(IStyleable control, bool subscribe)
protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe)
{
if (!(control is ILogical logical))
{
@ -105,6 +105,7 @@ namespace Avalonia.Styling
}
protected override Selector? MovePrevious() => _previous;
internal override bool HasValidNestingSelector() => _previous?.HasValidNestingSelector() ?? false;
public override string ToString()
{

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

@ -65,14 +65,14 @@ namespace Avalonia.Styling
return _selectorString;
}
protected override SelectorMatch Evaluate(IStyleable control, bool subscribe)
protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe)
{
var activators = new OrActivatorBuilder();
var neverThisInstance = false;
foreach (var selector in _selectors)
{
var match = selector.Match(control, subscribe);
var match = selector.Match(control, parent, subscribe);
switch (match.Result)
{
@ -104,6 +104,19 @@ namespace Avalonia.Styling
protected override Selector? MovePrevious() => null;
internal override bool HasValidNestingSelector()
{
foreach (var selector in _selectors)
{
if (!selector.HasValidNestingSelector())
{
return false;
}
}
return true;
}
private Type? EvaluateTargetType()
{
Type? result = null;

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

@ -74,7 +74,7 @@ namespace Avalonia.Styling
}
/// <inheritdoc/>
protected override SelectorMatch Evaluate(IStyleable control, bool subscribe)
protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe)
{
if (subscribe)
{
@ -90,6 +90,7 @@ namespace Avalonia.Styling
}
protected override Selector? MovePrevious() => _previous;
internal override bool HasValidNestingSelector() => _previous?.HasValidNestingSelector() ?? false;
internal static bool Compare(Type propertyType, object? propertyValue, object? value)
{

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

@ -33,22 +33,25 @@ namespace Avalonia.Styling
/// Tries to match the selector with a control.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="parent">
/// The parent style, if the style containing the selector is a nested style.
/// </param>
/// <param name="subscribe">
/// Whether the match should subscribe to changes in order to track the match over time,
/// or simply return an immediate result.
/// </param>
/// <returns>A <see cref="SelectorMatch"/>.</returns>
public SelectorMatch Match(IStyleable control, bool subscribe = true)
public SelectorMatch Match(IStyleable 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
// will be on the left.
var match = MatchUntilCombinator(control, this, subscribe, out var combinator);
var match = MatchUntilCombinator(control, this, parent, subscribe, out var combinator);
// If the pre-combinator selector matches, we can now match the combinator, if any.
if (match.IsMatch && combinator is object)
{
match = match.And(combinator.Match(control, subscribe));
match = match.And(combinator.Match(control, parent, subscribe));
// If we have a combinator then we can never say that we always match a control of
// this type, because by definition the combinator matches on things outside of the
@ -68,28 +71,34 @@ namespace Avalonia.Styling
/// Evaluates the selector for a match.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="parent">
/// The parent style, if the style containing the selector is a nested style.
/// </param>
/// <param name="subscribe">
/// Whether the match should subscribe to changes in order to track the match over time,
/// or simply return an immediate result.
/// </param>
/// <returns>A <see cref="SelectorMatch"/>.</returns>
protected abstract SelectorMatch Evaluate(IStyleable control, bool subscribe);
protected abstract SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe);
/// <summary>
/// Moves to the previous selector.
/// </summary>
protected abstract Selector? MovePrevious();
internal abstract bool HasValidNestingSelector();
private static SelectorMatch MatchUntilCombinator(
IStyleable control,
Selector start,
IStyle? parent,
bool subscribe,
out Selector? combinator)
{
combinator = null;
var activators = new AndActivatorBuilder();
var result = Match(control, start, subscribe, ref activators, ref combinator);
var result = Match(control, start, parent, subscribe, ref activators, ref combinator);
return result == SelectorMatchResult.Sometimes ?
new SelectorMatch(activators.Get()) :
@ -99,6 +108,7 @@ namespace Avalonia.Styling
private static SelectorMatchResult Match(
IStyleable control,
Selector selector,
IStyle? parent,
bool subscribe,
ref AndActivatorBuilder activators,
ref Selector? combinator)
@ -110,7 +120,7 @@ namespace Avalonia.Styling
// opportunity to exit early.
if (previous != null && !previous.IsCombinator)
{
var previousMatch = Match(control, previous, subscribe, ref activators, ref combinator);
var previousMatch = Match(control, previous, parent, subscribe, ref activators, ref combinator);
if (previousMatch < SelectorMatchResult.Sometimes)
{
@ -119,7 +129,7 @@ namespace Avalonia.Styling
}
// Match this selector.
var match = selector.Evaluate(control, subscribe);
var match = selector.Evaluate(control, parent, subscribe);
if (!match.IsMatch)
{

5
src/Avalonia.Base/Styling/Selectors.cs

@ -109,6 +109,11 @@ namespace Avalonia.Styling
}
}
public static Selector Nesting(this Selector? previous)
{
return new NestingSelector();
}
/// <summary>
/// Returns a selector which inverts the results of selector argument.
/// </summary>

46
src/Avalonia.Base/Styling/Style.cs

@ -4,8 +4,6 @@ using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Metadata;
#nullable enable
namespace Avalonia.Styling
{
/// <summary>
@ -14,9 +12,11 @@ namespace Avalonia.Styling
public class Style : AvaloniaObject, IStyle, IResourceProvider
{
private IResourceHost? _owner;
private StyleChildren? _children;
private IResourceDictionary? _resources;
private List<ISetter>? _setters;
private List<IAnimation>? _animations;
private StyleCache? _childCache;
/// <summary>
/// Initializes a new instance of the <see cref="Style"/> class.
@ -34,6 +34,14 @@ namespace Avalonia.Styling
Selector = selector(null);
}
/// <summary>
/// Gets the children of the style.
/// </summary>
public IList<IStyle> Children => _children ??= new(this);
/// <summary>
/// Gets the <see cref="StyledElement"/> or Application that hosts the style.
/// </summary>
public IResourceHost? Owner
{
get => _owner;
@ -47,6 +55,11 @@ namespace Avalonia.Styling
}
}
/// <summary>
/// Gets the parent style if this style is hosted in a <see cref="Style.Children"/> collection.
/// </summary>
public Style? Parent { get; private set; }
/// <summary>
/// Gets or sets a dictionary of style resources.
/// </summary>
@ -90,7 +103,7 @@ namespace Avalonia.Styling
public IList<IAnimation> Animations => _animations ??= new List<IAnimation>();
bool IResourceNode.HasResources => _resources?.Count > 0;
IReadOnlyList<IStyle> IStyle.Children => Array.Empty<IStyle>();
IReadOnlyList<IStyle> IStyle.Children => (IReadOnlyList<IStyle>?)_children ?? Array.Empty<IStyle>();
public event EventHandler? OwnerChanged;
@ -98,7 +111,7 @@ namespace Avalonia.Styling
{
target = target ?? throw new ArgumentNullException(nameof(target));
var match = Selector is object ? Selector.Match(target) :
var match = Selector is object ? Selector.Match(target, Parent) :
target == host ? SelectorMatch.AlwaysThisInstance : SelectorMatch.NeverThisInstance;
if (match.IsMatch && (_setters is object || _animations is object))
@ -108,7 +121,17 @@ namespace Avalonia.Styling
instance.Start();
}
return match.Result;
var result = match.Result;
if (_children is not null)
{
_childCache ??= new StyleCache();
var childResult = _childCache.TryAttach(_children, target, host);
if (childResult > result)
result = childResult;
}
return result;
}
public bool TryGetResource(object key, out object? result)
@ -156,5 +179,18 @@ namespace Avalonia.Styling
_resources?.RemoveOwner(owner);
}
}
internal void SetParent(Style? parent)
{
if (parent?.Selector is not null)
{
if (Selector is null)
throw new InvalidOperationException("Child styles must have a selector.");
if (!Selector.HasValidNestingSelector())
throw new InvalidOperationException("Child styles must have a nesting selector.");
}
Parent = parent;
}
}
}

58
src/Avalonia.Base/Styling/StyleCache.cs

@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
namespace Avalonia.Styling
{
/// <summary>
/// Simple cache for improving performance of applying styles.
/// </summary>
/// <remarks>
/// Maps <see cref="IStyleable.StyleKey"/> to a list of styles that are known be be possible
/// matches.
/// </remarks>
internal class StyleCache : Dictionary<Type, List<IStyle>?>
{
public SelectorMatchResult TryAttach(IList<IStyle> styles, IStyleable target, IStyleHost? host)
{
if (TryGetValue(target.StyleKey, out var cached))
{
if (cached is object)
{
var result = SelectorMatchResult.NeverThisType;
foreach (var style in cached)
{
var childResult = style.TryAttach(target, host);
if (childResult > result)
result = childResult;
}
return result;
}
else
{
return SelectorMatchResult.NeverThisType;
}
}
else
{
List<IStyle>? matches = null;
foreach (var child in styles)
{
if (child.TryAttach(target, host) != SelectorMatchResult.NeverThisType)
{
matches ??= new List<IStyle>();
matches.Add(child);
}
}
Add(target.StyleKey, matches);
return matches is null ?
SelectorMatchResult.NeverThisType :
SelectorMatchResult.AlwaysThisType;
}
}
}
}

35
src/Avalonia.Base/Styling/StyleChildren.cs

@ -0,0 +1,35 @@
using System.Collections.ObjectModel;
using Avalonia.Controls;
namespace Avalonia.Styling
{
internal class StyleChildren : Collection<IStyle>
{
private readonly Style _owner;
public StyleChildren(Style owner) => _owner = owner;
protected override void InsertItem(int index, IStyle item)
{
(item as Style)?.SetParent(_owner);
base.InsertItem(index, item);
}
protected override void RemoveItem(int index)
{
var item = Items[index];
(item as Style)?.SetParent(null);
if (_owner.Owner is IResourceHost host)
(item as IResourceProvider)?.RemoveOwner(host);
base.RemoveItem(index);
}
protected override void SetItem(int index, IStyle item)
{
(item as Style)?.SetParent(_owner);
base.SetItem(index, item);
if (_owner.Owner is IResourceHost host)
(item as IResourceProvider)?.AddOwner(host);
}
}
}

41
src/Avalonia.Base/Styling/Styles.cs

@ -20,7 +20,7 @@ namespace Avalonia.Styling
private readonly AvaloniaList<IStyle> _styles = new AvaloniaList<IStyle>();
private IResourceHost? _owner;
private IResourceDictionary? _resources;
private Dictionary<Type, List<IStyle>?>? _cache;
private StyleCache? _cache;
public Styles()
{
@ -111,43 +111,8 @@ namespace Avalonia.Styling
public SelectorMatchResult TryAttach(IStyleable target, IStyleHost? host)
{
_cache ??= new Dictionary<Type, List<IStyle>?>();
if (_cache.TryGetValue(target.StyleKey, out var cached))
{
if (cached is object)
{
foreach (var style in cached)
{
style.TryAttach(target, host);
}
return SelectorMatchResult.AlwaysThisType;
}
else
{
return SelectorMatchResult.NeverThisType;
}
}
else
{
List<IStyle>? matches = null;
foreach (var child in this)
{
if (child.TryAttach(target, host) != SelectorMatchResult.NeverThisType)
{
matches ??= new List<IStyle>();
matches.Add(child);
}
}
_cache.Add(target.StyleKey, matches);
return matches is null ?
SelectorMatchResult.NeverThisType :
SelectorMatchResult.AlwaysThisType;
}
_cache ??= new StyleCache();
return _cache.TryAttach(this, target, host);
}
/// <inheritdoc/>

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

@ -36,7 +36,7 @@ namespace Avalonia.Styling
return _selectorString;
}
protected override SelectorMatch Evaluate(IStyleable control, bool subscribe)
protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe)
{
var templatedParent = control.TemplatedParent as IStyleable;
@ -45,9 +45,10 @@ namespace Avalonia.Styling
return SelectorMatch.NeverThisInstance;
}
return _parent.Match(templatedParent, subscribe);
return _parent.Match(templatedParent, parent, subscribe);
}
protected override Selector? MovePrevious() => null;
internal override bool HasValidNestingSelector() => _parent?.HasValidNestingSelector() ?? false;
}
}

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

@ -94,7 +94,7 @@ namespace Avalonia.Styling
}
/// <inheritdoc/>
protected override SelectorMatch Evaluate(IStyleable control, bool subscribe)
protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe)
{
if (TargetType != null)
{
@ -140,6 +140,7 @@ namespace Avalonia.Styling
}
protected override Selector? MovePrevious() => _previous;
internal override bool HasValidNestingSelector() => _previous?.HasValidNestingSelector() ?? false;
private string BuildSelectorString()
{

141
src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml

@ -1,87 +1,51 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Styles.Resources>
<Thickness x:Key="DataGridTextColumnCellTextBlockMargin">12,0,12,0</Thickness>
<x:Double x:Key="ListAccentLowOpacity">0.6</x:Double>
<x:Double x:Key="ListAccentMediumOpacity">0.8</x:Double>
<Thickness x:Key="DataGridTextColumnCellTextBlockMargin">12,0,12,0</Thickness>
<StreamGeometry x:Key="DataGridSortIconDescendingPath">M1875 1011l-787 787v-1798h-128v1798l-787 -787l-90 90l941 941l941 -941z</StreamGeometry>
<StreamGeometry x:Key="DataGridSortIconAscendingPath">M1965 947l-941 -941l-941 941l90 90l787 -787v1798h128v-1798l787 787z</StreamGeometry>
<StreamGeometry x:Key="DataGridRowGroupHeaderIconClosedPath">M515 93l930 931l-930 931l90 90l1022 -1021l-1022 -1021z</StreamGeometry>
<StreamGeometry x:Key="DataGridRowGroupHeaderIconOpenedPath">M1939 1581l90 -90l-1005 -1005l-1005 1005l90 90l915 -915z</StreamGeometry>
<SolidColorBrush x:Key="DataGridColumnHeaderForegroundBrush" Color="{DynamicResource SystemBaseMediumColor}" />
<SolidColorBrush x:Key="DataGridColumnHeaderBackgroundBrush" Color="{DynamicResource SystemAltHighColor}" />
<SolidColorBrush x:Key="DataGridColumnHeaderHoveredBackgroundBrush" Color="{DynamicResource SystemListLowColor}" />
<SolidColorBrush x:Key="DataGridColumnHeaderPressedBackgroundBrush" Color="{DynamicResource SystemListMediumColor}" />
<SolidColorBrush x:Key="DataGridColumnHeaderDraggedBackgroundBrush" Color="{DynamicResource SystemChromeMediumLowColor}" />
<SolidColorBrush x:Key="DataGridRowGroupHeaderBackgroundBrush" Color="{DynamicResource SystemChromeMediumColor}" />
<SolidColorBrush x:Key="DataGridRowGroupHeaderPressedBackgroundBrush" Color="{DynamicResource SystemListMediumColor}" />
<SolidColorBrush x:Key="DataGridRowGroupHeaderForegroundBrush" Color="{DynamicResource SystemBaseHighColor}" />
<SolidColorBrush x:Key="DataGridRowGroupHeaderHoveredBackgroundBrush" Color="{DynamicResource SystemListLowColor}" />
<StaticResource x:Key="DataGridRowBackgroundBrush" ResourceKey="SystemControlTransparentBrush" />
<SolidColorBrush x:Key="DataGridRowSelectedBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<StaticResource x:Key="DataGridRowSelectedBackgroundOpacity" ResourceKey="ListAccentLowOpacity" />
<SolidColorBrush x:Key="DataGridRowSelectedHoveredBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<StaticResource x:Key="DataGridRowSelectedHoveredBackgroundOpacity" ResourceKey="ListAccentMediumOpacity" />
<SolidColorBrush x:Key="DataGridRowSelectedUnfocusedBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<StaticResource x:Key="DataGridRowSelectedUnfocusedBackgroundOpacity" ResourceKey="ListAccentLowOpacity" />
<SolidColorBrush x:Key="DataGridRowSelectedHoveredUnfocusedBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<StaticResource x:Key="DataGridRowSelectedHoveredUnfocusedBackgroundOpacity" ResourceKey="ListAccentMediumOpacity" />
<SolidColorBrush x:Key="DataGridRowHoveredBackgroundColor" Color="{DynamicResource SystemListLowColor}" />
<SolidColorBrush x:Key="DataGridRowInvalidBrush" Color="{DynamicResource SystemErrorTextColor}" />
<SolidColorBrush x:Key="DataGridRowHeaderForegroundBrush" Color="{DynamicResource SystemBaseMediumColor}" />
<SolidColorBrush x:Key="DataGridRowHeaderBackgroundBrush" Color="{DynamicResource SystemAltHighColor}" />
<StaticResource x:Key="DataGridCellBackgroundBrush" ResourceKey="SystemControlTransparentBrush" />
<SolidColorBrush x:Key="DataGridCellFocusVisualPrimaryBrush" Color="{DynamicResource SystemBaseHighColor}" />
<SolidColorBrush x:Key="DataGridCellFocusVisualSecondaryBrush" Color="{DynamicResource SystemAltMediumColor}" />
<SolidColorBrush x:Key="DataGridCellInvalidBrush" Color="{DynamicResource SystemErrorTextColor}" />
<SolidColorBrush x:Key="DataGridGridLinesBrush"
Color="{StaticResource SystemBaseMediumLowColor}"
Opacity="0.4" />
<SolidColorBrush x:Key="DataGridDropLocationIndicatorBackground"
Color="#3F4346" />
<SolidColorBrush x:Key="DataGridDisabledVisualElementBackground"
Color="#8CFFFFFF" />
<SolidColorBrush x:Key="DataGridFillerGridLinesBrush"
Color="Transparent" />
<SolidColorBrush x:Key="DataGridCurrencyVisualPrimaryBrush"
Color="Transparent" />
<StaticResource x:Key="DataGridColumnHeaderBackgroundColor"
ResourceKey="SystemAltHighColor" />
<SolidColorBrush x:Key="DataGridColumnHeaderBackgroundBrush"
Color="{StaticResource DataGridColumnHeaderBackgroundColor}" />
<StaticResource x:Key="DataGridScrollBarsSeparatorBackground"
ResourceKey="SystemChromeLowColor" />
<StaticResource x:Key="DataGridColumnHeaderForegroundBrush"
ResourceKey="SystemControlForegroundBaseMediumBrush" />
<StaticResource x:Key="DataGridColumnHeaderHoveredBackgroundColor"
ResourceKey="SystemListLowColor" />
<StaticResource x:Key="DataGridColumnHeaderPressedBackgroundColor"
ResourceKey="SystemListMediumColor" />
<StaticResource x:Key="DataGridColumnHeaderDraggedBackgroundBrush"
ResourceKey="SystemControlBackgroundChromeMediumLowBrush" />
<StaticResource x:Key="DataGridColumnHeaderPointerOverBrush"
ResourceKey="SystemControlHighlightListLowBrush" />
<StaticResource x:Key="DataGridColumnHeaderPressedBrush"
ResourceKey="SystemControlHighlightListMediumBrush" />
<StaticResource x:Key="DataGridDetailsPresenterBackgroundBrush"
ResourceKey="SystemControlBackgroundChromeMediumLowBrush" />
<StaticResource x:Key="DataGridFillerColumnGridLinesBrush"
ResourceKey="DataGridFillerGridLinesBrush" />
<StaticResource x:Key="DataGridRowSelectedBackgroundColor"
ResourceKey="SystemAccentColor" />
<StaticResource x:Key="DataGridRowSelectedBackgroundOpacity"
ResourceKey="ListAccentLowOpacity" />
<StaticResource x:Key="DataGridRowSelectedHoveredBackgroundColor"
ResourceKey="SystemAccentColor" />
<StaticResource x:Key="DataGridRowSelectedHoveredBackgroundOpacity"
ResourceKey="ListAccentMediumOpacity" />
<StaticResource x:Key="DataGridRowSelectedUnfocusedBackgroundColor"
ResourceKey="SystemAccentColor" />
<StaticResource x:Key="DataGridRowSelectedUnfocusedBackgroundOpacity"
ResourceKey="ListAccentLowOpacity" />
<StaticResource x:Key="DataGridRowSelectedHoveredUnfocusedBackgroundColor"
ResourceKey="SystemAccentColor" />
<StaticResource x:Key="DataGridRowSelectedHoveredUnfocusedBackgroundOpacity"
ResourceKey="ListAccentMediumOpacity" />
<StaticResource x:Key="DataGridRowHeaderForegroundBrush"
ResourceKey="SystemControlForegroundBaseMediumBrush" />
<StaticResource x:Key="DataGridRowHeaderBackgroundBrush"
ResourceKey="SystemControlBackgroundAltHighBrush" />
<StaticResource x:Key="DataGridRowGroupHeaderBackgroundBrush"
ResourceKey="SystemControlBackgroundChromeMediumBrush" />
<StaticResource x:Key="DataGridRowGroupHeaderHoveredBackgroundBrush"
ResourceKey="SystemControlBackgroundListLowBrush" />
<StaticResource x:Key="DataGridRowGroupHeaderPressedBackgroundBrush"
ResourceKey="SystemControlBackgroundListMediumBrush" />
<StaticResource x:Key="DataGridRowGroupHeaderForegroundBrush"
ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="DataGridRowInvalidBrush"
ResourceKey="SystemErrorTextColor" />
<StaticResource x:Key="DataGridCellBackgroundBrush"
ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="DataGridCellFocusVisualPrimaryBrush"
ResourceKey="SystemControlFocusVisualPrimaryBrush" />
<StaticResource x:Key="DataGridCellFocusVisualSecondaryBrush"
ResourceKey="SystemControlFocusVisualSecondaryBrush" />
<StaticResource x:Key="DataGridCellInvalidBrush"
ResourceKey="SystemErrorTextColor" />
Opacity="0.4"
Color="{DynamicResource SystemBaseMediumLowColor}" />
<StaticResource x:Key="DataGridCurrencyVisualPrimaryBrush" ResourceKey="SystemControlTransparentBrush" />
<SolidColorBrush x:Key="DataGridDetailsPresenterBackgroundBrush" Color="{DynamicResource SystemChromeMediumLowColor}" />
<StaticResource x:Key="DataGridFillerColumnGridLinesBrush" ResourceKey="SystemControlTransparentBrush" />
</Styles.Resources>
<Style Selector="DataGridCell">
@ -252,10 +216,10 @@
</Style>
<Style Selector="DataGridColumnHeader:pointerover /template/ Grid#PART_ColumnHeaderRoot">
<Setter Property="Background" Value="{DynamicResource DataGridColumnHeaderHoveredBackgroundColor}" />
<Setter Property="Background" Value="{DynamicResource DataGridColumnHeaderHoveredBackgroundBrush}" />
</Style>
<Style Selector="DataGridColumnHeader:pressed /template/ Grid#PART_ColumnHeaderRoot">
<Setter Property="Background" Value="{DynamicResource DataGridColumnHeaderPressedBackgroundColor}" />
<Setter Property="Background" Value="{DynamicResource DataGridColumnHeaderPressedBackgroundBrush}" />
</Style>
<Style Selector="DataGridColumnHeader:dragIndicator">
@ -336,29 +300,30 @@
</Style>
<Style Selector="DataGridRow /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource SystemControlTransparentBrush}" />
<Setter Property="Fill" Value="{DynamicResource DataGridRowBackgroundBrush}" />
</Style>
<Style Selector="DataGridRow:pointerover /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource SystemListLowColor}" />
<Setter Property="Fill" Value="{DynamicResource DataGridRowHoveredBackgroundColor}" />
</Style>
<Style Selector="DataGridRow:selected /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedUnfocusedBackgroundColor}" />
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedUnfocusedBackgroundBrush}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedUnfocusedBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRow:selected:pointerover /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedHoveredUnfocusedBackgroundColor}" />
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedHoveredUnfocusedBackgroundBrush}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedHoveredUnfocusedBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRow:selected:focus /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedBackgroundColor}" />
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedBackgroundBrush}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRow:selected:pointerover:focus /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedHoveredBackgroundColor}" />
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedHoveredBackgroundBrush}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedHoveredBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRowHeader">
<Setter Property="Foreground" Value="{DynamicResource DataGridRowHeaderForegroundBrush}" />
<Setter Property="Background" Value="{DynamicResource DataGridRowHeaderBackgroundBrush}" />
<Setter Property="Focusable" Value="False" />
<Setter Property="SeparatorBrush" Value="{DynamicResource DataGridGridLinesBrush}" />
@ -410,25 +375,25 @@
</Style>
<Style Selector="DataGridRowHeader /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource SystemControlTransparentBrush}" />
<Setter Property="Fill" Value="{DynamicResource DataGridRowBackgroundBrush}" />
</Style>
<Style Selector="DataGridRow:pointerover DataGridRowHeader /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource SystemListLowColor}" />
<Setter Property="Fill" Value="{DynamicResource DataGridRowHoveredBackgroundColor}" />
</Style>
<Style Selector="DataGridRowHeader:selected /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedUnfocusedBackgroundColor}" />
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedUnfocusedBackgroundBrush}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedUnfocusedBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRow:pointerover DataGridRowHeader:selected /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedHoveredUnfocusedBackgroundColor}" />
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedHoveredUnfocusedBackgroundBrush}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedHoveredUnfocusedBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRowHeader:selected:focus /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedBackgroundColor}" />
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedBackgroundBrush}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRow:pointerover DataGridRowHeader:selected:focus /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedHoveredBackgroundColor}" />
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedHoveredBackgroundBrush}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedHoveredBackgroundOpacity}" />
</Style>

4
src/Avalonia.Controls/MenuBase.cs

@ -18,8 +18,8 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="IsOpen"/> property.
/// </summary>
public static readonly DirectProperty<Menu, bool> IsOpenProperty =
AvaloniaProperty.RegisterDirect<Menu, bool>(
public static readonly DirectProperty<MenuBase, bool> IsOpenProperty =
AvaloniaProperty.RegisterDirect<MenuBase, bool>(
nameof(IsOpen),
o => o.IsOpen);

4
src/Avalonia.Controls/MenuItem.cs

@ -85,13 +85,13 @@ namespace Avalonia.Controls
/// Defines the <see cref="PointerEnterItem"/> event.
/// </summary>
public static readonly RoutedEvent<PointerEventArgs> PointerEnterItemEvent =
RoutedEvent.Register<InputElement, PointerEventArgs>(nameof(PointerEnterItem), RoutingStrategies.Bubble);
RoutedEvent.Register<MenuItem, PointerEventArgs>(nameof(PointerEnterItem), RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="PointerLeaveItem"/> event.
/// </summary>
public static readonly RoutedEvent<PointerEventArgs> PointerLeaveItemEvent =
RoutedEvent.Register<InputElement, PointerEventArgs>(nameof(PointerLeaveItem), RoutingStrategies.Bubble);
RoutedEvent.Register<MenuItem, PointerEventArgs>(nameof(PointerLeaveItem), RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="SubmenuOpened"/> event.

4
src/Avalonia.Controls/NativeMenuItemBase.cs

@ -11,8 +11,8 @@ namespace Avalonia.Controls
}
public static readonly DirectProperty<NativeMenuItem, NativeMenu?> ParentProperty =
AvaloniaProperty.RegisterDirect<NativeMenuItem, NativeMenu?>("Parent", o => o.Parent, (o, v) => o.Parent = v);
public static readonly DirectProperty<NativeMenuItemBase, NativeMenu?> ParentProperty =
AvaloniaProperty.RegisterDirect<NativeMenuItemBase, NativeMenu?>("Parent", o => o.Parent, (o, v) => o.Parent = v);
public NativeMenu? Parent
{

18
src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs

@ -56,6 +56,7 @@ namespace Avalonia.Controls.Platform
Menu.AddHandler(Avalonia.Controls.Menu.MenuOpenedEvent, this.MenuOpened);
Menu.AddHandler(MenuItem.PointerEnterItemEvent, PointerEnter);
Menu.AddHandler(MenuItem.PointerLeaveItemEvent, PointerLeave);
Menu.AddHandler(InputElement.PointerMovedEvent, PointerMoved);
_root = Menu.VisualRoot;
@ -91,6 +92,7 @@ namespace Avalonia.Controls.Platform
Menu.RemoveHandler(Avalonia.Controls.Menu.MenuOpenedEvent, this.MenuOpened);
Menu.RemoveHandler(MenuItem.PointerEnterItemEvent, PointerEnter);
Menu.RemoveHandler(MenuItem.PointerLeaveItemEvent, PointerLeave);
Menu.RemoveHandler(InputElement.PointerMovedEvent, PointerMoved);
if (_root is InputElement inputRoot)
{
@ -340,6 +342,22 @@ namespace Avalonia.Controls.Platform
}
}
protected internal virtual void PointerMoved(object? sender, PointerEventArgs e)
{
// HACK: #8179 needs to be addressed to correctly implement it in the PointerPressed method.
var item = GetMenuItem(e.Source as IControl) as MenuItem;
if (item?.TransformedBounds == null)
{
return;
}
var point = e.GetCurrentPoint(null);
if (point.Properties.IsLeftButtonPressed && item.TransformedBounds.Value.Contains(point.Position) == false)
{
e.Pointer.Capture(null);
}
}
protected internal virtual void PointerLeave(object? sender, PointerEventArgs e)
{
var item = GetMenuItem(e.Source as IControl);

4
src/Avalonia.Controls/RepeatButton.cs

@ -13,13 +13,13 @@ namespace Avalonia.Controls
/// Defines the <see cref="Interval"/> property.
/// </summary>
public static readonly StyledProperty<int> IntervalProperty =
AvaloniaProperty.Register<Button, int>(nameof(Interval), 100);
AvaloniaProperty.Register<RepeatButton, int>(nameof(Interval), 100);
/// <summary>
/// Defines the <see cref="Delay"/> property.
/// </summary>
public static readonly StyledProperty<int> DelayProperty =
AvaloniaProperty.Register<Button, int>(nameof(Delay), 300);
AvaloniaProperty.Register<RepeatButton, int>(nameof(Delay), 300);
private DispatcherTimer? _repeatTimer;

80
src/Avalonia.Themes.Fluent/Controls/Button.xaml

@ -7,9 +7,11 @@
</StackPanel>
</Border>
</Design.PreviewWith>
<Styles.Resources>
<Thickness x:Key="ButtonPadding">8,5,8,6</Thickness>
</Styles.Resources>
<Style Selector="Button">
<Setter Property="Background" Value="{DynamicResource ButtonBackground}" />
<!--<Setter Property="BackgroundSizing" Value="OuterBorderEdge" />-->
@ -37,43 +39,54 @@
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" />
</ControlTemplate>
</Setter>
</Style>
<!-- PointerOverState -->
<Style Selector="Button:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPointerOver}" />
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" />
</Style>
<Style.Children>
<Style Selector="^:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPointerOver}" />
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" />
</Style>
<Style Selector="Button:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPressed}" />
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPressed}" />
</Style>
<Style Selector="^:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPressed}" />
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPressed}" />
</Style>
<Style Selector="Button:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushDisabled}" />
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundDisabled}" />
</Style>
<Style Selector="^:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushDisabled}" />
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundDisabled}" />
</Style>
<Style Selector="Button.accent /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackground}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrush}" />
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForeground}" />
</Style>
<Style Selector="^.accent">
<Style.Children>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackground}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrush}" />
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForeground}" />
</Style>
<Style Selector="Button.accent:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushPointerOver}" />
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForegroundPointerOver}" />
</Style>
<Style Selector="^:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushPointerOver}" />
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForegroundPointerOver}" />
</Style>
<Style Selector="Button.accent:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushPressed}" />
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForegroundPressed}" />
<Style Selector="^:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushPressed}" />
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForegroundPressed}" />
</Style>
<Style Selector="^:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushDisabled}" />
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForegroundDisabled}" />
</Style>
</Style.Children>
</Style>
</Style.Children>
</Style>
<Style Selector="Button, RepeatButton, ToggleButton, DropDownButton">
@ -89,9 +102,4 @@
<Setter Property="RenderTransform" Value="scale(0.98)" />
</Style>
<Style Selector="Button.accent:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushDisabled}" />
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForegroundDisabled}" />
</Style>
</Styles>

603
src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml

@ -1,294 +1,321 @@
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Selector="CheckBox">
<Design.PreviewWith>
<Border Padding="20">
<CheckBox IsThreeState="True" IsChecked="True" Content="Content" Foreground="Gold" />
</Border>
</Design.PreviewWith>
<Style Selector="CheckBox">
<Setter Property="Padding" Value="8,0,0,0" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<Setter Property="MinHeight" Value="32" />
<!--<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
<Setter Property="FocusVisualMargin" Value="-7,-3,-7,-3" />-->
<Setter Property="Template">
<ControlTemplate>
<Grid x:Name="RootGrid" ColumnDefinitions="20,*">
<Border x:Name="PART_Border"
Grid.ColumnSpan="2"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}" />
<Grid VerticalAlignment="Top" Height="32">
<Border x:Name="NormalRectangle"
BorderThickness="{DynamicResource CheckBoxBorderThemeThickness}"
CornerRadius="{TemplateBinding CornerRadius}"
UseLayoutRounding="False"
Height="20"
Width="20" />
<Viewbox UseLayoutRounding="False">
<Panel>
<Panel Height="16" Width="16" />
<Path x:Name="CheckGlyph" Stretch="Uniform" VerticalAlignment="Center" />
</Panel>
</Viewbox>
</Grid>
<ContentPresenter x:Name="ContentPresenter"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}"
RecognizesAccessKey="True"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Grid.Column="1" />
<!-- TODO: TextWrapping="Wrap" on contentpresenter -->
<Setter Property="Padding" Value="8,0,0,0" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<Setter Property="MinHeight" Value="32" />
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundUnchecked}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundUnchecked}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushUnchecked}" />
<Setter Property="Template">
<ControlTemplate>
<Grid x:Name="RootGrid" ColumnDefinitions="20,*">
<Border x:Name="PART_Border"
Grid.ColumnSpan="2"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}" />
<Grid VerticalAlignment="Top" Height="32">
<Border x:Name="NormalRectangle"
BorderThickness="{DynamicResource CheckBoxBorderThemeThickness}"
CornerRadius="{TemplateBinding CornerRadius}"
UseLayoutRounding="False"
Height="20"
Width="20" />
<Viewbox UseLayoutRounding="False">
<Panel>
<Panel Height="16" Width="16" />
<Path x:Name="CheckGlyph" Stretch="Uniform" VerticalAlignment="Center" />
</Panel>
</Viewbox>
</Grid>
</ControlTemplate>
</Setter>
</Style>
<!-- Unchecked Normal State -->
<Style Selector="CheckBox">
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundUnchecked}" />
</Style>
<Style Selector="CheckBox">
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundUnchecked}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushUnchecked}" />
</Style>
<Style Selector="CheckBox /template/ Border#NormalRectangle">
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeUnchecked}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillUnchecked}" />
</Style>
<Style Selector="CheckBox /template/ Path#CheckGlyph">
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundUnchecked}" />
<Setter Property="Opacity" Value="0" />
</Style>
<!-- Unchecked PointerOver State -->
<Style Selector="CheckBox:pointerover /template/ ContentPresenter#ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundUncheckedPointerOver}" />
</Style>
<Style Selector="CheckBox:pointerover /template/ Border#PART_Border">
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundUncheckedPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushUncheckedPointerOver}" />
</Style>
<Style Selector="CheckBox:pointerover /template/ Border#NormalRectangle">
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeUncheckedPointerOver}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillUncheckedPointerOver}" />
</Style>
<Style Selector="CheckBox:pointerover /template/ Path#CheckGlyph">
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundUncheckedPointerOver}" />
</Style>
<!-- Unchecked Pressed State -->
<Style Selector="CheckBox:pressed /template/ ContentPresenter#ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundUncheckedPressed}" />
</Style>
<Style Selector="CheckBox:pressed /template/ Border#PART_Border">
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundUncheckedPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushUncheckedPressed}" />
</Style>
<Style Selector="CheckBox:pressed /template/ Border#NormalRectangle">
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeUncheckedPressed}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillUncheckedPressed}" />
</Style>
<Style Selector="CheckBox:pressed /template/ Path#CheckGlyph">
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundUncheckedPressed}" />
</Style>
<!-- Unchecked Disabled state -->
<Style Selector="CheckBox:disabled /template/ ContentPresenter#ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundUncheckedDisabled}" />
</Style>
<Style Selector="CheckBox:disabled /template/ Border#PART_Border">
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundUncheckedDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushUncheckedDisabled}" />
</Style>
<Style Selector="CheckBox:disabled /template/ Border#NormalRectangle">
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeUncheckedDisabled}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillUncheckedDisabled}" />
</Style>
<Style Selector="CheckBox:disabled /template/ Path#CheckGlyph">
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundUncheckedDisabled}" />
</Style>
<!-- Checked Normal State -->
<Style Selector="CheckBox:checked">
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundChecked}" />
</Style>
<Style Selector="CheckBox:checked">
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundChecked}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushChecked}" />
</Style>
<Style Selector="CheckBox:checked /template/ Border#NormalRectangle">
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundFillChecked}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillChecked}" />
</Style>
<Style Selector="CheckBox:checked /template/ Path#CheckGlyph">
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundChecked}" />
<Setter Property="Data" Value="M1507 31L438 1101L-119 543L-29 453L438 919L1417 -59L1507 31Z" />
<Setter Property="Width" Value="9" />
<Setter Property="Opacity" Value="1" />
<Setter Property="FlowDirection" Value="LeftToRight" />
</Style>
<!-- Checked PointerOver State -->
<Style Selector="CheckBox:checked:pointerover /template/ ContentPresenter#ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundCheckedPointerOver}" />
</Style>
<Style Selector="CheckBox:checked:pointerover /template/ Border#PART_Border">
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundCheckedPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushCheckedPointerOver}" />
</Style>
<Style Selector="CheckBox:checked:pointerover /template/ Border#NormalRectangle">
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeCheckedPointerOver}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillCheckedPointerOver}" />
</Style>
<Style Selector="CheckBox:checked:pointerover /template/ Path#CheckGlyph">
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundCheckedPointerOver}" />
</Style>
<!-- Checked Pressed State -->
<Style Selector="CheckBox:checked:pressed /template/ ContentPresenter#ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundCheckedPressed}" />
</Style>
<Style Selector="CheckBox:checked:pressed /template/ Border#PART_Border">
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundCheckedPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushCheckedPressed}" />
</Style>
<Style Selector="CheckBox:checked:pressed /template/ Border#NormalRectangle">
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeCheckedPressed}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillCheckedPressed}" />
</Style>
<Style Selector="CheckBox:checked:pressed /template/ Path#CheckGlyph">
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundCheckedPressed}" />
</Style>
<!-- Checked Disabled State -->
<Style Selector="CheckBox:checked:disabled /template/ ContentPresenter#ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundCheckedDisabled}" />
</Style>
<Style Selector="CheckBox:checked:disabled /template/ Border#PART_Border">
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundCheckedDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushCheckedDisabled}" />
</Style>
<Style Selector="CheckBox:checked:disabled /template/ Border#NormalRectangle">
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeCheckedDisabled}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillCheckedDisabled}" />
</Style>
<Style Selector="CheckBox:checked:disabled /template/ Path#CheckGlyph">
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundCheckedDisabled}" />
</Style>
<!-- Indeterminate Normal State -->
<Style Selector="CheckBox:indeterminate">
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundIndeterminate}" />
</Style>
<Style Selector="CheckBox:indeterminate">
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundIndeterminate}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushIndeterminate}" />
</Style>
<Style Selector="CheckBox:indeterminate /template/ Border#NormalRectangle">
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeIndeterminate}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillIndeterminate}" />
</Style>
<Style Selector="CheckBox:indeterminate /template/ Path#CheckGlyph">
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundIndeterminate}" />
<Setter Property="Data" Value="M1536 1536v-1024h-1024v1024h1024z" />
<Setter Property="Width" Value="7" />
<Setter Property="Opacity" Value="1" />
</Style>
<!-- Indeterminate PointerOver State -->
<Style Selector="CheckBox:indeterminate:pointerover /template/ ContentPresenter#ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundIndeterminatePointerOver}" />
</Style>
<Style Selector="CheckBox:indeterminate:pointerover /template/ Border#PART_Border">
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundIndeterminatePointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushIndeterminatePointerOver}" />
</Style>
<Style Selector="CheckBox:indeterminate:pointerover /template/ Border#NormalRectangle">
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeIndeterminatePointerOver}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillIndeterminatePointerOver}" />
</Style>
<Style Selector="CheckBox:indeterminate:pointerover /template/ Path#CheckGlyph">
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundIndeterminatePointerOver}" />
</Style>
<!-- Indeterminate Pressed State -->
<Style Selector="CheckBox:indeterminate:pressed /template/ ContentPresenter#ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundIndeterminatePressed}" />
</Style>
<Style Selector="CheckBox:indeterminate:pressed /template/ Border#PART_Border">
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundIndeterminatePressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushIndeterminatePressed}" />
</Style>
<Style Selector="CheckBox:indeterminate:pressed /template/ Border#NormalRectangle">
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeIndeterminatePressed}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillIndeterminatePressed}" />
</Style>
<Style Selector="CheckBox:indeterminate:pressed /template/ Path#CheckGlyph">
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundIndeterminatePressed}" />
</Style>
<!-- Indeterminate Disabled State -->
<Style Selector="CheckBox:indeterminate:disabled /template/ ContentPresenter#ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundIndeterminateDisabled}" />
</Style>
<Style Selector="CheckBox:indeterminate:disabled /template/ Border#PART_Border">
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundIndeterminateDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushIndeterminateDisabled}" />
</Style>
<Style Selector="CheckBox:indeterminate:disabled /template/ Border#NormalRectangle">
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeIndeterminateDisabled}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillIndeterminateDisabled}" />
</Style>
<Style Selector="CheckBox:indeterminate:disabled /template/ Path#CheckGlyph">
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundIndeterminateDisabled}" />
</Style>
</Styles>
<ContentPresenter x:Name="ContentPresenter"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}"
RecognizesAccessKey="True"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Grid.Column="1" />
<!-- TODO: TextWrapping="Wrap" on contentpresenter -->
</Grid>
</ControlTemplate>
</Setter>
<Style.Children>
<!-- Unchecked Normal State -->
<Style Selector="^ /template/ Border#NormalRectangle">
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeUnchecked}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillUnchecked}" />
</Style>
<Style Selector="^ /template/ Path#CheckGlyph">
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundUnchecked}" />
<Setter Property="Opacity" Value="0" />
</Style>
<!-- Unchecked PointerOver State -->
<Style Selector="^:pointerover">
<Style.Children>
<Style Selector="^ /template/ ContentPresenter#ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundUncheckedPointerOver}" />
</Style>
<Style Selector="^ /template/ Border#PART_Border">
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundUncheckedPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushUncheckedPointerOver}" />
</Style>
<Style Selector="^ /template/ Border#NormalRectangle">
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeUncheckedPointerOver}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillUncheckedPointerOver}" />
</Style>
<Style Selector="^ /template/ Path#CheckGlyph">
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundUncheckedPointerOver}" />
</Style>
</Style.Children>
</Style>
<!-- Unchecked Pressed State -->
<Style Selector="^:pressed">
<Style.Children>
<Style Selector="^ /template/ ContentPresenter#ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundUncheckedPressed}" />
</Style>
<Style Selector="^ /template/ Border#PART_Border">
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundUncheckedPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushUncheckedPressed}" />
</Style>
<Style Selector="^ /template/ Border#NormalRectangle">
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeUncheckedPressed}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillUncheckedPressed}" />
</Style>
<Style Selector="^ /template/ Path#CheckGlyph">
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundUncheckedPressed}" />
</Style>
</Style.Children>
</Style>
<!-- Unchecked Disabled state -->
<Style Selector="^:disabled">
<Style.Children>
<Style Selector="^ /template/ ContentPresenter#ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundUncheckedDisabled}" />
</Style>
<Style Selector="^ /template/ Border#PART_Border">
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundUncheckedDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushUncheckedDisabled}" />
</Style>
<Style Selector="^ /template/ Border#NormalRectangle">
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeUncheckedDisabled}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillUncheckedDisabled}" />
</Style>
<Style Selector="^ /template/ Path#CheckGlyph">
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundUncheckedDisabled}" />
</Style>
</Style.Children>
</Style>
<Style Selector="^:checked">
<!-- Checked Normal State -->
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundChecked}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundChecked}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushChecked}" />
<Style.Children>
<Style Selector="^ /template/ Border#NormalRectangle">
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundFillChecked}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillChecked}" />
</Style>
<Style Selector="^ /template/ Path#CheckGlyph">
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundChecked}" />
<Setter Property="Data" Value="M1507 31L438 1101L-119 543L-29 453L438 919L1417 -59L1507 31Z" />
<Setter Property="Width" Value="9" />
<Setter Property="Opacity" Value="1" />
<Setter Property="FlowDirection" Value="LeftToRight" />
</Style>
<!-- Checked PointerOver State -->
<Style Selector="^:pointerover">
<Style.Children>
<Style Selector="^ /template/ ContentPresenter#ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundCheckedPointerOver}" />
</Style>
<Style Selector="^ /template/ Border#PART_Border">
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundCheckedPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushCheckedPointerOver}" />
</Style>
<Style Selector="^ /template/ Border#NormalRectangle">
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeCheckedPointerOver}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillCheckedPointerOver}" />
</Style>
<Style Selector="^ /template/ Path#CheckGlyph">
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundCheckedPointerOver}" />
</Style>
</Style.Children>
</Style>
<!-- Checked Pressed State -->
<Style Selector="^:pressed">
<Style.Children>
<Style Selector="^ /template/ ContentPresenter#ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundCheckedPressed}" />
</Style>
<Style Selector="^ /template/ Border#PART_Border">
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundCheckedPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushCheckedPressed}" />
</Style>
<Style Selector="^ /template/ Border#NormalRectangle">
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeCheckedPressed}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillCheckedPressed}" />
</Style>
<Style Selector="^ /template/ Path#CheckGlyph">
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundCheckedPressed}" />
</Style>
</Style.Children>
</Style>
<!-- Checked Disabled State -->
<Style Selector="^:disabled">
<Style.Children>
<Style Selector="^ /template/ ContentPresenter#ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundCheckedDisabled}" />
</Style>
<Style Selector="^ /template/ Border#PART_Border">
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundCheckedDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushCheckedDisabled}" />
</Style>
<Style Selector="^ /template/ Border#NormalRectangle">
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeCheckedDisabled}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillCheckedDisabled}" />
</Style>
<Style Selector="^ /template/ Path#CheckGlyph">
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundCheckedDisabled}" />
</Style>
</Style.Children>
</Style>
</Style.Children>
</Style>
<Style Selector="^:indeterminate">
<!-- Indeterminate Normal State -->
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundIndeterminate}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundIndeterminate}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushIndeterminate}" />
<Style.Children>
<Style Selector="^ /template/ Border#NormalRectangle">
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeIndeterminate}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillIndeterminate}" />
</Style>
<Style Selector="^ /template/ Path#CheckGlyph">
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundIndeterminate}" />
<Setter Property="Data" Value="M1536 1536v-1024h-1024v1024h1024z" />
<Setter Property="Width" Value="7" />
<Setter Property="Opacity" Value="1" />
</Style>
<!-- Indeterminate PointerOver State -->
<Style Selector="^:pointerover">
<Style.Children>
<Style Selector="^ /template/ ContentPresenter#ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundIndeterminatePointerOver}" />
</Style>
<Style Selector="^ /template/ Border#PART_Border">
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundIndeterminatePointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushIndeterminatePointerOver}" />
</Style>
<Style Selector="^ /template/ Border#NormalRectangle">
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeIndeterminatePointerOver}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillIndeterminatePointerOver}" />
</Style>
<Style Selector="^ /template/ Path#CheckGlyph">
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundIndeterminatePointerOver}" />
</Style>
</Style.Children>
</Style>
<!-- Indeterminate Pressed State -->
<Style Selector="^:pressed">
<Style.Children>
<Style Selector="^ /template/ ContentPresenter#ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundIndeterminatePressed}" />
</Style>
<Style Selector="^ /template/ Border#PART_Border">
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundIndeterminatePressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushIndeterminatePressed}" />
</Style>
<Style Selector="^ /template/ Border#NormalRectangle">
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeIndeterminatePressed}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillIndeterminatePressed}" />
</Style>
<Style Selector="^ /template/ Path#CheckGlyph">
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundIndeterminatePressed}" />
</Style>
</Style.Children>
</Style>
<!-- Indeterminate Disabled State -->
<Style Selector="^:disabled">
<Style.Children>
<Style Selector="^ /template/ ContentPresenter#ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundIndeterminateDisabled}" />
</Style>
<Style Selector="^ /template/ Border#PART_Border">
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundIndeterminateDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushIndeterminateDisabled}" />
</Style>
<Style Selector="^ /template/ Border#NormalRectangle">
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeIndeterminateDisabled}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillIndeterminateDisabled}" />
</Style>
<Style Selector="^ /template/ Path#CheckGlyph">
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundIndeterminateDisabled}" />
</Style>
</Style.Children>
</Style>
</Style.Children>
</Style>
</Style.Children>
</Style>

24
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs

@ -151,6 +151,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
results.Add(result);
result = initialNode;
break;
case SelectorGrammar.NestingSyntax:
var parentTargetType = context.ParentNodes().OfType<AvaloniaXamlIlTargetTypeMetadataNode>().FirstOrDefault();
if (parentTargetType is null)
throw new XamlParseException($"Cannot find parent style for nested selector.", node);
result = new XamlIlNestingSelector(result, parentTargetType.TargetType.GetClrType());
break;
default:
throw new XamlParseException($"Unsupported selector grammar '{i.GetType()}'.", node);
}
@ -474,4 +482,20 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
m => m.Name == "Or" && m.Parameters.Count == 1 && m.Parameters[0].Name.StartsWith("IReadOnlyList"));
}
}
class XamlIlNestingSelector : XamlIlSelectorNode
{
public XamlIlNestingSelector(XamlIlSelectorNode previous, IXamlType targetType)
: base(previous)
{
TargetType = targetType;
}
public override IXamlType TargetType { get; }
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
EmitCall(context, codeGen,
m => m.Name == "Nesting" && m.Parameters.Count == 1);
}
}
}

30
src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs

@ -46,7 +46,7 @@ namespace Avalonia.Markup.Parsers
switch (state)
{
case State.Start:
state = ParseStart(ref r);
(state, syntax) = ParseStart(ref r);
break;
case State.Middle:
(state, syntax) = ParseMiddle(ref r, end);
@ -93,27 +93,31 @@ namespace Avalonia.Markup.Parsers
return selector;
}
private static State ParseStart(ref CharacterReader r)
private static (State, ISyntax?) ParseStart(ref CharacterReader r)
{
r.SkipWhitespace();
if (r.End)
{
return State.End;
return (State.End, null);
}
if (r.TakeIf(':'))
{
return State.Colon;
return (State.Colon, null);
}
else if (r.TakeIf('.'))
{
return State.Class;
return (State.Class, null);
}
else if (r.TakeIf('#'))
{
return State.Name;
return (State.Name, null);
}
else if (r.TakeIf('^'))
{
return (State.CanHaveType, new NestingSyntax());
}
return State.TypeName;
return (State.TypeName, null);
}
private static (State, ISyntax?) ParseMiddle(ref CharacterReader r, char? end)
@ -142,6 +146,10 @@ namespace Avalonia.Markup.Parsers
{
return (State.Start, new CommaSyntax());
}
else if (r.TakeIf('^'))
{
return (State.CanHaveType, new NestingSyntax());
}
else if (end.HasValue && !r.End && r.Peek == end.Value)
{
return (State.End, null);
@ -635,5 +643,13 @@ namespace Avalonia.Markup.Parsers
return obj is CommaSyntax or;
}
}
public class NestingSyntax : ISyntax
{
public override bool Equals(object? obj)
{
return obj is NestingSyntax;
}
}
}
}

275
tests/Avalonia.Base.UnitTests/Styling/SelectorTests_Nesting.cs

@ -0,0 +1,275 @@
using System;
using Avalonia.Controls;
using Avalonia.Styling;
using Avalonia.Styling.Activators;
using Xunit;
namespace Avalonia.Base.UnitTests.Styling
{
public class SelectorTests_Nesting
{
[Fact]
public void Nesting_Class_Doesnt_Match_Parent_OfType_Selector()
{
var control = new Control2();
Style nested;
var parent = new Style(x => x.OfType<Control1>())
{
Children =
{
(nested = new Style(x => x.Nesting().Class("foo"))),
}
};
var match = nested.Selector.Match(control, parent);
Assert.Equal(SelectorMatchResult.NeverThisType, match.Result);
}
[Fact]
public void Or_Nesting_Class_Doesnt_Match_Parent_OfType_Selector()
{
var control = new Control2();
Style nested;
var parent = new Style(x => x.OfType<Control1>())
{
Children =
{
(nested = new Style(x => Selectors.Or(
x.Nesting().Class("foo"),
x.Nesting().Class("bar")))),
}
};
var match = nested.Selector.Match(control, parent);
Assert.Equal(SelectorMatchResult.NeverThisType, match.Result);
}
[Fact]
public void Or_Nesting_Child_OfType_Doesnt_Match_Parent_OfType_Selector()
{
var control = new Control1();
var panel = new DockPanel { Children = { control } };
Style nested;
var parent = new Style(x => x.OfType<Panel>())
{
Children =
{
(nested = new Style(x => Selectors.Or(
x.Nesting().Child().OfType<Control1>(),
x.Nesting().Child().OfType<Control1>()))),
}
};
var match = nested.Selector.Match(control, parent);
Assert.Equal(SelectorMatchResult.NeverThisInstance, match.Result);
}
[Fact]
public void Double_Nesting_Class_Doesnt_Match_Grandparent_OfType_Selector()
{
var control = new Control2
{
Classes = { "foo", "bar" },
};
Style parent;
Style nested;
var grandparent = new Style(x => x.OfType<Control1>())
{
Children =
{
(parent = new Style(x => x.Nesting().Class("foo"))
{
Children =
{
(nested = new Style(x => x.Nesting().Class("bar")))
}
})
}
};
var match = nested.Selector.Match(control, parent);
Assert.Equal(SelectorMatchResult.NeverThisType, match.Result);
}
[Fact]
public void Nesting_Class_Matches()
{
var control = new Control1 { Classes = { "foo" } };
Style nested;
var parent = new Style(x => x.OfType<Control1>())
{
Children =
{
(nested = new Style(x => x.Nesting().Class("foo"))),
}
};
var match = nested.Selector.Match(control, parent);
Assert.Equal(SelectorMatchResult.Sometimes, match.Result);
var sink = new ActivatorSink(match.Activator);
Assert.True(sink.Active);
control.Classes.Clear();
Assert.False(sink.Active);
}
[Fact]
public void Double_Nesting_Class_Matches()
{
var control = new Control1
{
Classes = { "foo", "bar" },
};
Style parent;
Style nested;
var grandparent = new Style(x => x.OfType<Control1>())
{
Children =
{
(parent = new Style(x => x.Nesting().Class("foo"))
{
Children =
{
(nested = new Style(x => x.Nesting().Class("bar")))
}
})
}
};
var match = nested.Selector.Match(control, parent);
Assert.Equal(SelectorMatchResult.Sometimes, match.Result);
var sink = new ActivatorSink(match.Activator);
Assert.True(sink.Active);
control.Classes.Remove("foo");
Assert.False(sink.Active);
}
[Fact]
public void Or_Nesting_Class_Matches()
{
var control = new Control1 { Classes = { "foo" } };
Style nested;
var parent = new Style(x => x.OfType<Control1>())
{
Children =
{
(nested = new Style(x => Selectors.Or(
x.Nesting().Class("foo"),
x.Nesting().Class("bar")))),
}
};
var match = nested.Selector.Match(control, parent);
Assert.Equal(SelectorMatchResult.Sometimes, match.Result);
var sink = new ActivatorSink(match.Activator);
Assert.True(sink.Active);
control.Classes.Clear();
Assert.False(sink.Active);
}
[Fact]
public void Or_Nesting_Child_OfType_Matches()
{
var control = new Control1 { Classes = { "foo" } };
var panel = new Panel { Children = { control } };
Style nested;
var parent = new Style(x => x.OfType<Panel>())
{
Children =
{
(nested = new Style(x => Selectors.Or(
x.Nesting().Child().OfType<Control1>(),
x.Nesting().Child().OfType<Control1>()))),
}
};
var match = nested.Selector.Match(control, parent);
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, match.Result);
}
[Fact]
public void Nesting_With_No_Parent_Style_Fails()
{
var control = new Control1();
var style = new Style(x => x.Nesting().OfType<Control1>());
Assert.Throws<InvalidOperationException>(() => style.Selector.Match(control, null));
}
[Fact]
public void Nesting_With_No_Parent_Selector_Fails()
{
var control = new Control1();
Style nested;
var parent = new Style
{
Children =
{
(nested = new Style(x => x.Nesting().Class("foo"))),
}
};
Assert.Throws<InvalidOperationException>(() => nested.Selector.Match(control, parent));
}
[Fact]
public void Adding_Child_With_No_Nesting_Selector_Fails()
{
var parent = new Style(x => x.OfType<Control1>());
var child = new Style(x => x.Class("foo"));
Assert.Throws<InvalidOperationException>(() => parent.Children.Add(child));
}
[Fact]
public void Adding_Combinator_Selector_Child_With_No_Nesting_Selector_Fails()
{
var parent = new Style(x => x.OfType<Control1>());
var child = new Style(x => x.Class("foo").Descendant().Class("bar"));
Assert.Throws<InvalidOperationException>(() => parent.Children.Add(child));
}
[Fact]
public void Adding_Or_Selector_Child_With_No_Nesting_Selector_Fails()
{
var parent = new Style(x => x.OfType<Control1>());
var child = new Style(x => Selectors.Or(
x.Nesting().Class("foo"),
x.Class("bar")));
Assert.Throws<InvalidOperationException>(() => parent.Children.Add(child));
}
[Fact]
public void Can_Add_Child_Without_Nesting_Selector_To_Style_Without_Selector()
{
var parent = new Style();
var child = new Style(x => x.Class("foo"));
parent.Children.Add(child);
}
public class Control1 : Control
{
}
public class Control2 : Control
{
}
private class ActivatorSink : IStyleActivatorSink
{
public ActivatorSink(IStyleActivator source) => source.Subscribe(this);
public bool Active { get; private set; }
public void OnNext(bool value, int tag) => Active = value;
}
}
}

42
tests/Avalonia.Base.UnitTests/Styling/StyleTests.cs

@ -722,6 +722,48 @@ namespace Avalonia.Base.UnitTests.Styling
resources.Verify(x => x.AddOwner(host.Object), Times.Once);
}
[Fact]
public void Nested_Style_Can_Be_Added()
{
var parent = new Style(x => x.OfType<Class1>());
var nested = new Style(x => x.Nesting().Class("foo"));
parent.Children.Add(nested);
Assert.Same(parent, nested.Parent);
}
[Fact]
public void Nested_Or_Style_Can_Be_Added()
{
var parent = new Style(x => x.OfType<Class1>());
var nested = new Style(x => Selectors.Or(
x.Nesting().Class("foo"),
x.Nesting().Class("bar")));
parent.Children.Add(nested);
Assert.Same(parent, nested.Parent);
}
[Fact]
public void Nested_Style_Without_Selector_Throws()
{
var parent = new Style(x => x.OfType<Class1>());
var nested = new Style();
Assert.Throws<InvalidOperationException>(() => parent.Children.Add(nested));
}
[Fact(Skip = "TODO")]
public void Nested_Style_Without_Nesting_Operator_Throws()
{
var parent = new Style(x => x.OfType<Class1>());
var nested = new Style(x => x.Class("foo"));
Assert.Throws<InvalidOperationException>(() => parent.Children.Add(nested));
}
private class Class1 : Control
{
public static readonly StyledProperty<string> FooProperty =

138
tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs

@ -469,6 +469,144 @@ namespace Avalonia.Markup.UnitTests.Parsers
result);
}
[Fact]
public void Nesting_Class()
{
var result = SelectorGrammar.Parse("^.foo");
Assert.Equal(
new SelectorGrammar.ISyntax[]
{
new SelectorGrammar.NestingSyntax(),
new SelectorGrammar.ClassSyntax { Class = "foo" },
},
result);
}
[Fact]
public void Nesting_Child_Class()
{
var result = SelectorGrammar.Parse("^ > .foo");
Assert.Equal(
new SelectorGrammar.ISyntax[]
{
new SelectorGrammar.NestingSyntax(),
new SelectorGrammar.ChildSyntax { },
new SelectorGrammar.ClassSyntax { Class = "foo" },
},
result);
}
[Fact]
public void Nesting_Descendant_Class()
{
var result = SelectorGrammar.Parse("^ .foo");
Assert.Equal(
new SelectorGrammar.ISyntax[]
{
new SelectorGrammar.NestingSyntax(),
new SelectorGrammar.DescendantSyntax { },
new SelectorGrammar.ClassSyntax { Class = "foo" },
},
result);
}
[Fact]
public void Nesting_Template_Class()
{
var result = SelectorGrammar.Parse("^ /template/ .foo");
Assert.Equal(
new SelectorGrammar.ISyntax[]
{
new SelectorGrammar.NestingSyntax(),
new SelectorGrammar.TemplateSyntax { },
new SelectorGrammar.ClassSyntax { Class = "foo" },
},
result);
}
[Fact]
public void OfType_Template_Nesting()
{
var result = SelectorGrammar.Parse("Button /template/ ^");
Assert.Equal(
new SelectorGrammar.ISyntax[]
{
new SelectorGrammar.OfTypeSyntax { TypeName = "Button" },
new SelectorGrammar.TemplateSyntax { },
new SelectorGrammar.NestingSyntax(),
},
result);
}
[Fact]
public void Nesting_Property()
{
var result = SelectorGrammar.Parse("^[Foo=bar]");
Assert.Equal(
new SelectorGrammar.ISyntax[]
{
new SelectorGrammar.NestingSyntax(),
new SelectorGrammar.PropertySyntax { Property = "Foo", Value = "bar" },
},
result);
}
[Fact]
public void Not_Nesting()
{
var result = SelectorGrammar.Parse(":not(^)");
Assert.Equal(
new SelectorGrammar.ISyntax[]
{
new SelectorGrammar.NotSyntax
{
Argument = new[] { new SelectorGrammar.NestingSyntax() },
}
},
result);
}
[Fact]
public void Nesting_NthChild()
{
var result = SelectorGrammar.Parse("^:nth-child(2n+1)");
Assert.Equal(
new SelectorGrammar.ISyntax[]
{
new SelectorGrammar.NestingSyntax(),
new SelectorGrammar.NthChildSyntax()
{
Step = 2,
Offset = 1
}
},
result);
}
[Fact]
public void Nesting_Comma_Nesting_Class()
{
var result = SelectorGrammar.Parse("^, ^.foo");
Assert.Equal(
new SelectorGrammar.ISyntax[]
{
new SelectorGrammar.NestingSyntax(),
new SelectorGrammar.CommaSyntax(),
new SelectorGrammar.NestingSyntax(),
new SelectorGrammar.ClassSyntax { Class = "foo" },
},
result);
}
[Fact]
public void Namespace_Alone_Fails()
{

32
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs

@ -617,5 +617,37 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Assert.Equal(Colors.Red, ((ISolidColorBrush)foo.Background).Color);
}
}
[Fact]
public void Can_Use_Nested_Styles()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<Style Selector='Border'>
<Style.Children>
<Style Selector='^.foo'>
<Setter Property='Background' Value='Red'/>
</Style>
</Style.Children>
</Style>
</Window.Styles>
<StackPanel>
<Border Name='foo'/>
</StackPanel>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var foo = window.FindControl<Border>("foo");
Assert.Null(foo.Background);
foo.Classes.Add("foo");
Assert.Equal(Colors.Red, ((ISolidColorBrush)foo.Background).Color);
}
}
}
}

Loading…
Cancel
Save