scripts/widgets/ui_widgets.js
"use strict";
import * as util from '../path-controller/util/util.js';
import * as vectormath from '../path-controller/util/vectormath.js';
import * as ui_base from '../core/ui_base.js';
import * as events from '../path-controller/util/events.js';
import * as toolsys from '../path-controller/toolsys/toolsys.js';
import * as toolprop from '../path-controller/toolsys/toolprop.js';
import {DataPathError} from '../path-controller/controller/controller.js';
import {Vector3, Vector4, Quat, Matrix4} from '../path-controller/util/vectormath.js';
import {isNumber, PropFlags} from "../path-controller/toolsys/toolprop.js";
import * as units from '../core/units.js';
import cconst from '../config/const.js';
function myToFixed(s, n) {
s = s.toFixed(n);
while (s.endsWith('0')) {
s = s.slice(0, s.length - 1);
}
if (s.endsWith("\.")) {
s = s.slice(0, s.length - 1);
}
return s;
}
let keymap = events.keymap;
let EnumProperty = toolprop.EnumProperty,
PropTypes = toolprop.PropTypes;
let UIBase = ui_base.UIBase,
PackFlags = ui_base.PackFlags,
IconSheets = ui_base.IconSheets;
let parsepx = ui_base.parsepx;
import {Button, OldButton} from './ui_button.js';
import {eventWasTouch, popModalLight, pushModalLight} from '../path-controller/util/simple_events.js';
export {Button} from './ui_button.js';
export class IconLabel extends UIBase {
constructor() {
super();
this._icon = -1;
this.iconsheet = 1;
}
get icon() {
return this._icon;
}
set icon(id) {
this._icon = id;
this.setCSS();
}
static define() {
return {
tagname: "icon-label-x"
}
}
init() {
super.init();
this.style["display"] = "flex";
this.style["margin"] = this.style["padding"] = "0px";
this.setCSS();
}
setCSS() {
let size = ui_base.iconmanager.getTileSize(this.iconsheet);
ui_base.iconmanager.setCSS(this.icon, this);
this.style["width"] = size + "px";
this.style["height"] = size + "px";
}
}
UIBase.internalRegister(IconLabel);
export class ValueButtonBase extends OldButton {
constructor() {
super();
}
get value() {
return this._value;
}
set value(val) {
this._value = val;
if (this.ctx && this.hasAttribute("datapath")) {
this.setPathValue(this.ctx, this.getAttribute("datapath"), this._value);
}
}
updateDataPath() {
if (!this.hasAttribute("datapath")) return;
if (this.ctx === undefined) return;
let val = this.getPathValue(this.ctx, this.getAttribute("datapath"));
if (val === undefined) {
let redraw = !this.disabled;
this.internalDisabled = true;
if (redraw) this._redraw();
return;
} else {
let redraw = this.disabled;
this.internalDisabled = false;
if (redraw) this._redraw();
}
if (val !== this._value) {
this._value = val;
this.updateWidth();
this._repos_canvas();
this._redraw();
this.setCSS();
}
}
update() {
this.updateDataPath();
super.update();
}
}
export class Check extends UIBase {
constructor() {
super();
this._checked = false;
this._highlight = false;
this._focus = false;
let shadow = this.shadow;
//let form = document.createElement("form");
let span = document.createElement("span");
span.setAttribute("class", "checkx");
span.style["display"] = "flex";
span.style["flex-direction"] = "row";
span.style["margin"] = span.style["padding"] = "0px";
//span.style["background"] = ui_base.iconmanager.getCSS(1);
let sheet = 0;
let size = ui_base.iconmanager.getTileSize(0);
let check = this.canvas = document.createElement("canvas");
this.g = check.getContext("2d");
check.setAttribute("id", check._id);
check.setAttribute("name", check._id);
let mdown = (e) => {
this._highlight = false;
this.checked = !this.checked;
};
let mup = (e) => {
this._highlight = false;
this.blur();
this._redraw();
};
let mover = (e) => {
this._highlight = true;
this._redraw();
};
let mleave = (e) => {
this._highlight = false;
this._redraw();
};
span.addEventListener("pointerover", mover, {passive: true});
span.addEventListener("mousein", mover, {passive: true});
span.addEventListener("mouseleave", mleave, {passive: true});
span.addEventListener("pointerout", mleave, {passive: true});
this.addEventListener("blur", (e) => {
this._highlight = this._focus = false;
this._redraw();
});
this.addEventListener("focusin", (e) => {
this._focus = true;
this._redraw();
});
this.addEventListener("focus", (e) => {
this._focus = true;
this._redraw();
});
span.addEventListener("pointerdown", mdown, {passive: true});
span.addEventListener("pointerup", mup, {passive: true});
span.addEventListener("pointercancel", mup, {passive: true});
this.addEventListener("keydown", (e) => {
switch (e.keyCode) {
case keymap["Escape"]:
this._highlight = undefined;
this._redraw();
e.preventDefault();
e.stopPropagation();
this.blur();
break;
case keymap["Enter"]:
case keymap["Space"]:
this.checked = !this.checked;
e.preventDefault();
e.stopPropagation();
break;
}
});
this.checkbox = check;
span.appendChild(check);
let label = this._label = document.createElement("label");
label.setAttribute("class", "checkx");
span.setAttribute("class", "checkx");
label.style["align-self"] = "center";
let side = this.getDefault("CheckSide");
if (side === "right") {
span.prepend(label);
} else {
span.appendChild(label);
}
shadow.appendChild(span);
}
get internalDisabled() {
return super.internalDisabled;
}
set internalDisabled(val) {
if (!!this.internalDisabled === !!val) {
return;
}
super.internalDisabled = val;
this._redraw();
}
get value() {
return this.checked;
}
set value(v) {
this.checked = v;
}
get checked() {
return this._checked;
}
set checked(v) {
v = !!v;
if (this._checked !== v) {
this._checked = v;
this.setCSS();
//this.dom.checked = v;
this._redraw();
if (this.onclick) {
this.onclick(v);
}
if (this.onchange) {
this.onchange(v);
}
if (this.hasAttribute("datapath")) {
this.setPathValue(this.ctx, this.getAttribute("datapath"), this._checked);
}
}
}
get label() {
return this._label.textContent;
}
set label(l) {
this._label.textContent = l;
}
static define() {
return {
tagname : "check-x",
style : "checkbox",
parentStyle: "button"
};
}
init() {
this.tabIndex = 1;
this.setAttribute("class", "checkx");
let style = document.createElement("style");
//let style = this.cssStyleTag();
let color = this.getDefault("focus-border-color");
style.textContent = `
.checkx:focus {
outline : none;
}
`;
//document.body.prepend(style);
this.prepend(style);
}
setCSS() {
this._label.style["font"] = this.getDefault("DefaultText").genCSS();
this._label.style["color"] = this.getDefault("DefaultText").color;
this._label.style['font'] = 'normal 14px poppins'; // TODO - Jordan - add to settings
super.setCSS();
//force clear background
this.style["background-color"] = "rgba(0,0,0,0)";
}
updateDataPath() {
if (!this.getAttribute("datapath")) {
return;
}
let val = this.getPathValue(this.ctx, this.getAttribute("datapath"));
let redraw = false;
if (val === undefined) {
this.internalDisabled = true;
return;
} else {
redraw = this.internalDisabled;
this.internalDisabled = false;
}
val = !!val;
redraw = redraw || !!this._checked !== !!val;
if (redraw) {
this._checked = val;
this._repos_canvas();
this.setCSS();
this._redraw();
}
}
_repos_canvas() {
}
_redraw() {
if (this.canvas === undefined) {
//flag update
this._updatekey = "";
return;
}
let canvas = this.canvas, g = this.g;
let dpi = UIBase.getDPI();
let tilesize = ui_base.iconmanager.getTileSize(0);
let pad = this.getDefault("padding");
let csize = tilesize + pad*2;
canvas.style["margin"] = "2px";
canvas.style["width"] = csize + "px";
canvas.style["height"] = csize + "px";
csize = ~~(csize*dpi + 0.5);
tilesize = ~~(tilesize*dpi + 0.5);
canvas.width = csize;
canvas.height = csize;
g.clearRect(0, 0, canvas.width, canvas.height);
g.beginPath();
g.rect(0, 0, canvas.width, canvas.height);
g.fill();
let color;
if (!this._checked && this._highlight) {
color = this.getDefault("BoxHighlight");
}
ui_base.drawRoundBox(this, canvas, g, undefined, undefined, undefined, undefined, color);
if (this._checked) {
//canvasDraw(elem, canvas, g, icon, x=0, y=0, sheet=0) {
let x = (csize - tilesize)*0.5, y = (csize - tilesize)*0.5;
ui_base.iconmanager.canvasDraw(this, canvas, g, ui_base.Icons.LARGE_CHECK, x, y);
}
if (this._focus) {
color = this.getDefault("focus-border-color");
g.lineWidth *= dpi;
ui_base.drawRoundBox(this, canvas, g, undefined, undefined, undefined, "stroke", color);
}
}
updateDPI() {
let dpi = UIBase.getDPI();
if (dpi !== this._last_dpi) {
this._last_dpi = dpi;
this._redraw();
}
}
update() {
super.update();
this.updateDPI();
let ready = ui_base.getIconManager().isReady(0);
if (this.hasAttribute("datapath")) {
this.updateDataPath();
}
let updatekey = this.getDefault("DefaultText").hash();
updatekey += this._checked + ":" + this._label.textContent;
updatekey += ":" + ready;
if (updatekey !== this._updatekey) {
this._repos_canvas();
this.setCSS();
this._updatekey = updatekey;
this._redraw();
}
}
}
UIBase.internalRegister(Check);
export class IconButton extends UIBase {
constructor() {
super();
this._customIcon = undefined;
this._pressed = false;
this._highlight = false;
this._draw_pressed = true;
this._icon = -1;
this._icon_pressed = undefined;
this.iconsheet = 0;
this.drawButtonBG = true;
this._extraIcon = undefined; //draw another icon on top
this.extraDom = undefined;
//have to put icon in subdiv
this.dom = document.createElement("div");
this.shadow.appendChild(this.dom);
this._last_iconsheet = undefined;
this.addEventListener("keydown", (e) => {
switch (e.keyCode) {
case keymap["Enter"]:
case keymap["Space"]:
this.click();
break;
}
});
}
click() {
if (this._onpress) {
let rect = this.getClientRects();
let x = rect.x + rect.width*0.5;
let y = rect.y + rect.height*0.5;
let e = {x : x, y : y, stopPropagation : () => {}, preventDefault : () => {}};
this._onpress(e);
}
super.click();
}
get customIcon() {
return this._customIcon;
}
set customIcon(domImage) {
this._customIcon = domImage;
this.setCSS();
}
get icon() {
return this._icon;
}
set icon(val) {
this._icon = val;
this.setCSS();
}
static define() {
return {
tagname: "iconbutton-x",
style : "iconbutton"
};
}
_on_press() {
this._pressed = true;
this.setCSS();
}
_on_depress() {
this._pressed = false;
this.setCSS();
}
updateDefaultSize() {
}
setCSS() {
super.setCSS();
let def;
let pstyle = this.getDefault("depressed");
let hstyle = this.getDefault("highlight");
this.noMarginsOrPadding();
if (this._pressed && this._draw_pressed) {
def = k => pstyle && k in pstyle ? pstyle[k] : this.getDefault(k);
} else if (this._highlight) {
def = k => hstyle && k in hstyle ? hstyle[k] : this.getDefault(k);
} else {
def = k => this.getDefault(k);
}
let loadstyle = (key, addpx) => {
let val = def(key);
if (addpx) {
val = ("" + val).trim();
if (!val.toLowerCase().endsWith("px")) {
val += "px";
}
}
this.style[key] = val;
}
let keys = ["margin", "padding", "margin-left", "margin-right", "margin-top", "margin-botton",
"padding-left", "padding-bottom", "padding-top", "padding-right",
"border-radius"]
for (let k of keys) {
loadstyle(k, true);
}
loadstyle("background-color", false);
loadstyle("color", false);
let border = `${def("border-width", true)} ${def("border-style", false)} ${def("border-color", false)}`
this.style["border"] = border;
let w = this.getDefault("width");
let size = ui_base.iconmanager.getTileSize(this.iconsheet);
w = size;
this.style["width"] = w + "px";
this.style["height"] = w + "px";
this.dom.style["width"] = w + "px";
this.dom.style["height"] = w + "px";
this.dom.style["margin"] = this.dom.style["padding"] = "0px";
this.style["display"] = "flex";
this.style["align-items"] = "center";
if (this._customIcon) {
this.dom.style["background-image"] = `url("${this._customIcon.src}")`
this.dom.style["background-size"] = "contain";
this.dom.style["background-repeat"] = "no-repeat";
} else {
let icon = this.icon;
if (this._pressed && this._icon_pressed !== undefined) {
icon = this._icon_pressed;
}
ui_base.iconmanager.setCSS(icon, this.dom, this.iconsheet);
}
if (this._extraIcon !== undefined) {
let dom;
if (!this.extraDom) {
this.extraDom = dom = document.createElement("div");
this.shadow.appendChild(dom);
} else {
dom = this.extraDom;
}
dom.style["position"] = "absolute";
dom.style["margin"] = dom.style["padding"] = "0px";
dom.style["pointer-events"] = "none";
dom.style["width"] = size + "px";
dom.style["height"] = size + "px";
ui_base.iconmanager.setCSS(this._extraIcon, dom, this.iconsheet);
} else if (this.extraDom) {
this.extraDom.remove();
}
}
init() {
super.init();
let press = (e) => {
e.stopPropagation();
e.preventDefault();
if (this.modalRunning) {
this.popModal();
}
if (!eventWasTouch(e) && e.button !== 0) {
return;
}
if (1) { //!eventWasTouch(e)) {
let this2 = this;
this.pushModal({
on_mouseup(e) {
//touch events aren't fireing onclick automatically the way mouse ones are
if (this2.onclick && eventWasTouch(e)) {
this2.onclick();
}
this.end();
},
on_touchcancel(e) {
this.on_mouseup(e);
this.end();
},
on_touchend(e) {
this.on_mouseup(e);
this.end();
},
on_keydown(e) {
this.end();
},
end() {
if (this2.modalRunning) {
this2.popModal();
this2._on_depress(e)
this2.setCSS();
}
}
});
}
this._on_press(e);
}
let depress = (e) => {
e.stopPropagation();
e.preventDefault();
this._on_depress();
this.setCSS();
}
let high = (e) => {
this._highlight = true;
this.setCSS();
}
let unhigh = (e) => {
this._highlight = false;
this.setCSS();
}
this.tabIndex = 0;
this.addEventListener("mouseover", high);
this.addEventListener("mouseexit", unhigh);
this.addEventListener("mouseleave", unhigh);
this.addEventListener("focus", high);
this.addEventListener("blur", unhigh);
this.addEventListener("mousedown", press, {capture: true});
this.addEventListener("mouseup", depress, {capture: true});
//this.addEventListener("touchstart", press, {capture: true});
//this.addEventListener("touchcancel", depress, {capture: true});
//this.addEventListener("touchend", depress, {capture: true});
this.setCSS();
this.dom.style["pointer-events"] = "none";
}
update() {
super.update();
if (this.iconsheet !== this._last_iconsheet) {
this.setCSS();
this._last_iconsheet = this.iconsheet;
}
}
_getsize() {
let margin = this.getDefault("padding");
return ui_base.iconmanager.getTileSize(this.iconsheet) + margin*2;
}
}
UIBase.internalRegister(IconButton);
export class IconCheck extends IconButton {
constructor() {
super();
this._checked = undefined;
this._drawCheck = undefined;
}
get drawCheck() {
let ret = this._drawCheck;
ret = ret === undefined ? this.getDefault("drawCheck") : ret;
ret = ret === undefined ? true : ret;
return ret;
}
set drawCheck(val) {
val = !!val;
if (val && (this.packflag & PackFlags.HIDE_CHECK_MARKS)) {
this.packflag &= ~PackFlags.HIDE_CHECK_MARKS;
}
let old = !!this.drawCheck;
this._drawCheck = val;
if (val !== old) {
this.updateDrawCheck();
this.setCSS();
}
}
click() {
super.click();
this.checked ^= true;
}
get icon() {
return this._icon;
}
set icon(val) {
this._icon = val;
this.setCSS();
}
get checked() {
return this._checked;
}
set checked(val) {
if (!!val !== !!this._checked) {
this._checked = val
this._updatePressed(!!val);
this.setCSS();
if (this.onchange) {
this.onchange(val);
}
}
}
get noEmboss() {
let ret = this.getAttribute("no-emboss");
if (!ret) {
return false;
}
ret = ret.toLowerCase().trim();
return ret === 'true' || ret === 'yes' || ret === 'on';
}
set noEmboss(val) {
this.setAttribute('no-emboss', val ? 'true' : 'false');
}
static define() {
return {
tagname : "iconcheck-x",
style : "iconcheck",
parentStyle: "iconbutton"
};
}
_updatePressed(val) {
//don't set _pressed if we have a custom icon for press state
if (this._icon_pressed) {
this._draw_pressed = false;
}
this._pressed = val;
this.setCSS();
}
_on_depress() {
return;
}
_on_press() {
this.checked ^= true;
if (this.hasAttribute("datapath")) {
this.setPathValue(this.ctx, this.getAttribute("datapath"), !!this.checked);
}
this.setCSS();
}
updateDefaultSize() {
}
_calcUpdateKey() {
return super._calcUpdateKey() + ":" + this._icon;
}
updateDataPath() {
if (!this.hasAttribute("datapath") || !this.ctx) {
return;
}
if (this._icon < 0) {
let rdef;
try {
rdef = this.ctx.api.resolvePath(this.ctx, this.getAttribute("datapath"));
} catch (error) {
if (error instanceof DataPathError) {
return;
} else {
throw error;
}
}
if (rdef !== undefined && rdef.prop) {
let icon, icon2, title;
if (rdef.prop.flag & PropFlags.NO_UNDO) {
this.setUndo(false);
} else {
this.setUndo(true);
}
//console.log("SUBKEY", rdef.subkey, rdef.prop.iconmap);
if (rdef.subkey && (rdef.prop.type === PropTypes.FLAG || rdef.prop.type === PropTypes.ENUM)) {
icon = rdef.prop.iconmap[rdef.subkey];
icon2 = rdef.prop.iconmap2[rdef.subkey];
title = rdef.prop.descriptions[rdef.subkey];
if (title === undefined && rdef.subkey.length > 0) {
title = rdef.subkey;
title = title[0].toUpperCase() + title.slice(1, title.length).toLowerCase();
}
} else {
icon2 = rdef.prop.icon2;
icon = rdef.prop.icon;
title = rdef.prop.description;
}
if (icon2 !== undefined && icon2 !== -1) {
this._icon_pressed = icon;
icon = icon2;
}
if (icon !== undefined && icon !== this.icon)
this.icon = icon;
if (title !== undefined)
this.description = title;
}
}
let val = this.getPathValue(this.ctx, this.getAttribute("datapath"));
if (val === undefined) {
this.internalDisabled = true;
return;
} else {
this.internalDisabled = false;
}
val = !!val;
if (val !== !!this._checked) {
this._checked = val;
this._updatePressed(!!val);
this.setCSS();
}
}
updateDrawCheck() {
if (this.drawCheck) {
this._extraIcon = this._checked ? ui_base.Icons.ENUM_CHECKED : ui_base.Icons.ENUM_UNCHECKED;
} else {
this._extraIcon = undefined;
}
}
update() {
if (this.packflag & PackFlags.HIDE_CHECK_MARKS) {
this.drawCheck = false;
}
this.updateDrawCheck();
if (this.hasAttribute("datapath")) {
this.updateDataPath();
}
super.update();
}
_getsize() {
let margin = this.getDefault("padding");
return ui_base.iconmanager.getTileSize(this.iconsheet) + margin*2;
}
setCSS() {
this.updateDrawCheck();
super.setCSS();
}
}
UIBase.internalRegister(IconCheck);
export class Check1 extends Button {
constructor() {
super();
this._namePad = 40;
this._value = undefined;
}
static define() {
return {
tagname : "check1-x",
parentStyle: "button"
};
}
_redraw() {
//console.log("button draw");
let dpi = this.getDPI();
let box = 40;
ui_base.drawRoundBox(this, this.dom, this.g, box);
let ts = this.getDefault("DefaultText").size;
let text = this._genLabel();
//console.log(text, "text", this._name);
let tw = ui_base.measureText(this, text, this.dom, this.g).width;
let cx = this.dom.width/2 - tw/2;
let cy = this.dom.height/2;
ui_base.drawText(this, box, cy + ts/2, text, {
canvas: this.dom, g: this.g
});
}
}
UIBase.internalRegister(Check1);
export {checkForTextBox} from './ui_textbox.js';