diff --git a/modules/javafx.graphics/src/main/docs/javafx/scene/doc-files/cssref.html b/modules/javafx.graphics/src/main/docs/javafx/scene/doc-files/cssref.html index a67b053690b..22f060adb4c 100644 --- a/modules/javafx.graphics/src/main/docs/javafx/scene/doc-files/cssref.html +++ b/modules/javafx.graphics/src/main/docs/javafx/scene/doc-files/cssref.html @@ -248,6 +248,9 @@ .left40 { margin-left: 40px; } + .nowrap { + white-space: nowrap; + } @@ -864,7 +867,7 @@

@media

<media-feature>: -
Evaluating Media Features in a Boolean Context
+
Evaluating Media Features in a Boolean Context

If the colon and value is omitted, the media feature is evaluated in a boolean context. This is a convenient shorthand for features that have a reasonable default value. A media feature that is evaluated in a boolean context evaluates to true if it would be true for any value @@ -942,6 +945,12 @@

Combining Media Features
+ + + + + + @@ -962,7 +971,7 @@
Combining Media Features
- + @@ -974,7 +983,7 @@
Combining Media Features
- + @@ -986,35 +995,58 @@
Combining Media Features
- + - + - + - + - + + + + + + + + + + + + +
Available media features
Viewport Characteristics corresponds to Scene.height
aspect-ratioaspect-ratio <number> range aspect ratio = width / height portrait if height >= width, landscape otherwise
display-modedisplay-mode fullscreen | standalone discrete fullscreen if Stage.isFullScreen(), standalone otherwise Comments
prefers-color-schemeprefers-color-scheme light | dark discrete
prefers-reduced-dataprefers-reduced-data no-preference | reduce discrete no-preference evaluates as false
prefers-reduced-motionprefers-reduced-motion no-preference | reduce discrete no-preference evaluates as false
prefers-reduced-transparencyprefers-reduced-transparency no-preference | reduce discrete no-preference evaluates as false
-fx-prefers-persistent-scrollbars-fx-prefers-persistent-scrollbars no-preference | persistent discrete no-preference evaluates as false
Platform FeaturesValueTypeComments
-fx-supports-conditional-feature + graphics | controls | media | web | swt | swing | + fxml | scene3d | effect | shape-clip | + input-method | transparent-window | + unified-window | extended-window | + two-level-focus | virtual-keyboard | + input-touch | input-multitouch | + input-pointerdiscreteEvaluates to true if + + Platform.isSupported(ConditionalFeature) returns true for the specified + conditional feature.
+ This media feature cannot be evaluated in a boolean context.

Examples

diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/css/media/MediaFeatures.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/css/media/MediaFeatures.java index b0e31395f3c..5288451bd60 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/css/media/MediaFeatures.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/css/media/MediaFeatures.java @@ -25,11 +25,13 @@ package com.sun.javafx.css.media; +import com.sun.javafx.application.PlatformImpl; import com.sun.javafx.css.media.expression.ConjunctionExpression; import com.sun.javafx.css.media.expression.FunctionExpression; import com.sun.javafx.css.media.expression.RangeExpression; import com.sun.javafx.css.parser.Token; import javafx.application.ColorScheme; +import javafx.application.ConditionalFeature; import java.util.Locale; import java.util.function.BiFunction; import java.util.function.Function; @@ -138,6 +140,17 @@ lowerCaseFeatureName, lowerCaseTextValue(featureValue), case "-fx-prefers-persistent-scrollbars" -> booleanPreferenceExpression( lowerCaseFeatureName, featureValue, "persistent", MediaQueryContext::isPersistentScrollBars); + case "-fx-supports-conditional-feature" -> { + String lowerCaseFeatureValue = checkNotNullValue(lowerCaseFeatureName, lowerCaseTextValue(featureValue)); + var feature = enumValue(ConditionalFeature::valueOf, lowerCaseFeatureName, lowerCaseFeatureValue); + boolean supported = PlatformImpl.isSupported(feature); + + yield FunctionExpression.of( + lowerCaseFeatureName, lowerCaseFeatureValue, + () -> supported ? TriState.TRUE : TriState.FALSE, + _ -> supported, true); + } + default -> DEFAULT.apply(featureName, featureValue); }; } @@ -217,7 +230,7 @@ private static T checkNotNullValue(String featureName, T featureValue) { private static > T enumValue(Function func, String featureName, String featureValue) { try { - return func.apply(featureValue.toUpperCase(Locale.ROOT)); + return func.apply(featureValue.toUpperCase(Locale.ROOT).replace('-', '_')); } catch (IllegalArgumentException e) { throw unknownValue(featureName, featureValue); } diff --git a/modules/javafx.graphics/src/test/java/test/com/sun/javafx/css/media/MediaQuerySerializerTest.java b/modules/javafx.graphics/src/test/java/test/com/sun/javafx/css/media/MediaQuerySerializerTest.java index 7ea7a8ab2ed..e42d83317a9 100644 --- a/modules/javafx.graphics/src/test/java/test/com/sun/javafx/css/media/MediaQuerySerializerTest.java +++ b/modules/javafx.graphics/src/test/java/test/com/sun/javafx/css/media/MediaQuerySerializerTest.java @@ -26,7 +26,9 @@ package test.com.sun.javafx.css.media; import com.sun.javafx.css.media.MediaQuerySerializer; +import com.sun.javafx.css.media.MediaFeatures; import com.sun.javafx.css.media.SizeQueryType; +import com.sun.javafx.css.media.TriState; import com.sun.javafx.css.media.expression.ConjunctionExpression; import com.sun.javafx.css.media.expression.ConstantExpression; import com.sun.javafx.css.media.expression.EqualExpression; @@ -38,6 +40,8 @@ import com.sun.javafx.css.media.expression.NegationExpression; import com.sun.javafx.css.media.expression.DisjunctionExpression; import com.sun.javafx.css.media.MediaQuery; +import com.sun.javafx.css.parser.CssLexer; +import com.sun.javafx.css.parser.Token; import javafx.css.Size; import javafx.css.SizeUnits; import javafx.css.StyleConverter; @@ -177,6 +181,25 @@ void serializeComplexExpression() throws IOException { assertEquals(expected, actual); } + @Test + void serializeConditionalFeatureExpression() throws IOException { + var supported = MediaFeatures.discreteQueryExpression( + "-fx-supports-conditional-feature", new Token(CssLexer.IDENT, "scene3d")); + + var expected = FunctionExpression.of("-fx-supports-conditional-feature", "scene3d", _ -> null, true); + var actual = deserialize(serialize(supported)); + assertEquals(expected, actual); + assertEquals(TriState.TRUE, actual.evaluate()); + + var unsupported = MediaFeatures.discreteQueryExpression( + "-fx-supports-conditional-feature", new Token(CssLexer.IDENT, "media")); + + expected = FunctionExpression.of("-fx-supports-conditional-feature", "media", _ -> null, true); + actual = deserialize(serialize(unsupported)); + assertEquals(expected, actual); + assertEquals(TriState.FALSE, actual.evaluate()); + } + private byte[] serialize(MediaQuery mediaQuery) throws IOException { var output = new ByteArrayOutputStream(); MediaQuerySerializer.writeBinary(mediaQuery, new DataOutputStream(output), stringStore); diff --git a/modules/javafx.graphics/src/test/java/test/javafx/css/CssParser_mediaQuery_Test.java b/modules/javafx.graphics/src/test/java/test/javafx/css/CssParser_mediaQuery_Test.java index 028ede3b641..456a98a41d1 100644 --- a/modules/javafx.graphics/src/test/java/test/javafx/css/CssParser_mediaQuery_Test.java +++ b/modules/javafx.graphics/src/test/java/test/javafx/css/CssParser_mediaQuery_Test.java @@ -26,7 +26,9 @@ package test.javafx.css; import com.sun.javafx.css.RuleHelper; +import com.sun.javafx.css.media.MediaFeatures; import com.sun.javafx.css.media.SizeQueryType; +import com.sun.javafx.css.media.TriState; import com.sun.javafx.css.media.expression.ConjunctionExpression; import com.sun.javafx.css.media.expression.ConstantExpression; import com.sun.javafx.css.media.expression.DisjunctionExpression; @@ -37,12 +39,15 @@ import com.sun.javafx.css.media.expression.LessExpression; import com.sun.javafx.css.media.expression.LessOrEqualExpression; import com.sun.javafx.css.media.expression.NegationExpression; +import com.sun.javafx.scene.SceneContext; import javafx.application.ColorScheme; import javafx.css.CssParser; import javafx.css.CssParserShim; import javafx.css.Size; import javafx.css.SizeUnits; import javafx.css.Stylesheet; +import javafx.scene.Group; +import javafx.scene.Scene; import java.util.List; import java.util.Set; import org.junit.jupiter.api.Test; @@ -336,6 +341,78 @@ void parsePrefersPersistentScrollBars_booleanContext() { mediaRule.getQueries().getFirst()); } + @Test + void parseSupportsConditionalFeature() { + Stylesheet stylesheet = new CssParser().parse(""" + @media (-fx-supports-conditional-feature: ScEnE3D), + (-fx-supports-conditional-feature: transparent-window), + (-fx-supports-conditional-feature: media) { + .foo { bar: baz; } + } + """); + + var context = new SceneContext(new Scene(new Group())); + var mediaRule = RuleHelper.getMediaRule(stylesheet.getRules().getFirst()); + assertEquals(3, mediaRule.getQueries().size()); + + var expected = FunctionExpression.of("-fx-supports-conditional-feature", "scene3d", _ -> null, true); + var actual = mediaRule.getQueries().get(0); + assertEquals(expected, actual); + assertEquals(TriState.TRUE, actual.evaluate()); + assertTrue(expected.evaluate(context)); + + expected = FunctionExpression.of("-fx-supports-conditional-feature", "transparent-window", _ -> null, true); + actual = mediaRule.getQueries().get(1); + assertEquals(expected, actual); + assertEquals(TriState.TRUE, actual.evaluate()); + assertTrue(expected.evaluate(context)); + + expected = FunctionExpression.of("-fx-supports-conditional-feature", "media", _ -> null, true); + actual = mediaRule.getQueries().get(2); + assertEquals(expected, actual); + assertEquals(TriState.FALSE, actual.evaluate()); + assertFalse(expected.evaluate(context)); + } + + @Test + void invalidConditionalFeatureValueEvaluatesToFalse() { + String stylesheetText = """ + @media (-fx-supports-conditional-feature: invalid-feature) { + .foo { bar: baz; } + } + """; + + Stylesheet stylesheet = new CssParserShim().parseUnmerged(stylesheetText, true); + + var mediaRule = RuleHelper.getMediaRule(stylesheet.getRules().getFirst()); + assertEquals(1, mediaRule.getQueries().size()); + assertEquals(ConstantExpression.of(false), mediaRule.getQueries().getFirst()); + + stylesheet = new CssParser().parse(stylesheetText); + assertEquals(0, stylesheet.getRules().size()); + } + + @Test + void supportsConditionalFeatureCannotBeUsedInBooleanContext() { + assertThrows(IllegalArgumentException.class, + () -> MediaFeatures.discreteQueryExpression("-fx-supports-conditional-feature", null)); + + String stylesheetText = """ + @media (-fx-supports-conditional-feature) { + .foo { bar: baz; } + } + """; + + Stylesheet stylesheet = new CssParserShim().parseUnmerged(stylesheetText, true); + + var mediaRule = RuleHelper.getMediaRule(stylesheet.getRules().getFirst()); + assertEquals(1, mediaRule.getQueries().size()); + assertEquals(ConstantExpression.of(false), mediaRule.getQueries().getFirst()); + + stylesheet = new CssParser().parse(stylesheetText); + assertEquals(0, stylesheet.getRules().size()); + } + @Test void emptyMediaQuery() { Stylesheet stylesheet = new CssParser().parse("""