@thi.ng/imgui
Version:
Immediate mode GUI with flexible state handling & data only shape output
126 lines (125 loc) • 3.36 kB
JavaScript
import { group } from "@thi.ng/geom/group";
import { line } from "@thi.ng/geom/line";
import { polygon } from "@thi.ng/geom/polygon";
import { rect } from "@thi.ng/geom/rect";
import { triangle } from "@thi.ng/geom/triangle";
import { mix } from "@thi.ng/math/mix";
import { roundTo } from "@thi.ng/math/prec";
import { map } from "@thi.ng/transducers/map";
import { ONE2, ZERO2 } from "@thi.ng/vectors/api";
import { clamp01_2 } from "@thi.ng/vectors/clamp01";
import { fit2 } from "@thi.ng/vectors/fit";
import { hash } from "@thi.ng/vectors/hash";
import { mix2 } from "@thi.ng/vectors/mix";
import { Key } from "../api.js";
import { isHoverSlider } from "../behaviors/slider.js";
import { layoutBox } from "../layout.js";
import { tooltipRaw } from "./tooltip.js";
const ramp = ({
gui,
layout,
id,
ramp: ramp2,
mode = 0,
info,
eps = 0.05,
samples = 100,
fill = gui.textColor(false)
}) => {
const { x, y, w, h } = layoutBox(layout);
const maxX = x + w;
const maxY = y + h;
const pos = [x, maxY];
const maxPos = [maxX, y];
const key = hash([x, y, w, h]);
gui.registerID(id, key);
const box = gui.resource(id, key, () => rect([x, y], [w, h]));
const hover = isHoverSlider(gui, id, box, "move");
const stops = ramp2.stops;
let selID = -1;
let sel;
let res;
const focused = gui.requestFocus(id);
if (hover) {
sel = clamp01_2(null, fit2([], gui.mouse, pos, maxPos, ZERO2, ONE2));
selID = ramp2.closestIndex(sel[0], eps);
if (gui.isMouseDown()) {
gui.activeID = id;
if (selID >= 0) {
stops[selID] = sel;
} else {
ramp2.setStopAt(
roundTo(sel[0], 1e-3),
roundTo(sel[1], 1e-3),
eps
);
}
res = ramp2;
}
if (focused && selID >= 0 && __handleRampKeys(gui, ramp2, selID)) {
res = ramp2;
}
info && gui.draw && tooltipRaw(gui, info);
}
if (gui.draw) {
box.attribs = {
fill: gui.bgColor(hover || focused),
stroke: gui.focusColor(id)
};
gui.add(
box,
gui.resource(
id,
hash(stops.flatMap((x2) => x2)) + mode,
() => polygon(
[
[x, maxY],
mix2([], pos, maxPos, [0, stops[0][1]]),
...__rampVertices(ramp2, pos, maxPos, samples),
mix2([], pos, maxPos, [1, stops[stops.length - 1][1]]),
[maxX, maxY]
],
{ fill }
)
),
...stops.map(([t], i) => {
const xx = mix(x, maxX, t);
return triangle(
[
[xx - 5, maxY],
[xx + 5, maxY],
[xx, maxY - 5]
],
{ fill: gui.fgColor(selID === i) }
);
})
);
if (sel) {
const [cx, cy] = mix2([], pos, maxPos, sel);
gui.add(
group({ stroke: gui.fgColor(selID >= 0) }, [
line([x, cy], [maxX, cy]),
line([cx, y], [cx, maxY])
])
);
}
}
gui.lastID = id;
return res;
};
const __rampVertices = (ramp2, pos, maxPos, numSamples) => map((p) => mix2(p, pos, maxPos, p), ramp2.samples(numSamples));
const __handleRampKeys = (gui, ramp2, selID) => {
switch (gui.key) {
case Key.TAB:
gui.switchFocus();
break;
case "x":
case Key.DELETE:
ramp2.removeStopAtIndex(selID);
return true;
default:
}
};
export {
ramp
};