Skip to content

Commit

Permalink
feat(imgui): add dial widget, extract key handlers, update layout
Browse files Browse the repository at this point in the history
- add dial/diaRaw() widgets
- extract button & slider value updaters / key handlers
- add GridLayout.nextSquare()
- update button, toggle, sliders & xyPad widgets
  • Loading branch information
postspectacular committed Aug 8, 2019
1 parent 7e0bfeb commit d3d2b27
Show file tree
Hide file tree
Showing 12 changed files with 314 additions and 150 deletions.
2 changes: 1 addition & 1 deletion packages/imgui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"build:test": "rimraf build && tsc -p test/tsconfig.json",
"test": "yarn build:test && mocha build/test/*.js",
"cover": "yarn build:test && nyc mocha build/test/*.js && nyc report --reporter=lcov",
"clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib components",
"clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib behaviors components",
"doc": "node_modules/.bin/typedoc --mode modules --out doc --ignoreCompilerErrors src",
"pub": "yarn build:release && yarn publish --access public"
},
Expand Down
2 changes: 2 additions & 0 deletions packages/imgui/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ export interface IGridLayout extends ILayout<[number, number], LayoutBox> {
readonly cellH: number;
readonly gap: number;

nextSquare(): LayoutBox;

nest(cols: number, spans?: [number, number]): IGridLayout;
}

Expand Down
14 changes: 14 additions & 0 deletions packages/imgui/src/behaviors/button.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Key } from "../api";
import { IMGUI } from "../gui";

export const handleButtonKeys = (gui: IMGUI) => {
switch (gui.key) {
case Key.TAB:
gui.switchFocus();
break;
case Key.ENTER:
case Key.SPACE:
return true;
default:
}
};
73 changes: 73 additions & 0 deletions packages/imgui/src/behaviors/slider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { clamp, roundTo } from "@thi.ng/math";
import {
add2,
clamp2,
round2,
Vec
} from "@thi.ng/vectors";
import { Key } from "../api";
import { IMGUI } from "../gui";

export const slider1Val = (x: number, min: number, max: number, prec: number) =>
clamp(roundTo(x, prec), min, max);

export const slider2Val = (v: Vec, min: Vec, max: Vec, prec: number) =>
clamp2(v, round2(v, v, prec), min, max);

export const handleSlider1Keys = (
gui: IMGUI,
min: number,
max: number,
prec: number,
val: number[],
i = 0
) => {
switch (gui.key) {
case Key.TAB:
gui.switchFocus();
break;
case Key.UP:
case Key.DOWN: {
const step =
(gui.key === Key.UP ? prec : -prec) *
(gui.isShiftDown() ? 5 : 1);
val[i] = slider1Val(val[i] + step, min, max, prec);
gui.isAltDown() && val.fill(val[i]);
return true;
}
default:
}
};

