From 4370484bd8de263d760984b6f99c7d83a4d8895d Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Wed, 7 Jun 2023 16:48:10 +0000 Subject: [PATCH] add is-os selector --- samples/ControlCatalog/MainView.xaml | 24 ++++++++++ .../Platform/AndroidMediaProvider.cs | 5 +++ src/Avalonia.Base/Platform/IMediaProvider.cs | 1 + .../Platform/VisualMediaProvider.cs | 13 ++++++ .../Styling/Activators/DeviceActivator.cs | 14 ++++++ .../Activators/MediaQueryActivatorBase.cs | 2 +- .../Styling/DeviceMediaSelector.cs | 45 +++++++++++++++++++ src/Avalonia.Base/Styling/Selectors.cs | 5 +++ .../AvaloniaXamlIlSelectorTransformer.cs | 21 +++++++++ .../Markup/Parsers/SelectorGrammar.cs | 24 ++++++++++ .../Markup/Parsers/SelectorParser.cs | 3 ++ 11 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 src/Avalonia.Base/Styling/Activators/DeviceActivator.cs create mode 100644 src/Avalonia.Base/Styling/DeviceMediaSelector.cs diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index 43d48be881..dbc495d933 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -26,6 +26,25 @@ + + + + + @@ -34,6 +53,11 @@ + + + + + diff --git a/src/Android/Avalonia.Android/Platform/AndroidMediaProvider.cs b/src/Android/Avalonia.Android/Platform/AndroidMediaProvider.cs index fe59939340..958eea4b4d 100644 --- a/src/Android/Avalonia.Android/Platform/AndroidMediaProvider.cs +++ b/src/Android/Avalonia.Android/Platform/AndroidMediaProvider.cs @@ -51,5 +51,10 @@ namespace Avalonia.Android.Platform _ => DeviceOrientation.Portrait, }; } + + public string GetPlatform() + { + return "android"; + } } } diff --git a/src/Avalonia.Base/Platform/IMediaProvider.cs b/src/Avalonia.Base/Platform/IMediaProvider.cs index 7b53e919e8..ba087b5a4e 100644 --- a/src/Avalonia.Base/Platform/IMediaProvider.cs +++ b/src/Avalonia.Base/Platform/IMediaProvider.cs @@ -4,6 +4,7 @@ namespace Avalonia.Platform { public interface IMediaProvider { + string GetPlatform(); double GetScreenWidth(); double GetScreenHeight(); diff --git a/src/Avalonia.Base/Platform/VisualMediaProvider.cs b/src/Avalonia.Base/Platform/VisualMediaProvider.cs index 3bcf7c511a..8c387edef0 100644 --- a/src/Avalonia.Base/Platform/VisualMediaProvider.cs +++ b/src/Avalonia.Base/Platform/VisualMediaProvider.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using Avalonia.Reactive; @@ -53,5 +54,17 @@ namespace Avalonia.Platform return DeviceOrientation.Square; } + + public string GetPlatform() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return "windows"; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + return "linux"; + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + return "osx"; + + return ""; + } } } diff --git a/src/Avalonia.Base/Styling/Activators/DeviceActivator.cs b/src/Avalonia.Base/Styling/Activators/DeviceActivator.cs new file mode 100644 index 0000000000..236aa77f2b --- /dev/null +++ b/src/Avalonia.Base/Styling/Activators/DeviceActivator.cs @@ -0,0 +1,14 @@ +namespace Avalonia.Styling.Activators +{ + internal sealed class IsOsActivator : MediaQueryActivatorBase + { + private readonly string _argument; + + public IsOsActivator(Visual visual, string argument) : base(visual) + { + _argument = argument; + } + + protected override bool EvaluateIsActive() => CurrentMediaInfoProvider != null && IsOsMediaSelector.Evaluate(CurrentMediaInfoProvider, _argument).IsMatch; + } +} diff --git a/src/Avalonia.Base/Styling/Activators/MediaQueryActivatorBase.cs b/src/Avalonia.Base/Styling/Activators/MediaQueryActivatorBase.cs index ea5a9c6109..8699abba10 100644 --- a/src/Avalonia.Base/Styling/Activators/MediaQueryActivatorBase.cs +++ b/src/Avalonia.Base/Styling/Activators/MediaQueryActivatorBase.cs @@ -55,7 +55,7 @@ namespace Avalonia.Styling.Activators ReevaluateIsActive(); } - private void OrientationChanged(object sender, EventArgs e) + private void OrientationChanged(object? sender, EventArgs e) { ReevaluateIsActive(); } diff --git a/src/Avalonia.Base/Styling/DeviceMediaSelector.cs b/src/Avalonia.Base/Styling/DeviceMediaSelector.cs new file mode 100644 index 0000000000..cfeee24ea1 --- /dev/null +++ b/src/Avalonia.Base/Styling/DeviceMediaSelector.cs @@ -0,0 +1,45 @@ +using System; +using Avalonia.Platform; +using Avalonia.Styling.Activators; + +namespace Avalonia.Styling +{ + internal sealed class IsOsMediaSelector : MediaSelector + { + public IsOsMediaSelector(Selector? previous, string argument) : base(previous, argument) + { + } + + private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) + { + if (!(control is Visual visual)) + { + return SelectorMatch.NeverThisType; + } + + if (subscribe) + { + return new SelectorMatch(new IsOsActivator(visual, Argument)); + } + + if (visual.VisualRoot is IMediaProviderHost mediaProviderHost && mediaProviderHost.MediaProvider is { } mediaProvider) + { + return Evaluate(mediaProvider, Argument); + } + + return SelectorMatch.NeverThisInstance; + } + + internal static SelectorMatch Evaluate(IMediaProvider mediaProvider, string argument) + { + return mediaProvider.GetPlatform() == argument ? SelectorMatch.AlwaysThisInstance : SelectorMatch.NeverThisInstance; + } + + public override string ToString() => "is-os"; + + public override string ToString(Style? owner) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Avalonia.Base/Styling/Selectors.cs b/src/Avalonia.Base/Styling/Selectors.cs index 7ae9ec9f94..4b196e3515 100644 --- a/src/Avalonia.Base/Styling/Selectors.cs +++ b/src/Avalonia.Base/Styling/Selectors.cs @@ -221,6 +221,11 @@ namespace Avalonia.Styling { return new OrientationMediaSelector(previous, (DeviceOrientation)argument); } + + public static Selector IsOs(this Selector? previous, string argument) + { + return new IsOsMediaSelector(previous, argument); + } /// /// Returns a selector which matches a control with the specified property value. 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 b3c92622c8..3454b25c8c 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs @@ -162,6 +162,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers case SelectorGrammar.OrientationSyntax orientation: result = new XamlIlOrientationSelector(result, orientation.Argument); break; + case SelectorGrammar.IsOsSyntax isOs: + result = new XamlIlIsOsSelector(result, isOs.Argument); + break; case SelectorGrammar.CommaSyntax comma: if (results == null) results = new XamlIlOrSelectorNode(node, selectorType); @@ -490,6 +493,24 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers m => m.Name == "Orientation" && m.Parameters.Count == 2); } } + + class XamlIlIsOsSelector : XamlIlSelectorNode + { + private string _argument; + + public XamlIlIsOsSelector(XamlIlSelectorNode previous, string argument) : base(previous) + { + _argument = argument; + } + + public override IXamlType TargetType => Previous?.TargetType; + protected override void DoEmit(XamlEmitContext context, IXamlILEmitter codeGen) + { + codeGen.Ldstr(_argument); + EmitCall(context, codeGen, + m => m.Name == "IsOs" && m.Parameters.Count == 2); + } + } class XamlIlPropertyEqualsSelector : XamlIlSelectorNode { diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs index 2e59a6767c..97d71525fa 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs @@ -183,6 +183,7 @@ namespace Avalonia.Markup.Parsers const string NthChildKeyword = "nth-child"; const string NthLastChildKeyword = "nth-last-child"; const string OrientationKeyword = "orientation"; + const string IsOsKeyword = "is-os"; if (identifier.SequenceEqual(IsKeyword.AsSpan()) && r.TakeIf('(')) { @@ -253,6 +254,14 @@ namespace Avalonia.Markup.Parsers var syntax = new OrientationSyntax { Argument = argument }; return (State.Middle, syntax); } + if (identifier.SequenceEqual(IsOsKeyword.AsSpan()) && r.TakeIf('(')) + { + var argument = ParseString(ref r); + Expect(ref r, ')'); + + var syntax = new IsOsSyntax { Argument = argument }; + return (State.Middle, syntax); + } else { return ( @@ -341,6 +350,11 @@ namespace Avalonia.Markup.Parsers throw new ExpressionParseException(r.Position, $"Expected a {typeof(T)} after."); } + + private static string ParseString(ref CharacterReader r) + { + return r.ParseIdentifier().ToString(); + } private static (State, ISyntax) ParseTypeName(ref CharacterReader r) { @@ -763,6 +777,16 @@ namespace Avalonia.Markup.Parsers } } + public class IsOsSyntax : ISyntax + { + public string Argument { get; set; } = string.Empty; + + public override bool Equals(object? obj) + { + return (obj is IsOsSyntax orientation) && orientation.Argument == Argument; + } + } + public class CommaSyntax : ISyntax { public override bool Equals(object? obj) diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs index 021f4bd4b6..b0f227005b 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs @@ -173,6 +173,9 @@ namespace Avalonia.Markup.Parsers case SelectorGrammar.OrientationSyntax orientation: result = result.Orientation(orientation.Argument); break; + case SelectorGrammar.IsOsSyntax isOs: + result = result.IsOs(isOs.Argument); + break; case SelectorGrammar.CommaSyntax comma: if (results == null) {