@thi.ng/layout 2.1.35 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Change Log
2
2
 
3
- - **Last updated**: 2023-10-23T07:37:37Z
3
+ - **Last updated**: 2023-10-30T14:31:56Z
4
4
  - **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
5
5
 
6
6
  All notable changes to this project will be documented in this file.
@@ -9,6 +9,20 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
9
9
  **Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
10
10
  and/or version bumps of transitive dependencies.
11
11
 
12
+ # [3.0.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/layout@3.0.0) (2023-10-30)
13
+
14
+ #### 🛑 Breaking changes
15
+
16
+ - add IGridLayout generics ([52bad17](https://github.com/thi-ng/umbrella/commit/52bad17))
17
+ - BREAKING CHANGE: IGridLayout requires a generic type now (for `.nest()`)
18
+ - update GridLayout class
19
+
20
+ #### 🚀 Features
21
+
22
+ - add StackedLayout, update types & GridLayout ([1c1281f](https://github.com/thi-ng/umbrella/commit/1c1281f))
23
+ - update StackedLayout.largestSpan() ([9ce54cd](https://github.com/thi-ng/umbrella/commit/9ce54cd))
24
+ - add optional max. size constraint arg
25
+
12
26
  ## [2.1.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/layout@2.1.0) (2021-11-17)
13
27
 
14
28
  #### 🚀 Features
package/README.md CHANGED
@@ -12,22 +12,27 @@ This project is part of the
12
12
 
13
13
  - [About](#about)
14
14
  - [Status](#status)
15
+ - [Related packages](#related-packages)
15
16
  - [Installation](#installation)
16
17
  - [Dependencies](#dependencies)
17
18
  - [Usage examples](#usage-examples)
18
19
  - [API](#api)
19
20
  - [GridLayout](#gridlayout)
21
+ - [StackedLayout](#stackedlayout)
20
22
  - [Authors](#authors)
21
23
  - [License](#license)
22
24
 
23
25
  ## About
24
26
 
25
- Configurable nested 2D grid layout manager.
27
+ Configurable nested 2D grid layout generators.
26
28
 
27
- Currently, this package features only a single grid layout allocator, as
28
- well as more [generic supporting
29
- types](https://github.com/thi-ng/umbrella/tree/develop/packages/layout/src/api.ts)
30
- to define other layout types / implementations.
29
+ Currently, this package features two grid layout strategies (each based on
30
+ requesting/allocating cells of a desired size), as well as more general
31
+ supporting types to define other layout types / implementations using the same
32
+ shared API.
33
+
34
+ A brief overview and comparison of the available strategies is provided further
35
+ below.
31
36
 
32
37
  ## Status
33
38
 
@@ -35,6 +40,10 @@ to define other layout types / implementations.
35
40
 
36
41
  [Search or submit any issues for this package](https://github.com/thi-ng/umbrella/issues?q=%5Blayout%5D+in%3Atitle)
37
42
 
43
+ ## Related packages
44
+
45
+ - [@thi.ng/imgui](https://github.com/thi-ng/umbrella/tree/develop/packages/imgui) - Immediate mode GUI with flexible state handling & data only shape output
46
+
38
47
  ## Installation
39
48
 
40
49
  ```bash
@@ -55,25 +64,25 @@ For Node.js REPL:
55
64
  const layout = await import("@thi.ng/layout");
56
65
  ```
57
66
 
58
- Package sizes (brotli'd, pre-treeshake): ESM: 624 bytes
67
+ Package sizes (brotli'd, pre-treeshake): ESM: 1.01 KB
59
68
 
60
69
  ## Dependencies
61
70
 
71
+ - [@thi.ng/arrays](https://github.com/thi-ng/umbrella/tree/develop/packages/arrays)
62
72
  - [@thi.ng/checks](https://github.com/thi-ng/umbrella/tree/develop/packages/checks)
63
73
 
64
74
  ## Usage examples
65
75
 
66
- Several demos in this repo's
76
+ Several projects in this repo's
67
77
  [/examples](https://github.com/thi-ng/umbrella/tree/develop/examples)
68
- directory are using this package.
78
+ directory are using this package:
69
79
 
70
- A selection:
71
-
72
- | Screenshot | Description | Live demo | Source |
73
- |:--------------------------------------------------------------------------------------------------------------------|:-------------------------------------------|:---------------------------------------------------|:--------------------------------------------------------------------------------|
74
- | <img src="https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/examples/fft-synth.png" width="240"/> | Interactive inverse FFT toy synth | [Demo](https://demo.thi.ng/umbrella/fft-synth/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/fft-synth) |
75
- | <img src="https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/imgui/imgui-all.png" width="240"/> | Canvas based Immediate Mode GUI components | [Demo](https://demo.thi.ng/umbrella/imgui/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/imgui) |
76
- | <img src="https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/examples/imgui-basics.png" width="240"/> | Minimal IMGUI usage example | [Demo](https://demo.thi.ng/umbrella/imgui-basics/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/imgui-basics) |
80
+ | Screenshot | Description | Live demo | Source |
81
+ |:----------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------|:-----------------------------------------------------|:----------------------------------------------------------------------------------|
82
+ | <img src="https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/examples/fft-synth.png" width="240"/> | Interactive inverse FFT toy synth | [Demo](https://demo.thi.ng/umbrella/fft-synth/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/fft-synth) |
83
+ | <img src="https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/imgui/imgui-all.png" width="240"/> | Canvas based Immediate Mode GUI components | [Demo](https://demo.thi.ng/umbrella/imgui/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/imgui) |
84
+ | <img src="https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/examples/imgui-basics.png" width="240"/> | Minimal IMGUI usage example | [Demo](https://demo.thi.ng/umbrella/imgui-basics/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/imgui-basics) |
85
+ | <img src="https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/examples/layout-gridgen.png" width="240"/> | Randomized space-filling, nested grid layout generator | [Demo](https://demo.thi.ng/umbrella/layout-gridgen/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/layout-gridgen) |
77
86
 
78
87
  ## API
79
88
 
@@ -85,51 +94,181 @@ The `GridLayout` class supports infinite nesting and column/row-based
85
94
  space allocation, based on an initial configuration and supporting
86
95
  multiple column/row spans.
87
96
 
88
- ![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/layout/grid-layout.png)
89
-
90
- The code producing this structure:
91
-
92
- ```ts
93
- import { gridLayout } from "@thi.ng/layout";
94
-
95
- // create a single column layout @ position 10,10 / 200px wide
97
+ ![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/layout/readme-grid.png)
98
+
99
+ The code producing this layout (incl. the visualization itself):
100
+
101
+ ```ts tangle:export/readme-grid.ts
102
+ import * as g from "@thi.ng/geom";
103
+ import { gridLayout, type LayoutBox } from "@thi.ng/layout";
104
+ import { writeFileSync } from "fs";
105
+
106
+ // collection of generated layout cells
107
+ const cells: g.Group[] = [];
108
+
109
+ const addRect = (id: number, box: LayoutBox, fill: string) => {
110
+ console.log(box);
111
+ const shape = g.rect([box.x, box.y], [box.w, box.h], { fill });
112
+ cells.push(
113
+ g.group({}, [
114
+ shape,
115
+ g.text(g.centroid(shape)!, "#" + id, {
116
+ fill: "black",
117
+ stroke: "none",
118
+ }),
119
+ ])
120
+ );
121
+ };
122
+
123
+ // create a single column layout @ position [10,10], 1000px wide
96
124
  // the last values are row height and cell spacing
97
- const layout = gridLayout(10, 10, 200, 1, 16, 4);
125
+ const layout = gridLayout(10, 10, 1000, 1, 60, 4);
98
126
 
99
- // get next layout box (1st row)
100
- // usually you don't need to call .next() manually, but merely pass
101
- // the layout instance to a component...
102
- layout.next();
103
- // { x: 10, y: 10, w: 200, h: 16, cw: 200, ch: 16, gap: 4 }
127
+ // get next layout box (1st row, by default the column/row span is [1,1])
128
+ addRect(1, layout.next(), "#fec");
129
+ // { x: 10, y: 10, w: 1000, h: 60, cw: 1000, ch: 60, gap: 4, span: [ 1, 1 ] }
104
130
 
105
131
  // 2nd row
106
- layout.next();
107
- // { x: 10, y: 30, w: 200, h: 16, cw: 200, ch: 16, gap: 4 }
132
+ addRect(2, layout.next(), "#fec");
133
+ // { x: 10, y: 74, w: 1000, h: 60, cw: 1000, ch: 60, gap: 4, span: [ 1, 1 ] }
108
134
 
109
135
  // create nested 2-column layout (3rd row)
110
136
  const twoCols = layout.nest(2);
111
137
 
112
- twoCols.next();
113
- // { x: 10, y: 50, w: 98, h: 16, cw: 98, ch: 16, gap: 4 }
138
+ addRect(3, twoCols.next(), "#cfc");
139
+ // { x: 10, y: 138, w: 498, h: 60, cw: 498, ch: 60, gap: 4, span: [ 1, 1 ] }
114
140
 
115
- twoCols.next();
116
- // { x: 112, y: 50, w: 98, h: 16, cw: 98, ch: 16, gap: 4 }
141
+ addRect(4, twoCols.next(), "#cfc");
142
+ // { x: 512, y: 138, w: 498, h: 60, cw: 498, ch: 60, gap: 4, span: [ 1, 1 ] }
117
143
 
118
144
  // now nest 3-columns in the 1st column of twoCols
119
145
  // (i.e. now each column is 1/6th of the main layout's width)
120
146
  const inner = twoCols.nest(3);
121
147
 
122
148
  // allocate with col/rowspan, here 1 column x 4 rows
123
- inner.next([1, 4])
124
- // { x: 10, y: 70, w: 30, h: 76, cw: 30, ch: 16, gap: 4 }
125
- inner.next([1, 4])
126
- // { x: 44, y: 70, w: 30, h: 76, cw: 30, ch: 16, gap: 4 }
127
- inner.next([1, 4])
128
- // { x: 78, y: 70, w: 30, h: 76, cw: 30, ch: 16, gap: 4 }
149
+ addRect(5, inner.next([1, 4]), "#9ff");
150
+ // { x: 10, y: 202, w: 163.33, h: 252, cw: 163.33, ch: 60, gap: 4, span: [ 1, 4 ] }
151
+ addRect(6, inner.next([1, 4]), "#9ff");
152
+ // { x: 177.33, y: 202, w: 163.33, h: 252, cw: 163.33, ch: 60, gap: 4, span: [ 1, 4 ] }
153
+ addRect(7, inner.next([1, 4]), "#9ff");
154
+ // { x: 344.66, y: 202, w: 163.33, h: 252, cw: 163.33, ch: 60, gap: 4, span: [ 1, 4 ] }
129
155
 
130
156
  // back to twoCols (2nd column)
131
- twoCols.next([1, 2]);
132
- // { x: 112, y: 70, w: 98, h: 36, cw: 98, ch: 16, gap: 4 }
157
+ addRect(8, twoCols.next([1, 2]), "#cfc");
158
+ // { x: 512, y: 202, w: 498, h: 124, cw: 498, ch: 60, gap: 4, span: [ 1, 2 ] }
159
+
160
+ // export as SVG
161
+ writeFileSync(
162
+ "export/readme-grid.svg",
163
+ g.asSvg(
164
+ g.svgDoc(
165
+ {
166
+ __bleed: 10,
167
+ font: "12px Menlo, monospace",
168
+ align: "center",
169
+ baseline: "middle",
170
+ },
171
+ ...cells
172
+ )
173
+ )
174
+ );
175
+ ```
176
+
177
+ ### StackedLayout
178
+
179
+ An extension of [GridLayout](#gridlayout) which tracks individual column-based
180
+ heights and so can create more complex, irregular, packed, space-filling layout
181
+ arrangements. This layout algorithm prioritizes the column(s) with the lowest
182
+ height.
183
+
184
+ This class also provides a
185
+ [`.availableSpan()`](https://docs.thi.ng/umbrella/layout/classes/StackedLayout.html#availableSpan)
186
+ method to find available space and help equalize columns and fill/allocate any
187
+ bottom gaps.
188
+
189
+ IMPORTANT: As with GridLayout, nested layouts **MUST** be completed first before
190
+ requesting new cells (aka `LayoutBoxes`) from a parent, otherwise unintended
191
+ overlaps will occur.
192
+
193
+ ![screenshot](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/layout/readme-stacked.png)
194
+
195
+ The code producing this layout (incl. the visualization itself):
196
+
197
+ ```ts tangle:export/readme-stacked.ts
198
+ import * as g from "@thi.ng/geom";
199
+ import { stackedLayout, type LayoutBox } from "@thi.ng/layout";
200
+ import { writeFileSync } from "fs";
201
+
202
+ // collection of generated layout cells
203
+ const cells: g.Group[] = [];
204
+
205
+ const addRect = (id: number, box: LayoutBox, fill: string) => {
206
+ console.log(box);
207
+ const shape = g.rect([box.x, box.y], [box.w, box.h], { fill });
208
+ cells.push(
209
+ g.group({}, [
210
+ shape,
211
+ g.text(g.centroid(shape)!, "#" + id, {
212
+ fill: "black",
213
+ stroke: "none",
214
+ }),
215
+ ])
216
+ );
217
+ };
218
+
219
+ // create a 4-column layout @ position [0,0], 1000px wide
220
+ // the last values are row height and cell spacing
221
+ const layout = stackedLayout(0, 0, 1000, 4, 60, 4);
222
+
223
+ // get next layout box (1st column)
224
+ addRect(1, layout.next([1, 2]), "#fec");
225
+ // { x: 0, y: 0, w: 247, h: 124, cw: 247, ch: 60, gap: 4, span: [ 1, 2 ] }
226
+
227
+ // 2nd column
228
+ addRect(2, layout.next(), "#fec");
229
+ // { x: 251, y: 0, w: 247, h: 60, cw: 247, ch: 60, gap: 4, span: [ 1, 1 ] }
230
+
231
+ // 3rd column
232
+ addRect(3, layout.next([1, 4]), "#fec");
233
+ // { x: 502, y: 0, w: 247, h: 252, cw: 247, ch: 60, gap: 4, span: [ 1, 4 ] }
234
+
235
+ // 4th column
236
+ addRect(4, layout.next([1, 1]), "#fec");
237
+ // { x: 753, y: 0, w: 247, h: 60, cw: 247, ch: 60, gap: 4, span: [ 1, 1 ] }
238
+
239
+ // 2x2 span
240
+ // (note that this will create a gap in the 2nd column)
241
+ addRect(5, layout.next([2, 2]), "#fec");
242
+ // { x: 0, y: 128, w: 498, h: 124, cw: 247, ch: 60, gap: 4, span: [ 2, 2 ] }
243
+
244
+ const inner = layout.nest(2);
245
+
246
+ addRect(6, inner.next([1, 5]), "#cfc");
247
+ // { x: 753, y: 64, w: 121.5, h: 316, cw: 121.5, ch: 60, gap: 4, span: [ 1, 5 ] }
248
+ addRect(7, inner.next([1, 5]), "#cfc");
249
+ // { x: 878.5, y: 64, w: 121.5, h: 316, cw: 121.5, ch: 60, gap: 4, span: [ 1, 5 ] }
250
+
251
+ // fill available space in the other columns
252
+ // (depending on situation, this might have to be done multiple times
253
+ // to fill all available space, please consult documentation)
254
+ addRect(8, layout.next(layout.availableSpan()), "#9ff");
255
+ // { x: 0, y: 256, w: 749, h: 124, cw: 247, ch: 60, gap: 4, span: [ 3, 2 ] }
256
+
257
+ // export as SVG
258
+ writeFileSync(
259
+ "export/readme-stacked.svg",
260
+ g.asSvg(
261
+ g.svgDoc(
262
+ {
263
+ __bleed: 10,
264
+ font: "12px Menlo, monospace",
265
+ align: "center",
266
+ baseline: "middle",
267
+ },
268
+ ...cells
269
+ )
270
+ )
271
+ );
133
272
  ```
134
273
 
135
274
  ## Authors
package/api.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export type CellSpan = [number, number];
1
2
  export interface LayoutBox {
2
3
  /**
3
4
  * Top-left corner X
@@ -29,11 +30,15 @@ export interface LayoutBox {
29
30
  * Gutter size.
30
31
  */
31
32
  gap: number;
33
+ /**
34
+ *
35
+ */
36
+ span: CellSpan;
32
37
  }
33
38
  export interface ILayout<O, T> {
34
39
  next(opts?: O): T;
35
40
  }
36
- export interface IGridLayout extends ILayout<[number, number], LayoutBox> {
41
+ export interface IGridLayout<T extends IGridLayout<T>> extends ILayout<CellSpan, LayoutBox> {
37
42
  readonly x: number;
38
43
  readonly y: number;
39
44
  readonly width: number;
@@ -59,8 +64,8 @@ export interface IGridLayout extends ILayout<[number, number], LayoutBox> {
59
64
  *
60
65
  * @param size -
61
66
  */
62
- spansForSize(size: ArrayLike<number>): [number, number];
63
- spansForSize(w: number, h: number): [number, number];
67
+ spanForSize(size: ArrayLike<number>): CellSpan;
68
+ spanForSize(w: number, h: number): CellSpan;
64
69
  /**
65
70
  * Returns a squared {@link LayoutBox} based on this layout's column
66
71
  * width. This box will consume `ceil(columnWidth / rowHeight)`
@@ -104,7 +109,8 @@ export interface IGridLayout extends ILayout<[number, number], LayoutBox> {
104
109
  *
105
110
  * @param cols - columns in nested layout
106
111
  * @param spans - default [1, 1] (i.e. size of single cell)
112
+ * @param gap - gap for child layout
107
113
  */
108
- nest(cols: number, spans?: [number, number]): IGridLayout;
114
+ nest(cols: number, spans?: CellSpan, gap?: number): T;
109
115
  }
110
116
  //# sourceMappingURL=api.d.ts.map
package/box.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import type { LayoutBox } from "./api.js";
2
- export declare const layoutBox: (x: number, y: number, w: number, h: number, cw: number, ch: number, gap: number) => LayoutBox;
1
+ import type { CellSpan, LayoutBox } from "./api.js";
2
+ export declare const layoutBox: (x: number, y: number, w: number, h: number, cw: number, ch: number, gap: number, span?: CellSpan) => LayoutBox;
3
3
  //# sourceMappingURL=box.d.ts.map
package/box.js CHANGED
@@ -1 +1,10 @@
1
- export const layoutBox = (x, y, w, h, cw, ch, gap) => ({ x, y, w, h, cw, ch, gap });
1
+ export const layoutBox = (x, y, w, h, cw, ch, gap, span) => ({
2
+ x,
3
+ y,
4
+ w,
5
+ h,
6
+ cw,
7
+ ch,
8
+ gap,
9
+ span: span || [~~(w / cw), ~~(h / ch)],
10
+ });
package/grid-layout.d.ts CHANGED
@@ -1,5 +1,7 @@
1
- import type { IGridLayout, LayoutBox } from "./api.js";
2
- export declare class GridLayout implements IGridLayout {
1
+ import type { CellSpan, IGridLayout, LayoutBox } from "./api.js";
2
+ /** @internal */
3
+ export declare const __DEFAULT_SPANS: CellSpan;
4
+ export declare class GridLayout implements IGridLayout<GridLayout> {
3
5
  readonly parent: GridLayout | null;
4
6
  readonly cols: number;
5
7
  readonly width: number;
@@ -16,17 +18,17 @@ export declare class GridLayout implements IGridLayout {
16
18
  constructor(parent: GridLayout | null, x: number, y: number, width: number, cols: number, rowH: number, gap: number);
17
19
  colsForWidth(w: number): number;
18
20
  rowsForHeight(h: number): number;
19
- spansForSize(size: ArrayLike<number>): [number, number];
20
- spansForSize(w: number, h: number): [number, number];
21
- next(spans?: [number, number]): LayoutBox;
21
+ spanForSize(size: ArrayLike<number>): CellSpan;
22
+ spanForSize(w: number, h: number): CellSpan;
23
+ next(spans?: CellSpan): LayoutBox;
22
24
  nextSquare(): LayoutBox;
23
- nest(cols: number, spans?: [number, number]): GridLayout;
25
+ nest(cols: number, spans?: CellSpan, gap?: number): GridLayout;
24
26
  /**
25
27
  * Updates max rows used in this layout and all of its parents.
26
28
  *
27
29
  * @param rspan -
28
30
  */
29
- protected propagateSize(rspan: number): void;
31
+ propagateSize(rspan: number): void;
30
32
  }
31
33
  /**
32
34
  * Syntax sugar for {@link GridLayout} ctor. By default creates a
package/grid-layout.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { isNumber } from "@thi.ng/checks/is-number";
2
- const DEFAULT_SPANS = [1, 1];
2
+ /** @internal */
3
+ export const __DEFAULT_SPANS = [1, 1];
3
4
  export class GridLayout {
4
5
  constructor(parent, x, y, width, cols, rowH, gap) {
5
6
  this.parent = parent;
@@ -22,11 +23,11 @@ export class GridLayout {
22
23
  rowsForHeight(h) {
23
24
  return Math.ceil(h / this.cellHG);
24
25
  }
25
- spansForSize(w, h) {
26
+ spanForSize(w, h) {
26
27
  const size = isNumber(w) ? [w, h] : w;
27
28
  return [this.colsForWidth(size[0]), this.rowsForHeight(size[1])];
28
29
  }
29
- next(spans = DEFAULT_SPANS) {
30
+ next(spans = __DEFAULT_SPANS) {
30
31
  const { cellWG, cellHG, gap, cols } = this;
31
32
  const cspan = Math.min(spans[0], cols);
32
33
  const rspan = spans[1];
@@ -48,11 +49,13 @@ export class GridLayout {
48
49
  cw: this.cellW,
49
50
  ch: this.cellH,
50
51
  gap,
52
+ span: [cspan, rspan],
51
53
  };
52
54
  this.propagateSize(rspan);
53
55
  this.currCol = Math.min(this.currCol + cspan, cols) % cols;
54
56
  return cell;
55
57
  }
58
+ // TODO add optional colspan arg, fix rounding
56
59
  nextSquare() {
57
60
  const box = this.next([
58
61
  1,
@@ -61,9 +64,9 @@ export class GridLayout {
61
64
  box.h = box.w;
62
65
  return box;
63
66
  }
64
- nest(cols, spans) {
67
+ nest(cols, spans, gap = this.gap) {
65
68
  const { x, y, w } = this.next(spans);
66
- return new GridLayout(this, x, y, w, cols, this.cellH, this.gap);
69
+ return new GridLayout(this, x, y, w, cols, this.cellH, gap);
67
70
  }
68
71
  /**
69
72
  * Updates max rows used in this layout and all of its parents.
package/index.d.ts CHANGED
@@ -2,4 +2,5 @@ export * from "./api.js";
2
2
  export * from "./box.js";
3
3
  export * from "./checks.js";
4
4
  export * from "./grid-layout.js";
5
+ export * from "./stacked-layout.js";
5
6
  //# sourceMappingURL=index.d.ts.map
package/index.js CHANGED
@@ -2,3 +2,4 @@ export * from "./api.js";
2
2
  export * from "./box.js";
3
3
  export * from "./checks.js";
4
4
  export * from "./grid-layout.js";
5
+ export * from "./stacked-layout.js";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@thi.ng/layout",
3
- "version": "2.1.35",
4
- "description": "Configurable nested 2D grid layout manager",
3
+ "version": "3.0.0",
4
+ "description": "Configurable nested 2D grid layout generators",
5
5
  "type": "module",
6
6
  "module": "./index.js",
7
7
  "typings": "./index.d.ts",
@@ -34,6 +34,7 @@
34
34
  "test": "testament test"
35
35
  },
36
36
  "dependencies": {
37
+ "@thi.ng/arrays": "^2.7.1",
37
38
  "@thi.ng/checks": "^3.4.6"
38
39
  },
39
40
  "devDependencies": {
@@ -81,10 +82,16 @@
81
82
  },
82
83
  "./grid-layout": {
83
84
  "default": "./grid-layout.js"
85
+ },
86
+ "./stacked-layout": {
87
+ "default": "./stacked-layout.js"
84
88
  }
85
89
  },
86
90
  "thi.ng": {
91
+ "related": [
92
+ "imgui"
93
+ ],
87
94
  "year": 2019
88
95
  },
89
- "gitHead": "8d46d9326a9f9b81d65e7e274446f5964f9942ac\n"
96
+ "gitHead": "bfa16829786146bd24df3cdbd44649a45a603e44\n"
90
97
  }
@@ -0,0 +1,52 @@
1
+ import type { CellSpan, LayoutBox } from "./api.js";
2
+ import { GridLayout } from "./grid-layout.js";
3
+ /**
4
+ * An extension of {@link GridLayout} which tracks individual column-based
5
+ * heights and so can create more complex, irregular, packed, space-filling
6
+ * layout arrangements. This layout algorithm prioritizes the column(s) with the
7
+ * lowest height.
8
+ *
9
+ * The class also provides a {@link StackedLayout.availableSpan} method to find
10
+ * available space and help equalize columns and fill/allocate any bottom gaps.
11
+ *
12
+ * **IMPORTANT:** As with {@link GridLayout}, nested layouts MUST be completed
13
+ * first before requesting new cells (aka {@link LayoutBox}es) from a parent,
14
+ * otherwise unintended overlaps will occur.
15
+ */
16
+ export declare class StackedLayout extends GridLayout {
17
+ offsets: Uint32Array;
18
+ currSpan: number;
19
+ constructor(parent: GridLayout | null, x: number, y: number, width: number, cols: number, rowH: number, gap: number);
20
+ nest(cols: number, spans?: CellSpan, gap?: number): StackedLayout;
21
+ next(spans?: CellSpan): LayoutBox;
22
+ /**
23
+ * Finds the largest available span of free area, such that if it'll be
24
+ * allocated via {@link StackedLayout.next} or {@link StackedLayout.nest},
25
+ * the impacted columns will all have the same height, and that height will
26
+ * match that of the next column after (if any). Repeated use of this method
27
+ * can be used to fill up (aka equalize) any bottom gaps of a layout
28
+ * container until all columns are equal. If the function returns a vertical
29
+ * span of 0, all columns are equalized already.
30
+ *
31
+ * @remarks
32
+ * An optional `maxSpan` can be provided to constrain the returned span (by
33
+ * default unconstrained).
34
+ *
35
+ * @param maxSpan
36
+ */
37
+ availableSpan(maxSpan?: CellSpan): CellSpan;
38
+ propagateSize(rspan: number): void;
39
+ }
40
+ /**
41
+ * Syntax sugar for {@link StackedLayout} ctor. By default creates a 4-column
42
+ * layout at given position and total width.
43
+ *
44
+ * @param x -
45
+ * @param y -
46
+ * @param width -
47
+ * @param cols -
48
+ * @param rowH -
49
+ * @param gap -
50
+ */
51
+ export declare const stackedLayout: (x: number, y: number, width: number, cols?: number, rowH?: number, gap?: number) => StackedLayout;
52
+ //# sourceMappingURL=stacked-layout.d.ts.map
@@ -0,0 +1,110 @@
1
+ import { argMax, argMin } from "@thi.ng/arrays/argmin";
2
+ import { GridLayout, __DEFAULT_SPANS } from "./grid-layout.js";
3
+ /**
4
+ * An extension of {@link GridLayout} which tracks individual column-based
5
+ * heights and so can create more complex, irregular, packed, space-filling
6
+ * layout arrangements. This layout algorithm prioritizes the column(s) with the
7
+ * lowest height.
8
+ *
9
+ * The class also provides a {@link StackedLayout.availableSpan} method to find
10
+ * available space and help equalize columns and fill/allocate any bottom gaps.
11
+ *
12
+ * **IMPORTANT:** As with {@link GridLayout}, nested layouts MUST be completed
13
+ * first before requesting new cells (aka {@link LayoutBox}es) from a parent,
14
+ * otherwise unintended overlaps will occur.
15
+ */
16
+ export class StackedLayout extends GridLayout {
17
+ constructor(parent, x, y, width, cols, rowH, gap) {
18
+ super(parent, x, y, width, cols, rowH, gap);
19
+ this.currSpan = 1;
20
+ this.offsets = new Uint32Array(cols);
21
+ }
22
+ nest(cols, spans, gap = this.gap) {
23
+ const { x, y, w } = this.next(spans);
24
+ return new StackedLayout(this, x, y, w, cols, this.cellH, gap);
25
+ }
26
+ next(spans = __DEFAULT_SPANS) {
27
+ const { cellWG, cellHG, gap, cols, offsets } = this;
28
+ const cspan = Math.min(spans[0], cols);
29
+ const rspan = spans[1];
30
+ let minY = Infinity;
31
+ let maxY = 0;
32
+ let column = 0;
33
+ for (let i = 0; i <= cols - cspan; i++) {
34
+ const chunk = offsets.subarray(i, i + cspan);
35
+ const maxID = argMax(chunk);
36
+ const y = chunk[maxID];
37
+ if (y < minY) {
38
+ minY = y;
39
+ maxY = chunk[maxID];
40
+ column = i;
41
+ }
42
+ }
43
+ const h = rspan * cellHG - gap;
44
+ const cell = {
45
+ x: this.x + column * cellWG,
46
+ y: this.y + maxY * cellHG,
47
+ w: cspan * cellWG - gap,
48
+ h,
49
+ cw: this.cellW,
50
+ ch: this.cellH,
51
+ gap,
52
+ span: [cspan, rspan],
53
+ };
54
+ this.currRow = maxY;
55
+ this.currCol = column;
56
+ offsets.fill(maxY + rspan, column, column + cspan);
57
+ this.currSpan = cspan;
58
+ this.parent && this.parent.propagateSize(Math.max(...this.offsets));
59
+ return cell;
60
+ }
61
+ /**
62
+ * Finds the largest available span of free area, such that if it'll be
63
+ * allocated via {@link StackedLayout.next} or {@link StackedLayout.nest},
64
+ * the impacted columns will all have the same height, and that height will
65
+ * match that of the next column after (if any). Repeated use of this method
66
+ * can be used to fill up (aka equalize) any bottom gaps of a layout
67
+ * container until all columns are equal. If the function returns a vertical
68
+ * span of 0, all columns are equalized already.
69
+ *
70
+ * @remarks
71
+ * An optional `maxSpan` can be provided to constrain the returned span (by
72
+ * default unconstrained).
73
+ *
74
+ * @param maxSpan
75
+ */
76
+ availableSpan(maxSpan = [Infinity, Infinity]) {
77
+ const { offsets, cols } = this;
78
+ const minID = argMin(offsets);
79
+ const y = offsets[minID];
80
+ let result;
81
+ for (let i = minID + 1; i < cols; i++) {
82
+ if (offsets[i] > y) {
83
+ result = [i - minID, offsets[i] - y];
84
+ break;
85
+ }
86
+ }
87
+ if (!result)
88
+ result = [cols - minID, offsets[argMax(offsets)] - y];
89
+ result[0] = Math.min(result[0], maxSpan[0]);
90
+ result[1] = Math.min(result[1], maxSpan[1]);
91
+ return result;
92
+ }
93
+ propagateSize(rspan) {
94
+ const newY = Math.max(this.currRow + rspan, this.offsets[this.currCol]);
95
+ this.offsets.fill(newY, this.currCol, this.currCol + this.currSpan);
96
+ this.parent && this.parent.propagateSize(newY);
97
+ }
98
+ }
99
+ /**
100
+ * Syntax sugar for {@link StackedLayout} ctor. By default creates a 4-column
101
+ * layout at given position and total width.
102
+ *
103
+ * @param x -
104
+ * @param y -
105
+ * @param width -
106
+ * @param cols -
107
+ * @param rowH -
108
+ * @param gap -
109
+ */
110
+ export const stackedLayout = (x, y, width, cols = 4, rowH = 16, gap = 4) => new StackedLayout(null, x, y, width, cols, rowH, gap);