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;