This is an automated email from the ASF dual-hosted git repository.
gregdove pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/royale-asjs.git
The following commit(s) were added to refs/heads/develop by this push:
new 0c9dda8920 speculative changes for color theming, needs more work.
0c9dda8920 is described below
commit 0c9dda892008dbf092f8eaa91059c059e1d3d827
Author: greg-dove <[email protected]>
AuthorDate: Mon Mar 23 21:59:02 2026 +1300
speculative changes for color theming, needs more work.
---
.../org/apache/royale/style/colors/ColorSwatch.as | 162 ++++++++++++++++++---
.../apache/royale/style/colors/ThemeColorSet.as | 144 ++++++++++++++++++
.../org/apache/royale/style/skins/CheckBoxSkin.as | 50 +++++--
.../org/apache/royale/style/util/StyleTheme.as | 27 +++-
4 files changed, 346 insertions(+), 37 deletions(-)
diff --git
a/frameworks/projects/Style/src/main/royale/org/apache/royale/style/colors/ColorSwatch.as
b/frameworks/projects/Style/src/main/royale/org/apache/royale/style/colors/ColorSwatch.as
index 2ccb89963d..bbb541a03d 100644
---
a/frameworks/projects/Style/src/main/royale/org/apache/royale/style/colors/ColorSwatch.as
+++
b/frameworks/projects/Style/src/main/royale/org/apache/royale/style/colors/ColorSwatch.as
@@ -54,32 +54,32 @@ package org.apache.royale.style.colors
// Tailwind 500 base colors keyed by Tailwind swatch name.
private static const BASE_COLORS:Object = {
- "red": 0xFB2C36,
- "orange": 0xFF6900,
"amber": 0xFE9A00,
- "yellow": 0xF0B100,
- "lime": 0x7CCF00,
- "green": 0x00C950,
- "emerald": 0x00BC7D,
- "teal": 0x00BBA7,
- "cyan": 0x00B8DB,
- "sky": 0x00A6F4,
"blue": 0x2B7FFF,
- "indigo": 0x615FFF,
- "violet": 0x8E51FF,
- "purple": 0xAD46FF,
+ "cyan": 0x00B8DB,
+ "emerald": 0x00BC7D,
"fuchsia": 0xE12AFB,
+ "gray": 0x6A7282,
+ "green": 0x00C950,
+ "indigo": 0x615FFF,
+ "lime": 0x7CCF00,
+ "mauve": 0x79697B,
+ "mist": 0x67787C,
+ "neutral": 0x737373,
+ "olive": 0x7C7C67,
+ "orange": 0xFF6900,
"pink": 0xF6339A,
+ "purple": 0xAD46FF,
+ "red": 0xFB2C36,
"rose": 0xFF2056,
+ "sky": 0x00A6F4,
"slate": 0x62748E,
- "gray": 0x6A7282,
- "zinc": 0x71717B,
- "neutral": 0x737373,
"stone": 0x79716B,
"taupe": 0x7C6D67,
- "mauve": 0x79697B,
- "mist": 0x67787C,
- "olive": 0x7C7C67
+ "teal": 0x00BBA7,
+ "violet": 0x8E51FF,
+ "yellow": 0xF0B100,
+ "zinc": 0x71717B
};
/**
@@ -89,9 +89,20 @@ package org.apache.royale.style.colors
{
var base:Object = BASE_COLORS[swatch] ||
CSSLookup.getProperty(swatch);
assert(base, "Invalid color swatch: " + swatch);
+
+ var baseColor:uint = CSSUtils.toColor(base);
+ var rgbComponents:Array= [(baseColor &
0xff0000)>>16,(baseColor & 0xff00)>>8,(baseColor & 0xff)];
+ var lch:Array =
rgbToOKLCH(rgbComponents[0],rgbComponents[1],rgbComponents[2]);
+
// Convert from 50,100,200... to 5,10,20... for easier
math.
- shade = Math.round(shade/10);
- var colorVals:Array =
CSSColor.getVariation(CSSUtils.toColor(base),shade);
+
+
+
+ // shade = Math.round(shade/10);
+ // var colorVals:Array =
CSSColor.getVariation(CSSUtils.toColor(base),shade);
+
+ var colorVals:Array =
lchShade(lch,factorForShade(shade));
+ colorSpace = 'oklch';
assert(opacity >= 0 && opacity <= 100, "Opacity must be
between 0 and 100");
colorBase = swatch;
@@ -103,7 +114,8 @@ package org.apache.royale.style.colors
colorSpecifier += "/" + opacity;
}
colorValue = CSSColor.getColor(colorVals, opacity,
colorSpace);
- CSSLookup.register(colorSpecifier,colorValue);
+
+ CSSLookup.register (colorSpecifier,colorValue);
}
public var colorBase:String;
public var colorShade:Number;
@@ -111,6 +123,26 @@ package org.apache.royale.style.colors
public var colorValue:String;
public var colorSpace:String = "rgb";
public var colorSpecifier:String;
+
+
+ /**
+ * create a ColorSwatch variant from this instance
+ * @param alternateShade - alternate shade. If you want to keep
the same shade and adjust opacity, set this to NaN
+ * @param alternateOpacity - if not set it will inherit the
original value from this instance
+ * @return a new ColorSwatch with different shade or opacity
(or both)
+ */
+ public function getVariant(alternateShade:Number,
alternateOpacity:Number = NaN):ColorSwatch{
+ if (isNaN(alternateShade)) alternateShade = colorShade;
+ if (isNaN(alternateOpacity)) alternateOpacity =
colorOpacity;
+ var alternate:ColorSwatch = new
ColorSwatch(colorBase,alternateShade,alternateOpacity);
+ assert(alternate.colorShade != colorShade ||
alternate.colorOpacity != colorOpacity, "parameters not configured to create a
variant");
+ return alternate;
+ }
+
+ public function toString():String{
+ return colorSpecifier;
+ }
+
public static function
fromSpecifier(specifier:String):ColorSwatch
{
var parts:Array = specifier.split("-");
@@ -122,5 +154,93 @@ package org.apache.royale.style.colors
var opacity:Number = shadeParts.length > 1 ?
Number(shadeParts[1]) : 100;
return new ColorSwatch(base, shade, opacity);
}
+
+ public static function rgbToOKLCH(r:int, g:int, b:int):Array {
+ // Normalize
+ var R:Number = r / 255;
+ var G:Number = g / 255;
+ var B:Number = b / 255;
+
+ // Convert to linear
+ R = srgbToLinear(R);
+ G = srgbToLinear(G);
+ B = srgbToLinear(B);
+
+ // Convert to OKLab
+ var l:Number = 0.4122214708 * R + 0.5363325363 * G +
0.0514459929 * B;
+ var m:Number = 0.2119034982 * R + 0.6806995451 * G +
0.1073969566 * B;
+ var s:Number = 0.0883024619 * R + 0.2817188376 * G +
0.6299787005 * B;
+
+ var l_:Number = Math['cbrt'](l);
+ var m_2:Number = Math['cbrt'](m);
+ var s_2:Number = Math['cbrt'](s);
+
+ var L:Number = 0.2104542553 * l_ + 0.7936177850 * m_2 -
0.0040720468 * s_2;
+ var a:Number = 1.9779984951 * l_ - 2.4285922050 * m_2 +
0.4505937099 * s_2;
+ var b2:Number = 0.0259040371 * l_ + 0.7827717662 * m_2
- 0.8086757660 * s_2;
+
+ var C:Number = Math.sqrt(a * a + b2 * b2);
+ var H:Number = (Math.atan2(b2, a) * 180 / Math.PI +
360) % 360;
+
+ return [L, C, H];
+ }
+ private static function srgbToLinear(x:Number):Number {
+ return (x <= 0.04045) ? x / 12.92 : Math.pow((x +
0.055) / 1.055, 2.4);
+ }
+
+ private static const factors:Object = {
+ 50: 1.60,
+ 100: 1.45,
+ 200: 1.30,
+ 300: 1.15,
+ 400: 1.05,
+ 500: 1.00,
+ 600: 0.90,
+ 700: 0.75,
+ 800: 0.60,
+ 900: 0.45
+ }
+ public static function lchShade(base:Array,
factor:Number):Array {
+ return [
+ base[0] * factor, //
adjust lightness
+ base[1] * (0.5 + factor / 2), //
adjust chroma
+ base[2] //
keep hue constant
+ ];
+ }
+ private static const SHADE_KEYS:Array =
[50,100,200,300,400,500,600,700,800,900];
+
+ public static function factorForShade(shade:int):Number {
+
+ // clamp to valid range
+ if (shade <= 50) return factors[50];
+ if (shade >= 900) return factors[900];
+
+ // exact match
+ if (factors[shade] != null)
+ return factors[shade];
+
+ // find neighbors
+ var lower:int = 50;
+ var upper:int = 900;
+
+ for (var i:int = 0; i < SHADE_KEYS.length - 1; i++) {
+ var a:int = SHADE_KEYS[i];
+ var b:int = SHADE_KEYS[i+1];
+
+ if (shade > a && shade < b) {
+ lower = a;
+ upper = b;
+ break;
+ }
+ }
+
+ var f1:Number = factors[lower];
+ var f2:Number = factors[upper];
+
+ var t:Number = (shade - lower) / (upper - lower);
+
+ return f1 + t * (f2 - f1);
+ }
+
}
}
\ No newline at end of file
diff --git
a/frameworks/projects/Style/src/main/royale/org/apache/royale/style/colors/ThemeColorSet.as
b/frameworks/projects/Style/src/main/royale/org/apache/royale/style/colors/ThemeColorSet.as
new file mode 100644
index 0000000000..d605c8d2c0
--- /dev/null
+++
b/frameworks/projects/Style/src/main/royale/org/apache/royale/style/colors/ThemeColorSet.as
@@ -0,0 +1,144 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+package org.apache.royale.style.colors
+{
+ import org.apache.royale.style.util.CSSLookup;
+ import org.apache.royale.utils.CSSUtils;
+
+ import org.apache.royale.debugging.assert;
+
+ public class ThemeColorSet
+ {
+
+ public static const BASE:String = 'base';
+ public static const PRIMARY:String = 'primary';
+ public static const SECONDARY:String = 'secondary';
+ public static const ACCENT:String = 'accent';
+ public static const INFO:String = 'info';
+ public static const SUCCESS:String = 'success';
+ public static const WARNING:String = 'warning';
+ public static const ERROR:String = 'error';
+ public static const NEUTRAL:String = 'neutral';
+
+ public static const BASE_CONTENT:String = "base-content";
+ public static const PRIMARY_CONTENT:String =
"primary-content";
+ public static const SECONDARY_CONTENT:String =
"secondary-content";
+ public static const ACCENT_CONTENT:String =
"accent-content";
+ public static const INFO_CONTENT:String = "info-content";
+ public static const SUCCESS_CONTENT:String =
"success-content";
+ public static const WARNING_CONTENT:String =
"warning-content";
+ public static const ERROR_CONTENT:String =
"error-content";
+ public static const NEUTRAL_CONTENT:String =
"neutral-content";
+
+
+ private static const _fieldNames:Array = [
+ BASE,
+ PRIMARY,
+ SECONDARY,
+ ACCENT,
+ INFO,
+ SUCCESS,
+ WARNING,
+ ERROR,
+ NEUTRAL,
+ BASE_CONTENT,
+ PRIMARY_CONTENT,
+ SECONDARY_CONTENT,
+ ACCENT_CONTENT,
+ NEUTRAL_CONTENT,
+ INFO_CONTENT,
+ SUCCESS_CONTENT,
+ WARNING_CONTENT,
+ ERROR_CONTENT
+ ]
+
+ public static function get validFieldNames():Array{
+ return _fieldNames.slice();
+ }
+
+ COMPILE::JS
+ private const storage:Map = new Map();
+
+ /**
+ * Subclasses must specify colorBase before calling this
constructor.
+ */
+ public function ThemeColorSet(config:Object = null)
+ {
+ if (config) fromJSON(config);
+ }
+
+ public function setThemeColor(key:String, value:String):void{
+ assert(_fieldNames.indexOf(key) != -1, 'unknown key
"'+key+'" - must be one of :"'+_fieldNames.join('","')+'"')
+ if (value) {
+ // do we need to validate value?
+ const exceptions:Array = [
+ "transparent",
+ "currentColor",
+ "inherit",
+ "none",
+ "black",
+ "white"
+ ]
+ var valueToSet:Object =
exceptions.indexOf(value) == -1 ? ColorSwatch.fromSpecifier(value) : value;
+ COMPILE::JS {
+ storage.set(key,valueToSet);
+ }
+ } else {
+ COMPILE::JS {
+ if (storage.has(key))
storage.delete(key);
+ }
+ }
+ }
+
+ public function getThemeColorSwatch(key:String):ColorSwatch{
+ assert(_fieldNames.indexOf(key) != -1, 'unknown key
"'+key+'" - must be one of :"'+_fieldNames.join('","')+'"');
+ COMPILE::JS{
+ //Q: should there always be a neutral default
if no lookup is registered for a specific set?
+ if (!storage.has(key)) {
+ //do something?
+
storage.set(key,ColorSwatch.fromSpecifier(ColorSwatch.NEUTRAL+'-500'/*, key*/));
+ }
+ return storage.get(key)
+ }
+ COMPILE::SWF{
+ return null;
+ }
+ }
+
+ public function fromJSON(obj:Object):void{
+ if (typeof obj == 'string') obj = JSON.parse(obj as
String);
+ for (var key:String in obj) {
+ setThemeColor(key,obj[key]);
+ }
+ }
+
+ public function toJSON():Object{
+ const obj:Object = {};
+ COMPILE::JS{
+ var keys:Array = Object.keys(storage);
+ for each(var key:String in keys) {
+ var swatch:ColorSwatch =
storage.get(key);
+ obj[key] = swatch.colorSpecifier;
+ }
+ }
+ return obj;
+ }
+
+ }
+}
\ No newline at end of file
diff --git
a/frameworks/projects/Style/src/main/royale/org/apache/royale/style/skins/CheckBoxSkin.as
b/frameworks/projects/Style/src/main/royale/org/apache/royale/style/skins/CheckBoxSkin.as
index 2efee467f1..f025a538c2 100644
---
a/frameworks/projects/Style/src/main/royale/org/apache/royale/style/skins/CheckBoxSkin.as
+++
b/frameworks/projects/Style/src/main/royale/org/apache/royale/style/skins/CheckBoxSkin.as
@@ -24,6 +24,8 @@ package org.apache.royale.style.skins
import org.apache.royale.style.IStyleUIBase;
import org.apache.royale.core.IStrand;
import org.apache.royale.style.CheckBox;
+ import org.apache.royale.style.colors.ColorSwatch;
+ import org.apache.royale.style.colors.ThemeColorSet;
import org.apache.royale.style.stylebeads.layout.Display;
import org.apache.royale.style.stylebeads.interact.Cursor;
import org.apache.royale.style.stylebeads.flexgrid.GridAutoColumns;
@@ -85,6 +87,7 @@ package org.apache.royale.style.skins
var gap:String = computeSize(size * 0.75, host.unit);
var disabledStyle:DisabledState = new DisabledState();
disabledStyle.styles = [
+ //@todo: observed that this disabled style
seems not to be working ('pointer' stays active):
new Cursor("auto")
];
_styles = [
@@ -126,12 +129,19 @@ package org.apache.royale.style.skins
}
private function createBoxStyles():void
{
+ var colorSet:ThemeColorSet =
ThemeManager.instance.activeTheme.themeColorSet;
+ var primaryColor:ColorSwatch =
colorSet.getThemeColorSwatch(ThemeColorSet.PRIMARY);
+ var enabledBorder:ColorSwatch =
colorSet.getThemeColorSwatch(ThemeColorSet.NEUTRAL);
+ var disabledBorder:ColorSwatch =
colorSet.getThemeColorSwatch(ThemeColorSet.NEUTRAL).getVariant(300);
+ var disabledFillColor:ColorSwatch =
colorSet.getThemeColorSwatch(ThemeColorSet.NEUTRAL).getVariant(100);
+
var size:Number = 16 * getMultiplier();
var box:String = computeSize(size * 1.25, host.unit);
var outline:Outline = new Outline();
outline.width = 2;
- outline.color = "orange-500/40";
+ outline.color =
primaryColor.getVariant(NaN,40).colorSpecifier;
outline.offset = 2;
+
_boxStyles = [
new GridColumnStart("1"),
new GridRowStart("1"),
@@ -139,21 +149,21 @@ package org.apache.royale.style.skins
new WidthStyle(box),
new
BorderRadius(ThemeManager.instance.activeTheme.radiusSM),
new BorderWidth(2),
- new BorderColor("slate-500"),
+ new BorderColor(enabledBorder),
new Transition(),
new PeerPseudo([
new FocusVisibleState([outline]),
new CheckedState([
- new BorderColor("orange-500"),
- new
BackgroundColor("orange-500")
+ new BorderColor(primaryColor),
+ new
BackgroundColor(primaryColor)
]),
new IndeterminateState([
- new BorderColor("orange-500"),
- new
BackgroundColor("orange-500")
+ new BorderColor(primaryColor),
+ new
BackgroundColor(primaryColor)
]),
new DisabledState([
- new BorderColor("slate-300"),
- new BackgroundColor("slate-100")
+ new BorderColor(disabledBorder),
+ new
BackgroundColor(disabledFillColor)
])
])
];
@@ -175,16 +185,19 @@ package org.apache.royale.style.skins
{
var size:Number = 16 * getMultiplier();
var fontSize:String = computeSize(size, host.unit);
-
+ var colorSet:ThemeColorSet =
ThemeManager.instance.activeTheme.themeColorSet;
+ var enabledColor:ColorSwatch =
colorSet.getThemeColorSwatch(ThemeColorSet.BASE_CONTENT);
+ var disabledColor:ColorSwatch =
colorSet.getThemeColorSwatch(ThemeColorSet.NEUTRAL_CONTENT).getVariant(NaN, 60);
+
_labelStyles = [
new GridColumnStart("2"),
new GridRowStart("1"),
new FontSize(fontSize),
new FontWeight("600"),
- new TextColor("slate-800"),
+ new TextColor(enabledColor),
new PeerPseudo([
new DisabledState([
- new TextColor("slate-400")
+ new TextColor(disabledColor)
])
])
];
@@ -202,6 +215,10 @@ package org.apache.royale.style.skins
public function get checkIcon():IStyleUIBase
{
if(!_checkIcon){
+ var colorSet:ThemeColorSet =
ThemeManager.instance.activeTheme.themeColorSet;
+ var enabledColor:ColorSwatch =
colorSet.getThemeColorSwatch(ThemeColorSet.PRIMARY_CONTENT);
+ var disabledColor:ColorSwatch =
colorSet.getThemeColorSwatch(ThemeColorSet.NEUTRAL_CONTENT).getVariant(600, 40);
+
_checkIcon = new Div();
var size:Number = 16 * getMultiplier();
var transform:Transform = new Transform();
@@ -219,7 +236,7 @@ package org.apache.royale.style.skins
new PlaceSelf("center"),
transform,
borderWidth,
- new BorderColor("white"),
+ new BorderColor(enabledColor),
new Transition(),
new OpacityStyle(0),
new PeerPseudo([
@@ -230,7 +247,7 @@ package org.apache.royale.style.skins
new OpacityStyle(0)
]),
new DisabledState([
- new
BorderColor("slate-300"),
+ new
BorderColor(disabledColor)
])
])
];
@@ -252,6 +269,9 @@ package org.apache.royale.style.skins
public function get indeterminateIcon():IStyleUIBase
{
if(!_indeterminateIcon){
+ var colorSet:ThemeColorSet =
ThemeManager.instance.activeTheme.themeColorSet;
+ var enabledColor:ColorSwatch =
colorSet.getThemeColorSwatch(ThemeColorSet.PRIMARY_CONTENT);
+ var disabledColor:ColorSwatch =
colorSet.getThemeColorSwatch(ThemeColorSet.NEUTRAL_CONTENT).getVariant(600, 40);
_indeterminateIcon = new Div();
var size:Number = 16 * getMultiplier();
@@ -262,7 +282,7 @@ package org.apache.royale.style.skins
new WidthStyle(computeSize(size *
0.625, host.unit)),
new PlaceSelf("center"),
new
BorderRadius(ThemeManager.instance.activeTheme.radiusSM),
- new BackgroundColor("white"),
+ new BackgroundColor(enabledColor),
new Transition(),
new OpacityStyle(0),
new PeerPseudo([
@@ -270,7 +290,7 @@ package org.apache.royale.style.skins
new OpacityStyle(100)
]),
new DisabledState([
- new
BackgroundColor("slate-300")
+ new
BackgroundColor(disabledColor)
])
])
];
diff --git
a/frameworks/projects/Style/src/main/royale/org/apache/royale/style/util/StyleTheme.as
b/frameworks/projects/Style/src/main/royale/org/apache/royale/style/util/StyleTheme.as
index 13f18c885a..03ee2bc44d 100644
---
a/frameworks/projects/Style/src/main/royale/org/apache/royale/style/util/StyleTheme.as
+++
b/frameworks/projects/Style/src/main/royale/org/apache/royale/style/util/StyleTheme.as
@@ -18,7 +18,9 @@
////////////////////////////////////////////////////////////////////////////////
package org.apache.royale.style.util
{
-
+ import org.apache.royale.style.colors.ThemeColorSet;
+ import org.apache.royale.style.const.Theme;
+
/**
* @royalesuppressexport
*/
@@ -29,6 +31,29 @@ package org.apache.royale.style.util
themeName = name;
}
public var themeName:String = "default";
+
+
+ public var themeColorSet:ThemeColorSet = new ThemeColorSet({
+ 'primary': 'indigo-500',
+ "primary-content": "indigo-50",
+ 'secondary': 'rose-500',
+ "secondary-content": "rose-50",
+ 'accent': 'teal-400',
+ "accent-content": "teal-800",
+ 'neutral': 'neutral-200', // light neutral
surface
+ "neutral-content": "neutral-800", // readable dark
text/icon on neutral
+ 'info': 'sky-400',
+ "info-content": "blue-800",
+ 'success': 'emerald-400',
+ "success-content": "emerald-800",
+ 'warning': 'amber-500',
+ "warning-content": "amber-800",
+ 'error': 'red-500',
+ "error-content": "red-800",
+ "base": "slate-50",
+ "base-content": "slate-900"
+
+ });
public var spacing:Number = 4;
public var breakpointSM:String = "40rem";