export const handleSlider2Keys = (
gui: IMGUI,
min: Vec,
max: Vec,
prec: number,
val: Vec,
yUp: boolean
) => {
switch (gui.key) {
case Key.TAB:
gui.switchFocus();
break;
case Key.LEFT:
case Key.RIGHT: {
const step =
(gui.key === Key.RIGHT ? prec : -prec) *
(gui.isShiftDown() ? 5 : 1);
slider2Val(add2(val, val, [step, 0]), min, max, prec);
return true;
}
case Key.UP:
case Key.DOWN: {
const step =
(gui.key === Key.UP ? prec : -prec) *
(yUp ? 1 : -1) *
(gui.isShiftDown() ? 5 : 1);
slider2Val(add2(val, val, [0, step]), min, max, prec);
return true;
}
default:
}
};
20 changes: 4 additions & 16 deletions packages/imgui/src/components/button.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import { pointInside, rect } from "@thi.ng/geom";
import { IShape } from "@thi.ng/geom-api";
import { Vec } from "@thi.ng/vectors";
import {
IGridLayout,
Key,
LayoutBox,
MouseButton
} from "../api";
import { IGridLayout, LayoutBox, MouseButton } from "../api";
import { handleButtonKeys } from "../behaviors/button";
import { IMGUI } from "../gui";
import { isLayout } from "../layout";
import { textLabelRaw } from "./textlabel";
Expand Down Expand Up @@ -54,16 +50,8 @@ export const buttonRaw = (
};
gui.add(shape);
label && lpos && gui.add(textLabelRaw(lpos, gui.textColor(hover), label));
if (focused) {
switch (gui.key) {
case Key.TAB:
gui.switchFocus();
break;
case Key.ENTER:
case Key.SPACE:
return true;
default:
}
if (focused && handleButtonKeys(gui)) {
return true;
}
gui.lastID = id;
// only emit true on mouse release over this button
Expand Down
159 changes: 159 additions & 0 deletions packages/imgui/src/components/dial.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { Fn } from "@thi.ng/api";
import { polygon } from "@thi.ng/geom";
import { pointInCircle } from "@thi.ng/geom-isec";
import {
fit,
fitClamped,
HALF_PI,
mix,
norm,
PI,
TAU
} from "@thi.ng/math";
import { map, normRange } from "@thi.ng/transducers";
import {
cartesian2,
heading,
sub2,
Vec
} from "@thi.ng/vectors";
import { KeyModifier, LayoutBox, MouseButton } from "../api";
import { handleSlider1Keys, slider1Val } from "../behaviors/slider";
import { IMGUI } from "../gui";
import { GridLayout, isLayout } from "../layout";
import { textLabelRaw } from "./textlabel";
import { tooltipRaw } from "./tooltip";

const arcVerts = (
o: Vec,
r: number,
start: number,
end: number,
thetaRes = 12
): Iterable<Vec> =>
r > 1
? map(
(t) => cartesian2(null, [r, mix(start, end, t)], o),
normRange(
Math.max(1, Math.abs(end - start) / (PI / thetaRes)) | 0
)
)
: [o];

export const dial = (
gui: IMGUI,
layout: GridLayout | LayoutBox,
id: string,
min: number,
max: number,
prec: number,
val: number[],
i: number,
rscale: number,
label?: string,
fmt?: Fn<number, string>,
info?: string
) => {
const { x, y, w, h, ch } = isLayout(layout) ? layout.nextSquare() : layout;
return dialRaw(
gui,
id,
x,
y,
w,
h,
min,
max,
prec,
val,
i,
rscale,
gui.theme.pad,
h + ch / 2 + gui.theme.baseLine,
label,
fmt,
info
);
};

export const dialRaw = (
gui: IMGUI,
id: string,
x: number,
y: number,
w: number,
h: number,
min: number,
max: number,
prec: number,
val: number[],
i: number,
rscale: number,
lx: number,
ly: number,
label?: string,
fmt?: Fn<number, string>,
info?: string
) => {
const r = Math.min(w, h) / 2;
const pos = [x + w / 2, y + h / 2];
const hover = pointInCircle(gui.mouse, pos, r);
let active = false;
const thetaGap = PI / 3;
const startTheta = HALF_PI + thetaGap / 2;
const endTheta = HALF_PI + TAU - thetaGap / 2;
if (hover) {
gui.hotID = id;
const aid = gui.activeID;
if ((aid === "" || aid === id) && gui.buttons == MouseButton.LEFT) {
gui.activeID = id;
active = true;
let theta = heading(sub2([], gui.mouse, pos)) - startTheta;
theta < -0.5 && (theta += TAU);
val[i] = slider1Val(
fit(Math.min(theta / (TAU - thetaGap)), 0, 1, min, max),
min,
max,
prec
);
if (gui.modifiers & KeyModifier.ALT) {
val.fill(val[i]);
}
}
info && tooltipRaw(gui, info);
}
const focused = gui.requestFocus(id);
const v = val[i];
const valTheta = startTheta + (TAU - thetaGap) * norm(v, min, max);
const r2 = r * rscale;
// adaptive arc resolution
const res = fitClamped(r, 15, 60, 12, 30);
const bgShape = polygon(
[
...arcVerts(pos, r, startTheta, endTheta, res),
...arcVerts(pos, r2, endTheta, startTheta, res)
],
{ fill: gui.bgColor(hover || focused), stroke: gui.focusColor(id) }
);
const valShape = polygon(
[
...arcVerts(pos, r, startTheta, valTheta, res),
...arcVerts(pos, r2, valTheta, startTheta, res)
],
{ fill: gui.fgColor(hover) }
);
gui.add(
bgShape,
valShape,
textLabelRaw(
[x + lx, y + ly],
gui.textColor(false),
(label ? label + " " : "") + (fmt ? fmt(v) : v)
)
);
if (focused && handleSlider1Keys(gui, min, max, prec, val, i)) {
return true;
}
gui.lastID = id;
return active;
};
36 changes: 7 additions & 29 deletions packages/imgui/src/components/sliderh.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,18 @@
import { Fn } from "@thi.ng/api";
import { pointInside, rect } from "@thi.ng/geom";
import {
clamp,
fit,
norm,
roundTo
} from "@thi.ng/math";
import { fit, norm } from "@thi.ng/math";
import {
IGridLayout,
Key,
KeyModifier,
LayoutBox,
MouseButton
} from "../api";
import { handleSlider1Keys, slider1Val } from "../behaviors/slider";
import { IMGUI } from "../gui";
import { isLayout } from "../layout";
import { textLabelRaw } from "./textlabel";
import { tooltipRaw } from "./tooltip";

const $ = (x: number, prec: number, min: number, max: number) =>
clamp(roundTo(x, prec), min, max);

export const sliderH = (
gui: IMGUI,
layout: IGridLayout | LayoutBox,
Expand Down Expand Up @@ -88,11 +80,11 @@ export const sliderHRaw = (
if ((aid === "" || aid === id) && gui.buttons == MouseButton.LEFT) {
gui.activeID = id;
active = true;
val[i] = $(
val[i] = slider1Val(
fit(gui.mouse[0], x, x + w - 1, min, max),
prec,
min,
max
max,
prec
);
if (gui.modifiers & KeyModifier.ALT) {
val.fill(val[i]);
Expand All @@ -119,22 +111,8 @@ export const sliderHRaw = (
(label ? label + " " : "") + (fmt ? fmt(v) : v)
)
);
if (focused) {
switch (gui.key) {
case Key.TAB:
gui.switchFocus();
break;
case Key.UP:
case Key.DOWN: {
const step =
(gui.key === Key.UP ? prec : -prec) *
(gui.isShiftDown() ? 5 : 1);
val[i] = $(v + step, prec, min, max);
gui.isAltDown() && val.fill(val[i]);
return true;
}
default:
}
if (focused && handleSlider1Keys(gui, min, max, prec, val, i)) {
return true;
}
gui.lastID = id;
return active;
Expand Down
Loading

0 comments on commit d3d2b27

Please sign in to comment.