Skip to content

RFC: Add view layout compiler#10269

Open
ibgreen-openai wants to merge 7 commits into
masterfrom
ib/view-layouts
Open

RFC: Add view layout compiler#10269
ibgreen-openai wants to merge 7 commits into
masterfrom
ib/view-layouts

Conversation

@ibgreen-openai
Copy link
Copy Markdown
Collaborator

@ibgreen-openai ibgreen-openai commented May 1, 2026

Summary

Adds the declarative view layout compiler and public types to @deck.gl/widgets

  • This PR is the base of a PR stack and intentionally does not include the optional example UI widgets or website/playground examples.
  • Current plan is to land the compiler in deck.gl/widgets and promote it to core (perhaps in Deck.props.views) when audited and stamped.

Changes

  • Adds plain (non-class) discriminated view layout types for row, column, overlay, and spacer layouts.
  • Adds buildViewsFromViewLayout, including view reuse, length parsing, split metadata, and viewPropsById bounds overrides.
  • Exports the compiler and associated types from @deck.gl/widgets and deck.gl.
  • Adds focused compiler tests and API docs with a usage section.
image

View Layout Syntax Audit

We do have a point of reference in existing 9.3 splitter widget which provides a minimal layout syntax for its views prop

What changes could be made to the new view layout syntax that would make it more similar to the old syntax

The closest change would be to let the new compiler accept a split-container shape that mirrors

SplitterWidget ViewLayout Concerns with aligning
list of views, horizontal or vertical nested hierarchy of layouts and views Scope is different
<item>.orientation <item>.layout ViewLayout supports more layout types, not just orientation
<item>.orientation: horizontal <item>.layout: row Could be aligned <item>.layout: 'horizontal'
<item>: vertical layout: column Could be aligned
SplitterWidgetProps.views <item>.children Could be aligned though children can be layout items not just views

SplitterWidgetViewLayout:

const layout = {
  orientation: 'horizontal',
  splitId: 'sidebar-main',
  initialSplit: 0.25,
  minSplit: 0.15,
  maxSplit: 0.5,
  views: [
    new OrthographicView({id: 'sidebar'}),
    new OrthographicView({id: 'main'})
  ]
};

This would map internally to the proposed:

{
  type: 'row',
  splitId: 'sidebar-main',
  children: [...]
}

How to align?

Use views instead of children for row, column, and overlay.

  • Pro: This reads closer to deck.gl and SplitterWidgetViewLayout.
  • Downside: overlay can contain layout nodes and spacers too, so children is semantically a little more accurate.
  • Use orientation for split layouts, keep type only for non-split nodes

Split containers use orientation.

  • Overlay/spacer keep type: 'overlay' | 'spacer'.
  • This is closest to the old syntax, but the union becomes less uniform.

ViewLayout Compared To CSS

CSS is a useful benchmark for whether the ViewLayout API feels obvious. The current API maps most closely to a constrained subset of Flexbox plus absolute-positioned overlays.

CSS concept Current ViewLayout equivalent Fit
display: flex; flex-direction: row/column `type: 'row' 'column'ororientation`
Flex item fixed basis child width / height Strong
Flex item min/max minPixels / maxPixels Good, but less CSS-like naming
position: absolute inside parent overlay + child x/y/width/height Strong
padding inset Reasonable, but CSS would call this padding
gap manual spacer Weak
flex-grow / fr units flexible unspecified children divide remaining equally Basic only
CSS Grid named areas none Missing
minmax() / clamp() width: '50%' + minPixels/maxPixels Good conceptually
subgrid nested layouts Similar spirit, simpler
container queries caller recompiles with measured size Similar responsibility, not declarative
anchor positioning viewPropsById / overlay offsets Partial
resize/split panes splitId + splittersById Not CSS, but app-layout-specific

