From 770412444de012022f1fcb496ee923d27b232053 Mon Sep 17 00:00:00 2001 From: Shagen Ogandzhanian Date: Mon, 23 Aug 2021 16:20:18 +0200 Subject: [PATCH] Support filter in CSS API this resolves #923 --- .../compose/web/css/properties/filter.kt | 107 ++++++++++++++ web/core/src/jsTest/kotlin/css/FilterTests.kt | 130 ++++++++++++++++++ 2 files changed, 237 insertions(+) create mode 100644 web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/properties/filter.kt create mode 100644 web/core/src/jsTest/kotlin/css/FilterTests.kt diff --git a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/properties/filter.kt b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/properties/filter.kt new file mode 100644 index 0000000000..99aae1db88 --- /dev/null +++ b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/properties/filter.kt @@ -0,0 +1,107 @@ +/* + * Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +package org.jetbrains.compose.web.css + +import org.jetbrains.compose.web.ExperimentalComposeWebApi +import org.w3c.dom.css.CSS + +fun interface FilterFunction { + fun apply(): String +} + +interface FilterBuilder { + fun blur(radius: CSSLengthValue) + + fun brightness(amount: Number) + fun brightness(amount: CSSPercentageValue) + + fun dropShadow(offsetX: CSSLengthValue, offsetY: CSSLengthValue) + fun dropShadow(offsetX: CSSLengthValue, offsetY: CSSLengthValue, blurRadius: CSSLengthValue) + fun dropShadow(offsetX: CSSLengthValue, offsetY: CSSLengthValue, color: CSSColorValue) + fun dropShadow(offsetX: CSSLengthValue, offsetY: CSSLengthValue, blurRadius: CSSLengthValue, color: CSSColorValue) + + fun contrast(amount: Number) + fun contrast(amount: CSSPercentageValue) + + fun grayscale(amount: Number) + fun grayscale(amount: CSSPercentageValue) + + fun hueRotate(angle: CSSAngleValue) + + fun invert(amount: Number) + fun invert(amount: CSSPercentageValue) + + fun opacity(amount: Number) + fun opacity(amount: CSSPercentageValue) + + fun saturate(amount: Number) + fun saturate(amount: CSSPercentageValue) + + fun sepia(amount: Number) + fun sepia(amount: CSSPercentageValue) +} + +private class FilterBuilderImplementation : FilterBuilder { + private val transformations = mutableListOf() + + override fun blur(radius: CSSLengthValue) { transformations.add { "blur($radius)" } } + + override fun brightness(amount: Number) { transformations.add { "brightness($amount)" } } + override fun brightness(amount: CSSPercentageValue) { transformations.add { "brightness($amount)" } } + + override fun contrast(amount: Number) { transformations.add { "contrast($amount)" } } + override fun contrast(amount: CSSPercentageValue) { transformations.add { "contrast($amount)" } } + + override fun grayscale(amount: Number) { transformations.add { "grayscale($amount)" } } + override fun grayscale(amount: CSSPercentageValue) { transformations.add { "grayscale($amount)" } } + + override fun hueRotate(angle: CSSAngleValue) { transformations.add { "hue-rotate($angle)" } } + + override fun toString(): String { + return transformations.joinToString(" ") { it.apply() } + } + + override fun invert(amount: Number) { transformations.add { "invert($amount)" } } + override fun invert(amount: CSSPercentageValue) { transformations.add { "invert($amount)" } } + + override fun opacity(amount: Number) { transformations.add { "opacity($amount)" } } + override fun opacity(amount: CSSPercentageValue) { transformations.add { "opacity($amount)" } } + + override fun saturate(amount: Number) { transformations.add { "saturate($amount)" } } + override fun saturate(amount: CSSPercentageValue) { transformations.add { "saturate($amount)" } } + + override fun sepia(amount: Number) { transformations.add { "sepia($amount)" } } + override fun sepia(amount: CSSPercentageValue) { transformations.add { "sepia($amount)" } } + + override fun dropShadow(offsetX: CSSLengthValue, offsetY: CSSLengthValue) { + transformations.add { "drop-shadow($offsetX $offsetY)" } + } + + override fun dropShadow(offsetX: CSSLengthValue, offsetY: CSSLengthValue, blurRadius: CSSLengthValue) { + transformations.add { "drop-shadow($offsetX $offsetY $blurRadius)" } + } + + override fun dropShadow(offsetX: CSSLengthValue, offsetY: CSSLengthValue, color: CSSColorValue) { + transformations.add { "drop-shadow($offsetX $offsetY $color)" } + } + + override fun dropShadow( + offsetX: CSSLengthValue, + offsetY: CSSLengthValue, + blurRadius: CSSLengthValue, + color: CSSColorValue + ) { + transformations.add { "drop-shadow($offsetX $offsetY $blurRadius $color)" } + } +} + +@ExperimentalComposeWebApi +fun StyleBuilder.filter(filterContext: FilterBuilder.() -> Unit) { + val builder = FilterBuilderImplementation() + property("filter", builder.apply(filterContext).toString()) +} + + diff --git a/web/core/src/jsTest/kotlin/css/FilterTests.kt b/web/core/src/jsTest/kotlin/css/FilterTests.kt new file mode 100644 index 0000000000..4a2f1a98aa --- /dev/null +++ b/web/core/src/jsTest/kotlin/css/FilterTests.kt @@ -0,0 +1,130 @@ +/* + * Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +package org.jetbrains.compose.web.core.tests.css + +import org.jetbrains.compose.web.ExperimentalComposeWebApi +import org.jetbrains.compose.web.core.tests.runTest +import org.jetbrains.compose.web.css.* +import org.jetbrains.compose.web.dom.Div +import org.jetbrains.compose.web.dom.Img +import kotlin.test.Test +import kotlin.test.assertEquals + +@ExperimentalComposeWebApi +class FilterTests { + @Test + fun blur() = runTest { + composition { + Img(src = "icon.png", attrs = { style { filter { blur(10.px) } } }) + } + + assertEquals("blur(10px)", nextChild().style.filter) + } + + @Test + fun brightness() = runTest { + composition { + Div({ style { filter { brightness(1.75) } } }) + Div({ style { filter { brightness(200.percent) } } }) + } + + assertEquals("brightness(1.75)", nextChild().style.filter) + assertEquals("brightness(200%)", nextChild().style.filter) + } + + @Test + fun contrast() = runTest { + composition { + Div({ style { filter { contrast(2.75) } } }) + Div({ style { filter { contrast(177.percent) } } }) + } + + assertEquals("contrast(2.75)", nextChild().style.filter) + assertEquals("contrast(177%)", nextChild().style.filter) + } + + @Test + fun grayscale() = runTest { + composition { + Div({ style { filter { grayscale(0.15) } } }) + Div({ style { filter { grayscale(90.percent) } } }) + } + + assertEquals("grayscale(0.15)", nextChild().style.filter) + assertEquals("grayscale(90%)", nextChild().style.filter) + } + + @Test + fun hueRotate() = runTest { + composition { + Div({ style { filter { hueRotate(90.deg) } } }) + Div({ style { filter { hueRotate(0.5.turn) } } }) + } + + assertEquals("hue-rotate(90deg)", nextChild().style.filter) + assertEquals("hue-rotate(0.5turn)", nextChild().style.filter) + } + + @Test + fun invert() = runTest { + composition { + Div({ style { filter { invert(0.75) } } }) + Div({ style { filter { invert(30.percent) } } }) + } + + assertEquals("invert(0.75)", nextChild().style.filter) + assertEquals("invert(30%)", nextChild().style.filter) + } + + @Test + fun opacity() = runTest { + composition { + Div({ style { filter { opacity(.25) } } }) + Div({ style { filter { opacity(30.percent) } } }) + } + + assertEquals("opacity(0.25)", nextChild().style.filter) + assertEquals("opacity(30%)", nextChild().style.filter) + } + + @Test + fun saturate() = runTest { + composition { + Div({ style { filter { saturate(.25) } } }) + Div({ style { filter { saturate(20.percent) } } }) + } + + assertEquals("saturate(0.25)", nextChild().style.filter) + assertEquals("saturate(20%)", nextChild().style.filter) + } + + @Test + fun sepia() = runTest { + composition { + Div({ style { filter { sepia(.95) } } }) + Div({ style { filter { sepia(80.percent) } } }) + } + + assertEquals("sepia(0.95)", nextChild().style.filter) + assertEquals("sepia(80%)", nextChild().style.filter) + } + + @Test + fun dropShadow() = runTest { + composition { + Div({ style { filter { dropShadow(10.em, 5.px) } } }) + Div({ style { filter { dropShadow(7.px, 2.px, 20.px) } } }) + Div({ style { filter { dropShadow(7.px, 2.px, Color.yellow) } } }) + Div({ style { filter { dropShadow(16.px, 16.px, 10.px, Color.black) } } }) + } + + assertEquals("drop-shadow(10em 5px)", nextChild().style.filter) + assertEquals("drop-shadow(7px 2px 20px)", nextChild().style.filter) + assertEquals("drop-shadow(yellow 7px 2px)", nextChild().style.filter) + assertEquals("drop-shadow(black 16px 16px 10px)", nextChild().style.filter) + } + +} \ No newline at end of file