thi-ng/umbrella

View on GitHub
packages/imgui/src/components/button.ts

Summary

Maintainability
A
45 mins
Test Coverage
import type { Fn5 } from "@thi.ng/api";
import type { IShape } from "@thi.ng/geom";
import { rect } from "@thi.ng/geom/rect";
import { ZERO2 } from "@thi.ng/vectors/api";
import { hash } from "@thi.ng/vectors/hash";
import type { ComponentOpts, GUITheme, Hash } from "../api.js";
import { handleButtonKeys, hoverButton } from "../behaviors/button.js";
import type { IMGUI } from "../gui.js";
import { labelHash } from "../hash.js";
import { layoutBox } from "../layout.js";
import { textLabelRaw, textTransformH, textTransformV } from "./textlabel.js";

export interface ButtonOpts extends ComponentOpts {
    /**
     * Label text when button is in hover state (by default the same as
     * {@link ComponentOpts.label}).
     */
    labelHover?: string;
}

export const buttonH = ({
    gui,
    layout,
    id,
    label,
    labelHover,
    info,
}: ButtonOpts) => {
    const { x, y, w, h } = layoutBox(layout);
    const key = hash([x, y, w, h]);
    return buttonRaw(
        gui,
        id,
        gui.resource(id, key, () => rect([x, y], [w, h])),
        key,
        label
            ? __mkLabel(gui, textTransformH, id, key, x, y, w, h, false, label)
            : undefined,
        labelHover
            ? // prettier-ignore
              __mkLabel(gui, textTransformH, id, key, x, y, w, h, true, labelHover)
            : undefined,
        info
    );
};

export const buttonV = ({
    gui,
    layout,
    rows,
    id,
    label,
    labelHover,
    info,
}: ButtonOpts & { rows: number }) => {
    const { x, y, w, h } = layoutBox(layout, [1, rows]);
    const key = hash([x, y, w, h]);
    return buttonRaw(
        gui,
        id,
        gui.resource(id, key, () => rect([x, y], [w, h])),
        key,
        label
            ? __mkLabel(gui, textTransformV, id, key, x, y, w, h, false, label)
            : undefined,
        labelHover
            ? // prettier-ignore
              __mkLabel(gui, textTransformV, id, key, x, y, w, h, true, labelHover)
            : undefined,
        info
    );
};

export const buttonRaw = (
    gui: IMGUI,
    id: string,
    shape: IShape,
    hash: Hash,
    label?: any,
    labelHover?: any,
    info?: string
) => {
    gui.registerID(id, hash);
    const hover = hoverButton(gui, id, shape, info);
    const focused = gui.requestFocus(id);
    if (gui.draw) {
        shape.attribs = {
            fill: hover ? gui.fgColor(true) : gui.bgColor(focused),
            stroke: gui.focusColor(id),
        };
        gui.add(shape);
        label && gui.add(hover && labelHover ? labelHover : label);
    }
    if (focused && handleButtonKeys(gui)) {
        return true;
    }
    gui.lastID = id;
    // only emit true on mouse release over this button
    // TODO extract as behavior function
    return !gui.buttons && gui.hotID === id && gui.activeID === id;
};

/** @internal */
const __mkLabel = (
    gui: IMGUI,
    tx: Fn5<GUITheme, number, number, number, number, number[]>,
    id: string,
    key: number,
    x: number,
    y: number,
    w: number,
    h: number,
    hover: boolean,
    label: string
) =>
    gui.resource(id, labelHash(key, label, gui.disabled), () =>
        textLabelRaw(
            ZERO2,
            {
                transform: tx(gui.theme, x, y, w, h),
                fill: gui.textColor(hover),
            },
            label
        )
    );