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.
RnQ/Distro/Template/color-selector.js

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();
}
}