The biggest CSS-like gaps are:

  1. No gap

    Spacer works, but it is verbose. A gap: 8 prop on row/column would feel natural and map directly to CSS flex/grid.

  2. No flex weights

    Today unspecified children divide remaining space equally. CSS users may expect something like flex: 2 or weight: 2.

  3. Axis-dependent minPixels / maxPixels

    This is pragmatic, but CSS would likely spell this as minWidth / maxWidth and minHeight / maxHeight, or minSize / maxSize for stack-axis sizing.

  4. No named grid/areas

    If the API wants to compete with CSS Grid, it would need named cells or areas. That may be too much for the current PR.

  5. Generated split ids are less CSS-like than line names

    CSS Grid has named lines. A closer model might be splitIds: ['sidebar-main', 'main-inspector'] for multi-child splits. Generated ids are simpler, but less authored and less semantic.

Suggested docs language:

ViewLayout is closest to a constrained subset of Flexbox plus absolute-positioned overlays. Rows and columns distribute space, children may declare fixed or percentage sizes plus pixel min/max constraints, and overlay children are positioned within their parent rect.

For future CSS-inspired API additions, the highest-value options are gap and flex / weight.

CSS grid

CSS grids could be nice, to make a number of views for different cities etc. For “N city views” the current row/column API gets awkward because you either manually nest rows/columns or generate a tree. A grid layout would be more natural:

const layout = {
  type: 'grid',
  columns: 3,
  gap: 8,
  children: cities.map(city => new MapView({id: city.id}))
};

Stacked PRs

The Follow-up "PoC" PR adds a number experimental widgets, website example, playground JSON sample, and widget docs, this is mainly for exposition at the moment, to show the system working and things that could be built by users.

@ibgreen-openai ibgreen-openai force-pushed the ib/view-layouts branch 6 times, most recently from 014dfcb to 2d959b8 Compare May 2, 2026 14:54
@coveralls
Copy link
Copy Markdown

coveralls commented May 2, 2026

Coverage Status

coverage: 83.754% (-0.04%) from 83.796% — ib/view-layouts into master

@ibgreen ibgreen mentioned this pull request May 2, 2026
44 tasks
@ibgreen-openai ibgreen-openai force-pushed the ib/view-layouts branch 2 times, most recently from e823ae1 to 798c003 Compare May 2, 2026 16:06
@ibgreen-openai ibgreen-openai marked this pull request as ready for review May 2, 2026 16:15
@ibgreen-openai ibgreen-openai changed the title [codex] Add core view layout helpers [codex] Declarative View Layout system with supporting Widgets in example May 2, 2026
@ibgreen-openai ibgreen-openai changed the title [codex] Declarative View Layout system with supporting Widgets in example feat(core) Declarative View Layout system with supporting Widgets in example May 2, 2026
@ibgreen-openai ibgreen-openai added this to the v9.4 milestone May 2, 2026
@Pessimistress
Copy link
Copy Markdown
Collaborator

  • From my quick scan this utility is completely self-contained, and my preference is for it to go into extensions or widgets.
  • could you split the new widgets and example proposal to a different PR?

On the ViewLayoutItem class:

  • I thought the type keyword is reserved for declarative. Maybe layout?
  • If children is allowed to be more than two items (which would be nice) then a single splitterId in the parent is not going to cover it.
  • I'd like the split to also support minPixels/maxPixels

@Pessimistress
Copy link
Copy Markdown
Collaborator

If the intention is to be able to pass the descriptor to views then I prefer it to be a pure JSON object, not a class instance.

@chrisgervang
Copy link
Copy Markdown
Collaborator

chrisgervang commented May 2, 2026

JSON or a chained builder would be nicer than class instances

import { layout as l } from ...
l.view(new OrthographicView({id: 'sidebar', controller: false}))
l.column([])
l.row([])
l.overlay()

@ibgreen-openai ibgreen-openai force-pushed the ib/view-layouts branch 2 times, most recently from d460b77 to 4bf7668 Compare May 3, 2026 14:29
@ibgreen-openai
Copy link
Copy Markdown
Collaborator Author

  • From my quick scan this utility is completely self-contained, and my preference is for it to go into extensions or widgets.
  • Yes and no. It needs access to the current size of the deck.gl viewport, so if we want this to work in e.g. pydeck, it would be probably be best if this was in core.
  • To illustrate this, I added an declarative example to the playground, but had to add a "fake" viewLayouts prop to make it work.
  • could you split the new widgets and example proposal to a different PR?
  • For now I have moved the utility and the new widgets to the widgets module to make it easier to merge while we think more about it.
  • However the utility really has nothing to do with the widgets. The new widgets were just a fun thing I added on top to drum up some excitement.
  • If we really don't want it in core, extensions might be a better place.

