Skip to content

Commit 82a0283

Browse files
RFC: Add view layout compiler (#10269)
1 parent 2ff156c commit 82a0283

12 files changed

Lines changed: 1730 additions & 89 deletions

File tree

docs/api-reference/widgets/overview.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ This module contains the following widgets:
2121

2222
- [FullscreenWidget](./fullscreen-widget.md)
2323
- [SplitterWidget](./splitter-widget.md)
24+
- [View Layout](./view-layout.md)
2425

2526
### Information Widgets
2627

docs/api-reference/widgets/splitter-widget.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,10 @@ function App() {
140140
## Constructor
141141

142142
```ts
143-
import {_SplitterWidget as SplitterWidget, type SplitterWidgetProps} from '@deck.gl/widgets';
143+
import {
144+
_SplitterWidget as SplitterWidget,
145+
type SplitterWidgetProps
146+
} from '@deck.gl/widgets';
144147
new SplitterWidget<ViewType[]>({} satisfies SplitterWidgetProps);
145148
```
146149

@@ -150,7 +153,7 @@ new SplitterWidget<ViewType[]>({} satisfies SplitterWidgetProps);
150153

151154
The `SplitterWidget` accepts the generic [`WidgetProps`](../core/widget.md#widgetprops) and:
152155

153-
#### `viewLayout` (ViewLayout, required) {#viewlayout}
156+
#### `viewLayout` (SplitterWidgetViewLayout, required) {#viewlayout}
154157

155158
Layout descriptor of how views are arranged on the canvas. Contains the following fields:
156159

@@ -161,7 +164,7 @@ Layout descriptor of how views are arranged on the canvas. Contains the followin
161164
- `minSplit` (number, optional) - Min value of the split. The user cannot make the first view smaller than this ratio. Default `0.05`.
162165
- `maxSplit` (number, optional) - Max value of the split. The user cannot make the first view larger than this ratio. Default `0.95`.
163166

164-
You may also replace one or both item in `views` with a `ViewLayout` object, composing more than two views into a complex layout.
167+
You may also replace one or both items in `views` with a nested `SplitterWidgetViewLayout` object, composing more than two views into a complex layout.
165168

166169

167170
#### `onChange` (Function, optional) {#onchange}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# View Layout
2+
3+
The view layout helpers build stable deck.gl view arrays from a declarative layout tree. They are generic utilities for applications that need multiple coordinated views without hand-computing every view rectangle in render code.
4+
5+
```js
6+
import {buildViewsFromViewLayout} from '@deck.gl/widgets';
7+
```
8+
9+
## Usage
10+
11+
Start by defining the deck.gl `View` instances that your application needs. Then place them in a plain layout object tree and compile that tree for the current deck canvas size.
12+
13+
```tsx
14+
import React from 'react';
15+
import {DeckGL} from '@deck.gl/react';
16+
import {OrthographicView} from '@deck.gl/core';
17+
import {buildViewsFromViewLayout} from '@deck.gl/widgets';
18+
import type {ViewLayout} from '@deck.gl/widgets';
19+
20+
const VIEW_LAYOUT = {
21+
type: 'column',
22+
children: [
23+
new OrthographicView({id: 'header', height: 48, controller: false}),
24+
{
25+
type: 'row',
26+
children: [
27+
new OrthographicView({id: 'sidebar', controller: false}),
28+
{
29+
type: 'overlay',
30+
children: [
31+
new OrthographicView({id: 'main', controller: true}),
32+
new OrthographicView({
33+
id: 'minimap',
34+
x: 'calc(100% - 180px)',
35+
y: 16,
36+
width: 164,
37+
height: 120,
38+
controller: false,
39+
clear: true
40+
})
41+
]
42+
}
43+
]
44+
}
45+
]
46+
} satisfies ViewLayout;
47+
48+
export function App({width, height, layers}) {
49+
const compiled = buildViewsFromViewLayout({layout: VIEW_LAYOUT, width, height});
50+
return <DeckGL views={compiled.views} layers={layers} />;
51+
}
52+
```
53+
54+
The returned `compiled.rectsById` map contains the same resolved rectangles keyed by view id. Use it when you need to position DOM overlays next to deck views, debug the generated layout, or scope non-layer UI to a view rectangle.
55+
56+
The returned `compiled.splittersById` map contains splitter metadata for rows and columns that declare a `splitId`. For two-child splits, the splitter id is exactly `splitId`. For three or more children, the compiler creates one splitter between each adjacent pair using generated ids such as `splitId-0`, `splitId-1`, and so on. Applications that store split values can pass them back into `buildViewsFromViewLayout` via `splitValues`.
57+
58+
Layout items may also define `minPixels` and `maxPixels` to constrain their size in the parent stack axis. The compiler combines those pixel constraints with percentage-based `width` or `height` values, and with `minSplit` and `maxSplit` when returning splitter metadata.
59+
60+
Use `viewPropsById` when an application needs to control layout-only bounds for a view without rebuilding the static layout tree. Override values use the same length syntax as authored view props.
61+
62+
The layout tree is a discriminated union of plain objects:
63+
64+
- `row`: lays out children left to right.
65+
- `column`: lays out children top to bottom.
66+
- `overlay`: gives each child the same parent rectangle.
67+
- `spacer`: reserves empty fixed or flexible space.
68+
69+
Raw deck.gl `View` instances are leaf nodes in `children`. Put layout-only `width`, `height`, `x`, and `y` props directly on the `View` when a leaf needs fixed sizing or overlay positioning.
70+
71+
For split layouts, `ViewLayout` also accepts the `SplitterWidgetViewLayout`-style aliases `orientation: 'horizontal' | 'vertical'` and `views`. A horizontal orientation is equivalent to `type: 'row'`; a vertical orientation is equivalent to `type: 'column'`.
72+
73+
`buildViewsFromViewLayout` compiles a layout tree into:
74+
75+
- `views`: concrete deck.gl views with numeric `x`, `y`, `width`, and `height`.
76+
- `rectsById`: resolved rectangles keyed by deck view id.
77+
- `splittersById`: resolved splitter metadata keyed by split id.
78+
79+
## Layout Sizing
80+
81+
`width`, `height`, `x`, and `y` accept numbers or CSS-like length strings such as percentages and simple `calc(...)` expressions. The compiler resolves those values against the current parent rectangle before passing numeric bounds to deck.gl.
82+
83+
```ts
84+
new OrthographicView({
85+
id: 'overlay',
86+
x: '50%',
87+
width: 'calc(50% - 12px)',
88+
height: 80
89+
});
90+
```
91+
92+
## View Reuse
93+
94+
Pass the previous `CompiledDeckViews` result back to `buildViewsFromViewLayout` when a caller needs structural view reuse across renders. A previous view is reused when its id, constructor, and resolved props match the next compilation.
95+
96+
```ts
97+
let compiled = buildViewsFromViewLayout({layout, width, height});
98+
99+
compiled = buildViewsFromViewLayout({
100+
layout,
101+
width,
102+
height,
103+
previous: compiled
104+
});
105+
```
106+
107+
## Types
108+
109+
### `ViewLayout`
110+
111+
Plain discriminated layout object. Children may be nested layout objects, raw deck.gl `View` instances, or falsey optional children.
112+
113+
### `buildViewsFromViewLayout`
114+
115+
Compiles a layout tree for the current deck canvas size.
116+
117+
Parameters:
118+
119+
- `layout` (`ViewLayout`) - Root layout tree to compile.
120+
- `width` (`number`) - Current deck width in pixels.
121+
- `height` (`number`) - Current deck height in pixels.
122+
- `previous` (`CompiledDeckViews`, optional) - Previous compilation for view reuse.
123+
- `splitValues` (`Record<string, number>`, optional) - Controlled split ratios keyed by layout `splitId`.
124+
- `viewPropsById` (`Record<string, {x?, y?, width?, height?}>`, optional) - Controlled layout-only view prop overrides keyed by deck view id.
125+
126+
Returns:
127+
128+
- `views` (`View[]`) - Concrete deck.gl views.
129+
- `rectsById` (`Record<string, {x, y, width, height}>`) - Resolved rectangles keyed by view id.
130+
131+
## Source
132+
133+
[modules/widgets/src/view-layout/build-views-from-view-layout.ts](https://github.com/visgl/deck.gl/tree/master/modules/widgets/src/view-layout/build-views-from-view-layout.ts)

docs/table-of-contents.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@
298298
"type": "category",
299299
"label": "@deck.gl/react",
300300
"items": [
301-
"api-reference/react/overview",
301+
"api-reference/react/overview",
302302
"api-reference/react/deckgl",
303303
"api-reference/react/use-widget"
304304
]
@@ -338,6 +338,7 @@
338338
"api-reference/widgets/theme-widget",
339339
"api-reference/widgets/timeline-widget",
340340
"api-reference/widgets/toggle-widget",
341+
"api-reference/widgets/view-layout",
341342
"api-reference/widgets/zoom-widget"
342343
]
343344
}

modules/main/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ export {
151151
// View widgets
152152
FullscreenWidget,
153153
_SplitterWidget,
154+
buildViewsFromViewLayout,
154155
// Information widgets
155156
InfoWidget,
156157
PopupWidget,
@@ -244,6 +245,9 @@ export type {
244245
StatsWidgetProps,
245246
ContextMenuWidgetProps,
246247
SplitterWidgetProps,
248+
SplitterWidgetViewLayout,
249+
CompiledDeckViews,
250+
ViewLayout,
247251
TimelineWidgetProps,
248252
SelectorWidgetProps,
249253
GimbalWidgetProps,

modules/widgets/src/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ export {GeocoderWidget as _GeocoderWidget} from './geocoder-widget';
1515
// View widgets
1616
export {FullscreenWidget} from './fullscreen-widget';
1717
export {SplitterWidget as _SplitterWidget} from './splitter-widget';
18+
export {
19+
buildViewsFromViewLayout,
20+
type CompiledDeckViews
21+
} from './view-layout/build-views-from-view-layout';
22+
export type {ViewLayout} from './view-layout/view-layout';
1823

1924
// Information widgets
2025
export {InfoWidget} from './info-widget';
@@ -49,7 +54,7 @@ export type {InfoWidgetProps} from './info-widget';
4954
export type {PopupWidgetProps} from './popup-widget';
5055
export type {StatsWidgetProps} from './stats-widget';
5156
export type {ContextMenuWidgetProps} from './context-menu-widget';
52-
export type {SplitterWidgetProps} from './splitter-widget';
57+
export type {SplitterWidgetProps, SplitterWidgetViewLayout} from './splitter-widget';
5358
export type {TimelineWidgetProps} from './timeline-widget';
5459
export type {SelectorWidgetProps} from './selector-widget';
5560
export type {GimbalWidgetProps} from './gimbal-widget';

0 commit comments

Comments
 (0)