@kitschpatrol/tweakpane-plugin-essentials
Version:
A fork of @tweakpane/plugin-essentials with build optimizations.
1,449 lines (1,416 loc) • 64.5 kB
JavaScript
import { TpEvent, BladeApi, Emitter, ButtonController, ViewProps, ValueMap, PlainView, BladeController, LabelController, createPlugin, parseRecord, TpChangeEvent, isEmpty, mapRange, constrainRange, createNumberFormatter, ClassName, bindValueMap, valueToClassName, SVG_NS, bindValue, createValue, PointerHandler, isArrowKey, getStepForKey, getHorizontalStepKeys, getVerticalStepKeys, RangeConstraint, PointNdTextController, parseNumber, Foldable, TextController, PopupController, connectValues, bindFoldable, forceCast, findNextTarget, supportsTouch, LabeledValueBladeController, PointNdConstraint, GraphLogController, createPushedBuffer, initializeBuffer, ManualTicker, IntervalTicker, Constants, TpError, createNumberTextPropsObject, findConstraint, DefiniteRangeConstraint, createNumberTextInputParamsParser, createRangeConstraint, createStepConstraint, CompositeConstraint, writePrimitive, boolFromUnknown, numberFromUnknown, stringFromUnknown } from '@tweakpane/core';
class ButtonCellApi {
constructor(controller) {
this.controller_ = controller;
}
get disabled() {
return this.controller_.viewProps.get('disabled');
}
set disabled(disabled) {
this.controller_.viewProps.set('disabled', disabled);
}
get title() {
var _a;
return (_a = this.controller_.props.get('title')) !== null && _a !== void 0 ? _a : '';
}
set title(title) {
this.controller_.props.set('title', title);
}
on(eventName, handler) {
const bh = handler.bind(this);
const emitter = this.controller_.emitter;
emitter.on(eventName, () => {
bh(new TpEvent(this));
});
return this;
}
}
class TpButtonGridEvent extends TpEvent {
constructor(target, cell, index) {
super(target);
this.cell = cell;
this.index = index;
}
}
class ButtonGridApi extends BladeApi {
constructor(controller) {
super(controller);
this.cellToApiMap_ = new Map();
this.emitter_ = new Emitter();
const gc = this.controller.valueController;
gc.cellControllers.forEach((cc, i) => {
const api = new ButtonCellApi(cc);
this.cellToApiMap_.set(cc, api);
cc.emitter.on('click', () => {
const x = i % gc.size[0];
const y = Math.floor(i / gc.size[0]);
this.emitter_.emit('click', {
event: new TpButtonGridEvent(this, api, [x, y]),
});
});
});
}
cell(x, y) {
const gc = this.controller.valueController;
const cc = gc.cellControllers[y * gc.size[0] + x];
return this.cellToApiMap_.get(cc);
}
on(eventName, handler) {
const bh = handler.bind(this);
this.emitter_.on(eventName, (ev) => {
bh(ev.event);
});
return this;
}
}
class ButtonGridController {
constructor(doc, config) {
this.size = config.size;
const [w, h] = this.size;
const bcs = [];
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
const bc = new ButtonController(doc, {
props: ValueMap.fromObject(Object.assign({}, config.cellConfig(x, y))),
viewProps: ViewProps.create(),
});
bcs.push(bc);
}
}
this.cellCs_ = bcs;
this.viewProps = ViewProps.create();
this.viewProps.handleDispose(() => {
this.cellCs_.forEach((c) => {
c.viewProps.set('disposed', true);
});
});
this.view = new PlainView(doc, {
viewProps: this.viewProps,
viewName: 'btngrid',
});
this.view.element.style.gridTemplateColumns = `repeat(${w}, 1fr)`;
this.cellCs_.forEach((bc) => {
this.view.element.appendChild(bc.view.element);
});
}
get cellControllers() {
return this.cellCs_;
}
}
class ButtonGridBladeController extends BladeController {
constructor(doc, config) {
const bc = config.valueController;
const lc = new LabelController(doc, {
blade: config.blade,
props: config.labelProps,
valueController: bc,
});
super({
blade: config.blade,
view: lc.view,
viewProps: bc.viewProps,
});
this.valueController = bc;
this.labelController = lc;
}
}
const ButtonGridBladePlugin = createPlugin({
id: 'buttongrid',
type: 'blade',
accept(params) {
const result = parseRecord(params, (p) => ({
cells: p.required.function,
size: p.required.array(p.required.number),
view: p.required.constant('buttongrid'),
label: p.optional.string,
}));
return result ? { params: result } : null;
},
controller(args) {
return new ButtonGridBladeController(args.document, {
blade: args.blade,
labelProps: ValueMap.fromObject({
label: args.params.label,
}),
valueController: new ButtonGridController(args.document, {
cellConfig: args.params.cells,
size: args.params.size,
}),
});
},
api(args) {
if (args.controller instanceof ButtonGridBladeController) {
return new ButtonGridApi(args.controller);
}
return null;
},
});
class CubicBezierApi extends BladeApi {
get label() {
return this.controller.labelController.props.get('label');
}
set label(label) {
this.controller.labelController.props.set('label', label);
}
get value() {
return this.controller.valueController.value.rawValue;
}
set value(value) {
this.controller.valueController.value.rawValue = value;
}
on(eventName, handler) {
const bh = handler.bind(this);
this.controller.valueController.value.emitter.on(eventName, (ev) => {
bh(new TpChangeEvent(this, ev.rawValue, ev.options.last));
});
return this;
}
}
function interpolate(x1, x2, t) {
return x1 * (1 - t) + x2 * t;
}
const MAX_ITERATION = 20;
const X_DELTA = 0.001;
const CACHE_RESOLUTION = 100;
function y(cb, x) {
let dt = 0.25;
let t = 0.5;
let y = -1;
for (let i = 0; i < MAX_ITERATION; i++) {
const [tx, ty] = cb.curve(t);
t += dt * (tx < x ? 1 : -1);
y = ty;
dt *= 0.5;
if (Math.abs(x - tx) < X_DELTA) {
break;
}
}
return y;
}
class CubicBezier {
constructor(x1 = 0, y1 = 0, x2 = 1, y2 = 1) {
this.cache_ = [];
this.comps_ = [x1, y1, x2, y2];
}
get x1() {
return this.comps_[0];
}
get y1() {
return this.comps_[1];
}
get x2() {
return this.comps_[2];
}
get y2() {
return this.comps_[3];
}
static isObject(obj) {
if (isEmpty(obj)) {
return false;
}
if (!Array.isArray(obj)) {
return false;
}
return (typeof obj[0] === 'number' &&
typeof obj[1] === 'number' &&
typeof obj[2] === 'number' &&
typeof obj[3] === 'number');
}
static equals(v1, v2) {
return (v1.x1 === v2.x1 && v1.y1 === v2.y1 && v1.x2 === v2.x2 && v1.y2 === v2.y2);
}
curve(t) {
const x01 = interpolate(0, this.x1, t);
const y01 = interpolate(0, this.y1, t);
const x12 = interpolate(this.x1, this.x2, t);
const y12 = interpolate(this.y1, this.y2, t);
const x23 = interpolate(this.x2, 1, t);
const y23 = interpolate(this.y2, 1, t);
const xr0 = interpolate(x01, x12, t);
const yr0 = interpolate(y01, y12, t);
const xr1 = interpolate(x12, x23, t);
const yr1 = interpolate(y12, y23, t);
return [interpolate(xr0, xr1, t), interpolate(yr0, yr1, t)];
}
y(x) {
if (this.cache_.length === 0) {
const cache = [];
for (let i = 0; i < CACHE_RESOLUTION; i++) {
cache.push(y(this, mapRange(i, 0, CACHE_RESOLUTION - 1, 0, 1)));
}
this.cache_ = cache;
}
return this.cache_[Math.round(mapRange(constrainRange(x, 0, 1), 0, 1, 0, CACHE_RESOLUTION - 1))];
}
toObject() {
return [this.comps_[0], this.comps_[1], this.comps_[2], this.comps_[3]];
}
}
const CubicBezierAssembly = {
toComponents: (p) => p.toObject(),
fromComponents: (comps) => new CubicBezier(...comps),
};
function cubicBezierToString(cb) {
const formatter = createNumberFormatter(2);
const comps = cb.toObject().map((c) => formatter(c));
return `cubic-bezier(${comps.join(', ')})`;
}
const COMPS_EMPTY = [0, 0.5, 0.5, 1];
function cubicBezierFromString(text) {
const m = text.match(/^cubic-bezier\s*\(\s*([0-9.]+)\s*,\s*([0-9.]+)\s*,\s*([0-9.]+)\s*,\s*([0-9.]+)\s*\)$/);
if (!m) {
return new CubicBezier(...COMPS_EMPTY);
}
const comps = [m[1], m[2], m[3], m[4]].reduce((comps, comp) => {
if (!comps) {
return null;
}
const n = Number(comp);
if (isNaN(n)) {
return null;
}
return [...comps, n];
}, []);
return new CubicBezier(...(comps !== null && comps !== void 0 ? comps : COMPS_EMPTY));
}
const className$7 = ClassName('cbz');
class CubicBezierView {
constructor(doc, config) {
this.element = doc.createElement('div');
this.element.classList.add(className$7());
config.viewProps.bindClassModifiers(this.element);
config.foldable.bindExpandedClass(this.element, className$7(undefined, 'expanded'));
bindValueMap(config.foldable, 'completed', valueToClassName(this.element, className$7(undefined, 'cpl')));
const headElem = doc.createElement('div');
headElem.classList.add(className$7('h'));
this.element.appendChild(headElem);
const buttonElem = doc.createElement('button');
buttonElem.classList.add(className$7('b'));
config.viewProps.bindDisabled(buttonElem);
const iconElem = doc.createElementNS(SVG_NS, 'svg');
iconElem.innerHTML = '<path d="M2 13C8 13 8 3 14 3"/>';
buttonElem.appendChild(iconElem);
headElem.appendChild(buttonElem);
this.buttonElement = buttonElem;
const textElem = doc.createElement('div');
textElem.classList.add(className$7('t'));
headElem.appendChild(textElem);
this.textElement = textElem;
if (config.pickerLayout === 'inline') {
const pickerElem = doc.createElement('div');
pickerElem.classList.add(className$7('p'));
this.element.appendChild(pickerElem);
this.pickerElement = pickerElem;
}
else {
this.pickerElement = null;
}
}
}
const className$6 = ClassName('cbzp');
class CubicBezierPickerView {
constructor(doc, config) {
this.element = doc.createElement('div');
this.element.classList.add(className$6());
config.viewProps.bindClassModifiers(this.element);
const graphElem = doc.createElement('div');
graphElem.classList.add(className$6('g'));
this.element.appendChild(graphElem);
this.graphElement = graphElem;
const textElem = doc.createElement('div');
textElem.classList.add(className$6('t'));
this.element.appendChild(textElem);
this.textElement = textElem;
}
}
function waitToBeAddedToDom(elem, callback) {
const ob = new MutationObserver((ml) => {
for (const m of ml) {
if (m.type !== 'childList') {
continue;
}
m.addedNodes.forEach((elem) => {
if (!elem.contains(elem)) {
return;
}
callback();
ob.disconnect();
});
}
});
const doc = elem.ownerDocument;
ob.observe(doc.body, {
attributes: true,
childList: true,
subtree: true,
});
}
const className$5 = ClassName('cbzg');
// TODO: Apply to core
function compose(h1, h2) {
return (input) => h2(h1(input));
}
class CubicBezierGraphView {
constructor(doc, config) {
this.element = doc.createElement('div');
this.element.classList.add(className$5());
config.viewProps.bindClassModifiers(this.element);
config.viewProps.bindTabIndex(this.element);
const previewElem = doc.createElement('div');
previewElem.classList.add(className$5('p'));
this.element.appendChild(previewElem);
this.previewElement = previewElem;
const svgElem = doc.createElementNS(SVG_NS, 'svg');
svgElem.classList.add(className$5('g'));
this.element.appendChild(svgElem);
this.svgElem_ = svgElem;
const guideElem = doc.createElementNS(SVG_NS, 'path');
guideElem.classList.add(className$5('u'));
this.svgElem_.appendChild(guideElem);
this.guideElem_ = guideElem;
const lineElem = doc.createElementNS(SVG_NS, 'polyline');
lineElem.classList.add(className$5('l'));
this.svgElem_.appendChild(lineElem);
this.lineElem_ = lineElem;
this.handleElems_ = [doc.createElement('div'), doc.createElement('div')];
this.handleElems_.forEach((elem) => {
elem.classList.add(className$5('h'));
this.element.appendChild(elem);
});
this.vectorElems_ = [
doc.createElementNS(SVG_NS, 'line'),
doc.createElementNS(SVG_NS, 'line'),
];
this.vectorElems_.forEach((elem) => {
elem.classList.add(className$5('v'));
this.svgElem_.appendChild(elem);
});
this.value_ = config.value;
this.value_.emitter.on('change', this.onValueChange_.bind(this));
this.sel_ = config.selection;
this.handleElems_.forEach((elem, index) => {
bindValue(this.sel_, compose((selection) => selection === index, valueToClassName(elem, className$5('h', 'sel'))));
});
waitToBeAddedToDom(this.element, () => {
this.refresh();
});
}
getVertMargin_(h) {
return h * 0.25;
}
valueToPosition(x, y) {
const { clientWidth: w, clientHeight: h } = this.element;
const vm = this.getVertMargin_(h);
return {
x: mapRange(x, 0, 1, 0, w),
y: mapRange(y, 0, 1, h - vm, vm),
};
}
positionToValue(x, y) {
const bounds = this.element.getBoundingClientRect();
const w = bounds.width;
const h = bounds.height;
const vm = this.getVertMargin_(h);
return {
x: constrainRange(mapRange(x, 0, w, 0, 1), 0, 1),
y: mapRange(y, h - vm, vm, 0, 1),
};
}
refresh() {
this.guideElem_.setAttributeNS(null, 'd', [0, 1]
.map((index) => {
const p1 = this.valueToPosition(0, index);
const p2 = this.valueToPosition(1, index);
return [`M ${p1.x},${p1.y}`, `L ${p2.x},${p2.y}`].join(' ');
})
.join(' '));
const bezier = this.value_.rawValue;
const points = [];
let t = 0;
for (;;) {
const p = this.valueToPosition(...bezier.curve(t));
points.push([p.x, p.y].join(','));
if (t >= 1) {
break;
}
t = Math.min(t + 0.05, 1);
}
this.lineElem_.setAttributeNS(null, 'points', points.join(' '));
const obj = bezier.toObject();
[0, 1].forEach((index) => {
const p1 = this.valueToPosition(index, index);
const p2 = this.valueToPosition(obj[index * 2], obj[index * 2 + 1]);
const vElem = this.vectorElems_[index];
vElem.setAttributeNS(null, 'x1', String(p1.x));
vElem.setAttributeNS(null, 'y1', String(p1.y));
vElem.setAttributeNS(null, 'x2', String(p2.x));
vElem.setAttributeNS(null, 'y2', String(p2.y));
const hElem = this.handleElems_[index];
hElem.style.left = `${p2.x}px`;
hElem.style.top = `${p2.y}px`;
});
}
onValueChange_() {
this.refresh();
}
}
const TICK_COUNT = 24;
const PREVIEW_DELAY = 400;
const PREVIEW_DURATION = 1000;
const className$4 = ClassName('cbzprv');
class CubicBezierPreviewView {
constructor(doc, config) {
this.stopped_ = true;
this.startTime_ = -1;
this.requestId_ = -1;
this.onDispose_ = this.onDispose_.bind(this);
this.onTimer_ = this.onTimer_.bind(this);
this.onValueChange_ = this.onValueChange_.bind(this);
this.element = doc.createElement('div');
this.element.classList.add(className$4());
config.viewProps.bindClassModifiers(this.element);
const svgElem = doc.createElementNS(SVG_NS, 'svg');
svgElem.classList.add(className$4('g'));
this.element.appendChild(svgElem);
this.svgElem_ = svgElem;
const ticksElem = doc.createElementNS(SVG_NS, 'path');
ticksElem.classList.add(className$4('t'));
this.svgElem_.appendChild(ticksElem);
this.ticksElem_ = ticksElem;
const markerElem = doc.createElement('div');
markerElem.classList.add(className$4('m'));
this.element.appendChild(markerElem);
this.markerElem_ = markerElem;
this.value_ = config.value;
this.value_.emitter.on('change', this.onValueChange_);
config.viewProps.handleDispose(this.onDispose_);
waitToBeAddedToDom(this.element, () => {
this.refresh();
});
}
play() {
this.stop();
this.updateMarker_(0);
this.markerElem_.classList.add(className$4('m', 'a'));
this.startTime_ = new Date().getTime() + PREVIEW_DELAY;
this.stopped_ = false;
this.requestId_ = requestAnimationFrame(this.onTimer_);
}
stop() {
cancelAnimationFrame(this.requestId_);
this.stopped_ = true;
this.markerElem_.classList.remove(className$4('m', 'a'));
}
onDispose_() {
this.stop();
}
updateMarker_(progress) {
const p = this.value_.rawValue.y(constrainRange(progress, 0, 1));
this.markerElem_.style.left = `${p * 100}%`;
}
refresh() {
const { clientWidth: w, clientHeight: h } = this.svgElem_;
const ds = [];
const bezier = this.value_.rawValue;
for (let i = 0; i < TICK_COUNT; i++) {
const px = mapRange(i, 0, TICK_COUNT - 1, 0, 1);
const x = mapRange(bezier.y(px), 0, 1, 0, w);
ds.push(`M ${x},0 v${h}`);
}
this.ticksElem_.setAttributeNS(null, 'd', ds.join(' '));
}
onTimer_() {
if (this.startTime_ === null) {
return;
}
const dt = new Date().getTime() - this.startTime_;
const p = dt / PREVIEW_DURATION;
this.updateMarker_(p);
if (dt > PREVIEW_DURATION + PREVIEW_DELAY) {
this.stop();
}
if (!this.stopped_) {
this.requestId_ = requestAnimationFrame(this.onTimer_);
}
}
onValueChange_() {
this.refresh();
this.play();
}
}
function getDistance(x1, y1, x2, y2) {
const dx = x2 - x1;
const dy = y2 - y1;
return Math.sqrt(dx * dx + dy * dy);
}
function lockAngle(x1, y1, x2, y2) {
const d = getDistance(x1, y1, x2, y2);
const a = Math.atan2(y2 - y1, x2 - x1);
const la = (Math.round(a / (Math.PI / 4)) * Math.PI) / 4;
return {
x: x1 + Math.cos(la) * d,
y: y1 + Math.sin(la) * d,
};
}
class CubicBezierGraphController {
constructor(doc, config) {
this.onKeyDown_ = this.onKeyDown_.bind(this);
this.onKeyUp_ = this.onKeyUp_.bind(this);
this.onPointerDown_ = this.onPointerDown_.bind(this);
this.onPointerMove_ = this.onPointerMove_.bind(this);
this.onPointerUp_ = this.onPointerUp_.bind(this);
this.keyScale_ = config.keyScale;
this.value = config.value;
this.sel_ = createValue(0);
this.viewProps = config.viewProps;
this.view = new CubicBezierGraphView(doc, {
selection: this.sel_,
value: this.value,
viewProps: this.viewProps,
});
this.view.element.addEventListener('keydown', this.onKeyDown_);
this.view.element.addEventListener('keyup', this.onKeyUp_);
this.prevView_ = new CubicBezierPreviewView(doc, {
value: this.value,
viewProps: this.viewProps,
});
this.prevView_.element.addEventListener('mousedown', (ev) => {
ev.stopImmediatePropagation();
ev.preventDefault();
this.prevView_.play();
});
this.view.previewElement.appendChild(this.prevView_.element);
const ptHandler = new PointerHandler(this.view.element);
ptHandler.emitter.on('down', this.onPointerDown_);
ptHandler.emitter.on('move', this.onPointerMove_);
ptHandler.emitter.on('up', this.onPointerUp_);
}
refresh() {
this.view.refresh();
this.prevView_.refresh();
this.prevView_.play();
}
updateValue_(point, locksAngle, opts) {
const index = this.sel_.rawValue;
const comps = this.value.rawValue.toObject();
const vp = this.view.positionToValue(point.x, point.y);
const v = locksAngle ? lockAngle(index, index, vp.x, vp.y) : vp;
comps[index * 2] = v.x;
comps[index * 2 + 1] = v.y;
this.value.setRawValue(new CubicBezier(...comps), opts);
}
onPointerDown_(ev) {
const data = ev.data;
if (!data.point) {
return;
}
const bezier = this.value.rawValue;
const p1 = this.view.valueToPosition(bezier.x1, bezier.y1);
const d1 = getDistance(data.point.x, data.point.y, p1.x, p1.y);
const p2 = this.view.valueToPosition(bezier.x2, bezier.y2);
const d2 = getDistance(data.point.x, data.point.y, p2.x, p2.y);
this.sel_.rawValue = d1 <= d2 ? 0 : 1;
this.updateValue_(data.point, ev.shiftKey, {
forceEmit: false,
last: false,
});
}
onPointerMove_(ev) {
const data = ev.data;
if (!data.point) {
return;
}
this.updateValue_(data.point, ev.shiftKey, {
forceEmit: false,
last: false,
});
}
onPointerUp_(ev) {
const data = ev.data;
if (!data.point) {
return;
}
this.updateValue_(data.point, ev.shiftKey, {
forceEmit: true,
last: true,
});
}
onKeyDown_(ev) {
if (isArrowKey(ev.key)) {
ev.preventDefault();
}
const index = this.sel_.rawValue;
const comps = this.value.rawValue.toObject();
const keyScale = this.keyScale_.rawValue;
comps[index * 2] += getStepForKey(keyScale, getHorizontalStepKeys(ev));
comps[index * 2 + 1] += getStepForKey(keyScale, getVerticalStepKeys(ev));
this.value.setRawValue(new CubicBezier(...comps), {
forceEmit: false,
last: false,
});
}
onKeyUp_(ev) {
if (isArrowKey(ev.key)) {
ev.preventDefault();
}
const keyScale = this.keyScale_.rawValue;
const xStep = getStepForKey(keyScale, getHorizontalStepKeys(ev));
const yStep = getStepForKey(keyScale, getVerticalStepKeys(ev));
if (xStep === 0 && yStep === 0) {
return;
}
this.value.setRawValue(this.value.rawValue, {
forceEmit: true,
last: true,
});
}
}
class CubicBezierPickerController {
constructor(doc, config) {
this.value = config.value;
this.viewProps = config.viewProps;
this.view = new CubicBezierPickerView(doc, {
viewProps: this.viewProps,
});
this.gc_ = new CubicBezierGraphController(doc, {
keyScale: config.axis.textProps.value('keyScale'),
value: this.value,
viewProps: this.viewProps,
});
this.view.graphElement.appendChild(this.gc_.view.element);
const xAxis = Object.assign(Object.assign({}, config.axis), { constraint: new RangeConstraint({ max: 1, min: 0 }) });
const yAxis = Object.assign(Object.assign({}, config.axis), { constraint: undefined });
this.tc_ = new PointNdTextController(doc, {
assembly: CubicBezierAssembly,
axes: [xAxis, yAxis, xAxis, yAxis],
parser: parseNumber,
value: this.value,
viewProps: this.viewProps,
});
this.view.textElement.appendChild(this.tc_.view.element);
}
get allFocusableElements() {
return [
this.gc_.view.element,
...this.tc_.view.textViews.map((v) => v.inputElement),
];
}
refresh() {
this.gc_.refresh();
}
}
class CubicBezierController {
constructor(doc, config) {
this.onButtonBlur_ = this.onButtonBlur_.bind(this);
this.onButtonClick_ = this.onButtonClick_.bind(this);
this.onPopupChildBlur_ = this.onPopupChildBlur_.bind(this);
this.onPopupChildKeydown_ = this.onPopupChildKeydown_.bind(this);
this.value = config.value;
this.viewProps = config.viewProps;
this.foldable_ = Foldable.create(config.expanded);
this.view = new CubicBezierView(doc, {
foldable: this.foldable_,
pickerLayout: config.pickerLayout,
viewProps: this.viewProps,
});
this.view.buttonElement.addEventListener('blur', this.onButtonBlur_);
this.view.buttonElement.addEventListener('click', this.onButtonClick_);
this.tc_ = new TextController(doc, {
parser: cubicBezierFromString,
props: ValueMap.fromObject({
formatter: cubicBezierToString,
}),
value: this.value,
viewProps: this.viewProps,
});
this.view.textElement.appendChild(this.tc_.view.element);
this.popC_ =
config.pickerLayout === 'popup'
? new PopupController(doc, {
viewProps: this.viewProps,
})
: null;
const pickerC = new CubicBezierPickerController(doc, {
axis: config.axis,
value: this.value,
viewProps: this.viewProps,
});
pickerC.allFocusableElements.forEach((elem) => {
elem.addEventListener('blur', this.onPopupChildBlur_);
elem.addEventListener('keydown', this.onPopupChildKeydown_);
});
this.pickerC_ = pickerC;
if (this.popC_) {
this.view.element.appendChild(this.popC_.view.element);
this.popC_.view.element.appendChild(this.pickerC_.view.element);
bindValue(this.popC_.shows, (shows) => {
if (shows) {
pickerC.refresh();
}
});
connectValues({
primary: this.foldable_.value('expanded'),
secondary: this.popC_.shows,
forward: (p) => p,
backward: (_, s) => s,
});
}
else if (this.view.pickerElement) {
this.view.pickerElement.appendChild(this.pickerC_.view.element);
bindFoldable(this.foldable_, this.view.pickerElement);
}
}
onButtonBlur_(ev) {
if (!this.popC_) {
return;
}
const nextTarget = forceCast(ev.relatedTarget);
if (!nextTarget || !this.popC_.view.element.contains(nextTarget)) {
this.popC_.shows.rawValue = false;
}
}
onButtonClick_() {
this.foldable_.set('expanded', !this.foldable_.get('expanded'));
if (this.foldable_.get('expanded')) {
this.pickerC_.allFocusableElements[0].focus();
}
}
onPopupChildBlur_(ev) {
if (!this.popC_) {
return;
}
const elem = this.popC_.view.element;
const nextTarget = findNextTarget(ev);
if (nextTarget && elem.contains(nextTarget)) {
// Next target is in the popup
return;
}
if (nextTarget &&
nextTarget === this.view.buttonElement &&
!supportsTouch(elem.ownerDocument)) {
// Next target is the trigger button
return;
}
this.popC_.shows.rawValue = false;
}
onPopupChildKeydown_(ev) {
if (!this.popC_) {
return;
}
if (ev.key === 'Escape') {
this.popC_.shows.rawValue = false;
}
}
}
function createConstraint$1() {
return new PointNdConstraint({
assembly: CubicBezierAssembly,
components: [0, 1, 2, 3].map((index) => index % 2 === 0
? new RangeConstraint({
min: 0,
max: 1,
})
: undefined),
});
}
const CubicBezierBladePlugin = createPlugin({
id: 'cubicbezier',
type: 'blade',
accept(params) {
const result = parseRecord(params, (p) => ({
value: p.required.array(p.required.number),
view: p.required.constant('cubicbezier'),
expanded: p.optional.boolean,
label: p.optional.string,
picker: p.optional.custom((v) => {
return v === 'inline' || v === 'popup' ? v : undefined;
}),
}));
return result ? { params: result } : null;
},
controller(args) {
var _a, _b;
const rv = new CubicBezier(...args.params.value);
const v = createValue(rv, {
constraint: createConstraint$1(),
equals: CubicBezier.equals,
});
const vc = new CubicBezierController(args.document, {
axis: {
textProps: ValueMap.fromObject({
keyScale: 0.1,
pointerScale: 0.01,
formatter: createNumberFormatter(2),
}),
},
expanded: (_a = args.params.expanded) !== null && _a !== void 0 ? _a : false,
pickerLayout: (_b = args.params.picker) !== null && _b !== void 0 ? _b : 'popup',
value: v,
viewProps: args.viewProps,
});
return new LabeledValueBladeController(args.document, {
blade: args.blade,
props: ValueMap.fromObject({
label: args.params.label,
}),
value: v,
valueController: vc,
});
},
api(args) {
if (!(args.controller instanceof LabeledValueBladeController)) {
return null;
}
if (!(args.controller.valueController instanceof CubicBezierController)) {
return null;
}
return new CubicBezierApi(args.controller);
},
});
class FpsGraphBladeApi extends BladeApi {
get fps() {
return this.controller.valueController.fps;
}
get max() {
return this.controller.valueController.props.get('max');
}
set max(max) {
this.controller.valueController.props.set('max', max);
}
get min() {
return this.controller.valueController.props.get('min');
}
set min(min) {
this.controller.valueController.props.set('min', min);
}
begin() {
this.controller.valueController.begin();
}
end() {
this.controller.valueController.end();
}
on(eventName, handler) {
const bh = handler.bind(this);
const emitter = this.controller.valueController.ticker.emitter;
emitter.on(eventName, () => {
bh(new TpEvent(this));
});
return this;
}
}
const MAX_TIMESTAMPS = 20;
class Fpswatch {
constructor() {
this.start_ = null;
this.duration_ = 0;
this.fps_ = null;
this.frameCount_ = 0;
this.timestamps_ = [];
}
get duration() {
return this.duration_;
}
get fps() {
return this.fps_;
}
begin(now) {
this.start_ = now.getTime();
}
calculateFps_(nowTime) {
if (this.timestamps_.length === 0) {
return null;
}
const ts = this.timestamps_[0];
return (1000 * (this.frameCount_ - ts.frameCount)) / (nowTime - ts.time);
}
compactTimestamps_() {
if (this.timestamps_.length <= MAX_TIMESTAMPS) {
return;
}
const len = this.timestamps_.length - MAX_TIMESTAMPS;
this.timestamps_.splice(0, len);
const df = this.timestamps_[0].frameCount;
this.timestamps_.forEach((ts) => {
ts.frameCount -= df;
});
this.frameCount_ -= df;
}
end(now) {
if (this.start_ === null) {
return;
}
const t = now.getTime();
this.duration_ = t - this.start_;
this.start_ = null;
this.fps_ = this.calculateFps_(t);
this.timestamps_.push({
frameCount: this.frameCount_,
time: t,
});
++this.frameCount_;
this.compactTimestamps_();
}
}
const className$3 = ClassName('fps');
class FpsView {
constructor(doc, config) {
this.element = doc.createElement('div');
this.element.classList.add(className$3());
config.viewProps.bindClassModifiers(this.element);
this.graphElement = doc.createElement('div');
this.graphElement.classList.add(className$3('g'));
this.element.appendChild(this.graphElement);
const labelElement = doc.createElement('div');
labelElement.classList.add(className$3('l'));
this.element.appendChild(labelElement);
const valueElement = doc.createElement('span');
valueElement.classList.add(className$3('v'));
valueElement.textContent = '--';
labelElement.appendChild(valueElement);
this.valueElement = valueElement;
const unitElement = doc.createElement('span');
unitElement.classList.add(className$3('u'));
unitElement.textContent = 'FPS';
labelElement.appendChild(unitElement);
}
}
class FpsGraphController {
constructor(doc, config) {
this.stopwatch_ = new Fpswatch();
this.onTick_ = this.onTick_.bind(this);
this.ticker = config.ticker;
this.ticker.emitter.on('tick', this.onTick_);
this.props = config.props;
this.value_ = config.value;
this.viewProps = config.viewProps;
this.view = new FpsView(doc, {
viewProps: this.viewProps,
});
this.graphC_ = new GraphLogController(doc, {
formatter: createNumberFormatter(0),
props: this.props,
rows: config.rows,
value: this.value_,
viewProps: this.viewProps,
});
this.view.graphElement.appendChild(this.graphC_.view.element);
this.viewProps.handleDispose(() => {
this.graphC_.viewProps.set('disposed', true);
this.ticker.dispose();
});
}
get fps() {
return this.stopwatch_.fps;
}
begin() {
this.stopwatch_.begin(new Date());
}
end() {
this.stopwatch_.end(new Date());
}
onTick_() {
const fps = this.fps;
if (fps !== null) {
const buffer = this.value_.rawValue;
this.value_.rawValue = createPushedBuffer(buffer, fps);
this.view.valueElement.textContent = fps.toFixed(0);
}
}
}
class FpsGraphBladeController extends BladeController {
constructor(doc, config) {
const fc = config.valueController;
const lc = new LabelController(doc, {
blade: config.blade,
props: config.labelProps,
valueController: fc,
});
super({
blade: config.blade,
view: lc.view,
viewProps: fc.viewProps,
});
this.valueController = fc;
this.labelController = lc;
}
}
function createTicker(document, interval) {
return interval === 0
? new ManualTicker()
: new IntervalTicker(document, interval !== null && interval !== void 0 ? interval : Constants.monitor.defaultInterval);
}
const FpsGraphBladePlugin = createPlugin({
id: 'fpsgraph',
type: 'blade',
accept(params) {
const result = parseRecord(params, (p) => ({
view: p.required.constant('fpsgraph'),
interval: p.optional.number,
label: p.optional.string,
rows: p.optional.number,
max: p.optional.number,
min: p.optional.number,
}));
return result ? { params: result } : null;
},
controller(args) {
var _a, _b, _c, _d;
const interval = (_a = args.params.interval) !== null && _a !== void 0 ? _a : 500;
return new FpsGraphBladeController(args.document, {
blade: args.blade,
labelProps: ValueMap.fromObject({
label: args.params.label,
}),
valueController: new FpsGraphController(args.document, {
props: ValueMap.fromObject({
max: (_b = args.params.max) !== null && _b !== void 0 ? _b : 90,
min: (_c = args.params.min) !== null && _c !== void 0 ? _c : 0,
}),
rows: (_d = args.params.rows) !== null && _d !== void 0 ? _d : 2,
ticker: createTicker(args.document, interval),
value: createValue(initializeBuffer(80)),
viewProps: args.viewProps,
}),
});
},
api(args) {
if (!(args.controller instanceof FpsGraphBladeController)) {
return null;
}
return new FpsGraphBladeApi(args.controller);
},
});
class Interval {
constructor(min, max) {
this.min = min;
this.max = max;
}
static isObject(obj) {
if (typeof obj !== 'object' || obj === null) {
return false;
}
const min = obj.min;
const max = obj.max;
if (typeof min !== 'number' || typeof max !== 'number') {
return false;
}
return true;
}
static equals(v1, v2) {
return v1.min === v2.min && v1.max === v2.max;
}
get length() {
return this.max - this.min;
}
toObject() {
return {
min: this.min,
max: this.max,
};
}
}
const IntervalAssembly = {
fromComponents: (comps) => new Interval(comps[0], comps[1]),
toComponents: (p) => [p.min, p.max],
};
class IntervalConstraint {
constructor(edge) {
this.edge = edge;
}
constrain(value) {
var _a, _b, _c, _d, _e, _f, _g, _h;
if (value.min <= value.max) {
return new Interval((_b = (_a = this.edge) === null || _a === void 0 ? void 0 : _a.constrain(value.min)) !== null && _b !== void 0 ? _b : value.min, (_d = (_c = this.edge) === null || _c === void 0 ? void 0 : _c.constrain(value.max)) !== null && _d !== void 0 ? _d : value.max);
}
const c = (value.min + value.max) / 2;
return new Interval((_f = (_e = this.edge) === null || _e === void 0 ? void 0 : _e.constrain(c)) !== null && _f !== void 0 ? _f : c, (_h = (_g = this.edge) === null || _g === void 0 ? void 0 : _g.constrain(c)) !== null && _h !== void 0 ? _h : c);
}
}
const className$2 = ClassName('rsltxt');
class RangeSliderTextView {
constructor(doc, config) {
this.sliderView_ = config.sliderView;
this.textView_ = config.textView;
this.element = doc.createElement('div');
this.element.classList.add(className$2());
const sliderElem = doc.createElement('div');
sliderElem.classList.add(className$2('s'));
sliderElem.appendChild(this.sliderView_.element);
this.element.appendChild(sliderElem);
const textElem = doc.createElement('div');
textElem.classList.add(className$2('t'));
textElem.appendChild(this.textView_.element);
this.element.appendChild(textElem);
}
}
const className$1 = ClassName('rsl');
class RangeSliderView {
constructor(doc, config) {
this.onSliderPropsChange_ = this.onSliderPropsChange_.bind(this);
this.onValueChange_ = this.onValueChange_.bind(this);
this.sliderProps_ = config.sliderProps;
this.sliderProps_.emitter.on('change', this.onSliderPropsChange_);
this.element = doc.createElement('div');
this.element.classList.add(className$1());
config.viewProps.bindClassModifiers(this.element);
this.value_ = config.value;
this.value_.emitter.on('change', this.onValueChange_);
const trackElem = doc.createElement('div');
trackElem.classList.add(className$1('t'));
this.element.appendChild(trackElem);
this.trackElement = trackElem;
const barElem = doc.createElement('div');
barElem.classList.add(className$1('b'));
trackElem.appendChild(barElem);
this.barElement = barElem;
const knobElems = ['min', 'max'].map((modifier) => {
const elem = doc.createElement('div');
elem.classList.add(className$1('k'), className$1('k', modifier));
trackElem.appendChild(elem);
return elem;
});
this.knobElements = [knobElems[0], knobElems[1]];
this.update_();
}
valueToX_(value) {
const min = this.sliderProps_.get('min');
const max = this.sliderProps_.get('max');
return constrainRange(mapRange(value, min, max, 0, 1), 0, 1) * 100;
}
update_() {
const v = this.value_.rawValue;
if (v.length === 0) {
this.element.classList.add(className$1(undefined, 'zero'));
}
else {
this.element.classList.remove(className$1(undefined, 'zero'));
}
const xs = [this.valueToX_(v.min), this.valueToX_(v.max)];
this.barElement.style.left = `${xs[0]}%`;
this.barElement.style.right = `${100 - xs[1]}%`;
this.knobElements.forEach((elem, index) => {
elem.style.left = `${xs[index]}%`;
});
}
onSliderPropsChange_() {
this.update_();
}
onValueChange_() {
this.update_();
}
}
class RangeSliderController {
constructor(doc, config) {
this.grabbing_ = null;
this.grabOffset_ = 0;
this.onPointerDown_ = this.onPointerDown_.bind(this);
this.onPointerMove_ = this.onPointerMove_.bind(this);
this.onPointerUp_ = this.onPointerUp_.bind(this);
this.sliderProps = config.sliderProps;
this.viewProps = config.viewProps;
this.value = config.value;
this.view = new RangeSliderView(doc, {
sliderProps: this.sliderProps,
value: this.value,
viewProps: config.viewProps,
});
const ptHandler = new PointerHandler(this.view.trackElement);
ptHandler.emitter.on('down', this.onPointerDown_);
ptHandler.emitter.on('move', this.onPointerMove_);
ptHandler.emitter.on('up', this.onPointerUp_);
}
ofs_() {
if (this.grabbing_ === 'min') {
return this.view.knobElements[0].getBoundingClientRect().width / 2;
}
if (this.grabbing_ === 'max') {
return -this.view.knobElements[1].getBoundingClientRect().width / 2;
}
return 0;
}
valueFromData_(data) {
if (!data.point) {
return null;
}
const p = (data.point.x + this.ofs_()) / data.bounds.width;
const min = this.sliderProps.get('min');
const max = this.sliderProps.get('max');
return mapRange(p, 0, 1, min, max);
}
onPointerDown_(ev) {
if (!ev.data.point) {
return;
}
const p = ev.data.point.x / ev.data.bounds.width;
const v = this.value.rawValue;
const min = this.sliderProps.get('min');
const max = this.sliderProps.get('max');
const pmin = mapRange(v.min, min, max, 0, 1);
const pmax = mapRange(v.max, min, max, 0, 1);
if (Math.abs(pmax - p) <= 0.025) {
this.grabbing_ = 'max';
}
else if (Math.abs(pmin - p) <= 0.025) {
this.grabbing_ = 'min';
}
else if (p >= pmin && p <= pmax) {
this.grabbing_ = 'length';
this.grabOffset_ = mapRange(p - pmin, 0, 1, 0, max - min);
}
else if (p < pmin) {
this.grabbing_ = 'min';
this.onPointerMove_(ev);
}
else if (p > pmax) {
this.grabbing_ = 'max';
this.onPointerMove_(ev);
}
}
applyPointToValue_(data, opts) {
const v = this.valueFromData_(data);
if (v === null) {
return;
}
const rmin = this.sliderProps.get('min');
const rmax = this.sliderProps.get('max');
if (this.grabbing_ === 'min') {
this.value.setRawValue(new Interval(v, this.value.rawValue.max), opts);
}
else if (this.grabbing_ === 'max') {
this.value.setRawValue(new Interval(this.value.rawValue.min, v), opts);
}
else if (this.grabbing_ === 'length') {
const len = this.value.rawValue.length;
let min = v - this.grabOffset_;
let max = min + len;
if (min < rmin) {
min = rmin;
max = rmin + len;
}
else if (max > rmax) {
min = rmax - len;
max = rmax;
}
this.value.setRawValue(new Interval(min, max), opts);
}
}
onPointerMove_(ev) {
this.applyPointToValue_(ev.data, {
forceEmit: false,
last: false,
});
}
onPointerUp_(ev) {
this.applyPointToValue_(ev.data, {
forceEmit: true,
last: true,
});
this.grabbing_ = null;
}
}
class RangeSliderTextController {
constructor(doc, config) {
this.value = config.value;
this.viewProps = config.viewProps;
this.sc_ = new RangeSliderController(doc, config);
const axis = {
constraint: config.constraint,
textProps: config.textProps,
};
this.tc_ = new PointNdTextController(doc, {
assembly: IntervalAssembly,
axes: [axis, axis],
parser: config.parser,
value: this.value,
viewProps: config.viewProps,
});
this.view = new RangeSliderTextView(doc, {
sliderView: this.sc_.view,
textView: this.tc_.view,
});
}
get textController() {
return this.tc_;
}
}
function intervalFromUnknown(value) {
return Interval.isObject(value)
? new Interval(value.min, value.max)
: new Interval(0, 0);
}
function writeInterval(target, value) {
target.writeProperty('max', value.max);
target.writeProperty('min', value.min);
}
function createConstraint(params) {
const constraints = [];
const rc = createRangeConstraint(params);
if (rc) {
constraints.push(rc);
}
const sc = createStepConstraint(params);
if (sc) {
constraints.push(sc);
}
return new IntervalConstraint(new CompositeConstraint(constraints));
}
const IntervalInputPlugin = createPlugin({
id: 'input-interval',
type: 'input',
accept: (exValue, params) => {
if (!Interval.isObject(exValue)) {
return null;
}
const result = parseRecord(params, (p) => (Object.assign(Object.assign({}, createNumberTextInputParamsParser(p)), { readonly: p.optional.constant(false) })));
return result
? {
initialValue: new Interval(exValue.min, exValue.max),
params: result,
}
: null;
},
binding: {
reader: (_args) => intervalFromUnknown,
constraint: (args) => createConstraint(args.params),
equals: Interval.equals,
writer: (_args) => writeInterval,
},
controller(args) {
const v = args.value;
const c = args.constraint;
if (!(c instanceof IntervalConstraint)) {
throw TpError.shouldNeverHappen();
}
const midValue = (v.rawValue.min + v.rawValue.max) / 2;
const textProps = ValueMap.fromObject(createNumberTextPropsObject(args.params, midValue));
const drc = c.edge && findConstraint(c.edge, DefiniteRangeConstraint);
if (drc) {
return new RangeSliderTextController(args.document, {
constraint: c.edge,
parser: parseNumber,
sliderProps: new ValueMap({
keyScale: textProps.value('keyScale'),
max: drc.values.value('max'),
min: drc.values.value('min'),
}),
textProps: textProps,
value: v,
viewProps: args.viewProps,
});
}
const axis = {
constraint: c.edge,
textProps: textProps,
};
return new PointNdTextController(args.document, {
assembly: IntervalAssembly,
axes: [axis, axis],
parser: parseNumber,
value: v,
viewProps: args.viewProps,
});
},
});
class RadioCellApi {
constructor(controller) {
this.controller_ = con