@thi.ng/layout 2.1.34 → 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 +15 -1
- package/README.md +182 -43
- package/api.d.ts +10 -4
- package/box.d.ts +2 -2
- package/box.js +10 -1
- package/grid-layout.d.ts +9 -7
- package/grid-layout.js +8 -5
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/package.json +15 -8
- package/stacked-layout.d.ts +52 -0
- package/stacked-layout.js +110 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
-
- **Last updated**: 2023-
|
|
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
|
|
27
|
+
Configurable nested 2D grid layout generators.
|
|
26
28
|
|
|
27
|
-
Currently, this package features
|
|
28
|
-
well as more
|
|
29
|
-
types
|
|
30
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
|
73
|
-
|
|
74
|
-
| <img src="https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/examples/
|
|
75
|
-
| <img src="https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/
|
|
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
|
-

|
|
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,
|
|
125
|
+
const layout = gridLayout(10, 10, 1000, 1, 60, 4);
|
|
98
126
|
|
|
99
|
-
// get next layout box (1st row)
|
|
100
|
-
|
|
101
|
-
//
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
125
|
-
inner.next([1, 4])
|
|
126
|
-
// { x:
|
|
127
|
-
inner.next([1, 4])
|
|
128
|
-
// { x:
|
|
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:
|
|
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
|
+

|
|
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<
|
|
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
|
-
|
|
63
|
-
|
|
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?:
|
|
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
package/grid-layout.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import type { IGridLayout, LayoutBox } from "./api.js";
|
|
2
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
next(spans?:
|
|
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?:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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,
|
|
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
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thi.ng/layout",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Configurable nested 2D grid layout
|
|
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,14 +34,15 @@
|
|
|
34
34
|
"test": "testament test"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@thi.ng/
|
|
37
|
+
"@thi.ng/arrays": "^2.7.1",
|
|
38
|
+
"@thi.ng/checks": "^3.4.6"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
40
|
-
"@microsoft/api-extractor": "^7.
|
|
41
|
-
"@thi.ng/testament": "^0.3.
|
|
42
|
-
"rimraf": "^5.0.
|
|
41
|
+
"@microsoft/api-extractor": "^7.38.0",
|
|
42
|
+
"@thi.ng/testament": "^0.3.24",
|
|
43
|
+
"rimraf": "^5.0.5",
|
|
43
44
|
"tools": "^0.0.1",
|
|
44
|
-
"typedoc": "^0.25.
|
|
45
|
+
"typedoc": "^0.25.2",
|
|
45
46
|
"typescript": "^5.2.2"
|
|
46
47
|
},
|
|
47
48
|
"keywords": [
|
|
@@ -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": "
|
|
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);
|