On the ViewLayoutItem class:

  • I thought the type keyword is reserved for declarative. Maybe layout?

👍 Technically, it is not an issue, JSON uses @@type. But I changed the prop to layout, I think it looks better (and less confusing).

  • If children is allowed to be more than two items (which would be nice) then a single splitterId in the parent is not going to cover it.
  • In he current implementation, children does support more than two items, but the current implementation only supports a splitter between child 1 and the rest. But this feels like follow-up, not something that needs to be sorted in this PR.
  • For my usage I don't need splitters, this was just intended to be an illustration of how we can integrate widgets with this system.
  • I'd like the split to also support minPixels/maxPixels
  • Sure. Also feels like follow-up, again splitters are not a main motivation for this PR.

@ibgreen-openai
Copy link
Copy Markdown
Collaborator Author

If the intention is to be able to pass the descriptor to views then I prefer it to be a pure JSON object, not a class instance.

  • Updated. My initial version was a pure JSON system, including the views, but I didn't like it.
  • Now the view layouts are JSON but the Views are still Views. This is the best of both worlds I think.

@ibgreen-openai
Copy link
Copy Markdown
Collaborator Author

JSON or a chained builder would be nicer than class instances

import { layout as l } from ...
l.view(new OrthographicView({id: 'sidebar', controller: false}))
l.column([])
l.row([])
l.overlay()
  • That is a nice API though perhaps not very idiomatic when compared with our current API structure.
  • I changed the view layouts to pure JSON. Offering an API like this on top should be very easy.

@ibgreen-openai ibgreen-openai changed the title feat(core) Declarative View Layout system with supporting Widgets in example Add view layout compiler May 3, 2026
@ibgreen-openai
Copy link
Copy Markdown
Collaborator Author

could you split the new widgets and example proposal to a different PR?

I was able to get codex to split this into stacked PRs.
Examples in #10271

@chrisgervang
Copy link
Copy Markdown
Collaborator

That is a nice API though perhaps not very idiomatic when compared with our current API structure.
I changed the view layouts to pure JSON. Offering an API like this on top should be very easy.

Fair enough, the JSON looks pretty reasonable

Copy link
Copy Markdown
Collaborator

@chrisgervang chrisgervang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like a useful feature in-line with summit attendee requests to have more JSON/declarative APIs, and clearly adds value to multi-view applications.

See my comments about where this lives.. I think the json module would make a nicer home for this.

Comment thread modules/widgets/src/splitter-widget.tsx Outdated
};

