diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 95a597e831..38dcf12f6e 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -36,7 +36,8 @@ namespace Avalonia.Controls IStyleHost, ILogicalRoot, ITextInputMethodRoot, - IWeakEventSubscriber + IWeakEventSubscriber, + IScreenSizeProvider { /// /// Defines the property. @@ -529,7 +530,35 @@ namespace Avalonia.Controls KeyboardDevice.Instance?.SetFocusedElement(null, NavigationMethod.Unspecified, KeyModifiers.None); } + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == BoundsProperty) + { + var oldValue = change.OldValue.GetValueOrDefault(); + var newValue = change.NewValue.GetValueOrDefault(); + + if (oldValue.Size != newValue.Size) + { + ScreenSizeChanged?.Invoke(this, EventArgs.Empty); + } + } + } + ITextInputMethodImpl? ITextInputMethodRoot.InputMethod => (PlatformImpl as ITopLevelImplWithTextInputMethod)?.TextInputMethod; + + public double GetScreenWidth() + { + return Bounds.Size.Width; + } + + public double GetScreenHeight() + { + return Bounds.Size.Height; + } + + public event EventHandler? ScreenSizeChanged; } } diff --git a/src/Avalonia.Styling/LogicalTree/IScreenSizeProvider.cs b/src/Avalonia.Styling/LogicalTree/IScreenSizeProvider.cs new file mode 100644 index 0000000000..3dce1c7713 --- /dev/null +++ b/src/Avalonia.Styling/LogicalTree/IScreenSizeProvider.cs @@ -0,0 +1,12 @@ +using System; + +namespace Avalonia.LogicalTree; + +public interface IScreenSizeProvider +{ + double GetScreenWidth(); + + double GetScreenHeight(); + + event EventHandler? ScreenSizeChanged; +} diff --git a/src/Avalonia.Styling/LogicalTree/ITopLevelScreenSizeProvider.cs b/src/Avalonia.Styling/LogicalTree/ITopLevelScreenSizeProvider.cs new file mode 100644 index 0000000000..9e8af25742 --- /dev/null +++ b/src/Avalonia.Styling/LogicalTree/ITopLevelScreenSizeProvider.cs @@ -0,0 +1,10 @@ +using System; + +namespace Avalonia.LogicalTree; + +public interface ITopLevelScreenSizeProvider +{ + IScreenSizeProvider? GetScreenSizeProvider(); + + event EventHandler? ScreenSizeProviderChanged; +} diff --git a/src/Avalonia.Styling/Styling/Activators/ScreenActivator.cs b/src/Avalonia.Styling/Styling/Activators/ScreenActivator.cs new file mode 100644 index 0000000000..51760e4915 --- /dev/null +++ b/src/Avalonia.Styling/Styling/Activators/ScreenActivator.cs @@ -0,0 +1,64 @@ +using System; +using Avalonia.LogicalTree; + +namespace Avalonia.Styling.Activators; + +internal sealed class ScreenActivator : StyleActivatorBase +{ + private readonly ITopLevelScreenSizeProvider _provider; + private IScreenSizeProvider? _currentScreenSizeProvider; + + public ScreenActivator( + ITopLevelScreenSizeProvider provider) + { + _provider = provider; + } + + protected override void Initialize() + { + InitialiseScreenSizeProvider(); + PublishNext(IsMatching()); + _provider.ScreenSizeProviderChanged += ScreenSizeProviderChanged; + } + + protected override void Deinitialize() + { + _provider.ScreenSizeProviderChanged -= ScreenSizeProviderChanged; + + if (_currentScreenSizeProvider is { }) + { + _currentScreenSizeProvider.ScreenSizeChanged -= ScreenSizeChanged; + _currentScreenSizeProvider = null; + } + } + + private void ScreenSizeProviderChanged(object? sender, EventArgs e) + { + if (_currentScreenSizeProvider is { }) + { + _currentScreenSizeProvider.ScreenSizeChanged -= ScreenSizeChanged; + _currentScreenSizeProvider = null; + } + + InitialiseScreenSizeProvider(); + } + + private void InitialiseScreenSizeProvider() + { + if (_provider.GetScreenSizeProvider() is { } screenSizeProvider) + { + _currentScreenSizeProvider = screenSizeProvider; + + _currentScreenSizeProvider.ScreenSizeChanged += ScreenSizeChanged; + } + + PublishNext(IsMatching()); + } + + private void ScreenSizeChanged(object? sender, EventArgs e) + { + PublishNext(IsMatching()); + } + + private bool IsMatching() => _currentScreenSizeProvider != null && ScreenSelector.Evaluate(_currentScreenSizeProvider).IsMatch; +} diff --git a/src/Avalonia.Styling/Styling/ScreenSelector.cs b/src/Avalonia.Styling/Styling/ScreenSelector.cs new file mode 100644 index 0000000000..0a48b4880d --- /dev/null +++ b/src/Avalonia.Styling/Styling/ScreenSelector.cs @@ -0,0 +1,51 @@ +using System; +using Avalonia.LogicalTree; +using Avalonia.Styling.Activators; + +namespace Avalonia.Styling; + +public class ScreenSelector : Selector +{ + private readonly Selector? _previous; + + public ScreenSelector(Selector? previous) + { + _previous = previous; + } + public override bool InTemplate => _previous?.InTemplate ?? false; + + public override bool IsCombinator => false; + + public override Type? TargetType => _previous?.TargetType; + + protected override SelectorMatch Evaluate(IStyleable control, bool subscribe) + { + if (!(control is ITopLevelScreenSizeProvider logical)) + { + return SelectorMatch.NeverThisType; + } + + if (subscribe) + { + return new SelectorMatch(new ScreenActivator(logical)); + } + + if (logical.GetScreenSizeProvider() is { } screenSizeProvider) + { + return Evaluate(screenSizeProvider); + } + + return SelectorMatch.NeverThisInstance; + } + + internal static SelectorMatch Evaluate(IScreenSizeProvider screenSizeProvider) + { + var match = screenSizeProvider.GetScreenWidth() > 600 && screenSizeProvider.GetScreenHeight() > 600; + + return match ? SelectorMatch.AlwaysThisInstance : SelectorMatch.NeverThisInstance; + } + + protected override Selector? MovePrevious() => _previous; + + public override string ToString() => "screen"; +} diff --git a/src/Avalonia.Styling/Styling/Selectors.cs b/src/Avalonia.Styling/Styling/Selectors.cs index 7c66469cf1..0a4f69b23a 100644 --- a/src/Avalonia.Styling/Styling/Selectors.cs +++ b/src/Avalonia.Styling/Styling/Selectors.cs @@ -131,6 +131,11 @@ namespace Avalonia.Styling return new NotSelector(previous, argument); } + public static Selector Screen(this Selector? previous) + { + return new ScreenSelector(previous); + } + /// /// /// The selector. diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index 324b253a0f..c810fbcae0 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -25,7 +25,7 @@ namespace Avalonia /// extension methods defined in . /// [UsableDuringInitialization] - public class Visual : StyledElement, IVisual + public class Visual : StyledElement, IVisual, ITopLevelScreenSizeProvider { /// /// Defines the property. @@ -418,6 +418,8 @@ namespace Avalonia } } } + + ScreenSizeProviderChanged?.Invoke(this, EventArgs.Empty); } /// @@ -645,5 +647,12 @@ namespace Avalonia visual.SetVisualParent(parent); } } + + public IScreenSizeProvider? GetScreenSizeProvider() + { + return VisualRoot as IScreenSizeProvider; + } + + public event EventHandler? ScreenSizeProviderChanged; } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs index 4c4df1f53a..1298981442 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs @@ -139,6 +139,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers case SelectorGrammar.NotSyntax not: result = new XamlIlNotSelector(result, Create(not.Argument, typeResolver)); break; + case SelectorGrammar.ScreenSyntax screen: + result = new XamlIlScreenSelector(result); + break; case SelectorGrammar.NthChildSyntax nth: result = new XamlIlNthChildSelector(result, nth.Step, nth.Offset, XamlIlNthChildSelector.SelectorType.NthChild); break; @@ -349,6 +352,21 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers m => m.Name == _type.ToString() && m.Parameters.Count == 3); } } + + class XamlIlScreenSelector : XamlIlSelectorNode + { + + public XamlIlScreenSelector(XamlIlSelectorNode previous) : base(previous) + { + } + + public override IXamlType TargetType => Previous?.TargetType; + protected override void DoEmit(XamlEmitContext context, IXamlILEmitter codeGen) + { + EmitCall(context, codeGen, + m => m.Name == "Screen" && m.Parameters.Count == 1); + } + } class XamlIlPropertyEqualsSelector : XamlIlSelectorNode { diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs index a9fc18474c..5a60354ad2 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs @@ -169,6 +169,7 @@ namespace Avalonia.Markup.Parsers const string IsKeyword = "is"; const string NotKeyword = "not"; + const string ScreenKeyword = "screen"; const string NthChildKeyword = "nth-child"; const string NthLastChildKeyword = "nth-last-child"; @@ -187,6 +188,14 @@ namespace Avalonia.Markup.Parsers var syntax = new NotSyntax { Argument = argument }; return (State.Middle, syntax); } + if(identifier.SequenceEqual(ScreenKeyword.AsSpan()) && r.TakeIf('(')) + { + var argument = Parse(ref r, ')'); + Expect(ref r, ')'); + + var syntax = new ScreenSyntax { Argument = argument }; + return (State.Middle, syntax); + } if (identifier.SequenceEqual(NthChildKeyword.AsSpan()) && r.TakeIf('(')) { var (step, offset) = ParseNthChildArguments(ref r); @@ -605,6 +614,16 @@ namespace Avalonia.Markup.Parsers return (obj is NotSyntax not) && Argument.SequenceEqual(not.Argument); } } + + public class ScreenSyntax : ISyntax + { + public IEnumerable Argument { get; set; } = Enumerable.Empty(); + + public override bool Equals(object? obj) + { + return (obj is ScreenSyntax screen) && Argument.SequenceEqual(screen.Argument); + } + } public class NthChildSyntax : ISyntax { diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs index 5b6522064a..fc75b21a97 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs @@ -149,6 +149,9 @@ namespace Avalonia.Markup.Parsers case SelectorGrammar.NotSyntax not: result = result.Not(x => Create(not.Argument)!); break; + case SelectorGrammar.ScreenSyntax screen: + result = result.Screen(); + break; case SelectorGrammar.NthChildSyntax nth: result = result.NthChild(nth.Step, nth.Offset); break;