memlnaut-nisps/playground/js/ui/param-display.js

134 lines
4.4 KiB
JavaScript
Raw Normal View History

// Parameter bar display
// Shows output parameters as horizontal bars, draggable in examples mode
const DEFAULT_PARAM_NAMES = ['Flow', 'Scale', 'Speed', 'Hue', 'Spread', 'Size', 'Trail', 'Turb', 'Attract', 'Radius', 'DispRate', 'DispAmt', 'Lifetime', 'Respawn', 'Advection', 'Inertia', 'Drag', 'Repulse', 'RepCnt', 'RepRate'];
const DEFAULT_PARAM_COLORS = ['#ff6a00', '#00ccff', '#ff6600', '#ff00cc', '#ffcc00', '#88ff00', '#0088ff', '#ff3366', '#9bff5f', '#59d3ff', '#ff8f3f', '#a0b7ff', '#f4ff7a', '#ffa8db', '#7dffc8', '#ffd166', '#8ad4ff', '#ff5f5f', '#ffc15f', '#ff8a3d'];
export class ParamDisplay {
constructor(container, numParams = 20) {
this.container = container;
this.numParams = numParams;
this.values = new Array(numParams).fill(0.5);
this.draggable = false;
this.onChange = null;
this.activeBar = -1;
this.paramNames = [...DEFAULT_PARAM_NAMES];
this.paramColors = [...DEFAULT_PARAM_COLORS];
this.build();
}
build() {
this.container.innerHTML = '';
this.bars = [];
for (let i = 0; i < this.numParams; i++) {
const row = document.createElement('div');
row.className = 'param-row';
const label = document.createElement('span');
label.className = 'param-label';
label.textContent = this.paramNames[i] || `p${i}`;
const track = document.createElement('div');
track.className = 'param-track';
track.dataset.index = i;
const fill = document.createElement('div');
fill.className = 'param-fill';
fill.style.background = this.paramColors[i] || '#888';
fill.style.width = '50%';
const val = document.createElement('span');
val.className = 'param-value';
val.textContent = '0.50';
track.appendChild(fill);
row.appendChild(label);
row.appendChild(track);
row.appendChild(val);
this.container.appendChild(row);
this.bars.push({ fill, val, track });
}
// Touch/mouse drag events on the container
const onStart = (e) => {
if (!this.draggable) return;
const target = e.target.closest('.param-track');
if (!target) return;
e.preventDefault();
this.activeBar = parseInt(target.dataset.index);
this._updateFromEvent(e);
};
const onMove = (e) => {
if (this.activeBar < 0 || !this.draggable) return;
e.preventDefault();
this._updateFromEvent(e);
};
const onEnd = () => {
this.activeBar = -1;
};
this.container.addEventListener('mousedown', onStart);
window.addEventListener('mousemove', onMove);
window.addEventListener('mouseup', onEnd);
this.container.addEventListener('touchstart', onStart, { passive: false });
this.container.addEventListener('touchmove', onMove, { passive: false });
this.container.addEventListener('touchend', onEnd);
}
_updateFromEvent(e) {
const i = this.activeBar;
if (i < 0) return;
const track = this.bars[i].track;
const rect = track.getBoundingClientRect();
const clientX = e.touches ? e.touches[0].clientX : e.clientX;
const value = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
this.values[i] = value;
this._renderBar(i);
if (this.onChange) this.onChange(i, value, this.values);
}
update(values) {
for (let i = 0; i < this.numParams && i < values.length; i++) {
this.values[i] = values[i];
this._renderBar(i);
}
}
_renderBar(i) {
const v = this.values[i];
this.bars[i].fill.style.width = (v * 100) + '%';
this.bars[i].val.textContent = v.toFixed(2);
}
setDraggable(draggable) {
this.draggable = draggable;
this.container.classList.toggle('draggable', draggable);
}
setNamesAndColors(names, colors) {
this.paramNames = names || DEFAULT_PARAM_NAMES;
this.paramColors = colors || DEFAULT_PARAM_COLORS;
// Update existing bars in-place
for (let i = 0; i < this.numParams; i++) {
if (!this.bars[i]) continue;
const row = this.bars[i].fill.closest('.param-row');
const label = row?.querySelector('.param-label');
if (label) label.textContent = this.paramNames[i] || `p${i}`;
this.bars[i].fill.style.background = this.paramColors[i] || '#888';
}
}
rebuild(numParams, names, colors) {
this.numParams = numParams;
this.values = new Array(numParams).fill(0.5);
this.paramNames = names || DEFAULT_PARAM_NAMES;
this.paramColors = colors || DEFAULT_PARAM_COLORS;
this.activeBar = -1;
this.build();
}
}