Browse Source

Merge branch 'master' into rndr4

pull/8105/head
Jumar Macato 4 years ago
committed by GitHub
parent
commit
83aa43be70
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  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; ComPtr<WindowBaseImpl> _parent;
bool _closed; bool _closed;
bool _isEnabled; bool _isEnabled;
bool _canBecomeKeyWindow;
bool _isExtended; bool _isExtended;
AvnMenu* _menu; AvnMenu* _menu;
} }
@ -216,29 +217,38 @@
-(BOOL)canBecomeKeyWindow -(BOOL)canBecomeKeyWindow
{ {
// If the window has a child window being shown as a dialog then don't allow it to become the key window. if(_canBecomeKeyWindow)
for(NSWindow* uch in [self childWindows])
{ {
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 -(BOOL)canBecomeMainWindow
{ {
#ifdef IS_NSPANEL
return false;
#else
return true; return true;
}
#endif #endif
-(void)setCanBecomeKeyWindow:(bool)value
{
_canBecomeKeyWindow = value;
} }
-(bool)shouldTryToHandleEvents -(bool)shouldTryToHandleEvents

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

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

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

@ -16,8 +16,6 @@
class WindowBaseImpl : public virtual ComObject, class WindowBaseImpl : public virtual ComObject,
public virtual IAvnWindowBase, public virtual IAvnWindowBase,
public INSWindowHolder { public INSWindowHolder {
private:
NSCursor *cursor;
public: public:
FORWARD_IUNKNOWN() FORWARD_IUNKNOWN()
@ -28,22 +26,6 @@ BEGIN_INTERFACE_MAP()
virtual ~WindowBaseImpl(); 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); WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl);
virtual HRESULT ObtainNSWindowHandle(void **ret) override; virtual HRESULT ObtainNSWindowHandle(void **ret) override;
@ -122,11 +104,33 @@ protected:
virtual NSWindowStyleMask GetStyle(); virtual NSWindowStyleMask GetStyle();
void UpdateStyle(); void UpdateStyle();
virtual void OnInitialiseNSWindow ();
private: private:
void CreateNSWindow (bool isDialog); void CreateNSWindow (bool isDialog);
void CleanNSWindow (); void CleanNSWindow ();
void InitialiseNSWindow (); 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 #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]; View = [[AvnView alloc] initWithParent:this];
StandardContainer = [[AutoFitContentView new] initWithContent:View]; StandardContainer = [[AutoFitContentView new] initWithContent:View];
lastPositionSet.X = -1; lastPositionSet = { 0, 0 };
lastPositionSet.Y = -1; hasPosition = false;
lastSize = NSSize { 100, 100 }; lastSize = NSSize { 100, 100 };
lastMaxSize = NSSize { CGFLOAT_MAX, CGFLOAT_MAX}; lastMaxSize = NSSize { CGFLOAT_MAX, CGFLOAT_MAX};
lastMinSize = NSSize { 0, 0 }; lastMinSize = NSSize { 0, 0 };
_lastTitle = @"";
Window = nullptr; Window = nullptr;
lastMenu = nullptr; lastMenu = nullptr;
@ -92,15 +91,16 @@ HRESULT WindowBaseImpl::Show(bool activate, bool isDialog) {
CreateNSWindow(isDialog); CreateNSWindow(isDialog);
InitialiseNSWindow(); InitialiseNSWindow();
if(lastPositionSet.X >= 0 && lastPositionSet.Y >= 0) if(hasPosition)
{ {
SetPosition(lastPositionSet); SetPosition(lastPositionSet);
} else
{
[Window center];
} }
UpdateStyle(); UpdateStyle();
[Window setTitle:_lastTitle];
if (ShouldTakeFocusOnShow() && activate) { if (ShouldTakeFocusOnShow() && activate) {
[Window orderFront:Window]; [Window orderFront:Window];
[Window makeKeyAndOrderFront:Window]; [Window makeKeyAndOrderFront:Window];
@ -288,12 +288,12 @@ HRESULT WindowBaseImpl::Resize(double x, double y, AvnPlatformResizeReason reaso
} }
@try { @try {
lastSize = NSSize {x, y};
if (!_shown) { if (!_shown) {
BaseEvents->Resized(AvnSize{x, y}, reason); BaseEvents->Resized(AvnSize{x, y}, reason);
} }
lastSize = NSSize {x, y};
if(Window != nullptr) { if(Window != nullptr) {
[Window setContentSize:lastSize]; [Window setContentSize:lastSize];
} }
@ -385,6 +385,7 @@ HRESULT WindowBaseImpl::SetPosition(AvnPoint point) {
@autoreleasepool { @autoreleasepool {
lastPositionSet = point; lastPositionSet = point;
hasPosition = true;
if(Window != nullptr) { if(Window != nullptr) {
[Window setFrameTopLeftPoint:ToNSPoint(ConvertPointY(point))]; [Window setFrameTopLeftPoint:ToNSPoint(ConvertPointY(point))];
@ -566,6 +567,11 @@ void WindowBaseImpl::CreateNSWindow(bool isDialog) {
} }
} }
void WindowBaseImpl::OnInitialiseNSWindow()
{
}
void WindowBaseImpl::InitialiseNSWindow() { void WindowBaseImpl::InitialiseNSWindow() {
if(Window != nullptr) { if(Window != nullptr) {
[Window setContentView:StandardContainer]; [Window setContentView:StandardContainer];
@ -577,7 +583,6 @@ void WindowBaseImpl::InitialiseNSWindow() {
[Window setContentMaxSize:lastMaxSize]; [Window setContentMaxSize:lastMaxSize];
[Window setOpaque:false]; [Window setOpaque:false];
[Window center];
if (lastMenu != nullptr) { if (lastMenu != nullptr) {
[GetWindowProtocol() applyMenu:lastMenu]; [GetWindowProtocol() applyMenu:lastMenu];
@ -586,6 +591,8 @@ void WindowBaseImpl::InitialiseNSWindow() {
[GetWindowProtocol() showWindowMenuWithAppMenu]; [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 HRESULT SetWindowState (AvnWindowState state) override;
virtual bool IsDialog() override; virtual bool IsDialog() override;
virtual void OnInitialiseNSWindow() override;
protected: protected:
virtual NSWindowStyleMask GetStyle() override; virtual NSWindowStyleMask GetStyle() override;
private:
NSString *_lastTitle;
}; };
#endif //AVALONIA_NATIVE_OSX_WINDOWIMPL_H #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; _inSetWindowState = false;
_lastWindowState = Normal; _lastWindowState = Normal;
_actualWindowState = Normal; _actualWindowState = Normal;
_lastTitle = @"";
WindowEvents = events; WindowEvents = events;
[Window disableCursorRects];
[Window setTabbingMode:NSWindowTabbingModeDisallowed];
[Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
} }
void WindowImpl::HideOrShowTrafficLights() { 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) { HRESULT WindowImpl::Show(bool activate, bool isDialog) {
START_COM_CALL; START_COM_CALL;
@autoreleasepool { @autoreleasepool {
_isDialog = isDialog; _isDialog = isDialog;
bool created = Window == nullptr;
WindowBaseImpl::Show(activate, isDialog); WindowBaseImpl::Show(activate, isDialog);
if(created)
{
if(_isClientAreaExtended)
{
[GetWindowProtocol() setIsExtended:true];
SetExtendClientArea(true);
}
}
HideOrShowTrafficLights(); HideOrShowTrafficLights();
return SetWindowState(_lastWindowState); return SetWindowState(_lastWindowState);

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

@ -22,4 +22,6 @@
-(void) setIsExtended:(bool)value; -(void) setIsExtended:(bool)value;
-(void) disconnectParent; -(void) disconnectParent;
-(bool) isDialog; -(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); _gl = GlPlatformSurface.TryCreate(this);
_framebuffer = new FramebufferManager(this); _framebuffer = new FramebufferManager(this);
RenderScaling = (int)_view.Scaling; RenderScaling = _view.Scaling;
MaxClientSize = new PixelSize(_view.Resources.DisplayMetrics.WidthPixels, MaxClientSize = new PixelSize(_view.Resources.DisplayMetrics.WidthPixels,
_view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling); _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) private void ValueChanged(object? value, bool notify)
{ {
if (_subscriber is null) if (_subscriber is { } subscriber)
return;
var notification = value as BindingNotification;
if (notification == null)
{ {
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); LastValue = notification.Value != null ? new WeakReference<object?>(notification.Value) : NullReference;
}
}
else
{
LastValue = notification.Value != null ? new WeakReference<object?>(notification.Value) : NullReference;
if (Next != null) if (next != null)
{ {
Next.Target = LastValue; next.Target = LastValue;
} }
if (Next == null || notification.Error != null) if (next == null || notification.Error != null)
{ {
_subscriber(value); subscriber(value);
}
} }
} }
} }

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

@ -37,13 +37,13 @@ namespace Avalonia.Styling
return _selectorString; 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; var controlParent = ((ILogical)control).LogicalParent;
if (controlParent != null) if (controlParent != null)
{ {
var parentMatch = _parent.Match((IStyleable)controlParent, subscribe); var parentMatch = _parent.Match((IStyleable)controlParent, parent, subscribe);
if (parentMatch.Result == SelectorMatchResult.Sometimes) if (parentMatch.Result == SelectorMatchResult.Sometimes)
{ {
@ -65,5 +65,6 @@ namespace Avalonia.Styling
} }
protected override Selector? MovePrevious() => null; 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; 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 c = (ILogical)control;
var descendantMatches = new OrActivatorBuilder(); var descendantMatches = new OrActivatorBuilder();
@ -46,7 +46,7 @@ namespace Avalonia.Styling
if (c is IStyleable) if (c is IStyleable)
{ {
var match = _parent.Match((IStyleable)c, subscribe); var match = _parent.Match((IStyleable)c, parent, subscribe);
if (match.Result == SelectorMatchResult.Sometimes) if (match.Result == SelectorMatchResult.Sometimes)
{ {
@ -70,5 +70,6 @@ namespace Avalonia.Styling
} }
protected override Selector? MovePrevious() => null; 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; 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) switch (innerResult.Result)
{ {
@ -67,5 +67,6 @@ namespace Avalonia.Styling
} }
protected override Selector? MovePrevious() => _previous; 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 Step { get; }
public int Offset { 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)) if (!(control is ILogical logical))
{ {
@ -105,6 +105,7 @@ namespace Avalonia.Styling
} }
protected override Selector? MovePrevious() => _previous; protected override Selector? MovePrevious() => _previous;
internal override bool HasValidNestingSelector() => _previous?.HasValidNestingSelector() ?? false;
public override string ToString() public override string ToString()
{ {

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

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

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

@ -74,7 +74,7 @@ namespace Avalonia.Styling
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override SelectorMatch Evaluate(IStyleable control, bool subscribe) protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe)
{ {
if (subscribe) if (subscribe)
{ {
@ -90,6 +90,7 @@ namespace Avalonia.Styling
} }
protected override Selector? MovePrevious() => _previous; protected override Selector? MovePrevious() => _previous;
internal override bool HasValidNestingSelector() => _previous?.HasValidNestingSelector() ?? false;
internal static bool Compare(Type propertyType, object? propertyValue, object? value) 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. /// Tries to match the selector with a control.
/// </summary> /// </summary>
/// <param name="control">The control.</param> /// <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"> /// <param name="subscribe">
/// Whether the match should subscribe to changes in order to track the match over time, /// Whether the match should subscribe to changes in order to track the match over time,
/// or simply return an immediate result. /// or simply return an immediate result.
/// </param> /// </param>
/// <returns>A <see cref="SelectorMatch"/>.</returns> /// <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 // 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 // right-to-left, so MatchUntilCombinator reverses this order because the type selector
// will be on the left. // 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 the pre-combinator selector matches, we can now match the combinator, if any.
if (match.IsMatch && combinator is object) 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 // 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 // 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. /// Evaluates the selector for a match.
/// </summary> /// </summary>
/// <param name="control">The control.</param> /// <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"> /// <param name="subscribe">
/// Whether the match should subscribe to changes in order to track the match over time, /// Whether the match should subscribe to changes in order to track the match over time,
/// or simply return an immediate result. /// or simply return an immediate result.
/// </param> /// </param>
/// <returns>A <see cref="SelectorMatch"/>.</returns> /// <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> /// <summary>
/// Moves to the previous selector. /// Moves to the previous selector.
/// </summary> /// </summary>
protected abstract Selector? MovePrevious(); protected abstract Selector? MovePrevious();
internal abstract bool HasValidNestingSelector();
private static SelectorMatch MatchUntilCombinator( private static SelectorMatch MatchUntilCombinator(
IStyleable control, IStyleable control,
Selector start, Selector start,
IStyle? parent,
bool subscribe, bool subscribe,
out Selector? combinator) out Selector? combinator)
{ {
combinator = null; combinator = null;
var activators = new AndActivatorBuilder(); 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 ? return result == SelectorMatchResult.Sometimes ?
new SelectorMatch(activators.Get()) : new SelectorMatch(activators.Get()) :
@ -99,6 +108,7 @@ namespace Avalonia.Styling
private static SelectorMatchResult Match( private static SelectorMatchResult Match(
IStyleable control, IStyleable control,
Selector selector, Selector selector,
IStyle? parent,
bool subscribe, bool subscribe,
ref AndActivatorBuilder activators, ref AndActivatorBuilder activators,
ref Selector? combinator) ref Selector? combinator)
@ -110,7 +120,7 @@ namespace Avalonia.Styling
// opportunity to exit early. // opportunity to exit early.
if (previous != null && !previous.IsCombinator) 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) if (previousMatch < SelectorMatchResult.Sometimes)
{ {
@ -119,7 +129,7 @@ namespace Avalonia.Styling
} }
// Match this selector. // Match this selector.
var match = selector.Evaluate(control, subscribe); var match = selector.Evaluate(control, parent, subscribe);
if (!match.IsMatch) 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> /// <summary>
/// Returns a selector which inverts the results of selector argument. /// Returns a selector which inverts the results of selector argument.
/// </summary> /// </summary>

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

@ -4,8 +4,6 @@ using Avalonia.Animation;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Metadata; using Avalonia.Metadata;
#nullable enable
namespace Avalonia.Styling namespace Avalonia.Styling
{ {
/// <summary> /// <summary>
@ -14,9 +12,11 @@ namespace Avalonia.Styling
public class Style : AvaloniaObject, IStyle, IResourceProvider public class Style : AvaloniaObject, IStyle, IResourceProvider
{ {
private IResourceHost? _owner; private IResourceHost? _owner;
private StyleChildren? _children;
private IResourceDictionary? _resources; private IResourceDictionary? _resources;
private List<ISetter>? _setters; private List<ISetter>? _setters;
private List<IAnimation>? _animations; private List<IAnimation>? _animations;
private StyleCache? _childCache;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Style"/> class. /// Initializes a new instance of the <see cref="Style"/> class.
@ -34,6 +34,14 @@ namespace Avalonia.Styling
Selector = selector(null); 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 public IResourceHost? Owner
{ {
get => _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> /// <summary>
/// Gets or sets a dictionary of style resources. /// Gets or sets a dictionary of style resources.
/// </summary> /// </summary>
@ -90,7 +103,7 @@ namespace Avalonia.Styling
public IList<IAnimation> Animations => _animations ??= new List<IAnimation>(); public IList<IAnimation> Animations => _animations ??= new List<IAnimation>();
bool IResourceNode.HasResources => _resources?.Count > 0; 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; public event EventHandler? OwnerChanged;
@ -98,7 +111,7 @@ namespace Avalonia.Styling
{ {
target = target ?? throw new ArgumentNullException(nameof(target)); 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; target == host ? SelectorMatch.AlwaysThisInstance : SelectorMatch.NeverThisInstance;
if (match.IsMatch && (_setters is object || _animations is object)) if (match.IsMatch && (_setters is object || _animations is object))
@ -108,7 +121,17 @@ namespace Avalonia.Styling
instance.Start(); 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) public bool TryGetResource(object key, out object? result)
@ -156,5 +179,18 @@ namespace Avalonia.Styling
_resources?.RemoveOwner(owner); _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 readonly AvaloniaList<IStyle> _styles = new AvaloniaList<IStyle>();
private IResourceHost? _owner; private IResourceHost? _owner;
private IResourceDictionary? _resources; private IResourceDictionary? _resources;
private Dictionary<Type, List<IStyle>?>? _cache; private StyleCache? _cache;
public Styles() public Styles()
{ {
@ -111,43 +111,8 @@ namespace Avalonia.Styling
public SelectorMatchResult TryAttach(IStyleable target, IStyleHost? host) public SelectorMatchResult TryAttach(IStyleable target, IStyleHost? host)
{ {
_cache ??= new Dictionary<Type, List<IStyle>?>(); _cache ??= new StyleCache();
return _cache.TryAttach(this, target, host);
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;
}
} }
/// <inheritdoc/> /// <inheritdoc/>

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

@ -36,7 +36,7 @@ namespace Avalonia.Styling
return _selectorString; 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; var templatedParent = control.TemplatedParent as IStyleable;
@ -45,9 +45,10 @@ namespace Avalonia.Styling
return SelectorMatch.NeverThisInstance; return SelectorMatch.NeverThisInstance;
} }
return _parent.Match(templatedParent, subscribe); return _parent.Match(templatedParent, parent, subscribe);
} }
protected override Selector? MovePrevious() => null; 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/> /// <inheritdoc/>
protected override SelectorMatch Evaluate(IStyleable control, bool subscribe) protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe)
{ {
if (TargetType != null) if (TargetType != null)
{ {
@ -140,6 +140,7 @@ namespace Avalonia.Styling
} }
protected override Selector? MovePrevious() => _previous; protected override Selector? MovePrevious() => _previous;
internal override bool HasValidNestingSelector() => _previous?.HasValidNestingSelector() ?? false;
private string BuildSelectorString() private string BuildSelectorString()
{ {

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

@ -1,87 +1,51 @@
<Styles xmlns="https://github.com/avaloniaui" <Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Styles.Resources> <Styles.Resources>
<Thickness x:Key="DataGridTextColumnCellTextBlockMargin">12,0,12,0</Thickness>
<x:Double x:Key="ListAccentLowOpacity">0.6</x:Double> <x:Double x:Key="ListAccentLowOpacity">0.6</x:Double>
<x:Double x:Key="ListAccentMediumOpacity">0.8</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="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="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="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> <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" <SolidColorBrush x:Key="DataGridGridLinesBrush"
Color="{StaticResource SystemBaseMediumLowColor}" Opacity="0.4"
Opacity="0.4" /> Color="{DynamicResource SystemBaseMediumLowColor}" />
<SolidColorBrush x:Key="DataGridDropLocationIndicatorBackground" <StaticResource x:Key="DataGridCurrencyVisualPrimaryBrush" ResourceKey="SystemControlTransparentBrush" />
Color="#3F4346" /> <SolidColorBrush x:Key="DataGridDetailsPresenterBackgroundBrush" Color="{DynamicResource SystemChromeMediumLowColor}" />
<SolidColorBrush x:Key="DataGridDisabledVisualElementBackground" <StaticResource x:Key="DataGridFillerColumnGridLinesBrush" ResourceKey="SystemControlTransparentBrush" />
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" />
</Styles.Resources> </Styles.Resources>
<Style Selector="DataGridCell"> <Style Selector="DataGridCell">
@ -252,10 +216,10 @@
</Style> </Style>
<Style Selector="DataGridColumnHeader:pointerover /template/ Grid#PART_ColumnHeaderRoot"> <Style Selector="DataGridColumnHeader:pointerover /template/ Grid#PART_ColumnHeaderRoot">
<Setter Property="Background" Value="{DynamicResource DataGridColumnHeaderHoveredBackgroundColor}" /> <Setter Property="Background" Value="{DynamicResource DataGridColumnHeaderHoveredBackgroundBrush}" />
</Style> </Style>
<Style Selector="DataGridColumnHeader:pressed /template/ Grid#PART_ColumnHeaderRoot"> <Style Selector="DataGridColumnHeader:pressed /template/ Grid#PART_ColumnHeaderRoot">
<Setter Property="Background" Value="{DynamicResource DataGridColumnHeaderPressedBackgroundColor}" /> <Setter Property="Background" Value="{DynamicResource DataGridColumnHeaderPressedBackgroundBrush}" />
</Style> </Style>
<Style Selector="DataGridColumnHeader:dragIndicator"> <Style Selector="DataGridColumnHeader:dragIndicator">
@ -336,29 +300,30 @@
</Style> </Style>
<Style Selector="DataGridRow /template/ Rectangle#BackgroundRectangle"> <Style Selector="DataGridRow /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource SystemControlTransparentBrush}" /> <Setter Property="Fill" Value="{DynamicResource DataGridRowBackgroundBrush}" />
</Style> </Style>
<Style Selector="DataGridRow:pointerover /template/ Rectangle#BackgroundRectangle"> <Style Selector="DataGridRow:pointerover /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource SystemListLowColor}" /> <Setter Property="Fill" Value="{DynamicResource DataGridRowHoveredBackgroundColor}" />
</Style> </Style>
<Style Selector="DataGridRow:selected /template/ Rectangle#BackgroundRectangle"> <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}" /> <Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedUnfocusedBackgroundOpacity}" />
</Style> </Style>
<Style Selector="DataGridRow:selected:pointerover /template/ Rectangle#BackgroundRectangle"> <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}" /> <Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedHoveredUnfocusedBackgroundOpacity}" />
</Style> </Style>
<Style Selector="DataGridRow:selected:focus /template/ Rectangle#BackgroundRectangle"> <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}" /> <Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedBackgroundOpacity}" />
</Style> </Style>
<Style Selector="DataGridRow:selected:pointerover:focus /template/ Rectangle#BackgroundRectangle"> <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}" /> <Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedHoveredBackgroundOpacity}" />
</Style> </Style>
<Style Selector="DataGridRowHeader"> <Style Selector="DataGridRowHeader">
<Setter Property="Foreground" Value="{DynamicResource DataGridRowHeaderForegroundBrush}" />
<Setter Property="Background" Value="{DynamicResource DataGridRowHeaderBackgroundBrush}" /> <Setter Property="Background" Value="{DynamicResource DataGridRowHeaderBackgroundBrush}" />
<Setter Property="Focusable" Value="False" /> <Setter Property="Focusable" Value="False" />
<Setter Property="SeparatorBrush" Value="{DynamicResource DataGridGridLinesBrush}" /> <Setter Property="SeparatorBrush" Value="{DynamicResource DataGridGridLinesBrush}" />
@ -410,25 +375,25 @@
</Style> </Style>
<Style Selector="DataGridRowHeader /template/ Rectangle#BackgroundRectangle"> <Style Selector="DataGridRowHeader /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource SystemControlTransparentBrush}" /> <Setter Property="Fill" Value="{DynamicResource DataGridRowBackgroundBrush}" />
</Style> </Style>
<Style Selector="DataGridRow:pointerover DataGridRowHeader /template/ Rectangle#BackgroundRectangle"> <Style Selector="DataGridRow:pointerover DataGridRowHeader /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource SystemListLowColor}" /> <Setter Property="Fill" Value="{DynamicResource DataGridRowHoveredBackgroundColor}" />
</Style> </Style>
<Style Selector="DataGridRowHeader:selected /template/ Rectangle#BackgroundRectangle"> <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}" /> <Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedUnfocusedBackgroundOpacity}" />
</Style> </Style>
<Style Selector="DataGridRow:pointerover DataGridRowHeader:selected /template/ Rectangle#BackgroundRectangle"> <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}" /> <Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedHoveredUnfocusedBackgroundOpacity}" />
</Style> </Style>
<Style Selector="DataGridRowHeader:selected:focus /template/ Rectangle#BackgroundRectangle"> <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}" /> <Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedBackgroundOpacity}" />
</Style> </Style>
<Style Selector="DataGridRow:pointerover DataGridRowHeader:selected:focus /template/ Rectangle#BackgroundRectangle"> <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}" /> <Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedHoveredBackgroundOpacity}" />
</Style> </Style>

4
src/Avalonia.Controls/MenuBase.cs

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

4
src/Avalonia.Controls/MenuItem.cs

@ -85,13 +85,13 @@ namespace Avalonia.Controls
/// Defines the <see cref="PointerEnterItem"/> event. /// Defines the <see cref="PointerEnterItem"/> event.
/// </summary> /// </summary>
public static readonly RoutedEvent<PointerEventArgs> PointerEnterItemEvent = public static readonly RoutedEvent<PointerEventArgs> PointerEnterItemEvent =
RoutedEvent.Register<InputElement, PointerEventArgs>(nameof(PointerEnterItem), RoutingStrategies.Bubble); RoutedEvent.Register<MenuItem, PointerEventArgs>(nameof(PointerEnterItem), RoutingStrategies.Bubble);
/// <summary> /// <summary>
/// Defines the <see cref="PointerLeaveItem"/> event. /// Defines the <see cref="PointerLeaveItem"/> event.
/// </summary> /// </summary>
public static readonly RoutedEvent<PointerEventArgs> PointerLeaveItemEvent = public static readonly RoutedEvent<PointerEventArgs> PointerLeaveItemEvent =
RoutedEvent.Register<InputElement, PointerEventArgs>(nameof(PointerLeaveItem), RoutingStrategies.Bubble); RoutedEvent.Register<MenuItem, PointerEventArgs>(nameof(PointerLeaveItem), RoutingStrategies.Bubble);
/// <summary> /// <summary>
/// Defines the <see cref="SubmenuOpened"/> event. /// 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 = public static readonly DirectProperty<NativeMenuItemBase, NativeMenu?> ParentProperty =
AvaloniaProperty.RegisterDirect<NativeMenuItem, NativeMenu?>("Parent", o => o.Parent, (o, v) => o.Parent = v); AvaloniaProperty.RegisterDirect<NativeMenuItemBase, NativeMenu?>("Parent", o => o.Parent, (o, v) => o.Parent = v);
public NativeMenu? Parent 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(Avalonia.Controls.Menu.MenuOpenedEvent, this.MenuOpened);
Menu.AddHandler(MenuItem.PointerEnterItemEvent, PointerEnter); Menu.AddHandler(MenuItem.PointerEnterItemEvent, PointerEnter);
Menu.AddHandler(MenuItem.PointerLeaveItemEvent, PointerLeave); Menu.AddHandler(MenuItem.PointerLeaveItemEvent, PointerLeave);
Menu.AddHandler(InputElement.PointerMovedEvent, PointerMoved);
_root = Menu.VisualRoot; _root = Menu.VisualRoot;
@ -91,6 +92,7 @@ namespace Avalonia.Controls.Platform
Menu.RemoveHandler(Avalonia.Controls.Menu.MenuOpenedEvent, this.MenuOpened); Menu.RemoveHandler(Avalonia.Controls.Menu.MenuOpenedEvent, this.MenuOpened);
Menu.RemoveHandler(MenuItem.PointerEnterItemEvent, PointerEnter); Menu.RemoveHandler(MenuItem.PointerEnterItemEvent, PointerEnter);
Menu.RemoveHandler(MenuItem.PointerLeaveItemEvent, PointerLeave); Menu.RemoveHandler(MenuItem.PointerLeaveItemEvent, PointerLeave);
Menu.RemoveHandler(InputElement.PointerMovedEvent, PointerMoved);
if (_root is InputElement inputRoot) 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) protected internal virtual void PointerLeave(object? sender, PointerEventArgs e)
{ {
var item = GetMenuItem(e.Source as IControl); 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. /// Defines the <see cref="Interval"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<int> IntervalProperty = public static readonly StyledProperty<int> IntervalProperty =
AvaloniaProperty.Register<Button, int>(nameof(Interval), 100); AvaloniaProperty.Register<RepeatButton, int>(nameof(Interval), 100);
/// <summary> /// <summary>
/// Defines the <see cref="Delay"/> property. /// Defines the <see cref="Delay"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<int> DelayProperty = public static readonly StyledProperty<int> DelayProperty =
AvaloniaProperty.Register<Button, int>(nameof(Delay), 300); AvaloniaProperty.Register<RepeatButton, int>(nameof(Delay), 300);
private DispatcherTimer? _repeatTimer; private DispatcherTimer? _repeatTimer;

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

@ -7,9 +7,11 @@
</StackPanel> </StackPanel>
</Border> </Border>
</Design.PreviewWith> </Design.PreviewWith>
<Styles.Resources> <Styles.Resources>
<Thickness x:Key="ButtonPadding">8,5,8,6</Thickness> <Thickness x:Key="ButtonPadding">8,5,8,6</Thickness>
</Styles.Resources> </Styles.Resources>
<Style Selector="Button"> <Style Selector="Button">
<Setter Property="Background" Value="{DynamicResource ButtonBackground}" /> <Setter Property="Background" Value="{DynamicResource ButtonBackground}" />
<!--<Setter Property="BackgroundSizing" Value="OuterBorderEdge" />--> <!--<Setter Property="BackgroundSizing" Value="OuterBorderEdge" />-->
@ -37,43 +39,54 @@
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" /> VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" />
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style>
<!-- PointerOverState --> <Style.Children>
<Style Selector="Button:pointerover /template/ ContentPresenter#PART_ContentPresenter"> <Style Selector="^:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundPointerOver}" /> <Setter Property="Background" Value="{DynamicResource ButtonBackgroundPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPointerOver}" /> <Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPointerOver}" />
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" /> <Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" />
</Style> </Style>
<Style Selector="Button:pressed /template/ ContentPresenter#PART_ContentPresenter"> <Style Selector="^:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundPressed}" /> <Setter Property="Background" Value="{DynamicResource ButtonBackgroundPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPressed}" /> <Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPressed}" />
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPressed}" /> <Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPressed}" />
</Style> </Style>
<Style Selector="Button:disabled /template/ ContentPresenter#PART_ContentPresenter"> <Style Selector="^:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}" /> <Setter Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushDisabled}" /> <Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushDisabled}" />
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundDisabled}" /> <Setter Property="Foreground" Value="{DynamicResource ButtonForegroundDisabled}" />
</Style> </Style>
<Style Selector="Button.accent /template/ ContentPresenter#PART_ContentPresenter"> <Style Selector="^.accent">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackground}" /> <Style.Children>
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrush}" /> <Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForeground}" /> <Setter Property="Background" Value="{DynamicResource AccentButtonBackground}" />
</Style> <Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrush}" />
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForeground}" />
</Style>
<Style Selector="Button.accent:pointerover /template/ ContentPresenter#PART_ContentPresenter"> <Style Selector="^:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundPointerOver}" /> <Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushPointerOver}" /> <Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushPointerOver}" />
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForegroundPointerOver}" /> <Setter Property="Foreground" Value="{DynamicResource AccentButtonForegroundPointerOver}" />
</Style> </Style>
<Style Selector="Button.accent:pressed /template/ ContentPresenter#PART_ContentPresenter"> <Style Selector="^:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundPressed}" /> <Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushPressed}" /> <Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushPressed}" />
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForegroundPressed}" /> <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>
<Style Selector="Button, RepeatButton, ToggleButton, DropDownButton"> <Style Selector="Button, RepeatButton, ToggleButton, DropDownButton">
@ -89,9 +102,4 @@
<Setter Property="RenderTransform" Value="scale(0.98)" /> <Setter Property="RenderTransform" Value="scale(0.98)" />
</Style> </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> </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> <Design.PreviewWith>
<Border Padding="20"> <Border Padding="20">
<CheckBox IsThreeState="True" IsChecked="True" Content="Content" Foreground="Gold" /> <CheckBox IsThreeState="True" IsChecked="True" Content="Content" Foreground="Gold" />
</Border> </Border>
</Design.PreviewWith> </Design.PreviewWith>
<Style Selector="CheckBox">
<Setter Property="Padding" Value="8,0,0,0" /> <Setter Property="Padding" Value="8,0,0,0" />
<Setter Property="HorizontalAlignment" Value="Left" /> <Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" /> <Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Left" /> <Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" /> <Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" /> <Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" /> <Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<Setter Property="MinHeight" Value="32" /> <Setter Property="MinHeight" Value="32" />
<!--<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" /> <Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundUnchecked}" />
<Setter Property="FocusVisualMargin" Value="-7,-3,-7,-3" />--> <Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundUnchecked}" />
<Setter Property="Template"> <Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushUnchecked}" />
<ControlTemplate> <Setter Property="Template">
<Grid x:Name="RootGrid" ColumnDefinitions="20,*"> <ControlTemplate>
<Border x:Name="PART_Border" <Grid x:Name="RootGrid" ColumnDefinitions="20,*">
Grid.ColumnSpan="2" <Border x:Name="PART_Border"
Background="{TemplateBinding Background}" Grid.ColumnSpan="2"
BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="{TemplateBinding CornerRadius}" /> BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}" />
<Grid VerticalAlignment="Top" Height="32">
<Border x:Name="NormalRectangle" <Grid VerticalAlignment="Top" Height="32">
BorderThickness="{DynamicResource CheckBoxBorderThemeThickness}" <Border x:Name="NormalRectangle"
CornerRadius="{TemplateBinding CornerRadius}" BorderThickness="{DynamicResource CheckBoxBorderThemeThickness}"
UseLayoutRounding="False" CornerRadius="{TemplateBinding CornerRadius}"
Height="20" UseLayoutRounding="False"
Width="20" /> Height="20"
Width="20" />
<Viewbox UseLayoutRounding="False">
<Panel> <Viewbox UseLayoutRounding="False">
<Panel Height="16" Width="16" /> <Panel>
<Path x:Name="CheckGlyph" Stretch="Uniform" VerticalAlignment="Center" /> <Panel Height="16" Width="16" />
</Panel> <Path x:Name="CheckGlyph" Stretch="Uniform" VerticalAlignment="Center" />
</Viewbox> </Panel>
</Grid> </Viewbox>
<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> </Grid>
</ControlTemplate> <ContentPresenter x:Name="ContentPresenter"
</Setter> ContentTemplate="{TemplateBinding ContentTemplate}"
</Style> Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}"
<!-- Unchecked Normal State --> RecognizesAccessKey="True"
<Style Selector="CheckBox"> HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundUnchecked}" /> VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
</Style> Grid.Column="1" />
<!-- TODO: TextWrapping="Wrap" on contentpresenter -->
<Style Selector="CheckBox"> </Grid>
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundUnchecked}" /> </ControlTemplate>
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushUnchecked}" /> </Setter>
</Style>
<Style.Children>
<Style Selector="CheckBox /template/ Border#NormalRectangle"> <!-- Unchecked Normal State -->
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeUnchecked}" /> <Style Selector="^ /template/ Border#NormalRectangle">
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillUnchecked}" /> <Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeUnchecked}" />
</Style> <Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillUnchecked}" />
</Style>
<Style Selector="CheckBox /template/ Path#CheckGlyph">
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundUnchecked}" /> <Style Selector="^ /template/ Path#CheckGlyph">
<Setter Property="Opacity" Value="0" /> <Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundUnchecked}" />
</Style> <Setter Property="Opacity" Value="0" />
</Style>
<!-- Unchecked PointerOver State -->
<Style Selector="CheckBox:pointerover /template/ ContentPresenter#ContentPresenter"> <!-- Unchecked PointerOver State -->
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundUncheckedPointerOver}" /> <Style Selector="^:pointerover">
</Style> <Style.Children>
<Style Selector="^ /template/ ContentPresenter#ContentPresenter">
<Style Selector="CheckBox:pointerover /template/ Border#PART_Border"> <Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundUncheckedPointerOver}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundUncheckedPointerOver}" /> </Style>
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushUncheckedPointerOver}" />
</Style> <Style Selector="^ /template/ Border#PART_Border">
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundUncheckedPointerOver}" />
<Style Selector="CheckBox:pointerover /template/ Border#NormalRectangle"> <Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushUncheckedPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeUncheckedPointerOver}" /> </Style>
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillUncheckedPointerOver}" />
</Style> <Style Selector="^ /template/ Border#NormalRectangle">
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeUncheckedPointerOver}" />
<Style Selector="CheckBox:pointerover /template/ Path#CheckGlyph"> <Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillUncheckedPointerOver}" />
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundUncheckedPointerOver}" /> </Style>
</Style>
<Style Selector="^ /template/ Path#CheckGlyph">
<!-- Unchecked Pressed State --> <Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundUncheckedPointerOver}" />
<Style Selector="CheckBox:pressed /template/ ContentPresenter#ContentPresenter"> </Style>
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundUncheckedPressed}" /> </Style.Children>
</Style> </Style>
<Style Selector="CheckBox:pressed /template/ Border#PART_Border"> <!-- Unchecked Pressed State -->
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundUncheckedPressed}" /> <Style Selector="^:pressed">
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushUncheckedPressed}" /> <Style.Children>
</Style> <Style Selector="^ /template/ ContentPresenter#ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundUncheckedPressed}" />
<Style Selector="CheckBox:pressed /template/ Border#NormalRectangle"> </Style>
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeUncheckedPressed}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillUncheckedPressed}" /> <Style Selector="^ /template/ Border#PART_Border">
</Style> <Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundUncheckedPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushUncheckedPressed}" />
<Style Selector="CheckBox:pressed /template/ Path#CheckGlyph"> </Style>
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundUncheckedPressed}" />
</Style> <Style Selector="^ /template/ Border#NormalRectangle">
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeUncheckedPressed}" />
<!-- Unchecked Disabled state --> <Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillUncheckedPressed}" />
<Style Selector="CheckBox:disabled /template/ ContentPresenter#ContentPresenter"> </Style>
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundUncheckedDisabled}" />
</Style> <Style Selector="^ /template/ Path#CheckGlyph">
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundUncheckedPressed}" />
<Style Selector="CheckBox:disabled /template/ Border#PART_Border"> </Style>
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundUncheckedDisabled}" /> </Style.Children>
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushUncheckedDisabled}" /> </Style>
</Style>
<!-- Unchecked Disabled state -->
<Style Selector="CheckBox:disabled /template/ Border#NormalRectangle"> <Style Selector="^:disabled">
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeUncheckedDisabled}" /> <Style.Children>
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillUncheckedDisabled}" /> <Style Selector="^ /template/ ContentPresenter#ContentPresenter">
</Style> <Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundUncheckedDisabled}" />
</Style>
<Style Selector="CheckBox:disabled /template/ Path#CheckGlyph">
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundUncheckedDisabled}" /> <Style Selector="^ /template/ Border#PART_Border">
</Style> <Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundUncheckedDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushUncheckedDisabled}" />
</Style>
<!-- Checked Normal State -->
<Style Selector="CheckBox:checked"> <Style Selector="^ /template/ Border#NormalRectangle">
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundChecked}" /> <Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeUncheckedDisabled}" />
</Style> <Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillUncheckedDisabled}" />
</Style>
<Style Selector="CheckBox:checked">
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundChecked}" /> <Style Selector="^ /template/ Path#CheckGlyph">
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushChecked}" /> <Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundUncheckedDisabled}" />
</Style> </Style>
</Style.Children>
<Style Selector="CheckBox:checked /template/ Border#NormalRectangle"> </Style>
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundFillChecked}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillChecked}" /> <Style Selector="^:checked">
</Style> <!-- Checked Normal State -->
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundChecked}" />
<Style Selector="CheckBox:checked /template/ Path#CheckGlyph"> <Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundChecked}" />
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundChecked}" /> <Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushChecked}" />
<Setter Property="Data" Value="M1507 31L438 1101L-119 543L-29 453L438 919L1417 -59L1507 31Z" />
<Setter Property="Width" Value="9" /> <Style.Children>
<Setter Property="Opacity" Value="1" /> <Style Selector="^ /template/ Border#NormalRectangle">
<Setter Property="FlowDirection" Value="LeftToRight" /> <Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundFillChecked}" />
</Style> <Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillChecked}" />
</Style>
<!-- Checked PointerOver State -->
<Style Selector="CheckBox:checked:pointerover /template/ ContentPresenter#ContentPresenter"> <Style Selector="^ /template/ Path#CheckGlyph">
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundCheckedPointerOver}" /> <Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundChecked}" />
</Style> <Setter Property="Data" Value="M1507 31L438 1101L-119 543L-29 453L438 919L1417 -59L1507 31Z" />
<Setter Property="Width" Value="9" />
<Style Selector="CheckBox:checked:pointerover /template/ Border#PART_Border"> <Setter Property="Opacity" Value="1" />
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundCheckedPointerOver}" /> <Setter Property="FlowDirection" Value="LeftToRight" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushCheckedPointerOver}" /> </Style>
</Style>
<!-- Checked PointerOver State -->
<Style Selector="CheckBox:checked:pointerover /template/ Border#NormalRectangle"> <Style Selector="^:pointerover">
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeCheckedPointerOver}" /> <Style.Children>
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillCheckedPointerOver}" /> <Style Selector="^ /template/ ContentPresenter#ContentPresenter">
</Style> <Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundCheckedPointerOver}" />
</Style>
<Style Selector="CheckBox:checked:pointerover /template/ Path#CheckGlyph">
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundCheckedPointerOver}" /> <Style Selector="^ /template/ Border#PART_Border">
</Style> <Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundCheckedPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushCheckedPointerOver}" />
<!-- Checked Pressed State --> </Style>
<Style Selector="CheckBox:checked:pressed /template/ ContentPresenter#ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundCheckedPressed}" /> <Style Selector="^ /template/ Border#NormalRectangle">
</Style> <Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeCheckedPointerOver}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillCheckedPointerOver}" />
<Style Selector="CheckBox:checked:pressed /template/ Border#PART_Border"> </Style>
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundCheckedPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushCheckedPressed}" /> <Style Selector="^ /template/ Path#CheckGlyph">
</Style> <Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundCheckedPointerOver}" />
</Style>
<Style Selector="CheckBox:checked:pressed /template/ Border#NormalRectangle"> </Style.Children>
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeCheckedPressed}" /> </Style>
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillCheckedPressed}" />
</Style> <!-- Checked Pressed State -->
<Style Selector="^:pressed">
<Style Selector="CheckBox:checked:pressed /template/ Path#CheckGlyph"> <Style.Children>
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundCheckedPressed}" /> <Style Selector="^ /template/ ContentPresenter#ContentPresenter">
</Style> <Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundCheckedPressed}" />
</Style>
<!-- Checked Disabled State -->
<Style Selector="CheckBox:checked:disabled /template/ ContentPresenter#ContentPresenter"> <Style Selector="^ /template/ Border#PART_Border">
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundCheckedDisabled}" /> <Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundCheckedPressed}" />
</Style> <Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushCheckedPressed}" />
</Style>
<Style Selector="CheckBox:checked:disabled /template/ Border#PART_Border">
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundCheckedDisabled}" /> <Style Selector="^ /template/ Border#NormalRectangle">
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushCheckedDisabled}" /> <Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeCheckedPressed}" />
</Style> <Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillCheckedPressed}" />
</Style>
<Style Selector="CheckBox:checked:disabled /template/ Border#NormalRectangle">
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeCheckedDisabled}" /> <Style Selector="^ /template/ Path#CheckGlyph">
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillCheckedDisabled}" /> <Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundCheckedPressed}" />
</Style> </Style>
</Style.Children>
<Style Selector="CheckBox:checked:disabled /template/ Path#CheckGlyph"> </Style>
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundCheckedDisabled}" />
</Style> <!-- Checked Disabled State -->
<Style Selector="^:disabled">
<Style.Children>
<!-- Indeterminate Normal State --> <Style Selector="^ /template/ ContentPresenter#ContentPresenter">
<Style Selector="CheckBox:indeterminate"> <Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundCheckedDisabled}" />
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundIndeterminate}" /> </Style>
</Style>
<Style Selector="^ /template/ Border#PART_Border">
<Style Selector="CheckBox:indeterminate"> <Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundCheckedDisabled}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundIndeterminate}" /> <Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushCheckedDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushIndeterminate}" /> </Style>
</Style>
<Style Selector="^ /template/ Border#NormalRectangle">
<Style Selector="CheckBox:indeterminate /template/ Border#NormalRectangle"> <Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeCheckedDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeIndeterminate}" /> <Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillCheckedDisabled}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillIndeterminate}" /> </Style>
</Style>
<Style Selector="^ /template/ Path#CheckGlyph">
<Style Selector="CheckBox:indeterminate /template/ Path#CheckGlyph"> <Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundCheckedDisabled}" />
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundIndeterminate}" /> </Style>
<Setter Property="Data" Value="M1536 1536v-1024h-1024v1024h1024z" /> </Style.Children>
<Setter Property="Width" Value="7" /> </Style>
<Setter Property="Opacity" Value="1" /> </Style.Children>
</Style> </Style>
<!-- Indeterminate PointerOver State --> <Style Selector="^:indeterminate">
<Style Selector="CheckBox:indeterminate:pointerover /template/ ContentPresenter#ContentPresenter"> <!-- Indeterminate Normal State -->
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundIndeterminatePointerOver}" /> <Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundIndeterminate}" />
</Style> <Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundIndeterminate}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushIndeterminate}" />
<Style Selector="CheckBox:indeterminate:pointerover /template/ Border#PART_Border">
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundIndeterminatePointerOver}" /> <Style.Children>
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushIndeterminatePointerOver}" /> <Style Selector="^ /template/ Border#NormalRectangle">
</Style> <Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeIndeterminate}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillIndeterminate}" />
<Style Selector="CheckBox:indeterminate:pointerover /template/ Border#NormalRectangle"> </Style>
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeIndeterminatePointerOver}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillIndeterminatePointerOver}" /> <Style Selector="^ /template/ Path#CheckGlyph">
</Style> <Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundIndeterminate}" />
<Setter Property="Data" Value="M1536 1536v-1024h-1024v1024h1024z" />
<Style Selector="CheckBox:indeterminate:pointerover /template/ Path#CheckGlyph"> <Setter Property="Width" Value="7" />
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundIndeterminatePointerOver}" /> <Setter Property="Opacity" Value="1" />
</Style> </Style>
<!-- Indeterminate Pressed State --> <!-- Indeterminate PointerOver State -->
<Style Selector="CheckBox:indeterminate:pressed /template/ ContentPresenter#ContentPresenter"> <Style Selector="^:pointerover">
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundIndeterminatePressed}" /> <Style.Children>
</Style> <Style Selector="^ /template/ ContentPresenter#ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundIndeterminatePointerOver}" />
<Style Selector="CheckBox:indeterminate:pressed /template/ Border#PART_Border"> </Style>
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundIndeterminatePressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushIndeterminatePressed}" /> <Style Selector="^ /template/ Border#PART_Border">
</Style> <Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundIndeterminatePointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushIndeterminatePointerOver}" />
<Style Selector="CheckBox:indeterminate:pressed /template/ Border#NormalRectangle"> </Style>
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeIndeterminatePressed}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillIndeterminatePressed}" /> <Style Selector="^ /template/ Border#NormalRectangle">
</Style> <Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeIndeterminatePointerOver}" />
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillIndeterminatePointerOver}" />
<Style Selector="CheckBox:indeterminate:pressed /template/ Path#CheckGlyph"> </Style>
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundIndeterminatePressed}" />
</Style> <Style Selector="^ /template/ Path#CheckGlyph">
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundIndeterminatePointerOver}" />
<!-- Indeterminate Disabled State --> </Style>
<Style Selector="CheckBox:indeterminate:disabled /template/ ContentPresenter#ContentPresenter"> </Style.Children>
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundIndeterminateDisabled}" /> </Style>
</Style>
<!-- Indeterminate Pressed State -->
<Style Selector="CheckBox:indeterminate:disabled /template/ Border#PART_Border"> <Style Selector="^:pressed">
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundIndeterminateDisabled}" /> <Style.Children>
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushIndeterminateDisabled}" /> <Style Selector="^ /template/ ContentPresenter#ContentPresenter">
</Style> <Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundIndeterminatePressed}" />
</Style>
<Style Selector="CheckBox:indeterminate:disabled /template/ Border#NormalRectangle">
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeIndeterminateDisabled}" /> <Style Selector="^ /template/ Border#PART_Border">
<Setter Property="Background" Value="{DynamicResource CheckBoxCheckBackgroundFillIndeterminateDisabled}" /> <Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundIndeterminatePressed}" />
</Style> <Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushIndeterminatePressed}" />
</Style>
<Style Selector="CheckBox:indeterminate:disabled /template/ Path#CheckGlyph">
<Setter Property="Fill" Value="{DynamicResource CheckBoxCheckGlyphForegroundIndeterminateDisabled}" /> <Style Selector="^ /template/ Border#NormalRectangle">
</Style> <Setter Property="BorderBrush" Value="{DynamicResource CheckBoxCheckBackgroundStrokeIndeterminatePressed}" />
</Styles> <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); results.Add(result);
result = initialNode; result = initialNode;
break; 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: default:
throw new XamlParseException($"Unsupported selector grammar '{i.GetType()}'.", node); 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")); 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) switch (state)
{ {
case State.Start: case State.Start:
state = ParseStart(ref r); (state, syntax) = ParseStart(ref r);
break; break;
case State.Middle: case State.Middle:
(state, syntax) = ParseMiddle(ref r, end); (state, syntax) = ParseMiddle(ref r, end);
@ -93,27 +93,31 @@ namespace Avalonia.Markup.Parsers
return selector; return selector;
} }
private static State ParseStart(ref CharacterReader r) private static (State, ISyntax?) ParseStart(ref CharacterReader r)
{ {
r.SkipWhitespace(); r.SkipWhitespace();
if (r.End) if (r.End)
{ {
return State.End; return (State.End, null);
} }
if (r.TakeIf(':')) if (r.TakeIf(':'))
{ {
return State.Colon; return (State.Colon, null);
} }
else if (r.TakeIf('.')) else if (r.TakeIf('.'))
{ {
return State.Class; return (State.Class, null);
} }
else if (r.TakeIf('#')) 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) private static (State, ISyntax?) ParseMiddle(ref CharacterReader r, char? end)
@ -142,6 +146,10 @@ namespace Avalonia.Markup.Parsers
{ {
return (State.Start, new CommaSyntax()); return (State.Start, new CommaSyntax());
} }
else if (r.TakeIf('^'))
{
return (State.CanHaveType, new NestingSyntax());
}
else if (end.HasValue && !r.End && r.Peek == end.Value) else if (end.HasValue && !r.End && r.Peek == end.Value)
{ {
return (State.End, null); return (State.End, null);
@ -635,5 +643,13 @@ namespace Avalonia.Markup.Parsers
return obj is CommaSyntax or; 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); 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 private class Class1 : Control
{ {
public static readonly StyledProperty<string> FooProperty = public static readonly StyledProperty<string> FooProperty =

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

@ -469,6 +469,144 @@ namespace Avalonia.Markup.UnitTests.Parsers
result); 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] [Fact]
public void Namespace_Alone_Fails() 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); 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