function parseViewLayout(root: ViewLayout): ManagedViewLayout[] {
function parseViewLayout(root: SplitterWidgetViewLayout): ManagedViewLayout[] {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there overlap between this parser implementation and the extracted one? I think this PR's use case is missing for me.. is this PR adding a function to help me implement a widget, or is it a utility to use by an application to fill the top-level views prop?

I think at the root of this is a question of discoverability.. is the widgets module where I'd look for a view builder function?

We don't really have a good module for this.. a @deck.gl/views or /utils would be more intuitive, though I don't think this change alone warrants the addition of a new module.

/extensions has meant layer extensions up until now, but we could call this is a view extension of sorts..

I'll take a closer look at your examples and maybe that'll clear the use case up for me

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment in the examples PR.. my preference is to put the ViewLayout compiler in @deck.gl/json.. it seems like the natural home for a JSON utility like this.

What do you think?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my preference is to put the ViewLayout compiler in @deck.gl/json

As I thought about this, it struck me that one problem is dependency chains. If we have the widgets module using the types exported here, then we get a dependency from widgets on the json or experimental modules, which seems undesirable.

FWIW, This was not created as something JSON specific. I developed this for a real use case, a big non-geospatial application that composes a lot of views (headersm legends, overviews, separate timelines etc, and the views can be reconfigured by the user. Changing those views around with offsets and heights etc was a pain, and this system makes it effortless.

I personally think we could make advanced view support a "tentpole" of the 9.4 release:

  • Multicanvas Views, this dynamic View Layout system, GlobeView graduation (tons of improvements there) and a bunch of extra things in the view tracker.
  • The JSON/pydeck bindings and the new widgets would just be icing on that cake.

I think landing it in the widgets module for now would not be unreasonable, then we have a bit more time to consider how to make this work with multi-canvas views and maybe other improvements to the views we want to make.

But if the temperature is lukewarm, I can always land it in community instead.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I think that'd be a very compelling theme for 9.4 and this adds of value towards it.

We can always move where this lives up until 9.4 is released.

@Pessimistress I read your feedback around this being placed outside of core because it's self-contained, but I'm starting to see this as a branch that leaf modules are depending on in JSON and widgets

We could even consider this for a core API change: deck.setProps({ views: ViewLayout | ... })

Curious to get more perspective on how you're seeing it fit in

@ibgreen-openai
Copy link
Copy Markdown
Collaborator Author

ibgreen-openai commented May 6, 2026

@Pessimistress asked for a comparison between the view layout system in the existing splitter widget

The PR description has been updated with the syntax discussion

@Pessimistress
Copy link
Copy Markdown
Collaborator

Pessimistress commented May 6, 2026

I like the direction a lot, and the following are feature requests / design details:

  • How does splitId work if there are more than 2 children? If I have 3 horizontally laid out views, it would be really nice to not have to manually nest two row layouts. I can see two approaches:
    • Create a special child item {type: 'splitter', id: ...} and insert it between the children
    • Resolve to multiple ids in the shape of <splitId>-<index>
  • Can we add minPixels maxPixels to each layout item as well? IMO one of the biggest shortcomings of the current view positioning (x, y, width, height) options is that it's either percentage or pixel based and cannot be both.

@ibgreen-openai
Copy link
Copy Markdown
Collaborator Author

  • Updated the old/new syntax comparison with a table that shows the differences and concerns about aligning.

Can we add minPixels maxPixels to each layout item?

Yes, I asked Codex to do this, let's see how it looks.

Could we create a special child item {type: 'splitter', id: ...} and insert it between the children ?

Not a strong objection... I considered splitter nodes, but leaning away from them for now.

  • They make the layout tree mix “things that occupy space” with “controls between spaces,”
  • every stack algorithm then has to skip/reconcile pseudo-children
  • generated boundary ids keep the tree as just layout/view children and expose the same splitter metadata map to widgets.

@ibgreen-openai ibgreen-openai changed the title Add view layout compiler RPF: Add view layout compiler May 8, 2026
@ibgreen-openai ibgreen-openai changed the title RPF: Add view layout compiler RFC: Add view layout compiler May 8, 2026
@ibgreen-openai
Copy link
Copy Markdown
Collaborator Author

PR description is updated to reflect the current direction and with a section on the proposed syntax and how it compares with the 9.3 SplitterWidget's view props syntaz.

* @param props - Resolved props for the compiled view.
* @returns Fresh deck view instance with resolved numeric bounds.
*/
function instantiateView(view: View, props: Record<string, unknown>): View {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is already a view.clone method.

x: resolvedRect.x,
y: resolvedRect.y,
width: resolvedRect.width,
height: resolvedRect.height
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any plan to support padding? The current resolution of relative padding in core is super awkward, as they are relative to the whole canvas instead of each view, so I have to adjust the padding along with width/height if I have a splitter.


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.

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'`.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that's necessary... We just need a helper inside SplitterWidget to convert the old format to the new one.

export type SplitterWidgetProps<ViewsT extends View[] = View[]> = WidgetProps & {
/** Stacking views descriptor */
viewLayout: ViewLayout;
viewLayout: SplitterWidgetViewLayout;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accept the new ViewLayout as well?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would personally prefer not to. It is a more complex layout system not focused on widgets.
I would keep this SplitterWidget.views props as compatibility only for now and if we can't find a better way to integrate widgets with views we can always add it later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants