diff --git a/build.gradle.kts b/build.gradle.kts index 0d18c31..5ce136f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,10 +1,10 @@ plugins { - kotlin("multiplatform") version "1.4.30" + kotlin("multiplatform") version "1.4.31" `maven-publish` } group = "nl.astraeus" -version = "0.3.11" +version = "0.4.11" repositories { maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") } @@ -75,10 +75,12 @@ publishing { } } } +/* publications { val kotlinMultiplatform by getting { - //artifactId = "kotlin-css-generator" + artifactId = "kotlin-css-generator" } } +*/ } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ba94df8..442d913 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/commonMain/kotlin/nl/astraeus/css/properties/Color.kt b/src/commonMain/kotlin/nl/astraeus/css/properties/Color.kt index e9b6882..d68711a 100644 --- a/src/commonMain/kotlin/nl/astraeus/css/properties/Color.kt +++ b/src/commonMain/kotlin/nl/astraeus/css/properties/Color.kt @@ -1,194 +1,500 @@ package nl.astraeus.css.properties +import kotlin.math.PI +import kotlin.math.abs import kotlin.math.max -import kotlin.math.min +import kotlin.math.roundToInt -open class Color( - value: String, - val readOnly: Boolean = false -) : CssProperty(value) { +/** + * See [CSS Color Module Level 3](https://www.w3.org/TR/2018/REC-css-color-3-20180619/) + * + * This class represents a CSS color value. String parameters to the constructor argument + * can take one of the following forms: + * + * * HTML color name, e.g. ``Red``, ``DarkSalmon`` (case-insensitive), though in this case the use of the pre-defined constants is recommended. + * * ``#rgb`` or ``#rrggbb`` + * * ``rgb(0..255, 0..255, 0..255)``, ``rgb(0..100%, 0..100%, 0..100%)``, ``rgb(0..100%, 0..100%, 0..100%, 0..1)``, ``rgba(0..255, 0..255, 0..255, 0..1)`` + * * ``hsl(0..360, 0-100%, 0..100%)`` or ``hsla(0..360, 0-100%, 0..100%, 0..1)`` + * + * Technically, the Hue parameter to ``hsl`` or ``hsla`` can exceed ``360``, because it represents a *degree* (angle) on + * the color wheel. But as per the algorithm proposed by the W3C, the value will ultimately be capped to ``360`` through + * a series of modulus operations; see section *4.2.4. HSL color values* of the above specification. + * + * Taken from: https://github.com/JetBrains/kotlin-wrappers/tree/master/kotlin-css + */ +@Suppress("SpellCheckingInspection") +class Color(value: String) : CssProperty(value) { + private var rgb: String? = null - companion object { - val auto = Color("auto", true) - val transparant = Color("transparant", true) - val initial = Color("initial", true) - val inherit = Color("inherit", true) - - fun hex(hex: String) = Color("#$hex") - - fun rgb( - red: Int, - green: Int, - blue: Int - ) = RgbaColor(red, green, blue, 1.0) - - fun rgba( - red: Int, - green: Int, - blue: Int, - alpha: Double - ) = RgbaColor(red, green, blue, alpha) - - fun hsl( - hue: Int, - saturation: Int, - lightness: Int - ) = HslaColor(hue, saturation, lightness, 1.0) - - fun hsla( - hue: Int, - saturation: Int, - lightness: Int, - alpha: Double - ) = HslaColor(hue, saturation, lightness, alpha) - - val aliceBlue = Color("#F0F8FF") - val antiqueWhite = Color("#FAEBD7") - val aqua = Color("#00FFFF") - val aquamarine = Color("#7FFFD4") - val azure = Color("#F0FFFF") - val beige = Color("#F5F5DC") - val bisque = Color("#FFE4C4") - val black = Color("#000000") - val blanchedAlmond = Color("#FFEBCD") - val blue = Color("#0000FF") - val blueViolet = Color("#8A2BE2") - val brown = Color("#A52A2A") - val burlyWood = Color("#DEB887") - val cadetBlue = Color("#5F9EA0") - val chartreuse = Color("#7FFF00") - val chocolate = Color("#D2691E") - val coral = Color("#FF7F50") - val cornflowerBlue = Color("#6495ED") - val cornsilk = Color("#FFF8DC") - val crimson = Color("#DC143C") - val cyan = Color("#00FFFF") - val darkBlue = Color("#00008B") - val darkCyan = Color("#008B8B") - val darkGoldenRod = Color("#B8860B") - val darkGray = Color("#A9A9A9") - val darkGrey = Color("#A9A9A9") - val darkGreen = Color("#006400") - val darkKhaki = Color("#BDB76B") - val darkMagenta = Color("#8B008B") - val darkOliveGreen = Color("#556B2F") - val darkorange = Color("#FF8C00") - val darkOrchid = Color("#9932CC") - val darkRed = Color("#8B0000") - val darkSalmon = Color("#E9967A") - val darkSeaGreen = Color("#8FBC8F") - val darkSlateBlue = Color("#483D8B") - val darkSlateGray = Color("#2F4F4F") - val darkSlateGrey = Color("#2F4F4F") - val darkTurquoise = Color("#00CED1") - val darkViolet = Color("#9400D3") - val deepPink = Color("#FF1493") - val deepSkyBlue = Color("#00BFFF") - val dimGray = Color("#696969") - val dimGrey = Color("#696969") - val dodgerBlue = Color("#1E90FF") - val fireBrick = Color("#B22222") - val floralWhite = Color("#FFFAF0") - val forestGreen = Color("#228B22") - val fuchsia = Color("#FF00FF") - val gainsboro = Color("#DCDCDC") - val ghostWhite = Color("#F8F8FF") - val gold = Color("#FFD700") - val goldenRod = Color("#DAA520") - val gray = Color("#808080") - val grey = Color("#808080") - val green = Color("#008000") - val greenYellow = Color("#ADFF2F") - val honeyDew = Color("#F0FFF0") - val hotPink = Color("#FF69B4") - val indianRed = Color(" #CD5C5C") - val indigo = Color(" #4B0082") - val ivory = Color("#FFFFF0") - val khaki = Color("#F0E68C") - val lavender = Color("#E6E6FA") - val lavenderBlush = Color("#FFF0F5") - val lawnGreen = Color("#7CFC00") - val lemonChiffon = Color("#FFFACD") - val lightBlue = Color("#ADD8E6") - val lightCoral = Color("#F08080") - val lightCyan = Color("#E0FFFF") - val lightGoldenRodYellow = Color("#FAFAD2") - val lightGray = Color("#D3D3D3") - val lightGrey = Color("#D3D3D3") - val lightGreen = Color("#90EE90") - val lightPink = Color("#FFB6C1") - val lightSalmon = Color("#FFA07A") - val lightSeaGreen = Color("#20B2AA") - val lightSkyBlue = Color("#87CEFA") - val lightSlateGray = Color("#778899") - val lightSlateGrey = Color("#778899") - val lightSteelBlue = Color("#B0C4DE") - val lightYellow = Color("#FFFFE0") - val lime = Color("#00FF00") - val limeGreen = Color("#32CD32") - val linen = Color("#FAF0E6") - val magenta = Color("#FF00FF") - val maroon = Color("#800000") - val mediumAquaMarine = Color("#66CDAA") - val mediumBlue = Color("#0000CD") - val mediumOrchid = Color("#BA55D3") - val mediumPurple = Color("#9370D8") - val mediumSeaGreen = Color("#3CB371") - val mediumSlateBlue = Color("#7B68EE") - val mediumSpringGreen = Color("#00FA9A") - val mediumTurquoise = Color("#48D1CC") - val mediumVioletRed = Color("#C71585") - val midnightBlue = Color("#191970") - val mintCream = Color("#F5FFFA") - val mistyRose = Color("#FFE4E1") - val moccasin = Color("#FFE4B5") - val navajoWhite = Color("#FFDEAD") - val navy = Color("#000080") - val oldLace = Color("#FDF5E6") - val olive = Color("#808000") - val oliveDrab = Color("#6B8E23") - val orange = Color("#FFA500") - val orangeRed = Color("#FF4500") - val orchid = Color("#DA70D6") - val paleGoldenRod = Color("#EEE8AA") - val paleGreen = Color("#98FB98") - val paleTurquoise = Color("#AFEEEE") - val paleVioletRed = Color("#D87093") - val papayaWhip = Color("#FFEFD5") - val peachPuff = Color("#FFDAB9") - val peru = Color("#CD853F") - val pink = Color("#FFC0CB") - val plum = Color("#DDA0DD") - val powderBlue = Color("#B0E0E6") - val purple = Color("#800080") - val red = Color("#FF0000") - val rosyBrown = Color("#BC8F8F") - val royalBlue = Color("#4169E1") - val saddleBrown = Color("#8B4513") - val salmon = Color("#FA8072") - val sandyBrown = Color("#F4A460") - val seaGreen = Color("#2E8B57") - val seaShell = Color("#FFF5EE") - val sienna = Color("#A0522D") - val silver = Color("#C0C0C0") - val skyBlue = Color("#87CEEB") - val slateBlue = Color("#6A5ACD") - val slateGray = Color("#708090") - val slateGrey = Color("#708090") - val snow = Color("#FFFAFA") - val springGreen = Color("#00FF7F") - val steelBlue = Color("#4682B4") - val tan = Color("#D2B48C") - val teal = Color("#008080") - val thistle = Color("#D8BFD8") - val tomato = Color("#FF6347") - val turquoise = Color("#40E0D0") - val violet = Color("#EE82EE") - val wheat = Color("#F5DEB3") - val white = Color("#FFFFFF") - val whiteSmoke = Color("#F5F5F5") - val yellow = Color("#FFFF00") - val yellowGreen = Color("#9ACD32") + private constructor(value: String, rgb: String) : this(value) { + this.rgb = rgb } + companion object { + val initial = Color("initial") + val inherit = Color("inherit") + val unset = Color("unset") + + val transparent = Color("transparent") + val currentColor = Color("currentColor") + + // W3C predefined HTML colors (147), see the referenced specification above. + val aliceBlue = Color("aliceblue", "#f0f8ff") + val antiqueWhite = Color("antiquewhite", "#faebd7") + val aqua = Color("aqua", "#00ffff") + val aquamarine = Color("aquamarine", "#7fffd4") + val azure = Color("azure", "#f0ffff") + val beige = Color("beige", "#f5f5dc") + val bisque = Color("bisque", "#ffe4c4") + val black = Color("black", "#000000") + val blanchedAlmond = Color("blanchedalmond", "#ffebcd") + val blue = Color("blue", "#0000ff") + val blueViolet = Color("blueviolet", "#8a2be2") + val brown = Color("brown", "#a52a2a") + val burlyWood = Color("burlywood", "#deb887") + val cadetBlue = Color("cadetblue", "#5f9ea0") + val chartreuse = Color("chartreuse", "#7fff00") + val chocolate = Color("chocolate", "#d2691e") + val coral = Color("coral", "#ff7f50") + val cornflowerBlue = Color("cornflowerblue", "#6495ed") + val cornsilk = Color("cornsilk", "#fff8dc") + val crimson = Color("crimson", "#dc143c") + val cyan = Color("cyan", "#00ffff") + val darkBlue = Color("darkblue", "#00008b") + val darkCyan = Color("darkcyan", "#008b8b") + val darkGoldenrod = Color("darkgoldenrod", "#b8860b") + val darkGray = Color("darkgray", "#a9a9a9") + val darkGreen = Color("darkgreen", "#006400") + val darkGrey = Color("darkgrey", "#a9a9a9") + val darkKhaki = Color("darkkhaki", "#bdb76b") + val darkMagenta = Color("darkmagenta", "#8b008b") + val darkOliveGreen = Color("darkolivegreen", "#556b2f") + val darkOrange = Color("darkorange", "#ff8c00") + val darkOrchid = Color("darkorchid", "#9932cc") + val darkRed = Color("darkred", "#8b0000") + val darkSalmon = Color("darksalmon", "#e9967a") + val darkSeaGreen = Color("darkseagreen", "#8fbc8f") + val darkSlateBlue = Color("darkslateblue", "#483d8b") + val darkSlateGray = Color("darkslategray", "#2f4f4f") + val darkSlateGrey = Color("darkslategrey", "#2f4f4f") + val darkTurquoise = Color("darkturquoise", "#00ced1") + val darkViolet = Color("darkviolet", "#9400d3") + val deepPink = Color("deeppink", "#ff1493") + val deepSkyBlue = Color("deepskyblue", "#00bfff") + val dimGray = Color("dimgray", "#696969") + val dimGrey = Color("dimgrey", "#696969") + val dodgerBlue = Color("dodgerblue", "#1e90ff") + val firebrick = Color("firebrick", "#b22222") + val floralWhite = Color("floralwhite", "#fffaf0") + val forestGreen = Color("forestgreen", "#228b22") + val fuchsia = Color("fuchsia", "#ff00ff") + val gainsboro = Color("gainsboro", "#dcdcdc") + val ghostWhite = Color("ghostwhite", "#f8f8ff") + val gold = Color("gold", "#ffd700") + val goldenrod = Color("goldenrod", "#daa520") + val gray = Color("gray", "#808080") + val green = Color("green", "#008000") + val greenYellow = Color("greenyellow", "#adff2f") + val grey = Color("grey", "#808080") + val honeydew = Color("honeydew", "#f0fff0") + val hotPink = Color("hotpink", "#ff69b4") + val indianRed = Color("indianred", "#cd5c5c") + val indigo = Color("indigo", "#4b0082") + val ivory = Color("ivory", "#fffff0") + val khaki = Color("khaki", "#f0e68c") + val lavender = Color("lavender", "#e6e6fa") + val lavenderBlush = Color("lavenderblush", "#fff0f5") + val lawnGreen = Color("lawngreen", "#7cfc00") + val lemonChiffon = Color("lemonchiffon", "#fffacd") + val lightBlue = Color("lightblue", "#add8e6") + val lightCoral = Color("lightcoral", "#f08080") + val lightCyan = Color("lightcyan", "#e0ffff") + val lightGoldenrodYellow = Color("lightgoldenrodyellow", "#fafad2") + val lightGray = Color("lightgray", "#d3d3d3") + val lightGreen = Color("lightgreen", "#90ee90") + val lightGrey = Color("lightgrey", "#d3d3d3") + val lightPink = Color("lightpink", "#ffb6c1") + val lightSalmon = Color("lightsalmon", "#ffa07a") + val lightSeaGreen = Color("lightseagreen", "#20b2aa") + val lightSkyBlue = Color("lightskyblue", "#87cefa") + val lightSlateGray = Color("lightslategray", "#778899") + val lightSlateGrey = Color("lightslategrey", "#778899") + val lightSteelBlue = Color("lightsteelblue", "#b0c4de") + val lightYellow = Color("lightyellow", "#ffffe0") + val lime = Color("lime", "#00ff00") + val limeGreen = Color("limegreen", "#32cd32") + val linen = Color("linen", "#faf0e6") + val magenta = Color("magenta", "#ff00ff") + val maroon = Color("maroon", "#800000") + val mediumAquamarine = Color("mediumaquamarine", "#66cdaa") + val mediumBlue = Color("mediumblue", "#0000cd") + val mediumOrchid = Color("mediumorchid", "#ba55d3") + val mediumPurple = Color("mediumpurple", "#9370d8") + val mediumSeaGreen = Color("mediumseagreen", "#3cb371") + val mediumSlateBlue = Color("mediumslateblue", "#7b68ee") + val mediumSpringGreen = Color("mediumspringgreen", "#00fa9a") + val mediumTurquoise = Color("mediumturquoise", "#48d1cc") + val mediumVioletRed = Color("mediumvioletred", "#c71585") + val midnightBlue = Color("midnightblue", "#191970") + val mintCream = Color("mintcream", "#f5fffa") + val mistyRose = Color("mistyrose", "#ffe4e1") + val moccasin = Color("moccasin", "#ffe4b5") + val navajoWhite = Color("navajowhite", "#ffdead") + val navy = Color("navy", "#000080") + val oldLace = Color("oldlace", "#fdf5e6") + val olive = Color("olive", "#808000") + val oliveDrab = Color("olivedrab", "#6b8e23") + val orange = Color("orange", "#ffa500") + val orangeRed = Color("orangered", "#ff4500") + val orchid = Color("orchid", "#da70d6") + val paleGoldenrod = Color("palegoldenrod", "#eee8aa") + val paleGreen = Color("palegreen", "#98fb98") + val paleTurquoise = Color("paleturquoise", "#afeeee") + val paleVioletRed = Color("palevioletred", "#db7093") + val papayaWhip = Color("papayawhip", "#ffefd5") + val peachPuff = Color("peachpuff", "#ffdab9") + val peru = Color("peru", "#cd853f") + val pink = Color("pink", "#ffc0cb") + val plum = Color("plum", "#dda0dd") + val powderBlue = Color("powderblue", "#b0e0e6") + val purple = Color("purple", "#800080") + val red = Color("red", "#ff0000") + val rosyBrown = Color("rosybrown", "#bc8f8f") + val royalBlue = Color("royalblue", "#4169e1") + val saddleBrown = Color("saddlebrown", "#8b4513") + val salmon = Color("salmon", "#fa8072") + val sandyBrown = Color("sandybrown", "#f4a460") + val seaGreen = Color("seagreen", "#2e8b57") + val seaShell = Color("seashell", "#fff5ee") + val sienna = Color("sienna", "#a0522d") + val silver = Color("silver", "#c0c0c0") + val skyBlue = Color("skyblue", "#87ceeb") + val slateBlue = Color("slateblue", "#6a5acd") + val slateGray = Color("slategray", "#708090") + val slateGrey = Color("slategrey", "#708090") + val snow = Color("snow", "#fffafa") + val springGreen = Color("springgreen", "#00ff7f") + val steelBlue = Color("steelblue", "#4682b4") + val tan = Color("tan", "#d2b48c") + val teal = Color("teal", "#008080") + val thistle = Color("thistle", "#d8bfd8") + val tomato = Color("tomato", "#ff6347") + val turquoise = Color("turquoise", "#40e0d0") + val violet = Color("violet", "#ee82ee") + val wheat = Color("wheat", "#f5deb3") + val white = Color("white", "#ffffff") + val whiteSmoke = Color("whitesmoke", "#f5f5f5") + val yellow = Color("yellow", "#ffff00") + val yellowGreen = Color("yellowgreen", "#9acd32") + + fun normalizeFractionalPercent(value: Double): Double = + value.coerceIn(minimumValue = 0.0, maximumValue = 1.0) + + fun normalizePercent(value: Int): Int = + value.coerceIn(minimumValue = 0, maximumValue = 100) + + fun normalizeRGB(value: Int): Int = + value.coerceIn(minimumValue = 0, maximumValue = 255) + + // algorithm for capping from W3C + fun normalizeHue(value: Double): Int = + (((value % 360) + 360) % 360).roundToInt() + + fun normalizeAlpha(value: Double): Double = + normalizeFractionalPercent(value) + + // Match for hsl(int, int%, int%) | hsla(int, int%, int%, 0.5) | etc. + private val HSLA_REGEX by lazy { + Regex( + "^hsla?\\((-?[0-9]+\\.?[0-9]*(?:deg|grad|rad|turn)?)\\s*[, ]?\\s*(\\d{1,3})%\\s*[, ]\\s*(\\d{1,3})%\\s*[, ]?\\s*(\\d|(?:\\d?\\.\\d+))?\\)\$", + RegexOption.IGNORE_CASE + ) + } + + // Match for rgb(255, 255, 255) | rgba(255, 255, 255, 0.5) | rgb(100% 100% 100%) | etc. + private val RGBA_REGEX by lazy { + Regex( + "^rgba?\\((\\d{1,3}%?)\\s*[, ]\\s*(\\d{1,3}%?)\\s*[, ]\\s*(\\d{1,3}%?)[, ]?\\s*(\\d|(?:\\d?\\.\\d+))?\\)\$", + RegexOption.IGNORE_CASE + ) + } + } + + /** + * withAlpha preserves existing alpha value: rgba(0, 0, 0, 0.5).withAlpha(0.1) = rgba(0, 0, 0, 0.05) + */ + fun withAlpha(alpha: Double) = + when { + value.startsWith("hsl", true) -> with(fromHSLANotation()) { hsla(hue, saturation, lightness, normalizeAlpha(alpha) * this.alpha) } + else -> with(toRGBA()) { rgba(red, green, blue, normalizeAlpha(alpha) * this.alpha) } + } + + /** + * changeAlpha rewrites existing alpha value: rgba(0, 0, 0, 0.5).withAlpha(0.1) = rgba(0, 0, 0, 0.1) + */ + fun changeAlpha(alpha: Double) = + when { + value.startsWith("hsl", true) -> with(fromHSLANotation()) { hsla(hue, saturation, lightness, normalizeAlpha(alpha)) } + else -> with(toRGBA()) { rgba(red, green, blue, normalizeAlpha(alpha)) } + } + + // https://stackoverflow.com/questions/2049230/convert-rgba-color-to-rgb + fun blend(backgroundColor: Color): Color { + val source = this.toRGBA() + val background = backgroundColor.toRGBA() + + val targetR = ((1 - source.alpha) * background.red) + (source.alpha * source.red) + val targetG = ((1 - source.alpha) * background.green) + (source.alpha * source.green) + val targetB = ((1 - source.alpha) * background.blue) + (source.alpha * source.blue) + + return rgb(targetR.roundToInt(), targetG.roundToInt(), targetB.roundToInt()) + } + + /** + * Lighten the color by the specified percent (between 0-100), returning a new instance of Color. + * + * @param percent the percent to lighten the Color + * @return a new lightened version of this color + */ + fun lighten(percent: Int): Color { + val isHSLA = value.startsWith("hsl", ignoreCase = true) + val hsla = if (isHSLA) fromHSLANotation() else toRGBA().asHSLA() + + val lightness = hsla.lightness + (hsla.lightness * (normalizePercent(percent) / 100.0)).roundToInt() + val newHSLa = hsla.copy(lightness = normalizePercent(lightness)) + return if (isHSLA) { + hsla(newHSLa.hue, newHSLa.saturation, newHSLa.lightness, newHSLa.alpha) + } else { + with(newHSLa.asRGBA()) { rgba(red, green, blue, alpha) } + } + } + + /** + * Darken the color by the specified percent (between 0-100), returning a new instance of Color. + * + * @param percent the percent to darken the Color + * @return a new darkened version of this color + */ + fun darken(percent: Int): Color { + val isHSLA = value.startsWith("hsl", ignoreCase = true) + val hsla = if (isHSLA) fromHSLANotation() else toRGBA().asHSLA() + + val darkness = hsla.lightness - (hsla.lightness * (normalizePercent(percent) / 100.0)).roundToInt() + val newHSLa = hsla.copy(lightness = normalizePercent(darkness)) + return if (isHSLA) { + hsla(newHSLa.hue, newHSLa.saturation, newHSLa.lightness, newHSLa.alpha) + } else { + with(newHSLa.asRGBA()) { rgba(red, green, blue, alpha) } + } + } + + /** + * Increase contrast, if lightness > 50 then darken else lighten + * + * @param percent the percent to lighten/darken the Color + * @return a new ligtened/darkened version of this color + */ + fun contrast(percent: Int): Color { + val isHSLA = value.startsWith("hsl", ignoreCase = true) + val hsla = if (isHSLA) fromHSLANotation() else toRGBA().asHSLA() + + val darkness = if (hsla.lightness > 50) { + hsla.lightness - (hsla.lightness * (normalizePercent(percent) / 100.0)).roundToInt() + } else { + hsla.lightness + (hsla.lightness * (normalizePercent(percent) / 100.0)).roundToInt() + } + + val newHSLa = hsla.copy(lightness = normalizePercent(darkness)) + return if (isHSLA) { + hsla(newHSLa.hue, newHSLa.saturation, newHSLa.lightness, newHSLa.alpha) + } else { + with(newHSLa.asRGBA()) { rgba(red, green, blue, alpha) } + } + } + + /** + * Saturate the color by the specified percent (between 0-100), returning a new instance of Color. + * + * @param percent the percent to saturate the Color + * @return a new saturated version of this color + */ + fun saturate(percent: Int): Color { + val isHSLA = value.startsWith("hsl", ignoreCase = true) + val hsla = if (isHSLA) fromHSLANotation() else toRGBA().asHSLA() + + val saturation = hsla.saturation + (hsla.saturation * (normalizePercent(percent) / 100.0)).roundToInt() + val newHSLa = hsla.copy(saturation = normalizePercent(saturation)) + return if (isHSLA) { + hsla(newHSLa.hue, newHSLa.saturation, newHSLa.lightness, newHSLa.alpha) + } else { + with(newHSLa.asRGBA()) { rgba(red, green, blue, alpha) } + } + } + + /** + * Desaturate the color by the specified percent (between 0-100), returning a new instance of Color. + * + * @param percent the percent to desaturate the Color + * @return a new desaturated version of this color + */ + fun desaturate(percent: Int): Color { + val isHSLA = value.startsWith("hsl", ignoreCase = true) + val hsla = if (isHSLA) fromHSLANotation() else toRGBA().asHSLA() + + val desaturation = hsla.saturation - (hsla.saturation * (normalizePercent(percent) / 100.0)).roundToInt() + val newHSLa = hsla.copy(saturation = normalizePercent(desaturation)) + return if (isHSLA) { + hsla(newHSLa.hue, newHSLa.saturation, newHSLa.lightness, newHSLa.alpha) + } else { + with(newHSLa.asRGBA()) { rgba(red, green, blue, alpha) } + } + } + + internal data class RGBA( + val red: Int, + val green: Int, + val blue: Int, + val alpha: Double = 1.0 + ) { + + // Algorithm adapted from http://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/ + fun asHSLA(): HSLA { + // scale R, G, B values into 0..1 fractions + val r = red / 255.0 + val g = green / 255.0 + val b = blue / 255.0 + + val cMax = maxOf(r, g, b) + val cMin = minOf(r, g, b) + val chroma = cMax - cMin + + val lg = normalizeFractionalPercent((cMax + cMin) / 2) + val s = if (chroma != 0.0) normalizeFractionalPercent(chroma / (1.0 - abs((2.0 * lg) - 1.0))) else 0.0 + val h = when (cMax) { + cMin -> 0.0 + r -> 60 * (((g - b) / chroma) % 6.0) + g -> 60 * (((b - r) / chroma) + 2) + b -> 60 * (((r - g) / chroma) + 4) + else -> error("Unexpected value for max") // theoretically unreachable bc maxOf(r, g, b) above + } + + return HSLA(normalizeHue(h), (s * 100).roundToInt(), (lg * 100).roundToInt(), alpha) + } + } + + internal data class HSLA( + val hue: Int, + val saturation: Int, + val lightness: Int, + val alpha: Double = 1.0 + ) { + + // Algorithm from W3C link referenced in class comment (section 4.2.4. HSL color values) + fun asRGBA(): RGBA { + fun hueToRGB(m1: Double, m2: Double, h: Double): Double { + val hu = if (h < 0) h + 1 else if (h > 1) h - 1 else h + return when { + (hu < 1.0 / 6) -> m1 + (m2 - m1) * 6 * hu + (hu < 1.0 / 2) -> m2 + (hu < 2.0 / 3) -> m1 + ((m2 - m1) * 6 * (2.0 / 3 - hu)) + else -> m1 + } + } + + if (saturation == 0) return RGBA(lightness, lightness, lightness) + + // scale H, S, V values into 0..1 fractions + val h = (hue % 360.0) / 360.0 + val s = saturation / 100.0 + val lg = lightness / 100.0 + + val m2 = if (lg < 0.5) lg * (1 + s) else (lg + s - lg * s) + val m1 = 2 * lg - m2 + val r = normalizeFractionalPercent(hueToRGB(m1, m2, h + (1.0 / 3))) + val g = normalizeFractionalPercent(hueToRGB(m1, m2, h)) + val b = normalizeFractionalPercent(hueToRGB(m1, m2, h - (1.0 / 3))) + return RGBA((r * 255).roundToInt(), (g * 255).roundToInt(), (b * 255).roundToInt(), alpha) + } + } + + internal fun fromHSLANotation(): HSLA { + val match = HSLA_REGEX.find(value) + + fun getHSLParameter(index: Int) = + match?.groups?.get(index)?.value + ?: throw IllegalArgumentException("Expected hsl or hsla notation, got $value") + + val hueShape = getHSLParameter(1) + val hue = normalizeHue( + when { + hueShape.endsWith("grad", true) -> hueShape.substringBefore("grad").toDouble() * (9.0 / 10) + hueShape.endsWith("rad", true) -> (hueShape.substringBefore("rad").toDouble() * 180) / PI + hueShape.endsWith("turn", true) -> hueShape.substringBefore("turn").toDouble() * 360.0 + hueShape.endsWith("deg", true) -> hueShape.substringBefore("deg").toDouble() + else -> hueShape.toDouble() + } + ) + val saturation = normalizePercent(getHSLParameter(2).toInt()) + val lightness = normalizePercent(getHSLParameter(3).toInt()) + val alpha = normalizeAlpha(match?.groups?.get(4)?.value?.toDouble() ?: 1.0) + + return HSLA(hue, saturation, lightness, alpha) + } + + internal fun fromRGBANotation(): RGBA { + val match = RGBA_REGEX.find(value) + + fun getRGBParameter(index: Int): Int { + val group = match?.groups?.get(index)?.value + ?: throw IllegalArgumentException("Expected rgb or rgba notation, got $value") + + return when { + (group.endsWith('%')) -> (normalizeFractionalPercent(group.substringBefore('%').toDouble() / 100.0) * 255.0).toInt() + else -> normalizeRGB(group.toInt()) + } + } + + val red = getRGBParameter(1) + val green = getRGBParameter(2) + val blue = getRGBParameter(3) + val alpha = normalizeAlpha(match?.groups?.get(4)?.value?.toDouble() ?: 1.0) + + return RGBA(red, green, blue, alpha) + } + + internal fun toRGBA(): RGBA { + val v = rgb ?: value + return when { + v.startsWith("rgb") -> fromRGBANotation() + + // Matches #rgb + v.startsWith("#") && v.length == 4 -> RGBA( + "${v[1]}${v[1]}".toInt(16), + "${v[2]}${v[2]}".toInt(16), + "${v[3]}${v[3]}".toInt(16) + ) + + // Matches both #rrggbb and #rrggbbaa + v.startsWith("#") && (v.length == 7 || v.length == 9) -> RGBA( + (v.substring(1..2)).toInt(16), + (v.substring(3..4)).toInt(16), + (v.substring(5..6)).toInt(16) + ) + else -> throw IllegalArgumentException("Only hexadecimal, rgb, and rgba notations are accepted, got $v") + } + } } + +private fun String.withZeros() = this + "0".repeat(max(0, 3 - this.length)) +fun hex(value: Int) = Color("#${value.toString(16).withZeros()}") +fun rgb(red: Int, green: Int, blue: Int) = Color("rgb($red, $green, $blue)") +fun rgba(red: Int, green: Int, blue: Int, alpha: Double) = Color("rgba($red, $green, $blue, ${formatAlpha(alpha)})") +fun hsl(hue: Int, saturation: Int, lightness: Int) = Color("hsl($hue, $saturation%, $lightness%)") +fun hsla(hue: Int, saturation: Int, lightness: Int, alpha: Double) = Color("hsla($hue, $saturation%, $lightness%, ${formatAlpha(alpha)})") +fun blackAlpha(alpha: Double) = Color.black.withAlpha(alpha) +fun whiteAlpha(alpha: Double) = Color.white.withAlpha(alpha) + +private fun formatAlpha(alpha: Double): String = + alpha.toString().let { + if ("." in it) it else "$it.0" + } diff --git a/src/commonMain/kotlin/nl/astraeus/css/properties/HslaColor.kt b/src/commonMain/kotlin/nl/astraeus/css/properties/HslaColor.kt deleted file mode 100644 index 4bb92d0..0000000 --- a/src/commonMain/kotlin/nl/astraeus/css/properties/HslaColor.kt +++ /dev/null @@ -1,168 +0,0 @@ -package nl.astraeus.css.properties - -import kotlin.math.abs -import kotlin.math.max -import kotlin.math.min - -class HslaColor( - val hue: Int, - val saturation: Int, - val lightness: Int, - val alpha: Double = 1.0 -): Color("hsla($hue,$saturation%,$lightness%,$alpha)") { - - fun darken(amount: Int): HslaColor = HslaColor( - hue, - saturation, - max(0, lightness - amount), - alpha - ) - - fun lighten(amount: Int): HslaColor = HslaColor( - hue, - saturation, - min(100, lightness + amount), - alpha - ) - - fun saturize(amount: Int): HslaColor = HslaColor( - hue, - min(100, saturation + amount), - lightness, - alpha - ) - - fun desaturize(amount: Int): HslaColor = HslaColor( - hue, - max(0, saturation - amount), - lightness, - alpha - ) - - fun adjustHue(amount: Int): HslaColor { - var hue = this.hue + amount - while (hue < 0) { - hue += 360 - } - while(hue > 359) { - hue -= 360 - } - - return HslaColor( - hue, - saturation, - lightness, - alpha - ) - } - - fun toRgba() = RgbaColor.fromHsla(this) - - companion object { - private fun findValues(value: String, expectedNumber: Int): List { - check(value.contains('(') && value.contains(')')) - - val parts = value.split('(')[1].split(')')[0].split(',') - - check(parts.size == expectedNumber) - - return parts - } - - fun fromString(value: String): HslaColor = when { - value.trim().startsWith("#") -> { - fromHex(value.substringAfterLast('#')) - } - value.trim().startsWith("hsla") -> { - val values = findValues(value, 4) - HslaColor( - values[0].trim().toInt(), - values[1].trim().toInt(), - values[2].trim().toInt(), - values[3].trim().toDouble() - ) - } - value.trim().startsWith("hsl") -> { - val values = findValues(value, 3) - HslaColor( - values[0].trim().toInt(), - values[1].trim().toInt(), - values[2].trim().toInt() - ) - } - value.trim().startsWith("rgba") -> { - val values = findValues(value, 4) - fromRgba( - values[0].trim().toInt(), - values[1].trim().toInt(), - values[2].trim().toInt(), - values[3].trim().toDouble() - ) - } - value.trim().startsWith("rgb") -> { - val values = findValues(value, 3) - fromRgb( - values[0].trim().toInt(), - values[1].trim().toInt(), - values[2].trim().toInt() - ) - } - else -> { - throw IllegalArgumentException("Unable to parse $value as color") - } - } - - fun fromHex(value: String): HslaColor = fromRgba(RgbaColor.fromHex(value)) - - fun fromRgba(rgba: RgbaColor): HslaColor = fromRgba(rgba.red, rgba.green, rgba.blue, rgba.alpha) - - fun fromRgb( - red: Int, - green: Int, - blue: Int, - ): HslaColor { - return fromRgba(red, green, blue, 1.0) - } - - fun fromRgba( - red: Int, - green: Int, - blue: Int, - alpha: Double - ): HslaColor { - val r = red / 255.0 - val g = green / 255.0 - val b = blue / 255.0 - - val v = max(r,max(g,b)) - val c = v - min(r,min(g,b)) - val f = (1 - abs(v+v-c-1)) - var h = if (c > 0) { - when (v) { - r -> { - (g-b)/c - } - g -> { - 2+(b-r)/c - } - else -> { - 2+(b-r)/c - } - } - } else { - 0.0 - } - - if (h < 0) { - h += 6 - } - - return HslaColor( - (60*h).toInt(), - if (f>0) { ((c * 100)/f).toInt() } else { 0 }, - (100*((v+v-c)/2)).toInt(), - alpha - ) - } - } -} diff --git a/src/commonMain/kotlin/nl/astraeus/css/properties/RgbaColor.kt b/src/commonMain/kotlin/nl/astraeus/css/properties/RgbaColor.kt deleted file mode 100644 index a5fb42c..0000000 --- a/src/commonMain/kotlin/nl/astraeus/css/properties/RgbaColor.kt +++ /dev/null @@ -1,107 +0,0 @@ -package nl.astraeus.css.properties - -import kotlin.math.max -import kotlin.math.min - -class RgbaColor( - val red: Int, - val green: Int, - val blue: Int, - val alpha: Double = 1.0 -): Color("rgba($red,$green,$blue,$alpha)") { - - fun toHsla(): HslaColor = HslaColor.fromRgba(this) - - companion object { - private fun findValues(value: String, expectedNumber: Int): List { - check(value.contains('(') && value.contains(')')) - - val parts = value.split('(')[1].split(')')[0].split(',') - - check(parts.size == expectedNumber) - - return parts - } - - fun fromString(value: String): RgbaColor = when { - value.trim().startsWith("#") -> { - fromHex(value.substringAfterLast('#')) - } - value.trim().startsWith("hsla") -> { - val values = findValues(value, 4) - fromHsla( - values[0].toInt(), - values[1].toInt(), - values[2].toInt(), - values[3].toDouble() - ) - } - value.trim().startsWith("hsl") -> { - val values = findValues(value, 3) - fromHsl( - values[0].toInt(), - values[1].toInt(), - values[2].toInt() - ) - } - value.trim().startsWith("rgba") -> { - val values = findValues(value, 4) - RgbaColor( - values[0].toInt(), - values[1].toInt(), - values[2].toInt(), - values[3].toDouble() - ) - } - value.trim().startsWith("rgb") -> { - val values = findValues(value, 3) - RgbaColor( - values[0].toInt(), - values[1].toInt(), - values[2].toInt() - ) - } - else -> { - throw IllegalArgumentException("Unable to parse $value as color") - } - } - - fun fromHex(value: String): RgbaColor { - TODO("Implement parsing hex to rgba") - } - - fun fromHsla(hsla: HslaColor): RgbaColor = fromHsla( - hsla.hue, - hsla.saturation, - hsla.lightness, - hsla.alpha - ) - - fun fromHsl( - hue: Int, - saturation: Int, - lightness: Int - ): RgbaColor { - return fromHsla(hue, saturation, lightness, 1.0) - } - - fun fromHsla( - hue: Int, - saturation: Int, - lightness: Int, - alpha: Double - ): RgbaColor { - val h = hue - val s = saturation / 100.0 - val l = lightness / 100.0 - - val a = s * min(l, 1 - l) - fun f(n: Int): Int { - val k: Int = (n + h / 30) % 12 - return (255 * (l - a * max(min(k - 3, min(9 - k, 1)), -1))).toInt() - } - - return RgbaColor(f(0), f(8), f(4), alpha) - } - } -} diff --git a/src/commonMain/kotlin/nl/astraeus/css/style/Style.kt b/src/commonMain/kotlin/nl/astraeus/css/style/Style.kt index e1bac26..f622e6a 100644 --- a/src/commonMain/kotlin/nl/astraeus/css/style/Style.kt +++ b/src/commonMain/kotlin/nl/astraeus/css/style/Style.kt @@ -30,7 +30,7 @@ private fun prp(vararg css: String): List { } abstract class CssGenerator { - val definitions: MutableMap> = mutableMapOf() + val definitions: MutableMap> = LinkedHashMap() val props: MutableMap> = mutableMapOf() abstract fun getValidator(name: String): List? @@ -123,7 +123,7 @@ abstract class CssGenerator { } } - for (name in definitions.keys.sorted()) { + for (name in definitions.keys) { val props = definitions[name]!! val css = StringBuilder() @@ -140,14 +140,10 @@ abstract class CssGenerator { css.append(finalStyle.generatePropertyCss(" $indent")) if (css.isNotBlank()) { - if (name.startsWith(":")) { - builder.append("\n$namespace$name".trim()) - } else { - builder.append("\n$namespace $name".trim()) - } + builder.append("\n$namespace$name".trim()) - builder.append(" $indent") - builder.append("{\n") + //builder.append(" $indent") + builder.append(" {\n") finalStyle.fontFace?.let { ff -> builder.append(" $indent") @@ -192,7 +188,7 @@ abstract class CssGenerator { builder.append("}\n\n") } - builder.append(finalStyle.generateCss("$namespace $name".trim(), indent)) + builder.append(finalStyle.generateCss("$namespace$name".trim(), indent)) } return if (minified) { @@ -210,6 +206,35 @@ abstract class CssGenerator { } } +interface DescriptionProvider { + fun description(): String +} + +class ValueDescriptionProvider( + val value: String +) : DescriptionProvider { + + override fun description() = value + +} + +fun id(name: String) : DescriptionProvider = ValueDescriptionProvider("#$name") +fun cls(name: String) : DescriptionProvider = ValueDescriptionProvider(".$name") +fun attr(name: String) : DescriptionProvider = ValueDescriptionProvider("[$name]") +fun attrEquals(name: String, value: String) : DescriptionProvider = ValueDescriptionProvider("[$name=$value]") +fun attrContains(name: String, value: String) : DescriptionProvider = ValueDescriptionProvider("[$name*=$value]") +fun attrEntriesContain(name: String, value: String) : DescriptionProvider = ValueDescriptionProvider("[$name~=$value]") +fun attrEndsWith(name: String, value: String) : DescriptionProvider = ValueDescriptionProvider("[$name$=$value]") + +fun id(name: DescriptionProvider) = ValueDescriptionProvider("#${name.description()}") +fun cls(name: DescriptionProvider) : DescriptionProvider = ValueDescriptionProvider(".${name.description()}") +fun attr(name: DescriptionProvider) : DescriptionProvider = ValueDescriptionProvider("[${name.description()}]") +fun attrEquals(name: DescriptionProvider, value: String) : DescriptionProvider = ValueDescriptionProvider("[${name.description()}=$value]") +fun attrContains(name: DescriptionProvider, value: String) : DescriptionProvider = ValueDescriptionProvider("[${name.description()}*=$value]") +fun attrEntriesContain(name: DescriptionProvider, value: String) : DescriptionProvider = ValueDescriptionProvider("[${name.description()}~=$value]") +fun attrEndsWith(name: DescriptionProvider, value: String) : DescriptionProvider = ValueDescriptionProvider("[${name.description()}$=$value]") + + @CssTagMarker open class Style : CssGenerator() { var fontFace: FontFace? = null @@ -238,20 +263,56 @@ open class Style : CssGenerator() { definitions[selector]?.add(style) } - fun select(selector: String, style: Css) { + /** + * like the sccs & + * @param selector blabla + */ + fun and(selector: DescriptionProvider, style: Css) { + addStyle(selector.description(), style) + } + + fun and(selector: String, style: Css) { addStyle(selector, style) } - fun descendant(descName: String, style: Css) { - addStyle(descName, style) + fun select(selector: DescriptionProvider, style: Css) { + addStyle(" ${selector.description()}", style) } - fun cls(className: String, style: Css) { - addStyle(".$className", style) + fun select(selector: String, style: Css) { + addStyle(" $selector", style) + } + + fun descendant(descName: DescriptionProvider, style: Css) { + addStyle(" ${descName.description()}", style) + } + + fun descendant(descName: String, style: Css) { + addStyle(" $descName", style) + } + + fun child(childName: DescriptionProvider, style: Css) { + addStyle(" > ${childName.description()}", style) } fun child(childName: String, style: Css) { - addStyle("> $childName", style) + addStyle(" > $childName", style) + } + + fun sibling(childName: DescriptionProvider, style: Css) { + addStyle(" ~ ${childName.description()}", style) + } + + fun sibling(childName: String, style: Css) { + addStyle(" ~ $childName", style) + } + + fun adjSibling(childName: DescriptionProvider, style: Css) { + addStyle(" + ${childName.description()}", style) + } + + fun adjSibling(childName: String, style: Css) { + addStyle(" + $childName", style) } fun active(style: Css) { @@ -274,6 +335,10 @@ open class Style : CssGenerator() { addStyle(":visited", style) } + fun not(selector: DescriptionProvider, style: Css) { + addStyle(":not(${selector.description()})", style) + } + fun plain(name: String, value: String) { props[name] = prp(value) } @@ -374,7 +439,7 @@ open class Style : CssGenerator() { fun borderTopWidth(width: Measurement) { props["border-top-width"] = prp(width) } fun bottom(measurement: Measurement) { props["bottom"] = prp(measurement) } fun boxDecorationBreak(brk: BoxDecorationBreak) { props["box-decoration-break"] = prp(brk) } - fun boxShaduw(shadow: BoxShadow) { props["box-shaduw"] = prp(shadow) } + fun boxShadow(shadow: BoxShadow) { props["box-shadow"] = prp(shadow) } fun boxSizing(sizing: BoxSizing) { props["box-sizing"] = prp(sizing) } fun breakAfter(brk: Break) { props["break-after"] = prp(brk) } fun breakBefore(brk: Break) { props["break-before"] = prp(brk) } diff --git a/src/commonTest/kotlin/nl/astraeus/css/TestColor.kt b/src/commonTest/kotlin/nl/astraeus/css/TestColor.kt deleted file mode 100644 index 167edb1..0000000 --- a/src/commonTest/kotlin/nl/astraeus/css/TestColor.kt +++ /dev/null @@ -1,71 +0,0 @@ -package nl.astraeus.css - -import nl.astraeus.css.properties.* -import kotlin.test.Test - -class TestColor { - - @Test - fun testBuilder() { - val css = style { - val baseColor = HslaColor(20, 50, 50) - val baseColorRgb = RgbaColor.fromHsla(baseColor) - - println("BaseColor: ${baseColor.value} - ${baseColorRgb.value}") - - val darkerColor = baseColor.darken(10) - val darkerColorRgb = RgbaColor.fromHsla(darkerColor) - println("DarkerColor: ${darkerColor.value} - ${darkerColorRgb.value}") - - val saturizedColor = baseColor.saturize(10) - val saturizedColorRgb = RgbaColor.fromHsla(saturizedColor) - println("SaturizedColor: ${saturizedColor.value} - ${saturizedColorRgb.value}") - - val redColor = RgbaColor(0,255,0,1.0) - val redColorHsla = redColor.toHsla() - val shiftedColor = redColorHsla.adjustHue(120) - val shiftedColorRgb = shiftedColor.toRgba() - - println("RedColor: ${redColor.value} - ${redColorHsla.value}") - println("ShiftedColor: ${shiftedColor.value} - ${shiftedColorRgb.value}") - - select(".test") { - top(10.px) - left(4.em) - - backgroundColor( - RgbaColor.fromHsla(baseColor.darken(10)) - ) - - animationIterationMode( - Count.auto, - Count.auto, - Count.auto, - Count.auto, - Count.auto - ) - - child("li") { - color(Color.hsl(200, 50, 50)) - } - - select("> a") { - color(Color.hsl(200, 50, 50)) - } - } - } - - println(css.generateCss()) - - val css2 = style { - cls("button") { - fontSize(12.px) - color(Color.hsl(200, 50, 50)) - } - } - - println(css2.generateCss()) - - } - -} \ No newline at end of file diff --git a/src/commonTest/kotlin/nl/astraeus/css/TestCssBuilder.kt b/src/commonTest/kotlin/nl/astraeus/css/TestCssBuilder.kt index cabeb53..29b5857 100644 --- a/src/commonTest/kotlin/nl/astraeus/css/TestCssBuilder.kt +++ b/src/commonTest/kotlin/nl/astraeus/css/TestCssBuilder.kt @@ -1,51 +1,151 @@ package nl.astraeus.css -import nl.astraeus.css.properties.Color.Companion.hsl -import nl.astraeus.css.properties.Color.Companion.rgba -import nl.astraeus.css.properties.Count -import nl.astraeus.css.properties.em -import nl.astraeus.css.properties.px +import nl.astraeus.css.properties.* +import nl.astraeus.css.style.attr +import nl.astraeus.css.style.attrEquals +import nl.astraeus.css.style.cls +import nl.astraeus.css.style.id +import kotlin.test.Test -object TestCssBuilder { +class TestCssBuilder { - //@Test - fun testBuilder() { - val css = css { + @Test + fun testBuilder() { + val css = style { - select(".test") { - top(10.px) - left(4.em) - backgroundColor(rgba(255, 255, 255, 0.75)) - animationIterationMode( - Count.auto, - Count.auto, - Count.auto, - Count.auto, - Count.auto - ) + select(".test") { + top(10.px) + left(4.em) + backgroundColor(rgba(255, 255, 255, 0.75)) + animationIterationMode( + Count.auto, + Count.auto, + Count.auto, + Count.auto, + Count.auto + ) + child("li") { + color(hsl(200, 50, 50)) + } + + select("> a") { + color(hsl(200, 50, 50)) + } + + hover { + color(Color.red) + } + + child("li") { + listStyle("none") + + child("ul") { + opacity(0.0) + display(Display.none) + paddingLeft(20.px) + child("li") { + listStyle("none") + + child("ul") { + paddingLeft(30.px) child("li") { - color(hsl(200,50,50)) - } - - select("> a") { - color(hsl(200, 50, 50)) + listStyle("none") } + } } - } + } - println(css) - - val css2 = css { - cls("button") { - fontSize(12.px) - color(hsl(200, 50, 50)) + hover { + child("ul") { + opacity(1.0) + display(Display.block) } + } } - - println(css2) - - + } } + println(css.generateCss()) + } + + @Test + fun testClass() { + val css2 = style { + select(id("my-label")) { + color(Color.antiqueWhite) + } + + select(cls("my-label")) { + color(Color.aliceBlue) + } + + // tr.even {} + select("tr") { + and(cls("even")) { + color(Color.gray) + } + +/* + nthChild(2) { + + } + + // not(bla) { + not("bla") { + + } +*/ + + } + + // table .even {} + select("tr") { + select(cls("even")) { + color(Color.green) + } + + // [type] + select(attr("type")) { + + } + + // [type="checkbox"] + select(attrEquals("type", "checkbox")) { + + } + + // table > .odd + child(cls("odd")) { + + } + + //adjSibling() + } + + select(cls("button")) { + fontSize(12.px) + color(hsl(200, 50, 50)) + + // .button:hover + hover { + color(hsl(200, 40, 40)) + } + + child(".green") { + color(Color.green) + } + + sibling(".red") { + color(Color.red) + } + + adjSibling(".blue") { + color(Color.blue) + } + } + } + + println(css2.generateCss()) + } }