scripts/core/ui_theme.js
/*
THEME REFACTOR:
* Use CSS as much as possible
* Create a compatibility layer
*/
import * as util from "../path-controller/util/util.js";
import {Vector3, Vector4} from '../path-controller/util/vectormath.js';
import nstructjs from "../path-controller/util/struct.js";
import cconst from '../config/const.js';
export let compatMap = {
BoxMargin : "padding",
BoxBG : "background",
BoxRadius : "border-radius",
background : "background-color",
defaultWidth : "width",
defaultHeight : "height",
DefaultWidth : "width",
DefaultHeight : "height",
BoxBorder : "border-color",
BoxLineWidth : "border-width",
BoxSubBG : "background-color",
BoxSub2BG : "background-color",
DefaultPanelBG : "background-color",
InnerPanelBG : "background-color",
Background : "background-color",
numslider_width : "width",
numslider_height : "height",
};
export let ColorSchemeTypes = {
LIGHT : "light",
DARK : "dark"
};
export function parsepx(css) {
return parseFloat(css.trim().replace("px", ""))
}
export function color2css(c, alpha_override) {
let r = ~~(c[0]*255);
let g = ~~(c[1]*255);
let b = ~~(c[2]*255);
let a = c.length < 4 ? 1.0 : c[3];
a = alpha_override !== undefined ? alpha_override : a;
if (c.length === 3 && alpha_override === undefined) {
return `rgb(${r},${g},${b})`;
} else {
return `rgba(${r},${g},${b}, ${a})`;
}
}
window.color2css = color2css;
let css2color_rets = util.cachering.fromConstructor(Vector4, 64);
let basic_colors = {
'white' : [1,1,1],
'grey' : [0.5, 0.5, 0.5],
'gray' : [0.5, 0.5, 0.5],
'black' : [0, 0, 0],
'red' : [1, 0, 0],
'yellow' : [1, 1, 0],
'green' : [0, 1, 0],
'teal' : [0, 1, 1],
'cyan' : [0, 1, 1],
'blue' : [0, 0, 1],
'orange' : [1, 0.5, 0.25],
'brown' : [0.5, 0.4, 0.3],
'purple' : [1, 0, 1],
'pink' : [1, 0.5, 0.5]
};
export function color2web(color) {
function tostr(n) {
n = ~~(n*255);
let s = n.toString(16);
if (s.length > 2) {
s = s.slice(0, 2);
}
while (s.length < 2) {
s = "0" + s;
}
return s;
}
if (color.length === 3 || color[3] === 1.0) {
let r = tostr(color[0]);
let g = tostr(color[1]);
let b = tostr(color[2]);
return "#" + r + g + b;
} else {
let r = tostr(color[0]);
let g = tostr(color[1]);
let b = tostr(color[2]);
let a = tostr(color[3]);
return "#" + r + g + b + a;
}
}
window.color2web = color2web;
export function css2color(color) {
if (!color) {
return new Vector4([0,0,0,1]);
}
color = (""+color).trim();
let ret = css2color_rets.next();
if (color[0] === "#") {
color = color.slice(1, color.length);
let parts = [];
for (let i=0; i<color.length>>1; i++) {
let part = "0x" + color.slice(i*2, i*2+2);
parts.push(parseInt(part));
}
ret.zero();
let i;
for (i=0; i<Math.min(parts.length, ret.length); i++) {
ret[i] = parts[i] / 255.0;
}
if (i < 4) {
ret[3] = 1.0;
}
return ret;
}
if (color in basic_colors) {
ret.load(basic_colors[color]);
ret[3] = 1.0;
return ret;
}
color = color.replace("rgba", "").replace("rgb", "").replace(/[\(\)]/g, "").trim().split(",")
for (let i=0; i<color.length; i++) {
ret[i] = parseFloat(color[i]);
if (i < 3) {
ret[i] /= 255;
}
}
if (color.length === 3) {
color.push(1.0);
}
return ret;
}
window.css2color = css2color
export function web2color(str) {
if (typeof str === "string" && str.trim()[0] !== "#") {
str = "#" + str.trim();
}
return css2color(str);
}
window.web2color = web2color;
let validate_pat = /\#?[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$/;
export function validateWebColor(str) {
if (typeof str !== "string" && !(str instanceof String))
return false;
return str.trim().search(validate_pat) === 0;
}
let num = "(([0-9]+\.[0-9]+)|[0-9a-f]+)";
let validate_rgba = new RegExp(`rgba\\(${num},${num},${num},${num}\\)$`);
let validate_rgb = new RegExp(`rgb\\(${num},${num},${num}\\)$`);
export function validateCSSColor(color) {
if (color.toLowerCase() in basic_colors) {
return true;
}
let rgba = color.toLowerCase().replace(/[ \t]/g, "");
rgba = rgba.trim();
if (validate_rgba.test(rgba) || validate_rgb.exec(rgba)) {
return true;
}
return validateWebColor(color);
}
window.validateCSSColor = validateCSSColor;
export let theme = {};
export function invertTheme() {
cconst.colorSchemeType = cconst.colorSchemeType === ColorSchemeTypes.LIGHT ? ColorSchemeTypes.DARK : ColorSchemeTypes.LIGHT;
function inverted(color) {
if (Array.isArray(color)) {
for (let i = 0; i < 3; i++) {
color[i] = 1.0 - color[i];
}
return color;
}
color = css2color(color);
return color2css(inverted(color));
}
let bg = document.body.style["background-color"];
//if (!bg) {
bg = cconst.colorSchemeType === ColorSchemeTypes.LIGHT ? "rgb(200,200,200)" : "rgb(55, 55, 55)";
//} else {
// bg = inverted(bg);
//}
document.body.style["background-color"] = bg;
for (let style in theme) {
style = theme[style];
for (let k in style) {
let v = style[k];
if (v instanceof CSSFont) {
v.color = inverted(v.color);
} else if (typeof v === "string") {
v = v.trim().toLowerCase();
let iscolor = v.search("rgb") >= 0;
iscolor = iscolor || v in basic_colors;
iscolor = iscolor || validateWebColor(v);
if (iscolor) {
style[k] = inverted(v);
}
}
}
}
}
window.invertTheme = invertTheme;
export function setColorSchemeType(mode) {
if (!!mode !== cconst.colorSchemeType) {
invertTheme();
cconst.colorSchemeType = mode;
}
}
window.validateWebColor = validateWebColor;
let _digest = new util.HashDigest();
export class CSSFont {
constructor(args={}) {
this._size = args.size ? args.size : 12;
this.font = args.font;
this.style = args.style !== undefined ? args.style : "normal";
this.weight = args.weight !== undefined ? args.weight : "normal";
this.variant = args.variant !== undefined ? args.variant : "normal";
this.color = args.color;
}
calcHashUpdate(digest=_digest.reset()) {
digest.add(this._size || 0);
digest.add(this.font);
digest.add(this.style);
digest.add(this.weight);
digest.add(this.variant);
digest.add(this.color);
return digest.get();
}
set size(val) {
this._size = val;
}
get size() {
if (util.isMobile()) {
let mul = theme.base.mobileTextSizeMultiplier / visualViewport.scale;
if (mul) {
return this._size * mul;;
}
}
return this._size;
}
copyTo(b) {
b._size = this._size;
b.font = this.font;
b.style = this.style;
b.color = this.color;
b.variant = this.variant;
b.weight = this.weight;
}
copy() {
let ret = new CSSFont();
this.copyTo(ret);
return ret;
}
genCSS(size=this.size) {
return `${this.style} ${this.variant} ${this.weight} ${size}px ${this.font}`;
}
//deprecated, use genKey()
hash() {
return this.genKey();
}
genKey() {
let color = this.color;
if (typeof this.color === "object" || typeof this.color === "function") {
color = JSON.stringify(color);
}
return this.genCSS() + ":" + this.size + ":" + color;
}
}
CSSFont.STRUCT = `
CSSFont {
size : float | obj._size;
font : string | obj.font || "";
style : string | obj.font || "";
color : string | ""+obj.color;
variant : string | obj.variant || "";
weight : string | ""+obj.weight;
}
`;
nstructjs.register(CSSFont);
export function exportTheme(theme1=theme, addVarDecl=true) {
let sortkeys = (obj) => {
let keys = [];
for (let k in obj) {
keys.push(k);
}
keys.sort();
return keys;
}
let s = addVarDecl ? "var theme = {\n" : "{\n";
function writekey(v, indent="") {
if (typeof v === "string") {
if (v.search("\n") >= 0) {
v = "`" + v + "`";
} else {
v = "'" + v + "'";
}
return v;
} else if (typeof v === "object") {
if (v instanceof CSSFont) {
return `new CSSFont({
${indent} font : ${writekey(v.font)},
${indent} weight : ${writekey(v.weight)},
${indent} variant : ${writekey(v.variant)},
${indent} style : ${writekey(v.style)},
${indent} size : ${writekey(v._size)},
${indent} color : ${writekey(v.color)}
${indent}})`;
} else {
let s = "{\n";
for (let k of sortkeys(v)) {
let v2 = v[k];
if (k.search(" ") >= 0 || k.search("-") >= 0) {
k = "'" + k + "'";
}
s += indent + " " + k + " : " + writekey(v2, indent + " ") + ",\n";
}
s += indent + "}";
return s;
}
} else {
return ""+v;
}
return "error";
}
for (let k of sortkeys(theme1)) {
let k2 = k;
if (k.search("-") >= 0 || k.search(" ") >= 0) {
k2 = "'" + k + "'";
}
s += " " + k2 + ": ";
let v = theme1[k];
if (typeof v !== "object" || v instanceof CSSFont) {
s += writekey(v, " ") + ",\n";
} else {
s += " {\n"
let s2 = "";
let maxwid = 0;
for (let k2 of sortkeys(v)) {
if (k2.search("-") >= 0 || k2.search(" ") >= 0) {
k2 = "'" + k2 + "'";
}
maxwid = Math.max(maxwid, k2.length);
}
for (let k2 of sortkeys(v)) {
let v2 = v[k2];
if (k2.search("-") >= 0 || k2.search(" ") >= 0) {
k2 = "'" + k2 + "'";
}
let pad = "";
for (let i=0; i<maxwid-k2.length; i++) {
pad += " ";
}
s2 += " " + k2 + pad + ": " + writekey(v2, " ") + ",\n";
}
s += s2;
s += " },\n\n"
}
}
s += "};\n";
return s;
}
window._exportTheme = exportTheme;