diff --git a/assets/imgui/imgui-layout.drawio b/assets/layout/grid-layout.drawio similarity index 100% rename from assets/imgui/imgui-layout.drawio rename to assets/layout/grid-layout.drawio diff --git a/assets/imgui/imgui-layout.png b/assets/layout/grid-layout.png similarity index 100% rename from assets/imgui/imgui-layout.png rename to assets/layout/grid-layout.png diff --git a/examples/fft-synth/package.json b/examples/fft-synth/package.json index eafc585c85..18ffed985c 100644 --- a/examples/fft-synth/package.json +++ b/examples/fft-synth/package.json @@ -21,6 +21,7 @@ "@thi.ng/dsp": "latest", "@thi.ng/hdom-canvas": "latest", "@thi.ng/imgui": "latest", + "@thi.ng/layout": "latest", "@thi.ng/math": "latest", "@thi.ng/random": "latest", "@thi.ng/rstream": "latest", @@ -38,8 +39,9 @@ "thi.ng": { "readme": [ "dsp", - "imgui", "hdom-canvas", + "imgui", + "layout", "random" ], "screenshot": "examples/fft-synth.png" diff --git a/examples/fft-synth/src/gui.ts b/examples/fft-synth/src/gui.ts index 0a30e8bb90..4b3aa72469 100644 --- a/examples/fft-synth/src/gui.ts +++ b/examples/fft-synth/src/gui.ts @@ -1,14 +1,13 @@ import { buttonH, DEFAULT_THEME, - GridLayout, - gridLayout, IMGUI, sliderH, sliderVGroup, textLabel, toggle } from "@thi.ng/imgui"; +import { gridLayout, GridLayout } from "@thi.ng/layout"; import { initAudio, isAudioActive, stopAudio } from "./audio"; import { toggleAutoMode } from "./automode"; import { diff --git a/examples/imgui/package.json b/examples/imgui/package.json index b257b28f01..c96e92e20d 100644 --- a/examples/imgui/package.json +++ b/examples/imgui/package.json @@ -23,6 +23,7 @@ "@thi.ng/hdom-canvas": "latest", "@thi.ng/hiccup-carbon-icons": "latest", "@thi.ng/imgui": "latest", + "@thi.ng/layout": "latest", "@thi.ng/math": "latest", "@thi.ng/paths": "latest", "@thi.ng/rstream": "latest", @@ -45,6 +46,7 @@ "hdom", "hdom-canvas", "imgui", + "layout", "rstream", "rstream-gestures", "transducers-hdom" diff --git a/examples/imgui/src/index.ts b/examples/imgui/src/index.ts index 010bceabe5..39ee601e6c 100644 --- a/examples/imgui/src/index.ts +++ b/examples/imgui/src/index.ts @@ -1,21 +1,23 @@ -import { History, Atom } from "@thi.ng/atom"; +import { Atom, History } from "@thi.ng/atom"; import { timedResult } from "@thi.ng/bench"; -import { line, pathFromSvg, normalizedPath } from "@thi.ng/geom"; +import { line, normalizedPath, pathFromSvg } from "@thi.ng/geom"; import { canvas } from "@thi.ng/hdom-canvas"; import { DOWNLOAD, RESTART } from "@thi.ng/hiccup-carbon-icons"; import { buttonH, buttonV, DEFAULT_THEME, + dialGroup, dropdown, - GridLayout, GUITheme, iconButton, IMGUI, + Key, NONE, radialMenu, radio, ring, + ringGroup, sliderH, sliderHGroup, sliderVGroup, @@ -23,22 +25,39 @@ import { textLabel, textLabelRaw, toggle, - xyPad, - Key, - gridLayout, - layoutBox, - dialGroup, - ringGroup + xyPad } from "@thi.ng/imgui"; +import { gridLayout, GridLayout, layoutBox } from "@thi.ng/layout"; import { clamp, PI } from "@thi.ng/math"; import { setInMany } from "@thi.ng/paths"; -import { sync, fromDOMEvent, sidechainPartition, fromRAF, merge, fromAtom } from "@thi.ng/rstream"; +import { + fromAtom, + fromDOMEvent, + fromRAF, + merge, + sidechainPartition, + sync +} from "@thi.ng/rstream"; import { gestureStream } from "@thi.ng/rstream-gestures"; import { float } from "@thi.ng/strings"; -import { step, map, comp, mapcat, iterator } from "@thi.ng/transducers"; +import { + comp, + iterator, + map, + mapcat, + step +} from "@thi.ng/transducers"; import { updateDOM } from "@thi.ng/transducers-hdom"; import { sma } from "@thi.ng/transducers-stats"; -import { ZERO2, setC2, min2, Vec, vecOf, add2, hash } from "@thi.ng/vectors"; +import { + add2, + hash, + min2, + setC2, + Vec, + vecOf, + ZERO2 +} from "@thi.ng/vectors"; // define theme colors in RGBA format for future compatibility with // WebGL backend @@ -76,14 +95,17 @@ const THEME_IDS = ["Default", "Raspberry"]; // helper function to normalize hiccup icon paths // (transforms each path into one only consisting of cubic spline segments) -const mkIcon = (icon: any[]) => - [ - "g", { stroke: "none" }, - ...iterator( - comp(mapcat((p) => pathFromSvg(p[1].d)), map(normalizedPath)), - icon.slice(2) - ) - ]; +const mkIcon = (icon: any[]) => [ + "g", + { stroke: "none" }, + ...iterator( + comp( + mapcat((p) => pathFromSvg(p[1].d)), + map(normalizedPath) + ), + icon.slice(2) + ) +]; // icon definitions (from @thi.ng/hiccup-carbon-icons) const ICON1 = mkIcon(DOWNLOAD); @@ -102,24 +124,27 @@ const DB = new History( txt: "Hello there! This is a test, do not panic!", toggles: new Array(12).fill(false), flags: [true, false], - radio: 0, + radio: 0 }), // max. 500 undo steps 500 ); // theme merging helper -const themeForID = (theme: number): Partial => - ({ ...THEMES[theme % THEMES.length], font: FONT, cursorBlink: 0 }); +const themeForID = (theme: number): Partial => ({ + ...THEMES[theme % THEMES.length], + font: FONT, + cursorBlink: 0 +}); // state update handler for `rgb` value // if Alt key is pressed when this handler executes, // then all values will be set uniformly... const setRGB = (gui: IMGUI, res: number[]) => res !== undefined && - (gui.isAltDown() - ? DB.resetIn("rgb", vecOf(3, res[1])) - : DB.resetIn(["rgb", res[0]], res[1])); + (gui.isAltDown() + ? DB.resetIn("rgb", vecOf(3, res[1])) + : DB.resetIn(["rgb", res[0]], res[1])); // main application const app = () => { @@ -145,12 +170,12 @@ const app = () => { // merge all event streams into a single input to `main` // (we don't actually care about their actual values and merely // use them as mechanism to trigger updates) - merge({ + merge({ src: [ // mouse & touch events gestureStream(canv, {}).subscribe({ next(e) { - gui.setMouse(e.pos, e.buttons) + gui.setMouse(e.pos, e.buttons); } }), // keydown & undo/redo handler: @@ -161,7 +186,10 @@ const app = () => { if (e.key === Key.TAB) { e.preventDefault(); } - if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "z") { + if ( + (e.metaKey || e.ctrlKey) && + e.key.toLowerCase() === "z" + ) { e.shiftKey ? DB.redo() : DB.undo(); } else { gui.setKey(e); @@ -169,13 +197,21 @@ const app = () => { } }), fromDOMEvent(window, "keyup").subscribe({ - next(e) { gui.setKey(e); } + next(e) { + gui.setKey(e); + } }), fromDOMEvent(window, "resize").subscribe({ next() { maxW = Math.min(maxW, window.innerWidth - 16); - setC2(size, window.innerWidth, window.innerHeight); - DB.swapIn("pos", (pos: Vec) => min2([], pos, size)); + setC2( + size, + window.innerWidth, + window.innerHeight + ); + DB.swapIn("pos", (pos: Vec) => + min2([], pos, size) + ); } }) ] @@ -200,13 +236,16 @@ const app = () => { gui.beginDisabled(radialActive); // button components return true if clicked - if (buttonH(gui, grid, "show", state.uiVisible ? "Hide UI" : "Show UI")) { + if ( + buttonH(gui, grid, "show", state.uiVisible ? "Hide UI" : "Show UI") + ) { DB.resetIn("uiVisible", !state.uiVisible); } if (state.uiVisible) { let inner: GridLayout; let inner2: GridLayout; let res: any; + // prettier-ignore switch(state.uiMode) { case 0: // create empty row @@ -365,19 +404,42 @@ const app = () => { } // menu backdrop gui.add( - gui.resource("radial", hash(radialPos) + 1, ()=> - ["g",{}, - ["radialGradient", - { id: "shadow", from: radialPos, to: radialPos, r1: 5, r2: 300}, - [[0, [1, 1, 1, 0.8]], [0.5, [1, 1, 1, 0.66]], [1, [1, 1, 1, 0]]] - ], - ["circle", { fill: "$shadow" }, radialPos, 300] - ] - ) + gui.resource("radial", hash(radialPos) + 1, () => [ + "g", + {}, + [ + "radialGradient", + { + id: "shadow", + from: radialPos, + to: radialPos, + r1: 5, + r2: 300 + }, + [ + [0, [1, 1, 1, 0.8]], + [0.5, [1, 1, 1, 0.66]], + [1, [1, 1, 1, 0]] + ] + ], + ["circle", { fill: "$shadow" }, radialPos, 300] + ]) ); let res: number | undefined; - if ((res = radialMenu(gui, "radial", radialPos[0], radialPos[1], 100, RADIAL_LABELS, [])) !== undefined) { - DB.swap((db) => setInMany(db, "uiMode", res, "uiVisible", true)); + if ( + (res = radialMenu( + gui, + "radial", + radialPos[0], + radialPos[1], + 100, + RADIAL_LABELS, + [] + )) !== undefined + ) { + DB.swap((db) => + setInMany(db, "uiMode", res, "uiVisible", true) + ); } gui.add( textLabelRaw( @@ -399,7 +461,7 @@ const app = () => { radialActive = false; } // resize - const [w,h] = size; + const [w, h] = size; if ( gui.activeID === NONE && gui.isMouseDown() && @@ -412,7 +474,11 @@ const app = () => { const statLayout = gridLayout(10, h - 10 - 3 * 14, w, 1, 14, 0); textLabel(gui, statLayout, `Key: ${key}`); textLabel(gui, statLayout, `Focus: ${focusID} / ${lastID}`); - textLabel(gui, statLayout, `IDs: ${hotID || "none"} / ${activeID || "none"}`); + textLabel( + gui, + statLayout, + `IDs: ${hotID || "none"} / ${activeID || "none"}` + ); gui.end(); }; @@ -434,11 +500,18 @@ const app = () => { timedResult(() => { updateGUI(false); updateGUI(true); - } - )[1]); + })[1] + ); // since the MA will only be available after the configured period, // we will only display stats when they're ready... - t != null && gui.add(textLabelRaw([10, height - 10 - 4 * 14], "#ff0", `GUI time: ${F2(t)}ms`)); + t != null && + gui.add( + textLabelRaw( + [10, height - 10 - 4 * 14], + "#ff0", + `GUI time: ${F2(t)}ms` + ) + ); // return hdom-canvas component with embedded GUI return [ _canvas, @@ -473,22 +546,19 @@ const app = () => { // once the 1st frame renders, the canvas component will create and attach // event streams to this stream sync, which are then used to trigger future // updates on demand... -const main = sync({ +const main = sync({ src: { state: fromAtom(DB) - }, + } }); // transform the stream: main // group potentially higher frequency event updates & sync with RAF // to avoid extraneous real DOM/Canvas updates - .subscribe(sidechainPartition(fromRAF())) + .subscribe(sidechainPartition(fromRAF())) // then apply main compoment function & apply hdom - .transform( - map(app()), - updateDOM() - ); + .transform(map(app()), updateDOM()); // HMR handling / cleanup if (process.env.NODE_ENV !== "production") { diff --git a/packages/imgui/README.md b/packages/imgui/README.md index 2ec1e326de..c1ae9b49ce 100644 --- a/packages/imgui/README.md +++ b/packages/imgui/README.md @@ -1,6 +1,6 @@ -# ![@thi.ng/imgui](https://media.thi.ng/umbrella/banners/thing-imgui.svg?1581297789) +# ![@thi.ng/imgui](https://media.thi.ng/umbrella/banners/thing-imgui.svg?1581555259) [![npm version](https://img.shields.io/npm/v/@thi.ng/imgui.svg)](https://www.npmjs.com/package/@thi.ng/imgui) ![npm downloads](https://img.shields.io/npm/dm/@thi.ng/imgui.svg) @@ -119,25 +119,27 @@ res !== undefined && STATE.resetIn("foo", res); ### Layout support -Most component functions exist in two versions: Using a layout manager -or not (`Raw` suffix, e.g. `buttonRaw`). The latter versions are more -"low-level" & verbose to use, but offer complete layout freedom and are -re-used by other component types. - -Currently, this package features only a single grid layout type, but -components are not hard-coded to require it, and those which do need a -layout manager only expect a `ILayout` or `IGridLayout` interface, -allowing for custom implementations. Furthermore / alternatively, we -also define a simple [`LayoutBox` +Most component functions exist in two versions: Using a +[@thi.ng/layout](https://github.com/thi-ng/umbrella/tree/master/packages/layout)-compatible +grid layout manager or not (e.g. `dial` vs. `dialRaw`). The latter +versions are more "low-level" & verbose to use, but offer complete +layout freedom and are re-used by other component types. + +The components in this package not needing a layout manager are only +expecting a `ILayout` or `IGridLayout` interface, allowing for custom +implementations. Furthermore / alternatively, the +[@thi.ng/layout](https://github.com/thi-ng/umbrella/tree/develop/packages/layout) +package also defines a [`LayoutBox` interface](https://github.com/thi-ng/umbrella/tree/develop/packages/imgui/src/api.ts), -which can be passed instead and too is what `ILayout` implementations -are expected to produce when allocating space for a component. +which can be passed instead and too is the type `ILayout` +implementations are expected to produce when allocating space for a +component. The `GridLayout` class supports infinite nesting and column/row-based space allocation, based on an initial configuration and supporting multiple column/row spans. -![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/imgui/imgui-layout.png) +![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/layout/grid-layout.png) The code producing this structure: @@ -225,7 +227,7 @@ Some of the most obvious missing features: yarn add @thi.ng/imgui ``` -Package sizes (gzipped): ESM: 7.0KB / CJS: 7.2KB / UMD: 7.2KB +Package sizes (gzipped): ESM: 6.6KB / CJS: 6.8KB / UMD: 6.7KB ## Dependencies @@ -235,6 +237,7 @@ Package sizes (gzipped): ESM: 7.0KB / CJS: 7.2KB / UMD: 7.2KB - [@thi.ng/geom-api](https://github.com/thi-ng/umbrella/tree/develop/packages/geom-api) - [@thi.ng/geom-isec](https://github.com/thi-ng/umbrella/tree/develop/packages/geom-isec) - [@thi.ng/geom-tessellate](https://github.com/thi-ng/umbrella/tree/develop/packages/geom-tessellate) +- [@thi.ng/layout](https://github.com/thi-ng/umbrella/tree/develop/packages/layout) - [@thi.ng/math](https://github.com/thi-ng/umbrella/tree/develop/packages/math) - [@thi.ng/transducers](https://github.com/thi-ng/umbrella/tree/develop/packages/transducers) - [@thi.ng/vectors](https://github.com/thi-ng/umbrella/tree/develop/packages/vectors) diff --git a/packages/imgui/README.tpl.md b/packages/imgui/README.tpl.md index 0370c50921..4da9e92a9e 100644 --- a/packages/imgui/README.tpl.md +++ b/packages/imgui/README.tpl.md @@ -104,25 +104,27 @@ res !== undefined && STATE.resetIn("foo", res); ### Layout support -Most component functions exist in two versions: Using a layout manager -or not (`Raw` suffix, e.g. `buttonRaw`). The latter versions are more -"low-level" & verbose to use, but offer complete layout freedom and are -re-used by other component types. - -Currently, this package features only a single grid layout type, but -components are not hard-coded to require it, and those which do need a -layout manager only expect a `ILayout` or `IGridLayout` interface, -allowing for custom implementations. Furthermore / alternatively, we -also define a simple [`LayoutBox` +Most component functions exist in two versions: Using a +[@thi.ng/layout](https://github.com/thi-ng/umbrella/tree/master/packages/layout)-compatible +grid layout manager or not (e.g. `dial` vs. `dialRaw`). The latter +versions are more "low-level" & verbose to use, but offer complete +layout freedom and are re-used by other component types. + +The components in this package not needing a layout manager are only +expecting a `ILayout` or `IGridLayout` interface, allowing for custom +implementations. Furthermore / alternatively, the +[@thi.ng/layout](https://github.com/thi-ng/umbrella/tree/develop/packages/layout) +package also defines a [`LayoutBox` interface](https://github.com/thi-ng/umbrella/tree/develop/packages/imgui/src/api.ts), -which can be passed instead and too is what `ILayout` implementations -are expected to produce when allocating space for a component. +which can be passed instead and too is the type `ILayout` +implementations are expected to produce when allocating space for a +component. The `GridLayout` class supports infinite nesting and column/row-based space allocation, based on an initial configuration and supporting multiple column/row spans. -![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/master/assets/imgui/imgui-layout.png) +![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/layout/grid-layout.png) The code producing this structure: diff --git a/packages/imgui/package.json b/packages/imgui/package.json index f8eaa834dc..35c70f3bbc 100644 --- a/packages/imgui/package.json +++ b/packages/imgui/package.json @@ -44,6 +44,7 @@ "@thi.ng/geom-api": "^1.0.1", "@thi.ng/geom-isec": "^0.4.1", "@thi.ng/geom-tessellate": "^0.2.12", + "@thi.ng/layout": "^0.0.1", "@thi.ng/math": "^1.6.0", "@thi.ng/transducers": "^6.2.1", "@thi.ng/vectors": "^4.0.3" diff --git a/packages/imgui/src/api.ts b/packages/imgui/src/api.ts index f8fdf7fab3..21ecd4a324 100644 --- a/packages/imgui/src/api.ts +++ b/packages/imgui/src/api.ts @@ -1,5 +1,4 @@ import { Predicate } from "@thi.ng/api"; -import { ReadonlyVec } from "@thi.ng/vectors"; export type Color = string | number | number[]; @@ -32,123 +31,6 @@ export interface IMGUIOpts { theme?: Partial; } -export interface LayoutBox { - /** - * Top-left corner X - */ - x: number; - /** - * Top-left corner Y - */ - y: number; - /** - * Box width (based on requested col span and inner gutter(s)) - */ - w: number; - /** - * Box height (based on requested row span and inner gutter(s)) - */ - h: number; - /** - * Single cell column width (always w/o col span), based on - * layout's available space and configured number of columns. - */ - cw: number; - /** - * Single cell row height (always same as `rowHeight` arg given to - * layout ctor). - */ - ch: number; - /** - * Gutter size. - */ - gap: number; -} - -export interface ILayout { - next(opts?: O): T; -} - -export interface IGridLayout extends ILayout<[number, number], LayoutBox> { - readonly x: number; - readonly y: number; - readonly width: number; - readonly cols: number; - readonly cellW: number; - readonly cellH: number; - readonly gap: number; - - /** - * Returns the number of columns for given width. - * - * @param width - - */ - colsForWidth(width: number): number; - - /** - * Returns the number of rows for given height. - * - * @param height - - */ - rowsForHeight(height: number): number; - - /** - * Calculates the required number of columns & rows for the given - * size. - * - * @param size - - */ - spansForSize(size: ReadonlyVec): [number, number]; - spansForSize(w: number, h: number): [number, number]; - - /** - * Returns a squared {@link LayoutBox} based on this layout's column - * width. This box will consume `ceil(columnWidth / rowHeight)` - * rows, but the returned box height might be less to satisfy the - * square constraint. - */ - nextSquare(): LayoutBox; - - /** - * Requests a `spans` sized cell from this layout (via `.next()`) - * and creates and returns a new child {@link GridLayout} for the returned - * box / grid cell. This child layout is configured to use `cols` - * columns and shares same `gap` as this (parent) layout. The - * configured row span only acts as initial minimum vertical space - * reseervation, but is allowed to grow and if needed will propagate - * the new space requirements to parent layouts. - * - * Note: this size child-parent size propagation ONLY works until - * the next cell is requested from any parent. IOW, child layouts - * MUST be completed/populated first before continuing with - * siblings/ancestors of this current layout. - * - * ``` - * // single column layout (default config) - * const outer = gridLayout(null, 0, 0, 200, 1, 16, 4); - * - * // add button (full 1st row) - * button(gui, outer, "foo",...); - * - * // 2-column nested layout (2nd row) - * const inner = outer.nest(2) - * // these buttons are on same row - * button(gui, inner, "bar",...); - * button(gui, inner, "baz",...); - * - * // continue with outer, create empty row - * outer.next(); - * - * // continue with outer (4th row) - * button(gui, outer, "bye",...); - * ``` - * - * @param cols - columns in nested layout - * @param spans - default [1, 1] (i.e. size of single cell) - */ - nest(cols: number, spans?: [number, number]): IGridLayout; -} - export const enum MouseButton { LEFT = 1, RIGHT = 2, diff --git a/packages/imgui/src/components/button.ts b/packages/imgui/src/components/button.ts index 2222c41a4a..2fe27215f7 100644 --- a/packages/imgui/src/components/button.ts +++ b/packages/imgui/src/components/button.ts @@ -1,16 +1,11 @@ import { rect } from "@thi.ng/geom"; import { IShape } from "@thi.ng/geom-api"; +import { IGridLayout, isLayout, LayoutBox } from "@thi.ng/layout"; import { hash, ZERO2 } from "@thi.ng/vectors"; -import { - Color, - Hash, - IGridLayout, - LayoutBox -} from "../api"; +import { Color, Hash } from "../api"; import { handleButtonKeys, isHoverButton } from "../behaviors/button"; import { IMGUI } from "../gui"; import { labelHash } from "../hash"; -import { isLayout } from "../layout"; import { textLabelRaw, textTransformH, textTransformV } from "./textlabel"; import { tooltipRaw } from "./tooltip"; diff --git a/packages/imgui/src/components/dial.ts b/packages/imgui/src/components/dial.ts index a68a8fe8d3..32e164bf03 100644 --- a/packages/imgui/src/components/dial.ts +++ b/packages/imgui/src/components/dial.ts @@ -1,5 +1,6 @@ import { Fn } from "@thi.ng/api"; import { circle, line } from "@thi.ng/geom"; +import { IGridLayout, isLayout, LayoutBox } from "@thi.ng/layout"; import { HALF_PI, norm, @@ -7,12 +8,10 @@ import { TAU } from "@thi.ng/math"; import { cartesian2, hash } from "@thi.ng/vectors"; -import { IGridLayout, LayoutBox } from "../api"; import { dialVal } from "../behaviors/dial"; import { handleSlider1Keys, isHoverSlider } from "../behaviors/slider"; import { IMGUI } from "../gui"; import { valHash } from "../hash"; -import { isLayout } from "../layout"; import { textLabelRaw } from "./textlabel"; import { tooltipRaw } from "./tooltip"; diff --git a/packages/imgui/src/components/dropdown.ts b/packages/imgui/src/components/dropdown.ts index 977c6a1523..13de9d4364 100644 --- a/packages/imgui/src/components/dropdown.ts +++ b/packages/imgui/src/components/dropdown.ts @@ -1,8 +1,13 @@ import { polygon } from "@thi.ng/geom"; +import { + gridLayout, + IGridLayout, + isLayout, + LayoutBox +} from "@thi.ng/layout"; import { hash } from "@thi.ng/vectors"; -import { IGridLayout, Key, LayoutBox } from "../api"; +import { Key } from "../api"; import { IMGUI } from "../gui"; -import { gridLayout, isLayout } from "../layout"; import { buttonH } from "./button"; /** @@ -41,7 +46,11 @@ export const dropdown = ( gui.add( gui.resource(id, key + 1, () => polygon( - [[tx - 4, ty + 2], [tx + 4, ty + 2], [tx, ty - 2]], + [ + [tx - 4, ty + 2], + [tx + 4, ty + 2], + [tx, ty - 2] + ], { fill: gui.textColor(false) } @@ -82,7 +91,11 @@ export const dropdown = ( gui.add( gui.resource(id, key + 2, () => polygon( - [[tx - 4, ty - 2], [tx + 4, ty - 2], [tx, ty + 2]], + [ + [tx - 4, ty - 2], + [tx + 4, ty - 2], + [tx, ty + 2] + ], { fill: gui.textColor(false) } diff --git a/packages/imgui/src/components/icon-button.ts b/packages/imgui/src/components/icon-button.ts index 2d2e81cabd..2c5ba0728b 100644 --- a/packages/imgui/src/components/icon-button.ts +++ b/packages/imgui/src/components/icon-button.ts @@ -1,9 +1,8 @@ import { rect } from "@thi.ng/geom"; +import { IGridLayout, isLayout, LayoutBox } from "@thi.ng/layout"; import { hash } from "@thi.ng/vectors"; -import { IGridLayout, LayoutBox } from "../api"; import { IMGUI } from "../gui"; import { mixHash } from "../hash"; -import { isLayout } from "../layout"; import { buttonRaw } from "./button"; import { textLabelRaw } from "./textlabel"; diff --git a/packages/imgui/src/components/radio.ts b/packages/imgui/src/components/radio.ts index ad52eed757..208f5038e5 100644 --- a/packages/imgui/src/components/radio.ts +++ b/packages/imgui/src/components/radio.ts @@ -1,6 +1,10 @@ -import { IGridLayout, LayoutBox } from "../api"; +import { + gridLayout, + IGridLayout, + isLayout, + LayoutBox +} from "@thi.ng/layout"; import { IMGUI } from "../gui"; -import { gridLayout, isLayout } from "../layout"; import { toggle } from "./toggle"; export const radio = ( diff --git a/packages/imgui/src/components/ring.ts b/packages/imgui/src/components/ring.ts index dc722e92dc..99b2f66044 100644 --- a/packages/imgui/src/components/ring.ts +++ b/packages/imgui/src/components/ring.ts @@ -1,6 +1,7 @@ import { Fn } from "@thi.ng/api"; import { polygon } from "@thi.ng/geom"; import { pointInRect } from "@thi.ng/geom-isec"; +import { IGridLayout, isLayout, LayoutBox } from "@thi.ng/layout"; import { fitClamped, HALF_PI, @@ -11,12 +12,10 @@ import { } from "@thi.ng/math"; import { map, normRange } from "@thi.ng/transducers"; import { cartesian2, hash, Vec } from "@thi.ng/vectors"; -import { IGridLayout, LayoutBox } from "../api"; import { dialVal } from "../behaviors/dial"; import { handleSlider1Keys } from "../behaviors/slider"; import { IMGUI } from "../gui"; import { valHash } from "../hash"; -import { isLayout } from "../layout"; import { textLabelRaw } from "./textlabel"; import { tooltipRaw } from "./tooltip"; diff --git a/packages/imgui/src/components/sliderh.ts b/packages/imgui/src/components/sliderh.ts index 370e743621..f690ad7d98 100644 --- a/packages/imgui/src/components/sliderh.ts +++ b/packages/imgui/src/components/sliderh.ts @@ -1,12 +1,11 @@ import { Fn } from "@thi.ng/api"; import { rect } from "@thi.ng/geom"; +import { IGridLayout, isLayout, LayoutBox } from "@thi.ng/layout"; import { fit, norm } from "@thi.ng/math"; import { hash } from "@thi.ng/vectors"; -import { IGridLayout, LayoutBox } from "../api"; import { handleSlider1Keys, isHoverSlider, slider1Val } from "../behaviors/slider"; import { IMGUI } from "../gui"; import { valHash } from "../hash"; -import { isLayout } from "../layout"; import { textLabelRaw } from "./textlabel"; import { tooltipRaw } from "./tooltip"; diff --git a/packages/imgui/src/components/sliderv.ts b/packages/imgui/src/components/sliderv.ts index e476f47ae1..8880280c39 100644 --- a/packages/imgui/src/components/sliderv.ts +++ b/packages/imgui/src/components/sliderv.ts @@ -1,12 +1,11 @@ import { Fn } from "@thi.ng/api"; import { rect } from "@thi.ng/geom"; +import { IGridLayout, isLayout, LayoutBox } from "@thi.ng/layout"; import { fit, norm } from "@thi.ng/math"; import { hash, ZERO2 } from "@thi.ng/vectors"; -import { IGridLayout, LayoutBox } from "../api"; import { handleSlider1Keys, isHoverSlider, slider1Val } from "../behaviors/slider"; import { IMGUI } from "../gui"; import { valHash } from "../hash"; -import { isLayout } from "../layout"; import { textLabelRaw, textTransformV } from "./textlabel"; import { tooltipRaw } from "./tooltip"; diff --git a/packages/imgui/src/components/textfield.ts b/packages/imgui/src/components/textfield.ts index 5c42285f2e..1b50baa735 100644 --- a/packages/imgui/src/components/textfield.ts +++ b/packages/imgui/src/components/textfield.ts @@ -1,11 +1,11 @@ import { Predicate } from "@thi.ng/api"; import { rect } from "@thi.ng/geom"; +import { IGridLayout, isLayout, LayoutBox } from "@thi.ng/layout"; import { fitClamped } from "@thi.ng/math"; import { hash } from "@thi.ng/vectors"; -import { IGridLayout, Key, LayoutBox } from "../api"; +import { Key } from "../api"; import { isHoverSlider } from "../behaviors/slider"; import { IMGUI } from "../gui"; -import { isLayout } from "../layout"; import { textLabelRaw } from "./textlabel"; import { tooltipRaw } from "./tooltip"; diff --git a/packages/imgui/src/components/textlabel.ts b/packages/imgui/src/components/textlabel.ts index 6298f43111..237fa27e0c 100644 --- a/packages/imgui/src/components/textlabel.ts +++ b/packages/imgui/src/components/textlabel.ts @@ -1,13 +1,8 @@ import { isPlainObject } from "@thi.ng/checks"; +import { IGridLayout, isLayout, LayoutBox } from "@thi.ng/layout"; import { ReadonlyVec } from "@thi.ng/vectors"; -import { - Color, - GUITheme, - IGridLayout, - LayoutBox -} from "../api"; +import { Color, GUITheme } from "../api"; import { IMGUI } from "../gui"; -import { isLayout } from "../layout"; export const textLabel = ( gui: IMGUI, diff --git a/packages/imgui/src/components/toggle.ts b/packages/imgui/src/components/toggle.ts index 9b94cd7661..75d102937b 100644 --- a/packages/imgui/src/components/toggle.ts +++ b/packages/imgui/src/components/toggle.ts @@ -1,9 +1,8 @@ import { rect } from "@thi.ng/geom"; +import { IGridLayout, isLayout, LayoutBox } from "@thi.ng/layout"; import { hash } from "@thi.ng/vectors"; -import { IGridLayout, LayoutBox } from "../api"; import { handleButtonKeys, isHoverButton } from "../behaviors/button"; import { IMGUI } from "../gui"; -import { isLayout } from "../layout"; import { textLabelRaw } from "./textlabel"; import { tooltipRaw } from "./tooltip"; diff --git a/packages/imgui/src/components/xypad.ts b/packages/imgui/src/components/xypad.ts index 20a8f4bcd3..502911097a 100644 --- a/packages/imgui/src/components/xypad.ts +++ b/packages/imgui/src/components/xypad.ts @@ -1,7 +1,7 @@ import { Fn } from "@thi.ng/api"; import { line, rect } from "@thi.ng/geom"; +import { IGridLayout, LayoutBox } from "@thi.ng/layout"; import { fit2, hash, Vec } from "@thi.ng/vectors"; -import { IGridLayout, LayoutBox } from "../api"; import { handleSlider2Keys, isHoverSlider, slider2Val } from "../behaviors/slider"; import { IMGUI } from "../gui"; import { textLabelRaw } from "./textlabel"; @@ -44,7 +44,7 @@ export const xyPad = ( let box: LayoutBox; const ch = layout.cellH; const gap = layout.gap; - if (mode == -2) { + if (mode === -2) { box = layout.nextSquare(); } else { let rows = (mode > 0 ? mode : layout.cellW / (ch + gap)) | 0; diff --git a/packages/imgui/src/index.ts b/packages/imgui/src/index.ts index 617460890a..53e0378b71 100644 --- a/packages/imgui/src/index.ts +++ b/packages/imgui/src/index.ts @@ -1,7 +1,6 @@ export * from "./api"; export * from "./events"; export * from "./gui"; -export * from "./layout"; export * from "./components/button"; export * from "./components/dial"; diff --git a/packages/layout/.npmignore b/packages/layout/.npmignore new file mode 100644 index 0000000000..c6f44618c9 --- /dev/null +++ b/packages/layout/.npmignore @@ -0,0 +1,22 @@ +.ae +.cache +.meta +.nyc_output +*.gz +*.html +*.svg +*.tgz +*.h +*.o +*.wasm +*.tpl.md +bench +build +coverage +dev +doc +export +src* +test +tools +tsconfig.json diff --git a/packages/layout/LICENSE b/packages/layout/LICENSE new file mode 100644 index 0000000000..8dada3edaf --- /dev/null +++ b/packages/layout/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/layout/README.md b/packages/layout/README.md new file mode 100644 index 0000000000..594c3ed81f --- /dev/null +++ b/packages/layout/README.md @@ -0,0 +1,135 @@ + + +# ![@thi.ng/layout](https://media.thi.ng/umbrella/banners/thing-layout.svg?1581555255) + +[![npm version](https://img.shields.io/npm/v/@thi.ng/layout.svg)](https://www.npmjs.com/package/@thi.ng/layout) +![npm downloads](https://img.shields.io/npm/dm/@thi.ng/layout.svg) +[![Twitter Follow](https://img.shields.io/twitter/follow/thing_umbrella.svg?style=flat-square&label=twitter)](https://twitter.com/thing_umbrella) + +This project is part of the +[@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo. + +- [About](#about) + - [Status](#status) +- [Installation](#installation) +- [Dependencies](#dependencies) +- [Usage examples](#usage-examples) +- [API](#api) + - [GridLayout](#gridlayout) +- [Authors](#authors) +- [License](#license) + +## About + +TODO. + +Currently, this package features only a single grid layout allocator, as +well as more [generic supporting +types](https://github.com/thi-ng/umbrella/tree/develop/packages/layout/src/api.ts) +to define other layout types / implementations. + +### Status + +**ALPHA** - bleeding edge / work-in-progress + +## Installation + +```bash +yarn add @thi.ng/layout +``` + +Package sizes (gzipped): ESM: 0.6KB / CJS: 0.7KB / UMD: 0.8KB + +## Dependencies + +- [@thi.ng/api](https://github.com/thi-ng/umbrella/tree/develop/packages/api) +- [@thi.ng/checks](https://github.com/thi-ng/umbrella/tree/develop/packages/checks) + +## Usage examples + +Several demos in this repo's +[/examples](https://github.com/thi-ng/umbrella/tree/develop/examples) +directory are using this package. + +A selection: + +### fft-synth + +![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/examples/fft-synth.png) + +Interactive inverse FFT toy synth + +[Live demo](https://demo.thi.ng/umbrella/fft-synth/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/fft-synth) + +### imgui + +![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/imgui/imgui-all.png) + +Canvas based Immediate Mode GUI components + +[Live demo](https://demo.thi.ng/umbrella/imgui/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/imgui) + +## API + +[Generated API docs](https://docs.thi.ng/umbrella/layout/) + +### GridLayout + +The `GridLayout` class supports infinite nesting and column/row-based +space allocation, based on an initial configuration and supporting +multiple column/row spans. + +![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/layout/grid-layout.png) + +The code producing this structure: + +```ts +import { gridLayout } from "@thi.ng/layout"; + +// create a single column layout @ position 10,10 / 200px wide +// the last values are row height and cell spacing +const layout = gridLayout(10, 10, 200, 1, 16, 4); + +// get next layout box (1st row) +// usually you don't need to call .next() manually, but merely pass +// the layout instance to a component... +layout.next(); +// { x: 10, y: 10, w: 200, h: 16, cw: 200, ch: 16, gap: 4 } + +// 2nd row +layout.next(); +// { x: 10, y: 30, w: 200, h: 16, cw: 200, ch: 16, gap: 4 } + +// create nested 2-column layout (3rd row) +const twoCols = layout.nest(2); + +twoCols.next(); +// { x: 10, y: 50, w: 98, h: 16, cw: 98, ch: 16, gap: 4 } + +twoCols.next(); +// { x: 112, y: 50, w: 98, h: 16, cw: 98, ch: 16, gap: 4 } + +// now nest 3-columns in the 1st column of twoCols +// (i.e. now each column is 1/6th of the main layout's width) +const inner = twoCols.nest(3); + +// allocate with col/rowspan, here 1 column x 4 rows +inner.next([1, 4]) +// { x: 10, y: 70, w: 30, h: 76, cw: 30, ch: 16, gap: 4 } +inner.next([1, 4]) +// { x: 44, y: 70, w: 30, h: 76, cw: 30, ch: 16, gap: 4 } +inner.next([1, 4]) +// { x: 78, y: 70, w: 30, h: 76, cw: 30, ch: 16, gap: 4 } + +// back to twoCols (2nd column) +twoCols.next([1, 2]); +// { x: 112, y: 70, w: 98, h: 36, cw: 98, ch: 16, gap: 4 } +``` + +## Authors + +Karsten Schmidt + +## License + +© 2019 - 2020 Karsten Schmidt // Apache Software License 2.0 diff --git a/packages/layout/README.tpl.md b/packages/layout/README.tpl.md new file mode 100644 index 0000000000..880fb0b230 --- /dev/null +++ b/packages/layout/README.tpl.md @@ -0,0 +1,106 @@ +# ${pkg.banner} + +[![npm version](https://img.shields.io/npm/v/${pkg.name}.svg)](https://www.npmjs.com/package/${pkg.name}) +![npm downloads](https://img.shields.io/npm/dm/${pkg.name}.svg) +[![Twitter Follow](https://img.shields.io/twitter/follow/thing_umbrella.svg?style=flat-square&label=twitter)](https://twitter.com/thing_umbrella) + +This project is part of the +[@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo. + + + +## About + +${pkg.description} + +Currently, this package features only a single grid layout allocator, as +well as more [generic supporting +types](https://github.com/thi-ng/umbrella/tree/develop/packages/layout/src/api.ts) +to define other layout types / implementations. + +${status} + +${supportPackages} + +${relatedPackages} + +${blogPosts} + +## Installation + +```bash +yarn add ${pkg.name} +``` + +${pkg.size} + +## Dependencies + +${pkg.deps} + +${examples} + +## API + +${docLink} + +### GridLayout + +The `GridLayout` class supports infinite nesting and column/row-based +space allocation, based on an initial configuration and supporting +multiple column/row spans. + +![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/layout/grid-layout.png) + +The code producing this structure: + +```ts +import { gridLayout } from "@thi.ng/layout"; + +// create a single column layout @ position 10,10 / 200px wide +// the last values are row height and cell spacing +const layout = gridLayout(10, 10, 200, 1, 16, 4); + +// get next layout box (1st row) +// usually you don't need to call .next() manually, but merely pass +// the layout instance to a component... +layout.next(); +// { x: 10, y: 10, w: 200, h: 16, cw: 200, ch: 16, gap: 4 } + +// 2nd row +layout.next(); +// { x: 10, y: 30, w: 200, h: 16, cw: 200, ch: 16, gap: 4 } + +// create nested 2-column layout (3rd row) +const twoCols = layout.nest(2); + +twoCols.next(); +// { x: 10, y: 50, w: 98, h: 16, cw: 98, ch: 16, gap: 4 } + +twoCols.next(); +// { x: 112, y: 50, w: 98, h: 16, cw: 98, ch: 16, gap: 4 } + +// now nest 3-columns in the 1st column of twoCols +// (i.e. now each column is 1/6th of the main layout's width) +const inner = twoCols.nest(3); + +// allocate with col/rowspan, here 1 column x 4 rows +inner.next([1, 4]) +// { x: 10, y: 70, w: 30, h: 76, cw: 30, ch: 16, gap: 4 } +inner.next([1, 4]) +// { x: 44, y: 70, w: 30, h: 76, cw: 30, ch: 16, gap: 4 } +inner.next([1, 4]) +// { x: 78, y: 70, w: 30, h: 76, cw: 30, ch: 16, gap: 4 } + +// back to twoCols (2nd column) +twoCols.next([1, 2]); +// { x: 112, y: 70, w: 98, h: 36, cw: 98, ch: 16, gap: 4 } +``` + +## Authors + +${authors} + +## License + +© ${copyright} // ${license} diff --git a/packages/layout/api-extractor.json b/packages/layout/api-extractor.json new file mode 100644 index 0000000000..94972e6bed --- /dev/null +++ b/packages/layout/api-extractor.json @@ -0,0 +1,3 @@ +{ + "extends": "../../api-extractor.json" +} diff --git a/packages/layout/package.json b/packages/layout/package.json new file mode 100644 index 0000000000..befb550241 --- /dev/null +++ b/packages/layout/package.json @@ -0,0 +1,59 @@ +{ + "name": "@thi.ng/layout", + "version": "0.0.1", + "description": "TODO", + "module": "./index.js", + "main": "./lib/index.js", + "umd:main": "./lib/index.umd.js", + "typings": "./index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/layout", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && yarn build:es6 && node ../../scripts/bundle-module", + "build:release": "yarn clean && yarn build:es6 && node ../../scripts/bundle-module all", + "build:es6": "tsc --declaration", + "build:test": "rimraf build && tsc -p test/tsconfig.json", + "test": "mocha test", + "cover": "nyc mocha test && nyc report --reporter=lcov", + "clean": "rimraf *.js *.d.ts *.map .nyc_output build coverage doc lib", + "doc:readme": "../../scripts/generate-readme", + "doc:ae": "mkdir -p .ae/doc .ae/temp && node_modules/.bin/api-extractor run --local --verbose", + "doc": "node_modules/.bin/typedoc --mode modules --out doc src", + "pub": "yarn build:release && yarn publish --access public" + }, + "devDependencies": { + "@istanbuljs/nyc-config-typescript": "^1.0.1", + "@microsoft/api-extractor": "^7.7.7", + "@types/mocha": "^5.2.7", + "@types/node": "^13.5.0", + "mocha": "^7.0.0", + "nyc": "^15.0.0", + "ts-node": "^8.6.2", + "typedoc": "^0.16.8", + "typescript": "^3.7.5" + }, + "dependencies": { + "@thi.ng/api": "^6.7.1", + "@thi.ng/checks": "^2.5.0" + }, + "keywords": [ + "ES6", + "grid", + "layout", + "typescript", + "UI" + ], + "publishConfig": { + "access": "public" + }, + "sideEffects": false, + "thi.ng": { + "status": "alpha", + "year": 2019 + } +} diff --git a/packages/layout/src/api.ts b/packages/layout/src/api.ts new file mode 100644 index 0000000000..4a43a598a3 --- /dev/null +++ b/packages/layout/src/api.ts @@ -0,0 +1,116 @@ +export interface LayoutBox { + /** + * Top-left corner X + */ + x: number; + /** + * Top-left corner Y + */ + y: number; + /** + * Box width (based on requested col span and inner gutter(s)) + */ + w: number; + /** + * Box height (based on requested row span and inner gutter(s)) + */ + h: number; + /** + * Single cell column width (always w/o col span), based on + * layout's available space and configured number of columns. + */ + cw: number; + /** + * Single cell row height (always same as `rowHeight` arg given to + * layout ctor). + */ + ch: number; + /** + * Gutter size. + */ + gap: number; +} + +export interface ILayout { + next(opts?: O): T; +} + +export interface IGridLayout extends ILayout<[number, number], LayoutBox> { + readonly x: number; + readonly y: number; + readonly width: number; + readonly cols: number; + readonly cellW: number; + readonly cellH: number; + readonly gap: number; + + /** + * Returns the number of columns for given width. + * + * @param width - + */ + colsForWidth(width: number): number; + + /** + * Returns the number of rows for given height. + * + * @param height - + */ + rowsForHeight(height: number): number; + + /** + * Calculates the required number of columns & rows for the given + * size. + * + * @param size - + */ + spansForSize(size: ArrayLike): [number, number]; + spansForSize(w: number, h: number): [number, number]; + + /** + * Returns a squared {@link LayoutBox} based on this layout's column + * width. This box will consume `ceil(columnWidth / rowHeight)` + * rows, but the returned box height might be less to satisfy the + * square constraint. + */ + nextSquare(): LayoutBox; + + /** + * Requests a `spans` sized cell from this layout (via `.next()`) + * and creates and returns a new child {@link GridLayout} for the returned + * box / grid cell. This child layout is configured to use `cols` + * columns and shares same `gap` as this (parent) layout. The + * configured row span only acts as initial minimum vertical space + * reseervation, but is allowed to grow and if needed will propagate + * the new space requirements to parent layouts. + * + * Note: this size child-parent size propagation ONLY works until + * the next cell is requested from any parent. IOW, child layouts + * MUST be completed/populated first before continuing with + * siblings/ancestors of this current layout. + * + * ``` + * // single column layout (default config) + * const outer = gridLayout(null, 0, 0, 200, 1, 16, 4); + * + * // add button (full 1st row) + * button(gui, outer, "foo",...); + * + * // 2-column nested layout (2nd row) + * const inner = outer.nest(2) + * // these buttons are on same row + * button(gui, inner, "bar",...); + * button(gui, inner, "baz",...); + * + * // continue with outer, create empty row + * outer.next(); + * + * // continue with outer (4th row) + * button(gui, outer, "bye",...); + * ``` + * + * @param cols - columns in nested layout + * @param spans - default [1, 1] (i.e. size of single cell) + */ + nest(cols: number, spans?: [number, number]): IGridLayout; +} diff --git a/packages/layout/src/box.ts b/packages/layout/src/box.ts new file mode 100644 index 0000000000..7d8073deb3 --- /dev/null +++ b/packages/layout/src/box.ts @@ -0,0 +1,11 @@ +import { LayoutBox } from "./api"; + +export const layoutBox = ( + x: number, + y: number, + w: number, + h: number, + cw: number, + ch: number, + gap: number +): LayoutBox => ({ x, y, w, h, cw, ch, gap }); diff --git a/packages/imgui/src/layout.ts b/packages/layout/src/grid-layout.ts similarity index 82% rename from packages/imgui/src/layout.ts rename to packages/layout/src/grid-layout.ts index f157a48381..84eb2b7fcb 100644 --- a/packages/imgui/src/layout.ts +++ b/packages/layout/src/grid-layout.ts @@ -1,7 +1,5 @@ -import { implementsFunction } from "@thi.ng/checks"; import { isNumber } from "@thi.ng/checks"; -import { ReadonlyVec } from "@thi.ng/vectors"; -import { IGridLayout, ILayout, LayoutBox } from "./api"; +import { IGridLayout, LayoutBox } from "./api"; const DEFAULT_SPANS: [number, number] = [1, 1]; @@ -53,11 +51,11 @@ export class GridLayout implements IGridLayout { return Math.ceil(h / this.cellHG); } - spansForSize(size: ReadonlyVec): [number, number]; + spansForSize(size: ArrayLike): [number, number]; spansForSize(w: number, h: number): [number, number]; - spansForSize(w: ReadonlyVec | number, h?: number): [number, number] { - const [ww, hh] = isNumber(w) ? [w, h!] : w; - return [this.colsForWidth(ww), this.rowsForHeight(hh)]; + spansForSize(w: ArrayLike | number, h?: number): [number, number] { + const size = isNumber(w) ? [w, h!] : w; + return [this.colsForWidth(size[0]), this.rowsForHeight(size[1])]; } next(spans = DEFAULT_SPANS) { @@ -133,16 +131,3 @@ export const gridLayout = ( rowH = 16, gap = 4 ) => new GridLayout(null, x, y, width, cols, rowH, gap); - -export const layoutBox = ( - x: number, - y: number, - w: number, - h: number, - cw: number, - ch: number, - gap: number -) => ({ x, y, w, h, cw, ch, gap }); - -export const isLayout = (x: any): x is ILayout => - implementsFunction(x, "next"); diff --git a/packages/layout/src/index.ts b/packages/layout/src/index.ts new file mode 100644 index 0000000000..383c64f3cb --- /dev/null +++ b/packages/layout/src/index.ts @@ -0,0 +1,4 @@ +export * from "./api"; +export * from "./box"; +export * from "./grid-layout"; +export * from "./is-layout"; diff --git a/packages/layout/src/is-layout.ts b/packages/layout/src/is-layout.ts new file mode 100644 index 0000000000..7b52167ade --- /dev/null +++ b/packages/layout/src/is-layout.ts @@ -0,0 +1,5 @@ +import { implementsFunction } from "@thi.ng/checks"; +import { ILayout } from "./api"; + +export const isLayout = (x: any): x is ILayout => + implementsFunction(x, "next"); diff --git a/packages/layout/test/index.ts b/packages/layout/test/index.ts new file mode 100644 index 0000000000..53a8b89b5d --- /dev/null +++ b/packages/layout/test/index.ts @@ -0,0 +1,6 @@ +// import * as assert from "assert"; +// import { } from "../src"; + +describe("layout", () => { + it("tests pending"); +}); diff --git a/packages/layout/test/tsconfig.json b/packages/layout/test/tsconfig.json new file mode 100644 index 0000000000..f6e63560dd --- /dev/null +++ b/packages/layout/test/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../build", + "module": "commonjs" + }, + "include": [ + "./**/*.ts", + "../src/**/*.ts" + ] +} diff --git a/packages/layout/tsconfig.json b/packages/layout/tsconfig.json new file mode 100644 index 0000000000..893b9979c5 --- /dev/null +++ b/packages/layout/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": ".", + "module": "es6", + "target": "es6" + }, + "include": [ + "./src/**/*.ts" + ] +}