You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
376 lines
11 KiB
JavaScript
376 lines
11 KiB
JavaScript
function ReRGB({colorObj, alpha, cb}) {
|
|
function onChange() {
|
|
const {R,G,B,A} = this.value;
|
|
cb(Color.RGB(R,G,B,A ?? 255));
|
|
return true;
|
|
}
|
|
return <form onchange={onChange} alpha={alpha}>
|
|
<input|integer(R) uwp min="0" max="255" value={colorObj.r * 255}/>
|
|
<input|integer(G) uwp min="0" max="255" value={colorObj.g * 255}/>
|
|
<input|integer(B) uwp min="0" max="255" value={colorObj.b * 255}/>
|
|
{alpha && <input|integer(A) uwp min="0" max="255" value={colorObj.a * 255}/>}
|
|
<span>R</span>
|
|
<span>G</span>
|
|
<span>B</span>
|
|
{alpha && <span>A</span>}
|
|
</form>
|
|
}
|
|
|
|
function ReHSV({colorObj, alpha, cb}) {
|
|
function onChange(e) {
|
|
const {H,S,V,A} = this.value;
|
|
cb(Color.hsv(H,S/100,V/100,(A ?? 100)/100));
|
|
return true;
|
|
}
|
|
return <form onchange={onChange} alpha={alpha}>
|
|
<input|integer(H) uwp min="0" max="360" value={colorObj.h}/>
|
|
<input|integer(S) uwp min="0" max="100" value={colorObj.s * 100}/>
|
|
<input|integer(V) uwp min="0" max="100" value={colorObj.v * 100}/>
|
|
{ alpha && <input|integer(A) uwp min="0" max="100" value={colorObj.a * 100}/> }
|
|
<span>H°</span>
|
|
<span>S%</span>
|
|
<span>V%</span>
|
|
{ alpha && <span>A%</span> }
|
|
</form>
|
|
}
|
|
|
|
class Rep extends Element {
|
|
format;
|
|
alpha;
|
|
color;
|
|
cb;
|
|
|
|
this({format, alpha, colorObj, colors, cb}) {
|
|
this.format = format;
|
|
this.colorObj = colorObj;
|
|
this.colors = colors;
|
|
this.alpha = alpha;
|
|
this.cb = cb;
|
|
}
|
|
|
|
render() {
|
|
let rep;
|
|
switch(this.format) {
|
|
default:
|
|
case 0: rep = <ReRGB key="RGB" colorObj={this.colorObj} alpha={this.alpha} cb={this.cb} />; break;
|
|
case 1: rep = <ReHSV key="HSV" colorObj={this.colorObj} alpha={this.alpha} cb={this.cb} />; break;
|
|
}
|
|
return <div.rep>{this.colors && (this.colors[0] === "default" || this.colors.length > 1) ? <icon|matrix #colors /> : []}{rep}<icon|swap #formats /></div>;
|
|
}
|
|
}
|
|
|
|
const gitColors = ["#FFB900","#FF8C00","#F7630C","#CA5010","#DA3B01","#EF6950","#FF4343","#D13438","#E74856","#E81123","#EA005E","#C30052",
|
|
"#E3008C","#BF0077","#C239B3","#9A0089","#881798","#B146C2","#8764B8","#744DA9","#8E8CD8","#6B69D6","#0078D7","#0063B1",
|
|
"#2D7D9A","#0099BC","#00B7C3","#038387","#018574","#00B294","#00CC6A","#10893E","#107C10","#498205","#567C73","#486860"];
|
|
|
|
class PredefinedColors extends Element {
|
|
format;
|
|
alpha;
|
|
color;
|
|
cb;
|
|
|
|
this({color, colors}) {
|
|
this.color = color;
|
|
this.colors = colors;
|
|
}
|
|
|
|
render() {
|
|
if (!this.colors || this.colors.length == 0) return [];
|
|
if (!Array.isArray(this.colors) || this.colors[0] === "default") this.colors = gitColors;
|
|
return <div.predefined>{this.colors.map(c => <span class={"color " + (this.color.valueOf() == new Color(c).valueOf() ? "current" : "")} title={c} style=`background-color:${c}` />)}</div>;
|
|
}
|
|
}
|
|
|
|
export class ColorSelector extends Element {
|
|
r = 0; g = 0; b = 0; a = 1;
|
|
h = 0; s = 0; v = 0;
|
|
static format = 0;
|
|
hasAlpha = false;
|
|
colors = false;
|
|
extra;
|
|
|
|
divValueLayer;
|
|
divSaturationLayer;
|
|
divCurrentColor;
|
|
sliderH;
|
|
sliderA = null;
|
|
divRep;
|
|
divSliders;
|
|
predefinedColors;
|
|
|
|
constructor(props,kids) {
|
|
super();
|
|
this.hasAlpha = (props?.alpha ?? false) !== false;
|
|
this.colors = props?.colors;
|
|
this.extra = kids?.[0];
|
|
this.showPredefined = false;
|
|
}
|
|
|
|
this({value}) {
|
|
this.color = value;
|
|
}
|
|
|
|
get color() {
|
|
return Color.rgb(this.r, this.g, this.b, this.a);
|
|
}
|
|
|
|
get colorObj() {
|
|
return {
|
|
"r": this.r,
|
|
"g": this.g,
|
|
"b": this.b,
|
|
"h": this.h,
|
|
"s": this.s,
|
|
"v": this.v,
|
|
"a": this.a
|
|
};
|
|
}
|
|
set color(v) {
|
|
if (!v) return;
|
|
this.r = v.r;
|
|
this.g = v.g;
|
|
this.b = v.b;
|
|
[ this.h, this.s, this.v ] = v.hsv;
|
|
this.a = this.hasAlpha ? v.a : 1.0;
|
|
}
|
|
|
|
HSVtoRGB() {
|
|
let clr = Color.hsv(this.h, this.s, this.v);
|
|
this.r = clr.r;
|
|
this.g = clr.g;
|
|
this.b = clr.b;
|
|
}
|
|
|
|
render() {
|
|
const cb = (v) => {
|
|
this.color = v;
|
|
this.updateView(this.divRep);
|
|
this.postEvent(new Event("change", { bubbles: true }));
|
|
}
|
|
return <widget|color styleset={__DIR__ + "color-selector.css#color-selector"}>
|
|
<div.layers>
|
|
<div.saturation var={this.divSaturationLayer} />
|
|
<div.value var={this.divValueLayer} />
|
|
</div>
|
|
<div.sliders var={this.divSliders}>
|
|
<input|hslider(h) var={this.sliderH} min="0.0" max="360.0" value={this.h} />
|
|
{ this.hasAlpha ? <input|hslider(a) var={this.sliderA} min="0.0" max="1.0" value={this.a} />: []}
|
|
</div>
|
|
<Rep var={this.divRep} format={ColorSelector.format} colorObj={this.colorObj} colors={this.colors} alpha={this.hasAlpha} cb={cb} />
|
|
{this.extra ? (this.extra[1].color = this.value, this.extra[1].name = "extra", this.extra) : []}
|
|
{this.showPredefined ? <PredefinedColors var={this.predefinedColors} color={this.color} colors={this.colors} /> : []}
|
|
</widget>;
|
|
//
|
|
}
|
|
|
|
componentDidMount(byCSS) {
|
|
if (byCSS) {
|
|
this.hasAlpha = (this.attributes["alpha"] ?? false) !== false;
|
|
this.color = new Color(this.attributes["value"] ?? "#f00");
|
|
this.patch(this.render(),true);
|
|
}
|
|
this.setupValueLayer();
|
|
this.updateView();
|
|
//this.requestPaint();
|
|
}
|
|
|
|
setupValueLayer() {
|
|
const valLayer = this.divValueLayer;
|
|
valLayer.paintForeground = gfx => {
|
|
const { width: sx, height: sy } = valLayer.box("inner");
|
|
const cx = sx - sx * this.s;
|
|
const cy = sy - sy * this.v;
|
|
gfx.strokeStyle = Color.RGB(255,255,255);
|
|
gfx.strokeWidth = 1;
|
|
gfx.beginPath();
|
|
gfx.ellipse(cx,cy,5.5,5.5,0deg,360deg);
|
|
gfx.stroke();
|
|
gfx.strokeStyle = Color.RGB(0,0,0);
|
|
gfx.beginPath();
|
|
gfx.ellipse(cx,cy,4.5,4.5,0deg,360deg);
|
|
gfx.stroke();
|
|
};
|
|
|
|
const selectColorByXY = ({x,y}) => {
|
|
const { width: sx, height: sy } = valLayer.box("inner");
|
|
x = Math.min(sx,Math.max(0.0,x));
|
|
y = Math.min(sy,Math.max(0.0,y));
|
|
this.s = 1.0 - x / sx;
|
|
this.v = 1.0 - y / sy;
|
|
this.HSVtoRGB();
|
|
this.postEvent(new Event("change", { bubbles: true }));
|
|
this.updateView();
|
|
this.flushPaint();
|
|
};
|
|
|
|
valLayer.on("^mousedown", evt => {
|
|
if (evt.button == 1) {
|
|
selectColorByXY(evt);
|
|
valLayer.state.capture(true);
|
|
}
|
|
});
|
|
valLayer.on("^mousemove", evt => {
|
|
if (evt.button == 1) selectColorByXY(evt);
|
|
});
|
|
valLayer.on("^mouseup", evt => {
|
|
valLayer.state.capture(false);
|
|
});
|
|
}
|
|
|
|
["on change at input(h)"](evt, sliderH) {
|
|
this.h = sliderH.value;
|
|
this.HSVtoRGB();
|
|
this.updateView(sliderH);
|
|
this.postEvent(new Event("change", { bubbles: true }));
|
|
}
|
|
|
|
["on change at input(a)"](evt, sliderA) {
|
|
this.a = sliderA.value;
|
|
this.updateView(sliderA);
|
|
this.postEvent(new Event("change", { bubbles: true }));
|
|
}
|
|
|
|
["on click at span.color"](e, dc){
|
|
this.color = new Color(dc.attributes["title"]);
|
|
this.updateView();
|
|
this.postEvent(new Event("change", { bubbles: true }));
|
|
return true;
|
|
}
|
|
|
|
updateView(byWhom) {
|
|
if (byWhom !== this.divRep) this.divRep.componentUpdate({colorObj: this.colorObj});
|
|
if (byWhom !== this.sliderH) this.sliderH.value = this.h;
|
|
if ((byWhom !== this.sliderA) && this.sliderA) this.sliderA.value = this.a;
|
|
this.divSliders.style?.variable("clr", this.color);
|
|
this.divSaturationLayer.style?.variable("clr", Color.hsv(this.h, 1, 1));
|
|
if (this.predefinedColors) this.predefinedColors.componentUpdate({ color: this.color, colors: this.colors });
|
|
if (this.extra) {
|
|
this.extra[1].color = this.color;
|
|
this.$("[name='extra']").patch(this.extra);
|
|
}
|
|
this.requestPaint();
|
|
}
|
|
|
|
get value() {
|
|
return this.color;
|
|
}
|
|
|
|
set value(v) {
|
|
if (typeof v == "string") v = new Color(v);
|
|
if (this.color.valueOf() == v.valueOf()) return;
|
|
this.color = v;
|
|
this.componentUpdate();
|
|
}
|
|
|
|
["on click at icon#formats"]() {
|
|
ColorSelector.format = (ColorSelector.format + 1) % 2;
|
|
this.componentUpdate();
|
|
return true;
|
|
}
|
|
|
|
["on click at icon#colors"](){
|
|
this.showPredefined = !this.showPredefined;
|
|
this.componentUpdate();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class ColorInput extends Element {
|
|
color;
|
|
shown = false;
|
|
hasAlpha;
|
|
colors;
|
|
extra;
|
|
colorSpan;
|
|
colorCaption;
|
|
embedded;
|
|
|
|
constructor(props,kids) {
|
|
super();
|
|
this.hasAlpha = (props?.alpha ?? false) !== false;
|
|
this.color = props?.value;
|
|
this.colors = props?.colors;
|
|
this.extra = kids?.[0];
|
|
this.embedded = props?.embedded;
|
|
}
|
|
|
|
this({value}) {
|
|
if (value) this.color = value;
|
|
}
|
|
|
|
componentDidMount(byCSS) {
|
|
if (byCSS) {
|
|
this.hasAlpha = (this.attributes["alpha"] ?? false) !== false;
|
|
this.colors = this.attributes["colors"]?.split(",");
|
|
this.embedded = this.attributes["embedded"] !== undefined;
|
|
const sval = this.attributes["value"];
|
|
if (sval) this.color = new Color(sval);
|
|
this.patch(this.render(), true);
|
|
}
|
|
this.updateView();
|
|
}
|
|
|
|
render() {
|
|
const atts = this.hasAlpha ? {"alpha":true} : {};
|
|
return <input|color {atts} styleset={__DIR__ + "color-selector.css#color-input"}>
|
|
<span var={this.colorSpan} />
|
|
<input uwp type="text" var={this.colorCaption} filter="#0~9A~Fa~f" maxlength="7" />
|
|
{!this.embedded && <button/>}
|
|
</input>;
|
|
}
|
|
|
|
updateView() {
|
|
if (this.colorSpan) {
|
|
this.colorSpan.style["background-color"] = this.color ? this.color.toString("RGB") : "#556469";
|
|
if (!this.colorCaption.state.focus)
|
|
this.colorCaption.value = this.color ? this.color.toString(this.hasAlpha ? "RGBA" : "RGB"):"";
|
|
}
|
|
}
|
|
|
|
showPicker() {
|
|
this.shown = true;
|
|
this.classList.add("focused");
|
|
if (this.extra) {
|
|
this.extra[1].color = this.color;
|
|
this.popup(<ColorSelector value={this.color} alpha={this.hasAlpha} colors={this.colors}>{this.extra}</ColorSelector>);
|
|
} else {
|
|
this.popup(<ColorSelector value={this.color} alpha={this.hasAlpha} colors={this.colors} />);
|
|
}
|
|
}
|
|
|
|
["on click at :root>span"](evt) { if (!this.shown) this.showPicker(); }
|
|
["on click at :root>button"](evt) { if (!this.shown) this.showPicker(); }
|
|
["on focus at :root>input"](evt) { this.classList.add("focused"); }
|
|
["on blur at :root>input"](evt) { this.classList.remove("focused"); }
|
|
|
|
["on change at :popup"](e,popup) {
|
|
this.color = popup.value;
|
|
this.updateView();
|
|
this.postEvent(new Event("change", { bubbles: true }) );
|
|
return true;
|
|
}
|
|
["on change at :root>input"](e,input) {
|
|
try {
|
|
this.color = new Color(input.value);
|
|
this.updateView();
|
|
this.postEvent(new Event("change", { bubbles: true }) )
|
|
} catch(e) {}
|
|
return true;
|
|
}
|
|
|
|
["on popupdismissing at :popup"](e,popup) {
|
|
this.timer(100ms, ()=> {
|
|
this.shown = false;
|
|
if (!this.colorCaption.state.focus) this.classList.remove("focused");
|
|
})
|
|
}
|
|
|
|
get value() {
|
|
return this.color;
|
|
}
|
|
set value(v) {
|
|
if (typeof v == "string") v = new Color(v);
|
|
this.color = new Color(v);
|
|
this.updateView();
|
|
}
|
|
} |