diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index 6fbe5db3ec..dbf1c1a0aa 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -6,10 +6,24 @@ xmlns:models="clr-namespace:ControlCatalog.Models"> - + + + - + + + + + + diff --git a/src/Avalonia.Styling/Styling/Activators/ScreenActivator.cs b/src/Avalonia.Styling/Styling/Activators/ScreenActivator.cs index af5125cc32..e2d9db6446 100644 --- a/src/Avalonia.Styling/Styling/Activators/ScreenActivator.cs +++ b/src/Avalonia.Styling/Styling/Activators/ScreenActivator.cs @@ -15,6 +15,42 @@ internal sealed class MinWidthActivator : MediaQueryActivatorBase protected override bool IsMatching() => CurrentMediaInfoProvider != null && MinWidthMediaSelector.Evaluate(CurrentMediaInfoProvider, _argument).IsMatch; } +internal sealed class MaxWidthActivator : MediaQueryActivatorBase +{ + private readonly double _argument; + + public MaxWidthActivator(ITopLevelScreenSizeProvider provider, double argument) : base(provider) + { + _argument = argument; + } + + protected override bool IsMatching() => CurrentMediaInfoProvider != null && MaxWidthMediaSelector.Evaluate(CurrentMediaInfoProvider, _argument).IsMatch; +} + +internal sealed class MinHeightActivator : MediaQueryActivatorBase +{ + private readonly double _argument; + + public MinHeightActivator(ITopLevelScreenSizeProvider provider, double argument) : base(provider) + { + _argument = argument; + } + + protected override bool IsMatching() => CurrentMediaInfoProvider != null && MinHeightMediaSelector.Evaluate(CurrentMediaInfoProvider, _argument).IsMatch; +} + +internal sealed class MaxHeightActivator : MediaQueryActivatorBase +{ + private readonly double _argument; + + public MaxHeightActivator(ITopLevelScreenSizeProvider provider, double argument) : base(provider) + { + _argument = argument; + } + + protected override bool IsMatching() => CurrentMediaInfoProvider != null && MaxHeightMediaSelector.Evaluate(CurrentMediaInfoProvider, _argument).IsMatch; +} + internal abstract class MediaQueryActivatorBase : StyleActivatorBase { private readonly ITopLevelScreenSizeProvider _provider; diff --git a/src/Avalonia.Styling/Styling/ScreenSelector.cs b/src/Avalonia.Styling/Styling/ScreenSelector.cs index e32b3cfab0..6f4baa540e 100644 --- a/src/Avalonia.Styling/Styling/ScreenSelector.cs +++ b/src/Avalonia.Styling/Styling/ScreenSelector.cs @@ -32,11 +32,110 @@ public sealed class MinWidthMediaSelector : MediaSelector internal static SelectorMatch Evaluate(IScreenSizeProvider screenSizeProvider, double argument) { - return screenSizeProvider.GetScreenWidth() > argument ? SelectorMatch.AlwaysThisInstance : SelectorMatch.NeverThisInstance; + return screenSizeProvider.GetScreenWidth() >= argument ? SelectorMatch.AlwaysThisInstance : SelectorMatch.NeverThisInstance; } public override string ToString() => "min-width"; } +public sealed class MaxWidthMediaSelector : MediaSelector +{ + public MaxWidthMediaSelector(Selector? previous, double argument) : base(previous, argument) + { + } + + protected override SelectorMatch Evaluate(IStyleable control, bool subscribe) + { + if (!(control is ITopLevelScreenSizeProvider logical)) + { + return SelectorMatch.NeverThisType; + } + + if (subscribe) + { + return new SelectorMatch(new MaxWidthActivator(logical, Argument)); + } + + if (logical.GetScreenSizeProvider() is { } screenSizeProvider) + { + return Evaluate(screenSizeProvider, Argument); + } + + return SelectorMatch.NeverThisInstance; + } + + internal static SelectorMatch Evaluate(IScreenSizeProvider screenSizeProvider, double argument) + { + return screenSizeProvider.GetScreenWidth() <= argument ? SelectorMatch.AlwaysThisInstance : SelectorMatch.NeverThisInstance; + } + public override string ToString() => "max-width"; +} + +public sealed class MinHeightMediaSelector : MediaSelector +{ + public MinHeightMediaSelector(Selector? previous, double argument) : base(previous, argument) + { + } + + protected override SelectorMatch Evaluate(IStyleable control, bool subscribe) + { + if (!(control is ITopLevelScreenSizeProvider logical)) + { + return SelectorMatch.NeverThisType; + } + + if (subscribe) + { + return new SelectorMatch(new MinHeightActivator(logical, Argument)); + } + + if (logical.GetScreenSizeProvider() is { } screenSizeProvider) + { + return Evaluate(screenSizeProvider, Argument); + } + + return SelectorMatch.NeverThisInstance; + } + + internal static SelectorMatch Evaluate(IScreenSizeProvider screenSizeProvider, double argument) + { + return screenSizeProvider.GetScreenHeight() >= argument ? SelectorMatch.AlwaysThisInstance : SelectorMatch.NeverThisInstance; + } + public override string ToString() => "min-height"; +} + +public sealed class MaxHeightMediaSelector : MediaSelector +{ + public MaxHeightMediaSelector(Selector? previous, double argument) : base(previous, argument) + { + } + + protected override SelectorMatch Evaluate(IStyleable control, bool subscribe) + { + if (!(control is ITopLevelScreenSizeProvider logical)) + { + return SelectorMatch.NeverThisType; + } + + if (subscribe) + { + return new SelectorMatch(new MaxHeightActivator(logical, Argument)); + } + + if (logical.GetScreenSizeProvider() is { } screenSizeProvider) + { + return Evaluate(screenSizeProvider, Argument); + } + + return SelectorMatch.NeverThisInstance; + } + + internal static SelectorMatch Evaluate(IScreenSizeProvider screenSizeProvider, double argument) + { + return screenSizeProvider.GetScreenHeight() <= argument ? SelectorMatch.AlwaysThisInstance : SelectorMatch.NeverThisInstance; + } + public override string ToString() => "max-height"; +} + public abstract class MediaSelector : Selector { private readonly Selector? _previous; diff --git a/src/Avalonia.Styling/Styling/Selectors.cs b/src/Avalonia.Styling/Styling/Selectors.cs index ced750ad5d..d4f8ec6ba7 100644 --- a/src/Avalonia.Styling/Styling/Selectors.cs +++ b/src/Avalonia.Styling/Styling/Selectors.cs @@ -135,6 +135,21 @@ namespace Avalonia.Styling { return new MinWidthMediaSelector(previous, argument); } + + public static Selector MaxWidth(this Selector? previous, double argument) + { + return new MaxWidthMediaSelector(previous, argument); + } + + public static Selector MinHeight(this Selector? previous, double argument) + { + return new MinHeightMediaSelector(previous, argument); + } + + public static Selector MaxHeight(this Selector? previous, double argument) + { + return new MaxHeightMediaSelector(previous, argument); + } /// /// 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 9f3e8ebca7..488b5c6fc8 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs @@ -142,6 +142,15 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers case SelectorGrammar.MinWidthSyntax minWidth: result = new XamlIlMinWidthSelector(result, minWidth.Argument); break; + case SelectorGrammar.MaxWidthSyntax maxWidth: + result = new XamlIlMaxWidthSelector(result, maxWidth.Argument); + break; + case SelectorGrammar.MinHeightSyntax minHeight: + result = new XamlIlMinHeightSelector(result, minHeight.Argument); + break; + case SelectorGrammar.MaxHeightSyntax maxHeight: + result = new XamlIlMaxHeightSelector(result, maxHeight.Argument); + break; case SelectorGrammar.NthChildSyntax nth: result = new XamlIlNthChildSelector(result, nth.Step, nth.Offset, XamlIlNthChildSelector.SelectorType.NthChild); break; @@ -370,6 +379,61 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers m => m.Name == "MinWidth" && m.Parameters.Count == 2); } } + + class XamlIlMaxWidthSelector : XamlIlSelectorNode + { + private double _argument; + + public XamlIlMaxWidthSelector(XamlIlSelectorNode previous, double argument) : base(previous) + { + _argument = argument; + } + + public override IXamlType TargetType => Previous?.TargetType; + protected override void DoEmit(XamlEmitContext context, IXamlILEmitter codeGen) + { + codeGen.Ldc_R8(_argument); + EmitCall(context, codeGen, + m => m.Name == "MaxWidth" && m.Parameters.Count == 2); + } + } + + class XamlIlMinHeightSelector : XamlIlSelectorNode + { + private double _argument; + + public XamlIlMinHeightSelector(XamlIlSelectorNode previous, double argument) : base(previous) + { + _argument = argument; + } + + public override IXamlType TargetType => Previous?.TargetType; + + protected override void DoEmit(XamlEmitContext context, IXamlILEmitter codeGen) + { + codeGen.Ldc_R8(_argument); + EmitCall(context, codeGen, + m => m.Name == "MinHeight" && m.Parameters.Count == 2); + } + } + + class XamlIlMaxHeightSelector : XamlIlSelectorNode + { + private double _argument; + + public XamlIlMaxHeightSelector(XamlIlSelectorNode previous, double argument) : base(previous) + { + _argument = argument; + } + + public override IXamlType TargetType => Previous?.TargetType; + protected override void DoEmit(XamlEmitContext context, IXamlILEmitter codeGen) + { + codeGen.Ldc_R8(_argument); + EmitCall(context, codeGen, + m => m.Name == "MaxHeight" && 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 1ccd4c37b0..323cfe4525 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs @@ -171,6 +171,9 @@ namespace Avalonia.Markup.Parsers const string IsKeyword = "is"; const string NotKeyword = "not"; const string MinWidthKeyword = "min-width"; + const string MaxWidthKeyword = "max-width"; + const string MinHeightKeyword = "min-height"; + const string MaxHeightKeyword = "max-height"; const string NthChildKeyword = "nth-child"; const string NthLastChildKeyword = "nth-last-child"; @@ -197,6 +200,30 @@ namespace Avalonia.Markup.Parsers var syntax = new MinWidthSyntax { Argument = argument }; return (State.Middle, syntax); } + if(identifier.SequenceEqual(MaxWidthKeyword.AsSpan()) && r.TakeIf('(')) + { + var argument = ParseDecimal(ref r); + Expect(ref r, ')'); + + var syntax = new MaxWidthSyntax { Argument = argument }; + return (State.Middle, syntax); + } + if(identifier.SequenceEqual(MinHeightKeyword.AsSpan()) && r.TakeIf('(')) + { + var argument = ParseDecimal(ref r); + Expect(ref r, ')'); + + var syntax = new MinHeightSyntax { Argument = argument }; + return (State.Middle, syntax); + } + if(identifier.SequenceEqual(MaxHeightKeyword.AsSpan()) && r.TakeIf('(')) + { + var argument = ParseDecimal(ref r); + Expect(ref r, ')'); + + var syntax = new MaxHeightSyntax { Argument = argument }; + return (State.Middle, syntax); + } if (identifier.SequenceEqual(NthChildKeyword.AsSpan()) && r.TakeIf('(')) { var (step, offset) = ParseNthChildArguments(ref r); diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs index 3cb41fe8f2..a30af26d75 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs @@ -152,6 +152,15 @@ namespace Avalonia.Markup.Parsers case SelectorGrammar.MinWidthSyntax minWidth: result = result.MinWidth(minWidth.Argument); break; + case SelectorGrammar.MaxWidthSyntax maxWidth: + result = result.MaxWidth(maxWidth.Argument); + break; + case SelectorGrammar.MinHeightSyntax minHeight: + result = result.MinHeight(minHeight.Argument); + break; + case SelectorGrammar.MaxHeightSyntax maxHeight: + result = result.MaxHeight(maxHeight.Argument); + break; case SelectorGrammar.NthChildSyntax nth: result = result.NthChild(nth.Step, nth.Offset); break;