---
title: "Views and grids"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Views and grids}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include=FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  eval = FALSE
)
```

```{r setup}
library(blockr.core)
library(blockr.dock)
```

A `dock_board`'s arrangement is two independent things, and it takes two
constructor arguments to match:

* **`views`** --- the *structure*: which panels belong to each view (the
  global tabs across the top of the app) and the view's display name. One
  `dock_view` per view.
* **`grids`** --- the *geometry*: how each view's panels are split, nested,
  tabbed and sized. One `dock_grid` per view.

This vignette covers the grid syntax you use to describe geometry, how the
two slots fit together at construction, and the patterns you'll reach for
most.

# The big picture

A board stores structure and geometry as two aligned slots, keyed by view
id --- read them with `board_views()` and `board_grids()`. They are kept
apart on purpose: membership is owned by the update lifecycle (add / remove
a block), geometry by the client (you drag a sash, the board records it).
A panel is *placed* in a view when it is in both slots; the two are
reconciled only where the placement is read.

There is a third representation you should know the name of but will rarely
touch: **`dock_layout`**. It is *dockView's own* form --- the
`{grid, panels, activeGroup}` payload the front end renders and echoes
back. It is the wire type at the client boundary, not something you author
with. Our model is `dock_view` + `dock_grid`; `dock_layout` is what crosses
to dockView (`?dock_layout`).

The constructor is forgiving about which slot you fill:

| You pass | Result |
| --- | --- |
| `grids` only | one view per grid (id = the list name), members taken from the grid's panels |
| `views` only | structure with no explicit geometry; each view renders a flat default over its members |
| both | aligned by id: `views` sets membership + names, `grids` sets geometry |
| neither | a single default view (sidebar + main when there are extensions) |

So for a layout with real geometry you usually just pass `grids`; the views
come along for free. Reach for `views` when you want a display name that
differs from the id, or a view without an explicit arrangement.

# Grid syntax

You describe geometry with `dock_grid()` (and its helpers `panels()` and
`group()`). Two rules cover everything:

1. **List nesting alternates orientation.** `dock_grid()`'s top level lays
   its children out horizontally; one level of `list()` nesting flips to
   vertical; another flips back, and so on.
2. **Character vectors create tabs.** A vector of IDs shares one DockView
   panel as tabs; a `list()` of IDs is split into separate panels.

## One panel

A single ID fills the whole view:

```{r}
new_dock_board(
  blocks = c(a = new_dataset_block()),
  grids = list(Page = dock_grid("a"))
)
```

```
┌────────────────────────────┐
│  Page  [+]                 │
├────────────────────────────┤
│                            │
│            a               │
│                            │
└────────────────────────────┘
```

The view is named after the grid's list name (`Page`), and its members are
that grid's panels (`a`).

## Two panels side by side

Two top-level children → two columns split horizontally:

```{r}
new_dock_board(
  blocks = c(a = new_dataset_block(), b = new_head_block()),
  grids = list(Page = dock_grid("a", "b"))
)
```

```
┌────────────────────────────┐
│  Page  [+]                 │
├─────────────┬──────────────┤
│             │              │
│      a      │      b       │
│             │              │
└─────────────┴──────────────┘
```

## Two panels stacked vertically

Wrap the children in **one extra layer** of `list()` to introduce a
vertical split:

```{r}
new_dock_board(
  blocks = c(a = new_dataset_block(), b = new_head_block()),
  grids = list(Page = dock_grid(list("a", "b")))
)
```

```
┌────────────────────────────┐
│  Page  [+]                 │
├────────────────────────────┤
│            a               │
├────────────────────────────┤
│            b               │
└────────────────────────────┘
```

The outer level still describes a horizontal split, but with a single
child that "split" is one full-width column. The inner `list("a", "b")` is
at depth 1, so it splits **vertically**: `a` stacks on top of `b`.

## Tabs (multiple panels in one)

Use a **character vector** (not a list) to put several panels in one
DockView panel as tabs:

```{r}
new_dock_board(
  blocks = c(a = new_dataset_block(), b = new_head_block()),
  grids = list(Page = dock_grid(c("a", "b")))
)
```

```
┌────────────────────────────┐
│  Page  [+]                 │
├────────────────────────────┤
│ ┌──────┐┌──────┐           │
│ │  a   ││  b   │           │
│ └──────┘└──────┘           │
│                            │
│  (a is shown, b is a tab)  │
│                            │
└────────────────────────────┘
```

`dock_grid(list("a", "b"))` (panels split) and `dock_grid(c("a", "b"))`
(panels tabbed) read almost the same but produce very different UIs --- the
list/vector distinction flips between *split a panel* and *tabify a panel*.

# Nested grids

Combine the two rules to build any arrangement.

## Two columns, the right one stacked

```{r}
new_dock_board(
  blocks = c(
    a = new_dataset_block(),
    b = new_head_block(),
    c = new_head_block()
  ),
  grids = list(Page = dock_grid("a", list("b", "c")))
)
```

```
┌────────────────────────────┐
│  Page  [+]                 │
├─────────────┬──────────────┤
│             │      b       │
│      a      ├──────────────┤
│             │      c       │
└─────────────┴──────────────┘
```

## Two columns, both stacked

```{r}
new_dock_board(
  blocks = c(
    a = new_dataset_block(),
    b = new_head_block(),
    c = new_head_block(),
    d = new_head_block()
  ),
  grids = list(Page = dock_grid(list("a", "b"), list("c", "d")))
)
```

```
┌────────────────────────────┐
│  Page  [+]                 │
├─────────────┬──────────────┤
│      a      │      c       │
├─────────────┼──────────────┤
│      b      │      d       │
└─────────────┴──────────────┘
```

## Three rows in one column

A third level of nesting flips back to horizontal inside the vertical
stack --- a row holding two panels side by side:

```{r}
new_dock_board(
  blocks = c(
    a = new_dataset_block(),
    b = new_head_block(),
    c = new_head_block()
  ),
  grids = list(Page = dock_grid(list("a", list("b", "c"))))
)
```

```
┌────────────────────────────┐
│  Page  [+]                 │
├────────────────────────────┤
│            a               │
├─────────────┬──────────────┤
│      b      │      c       │
└─────────────┴──────────────┘
```

## Sidebar + tabs

A common dashboard shape: a narrow column on the left holding an extension,
a tabbed panel on the right with several blocks:

```{r}
new_dock_board(
  blocks = c(a = new_dataset_block(), b = new_head_block()),
  extensions = new_edit_board_extension(),
  grids = list(
    Page = dock_grid("edit_board", c("a", "b"))
  )
)
```

```
┌────────────────────────────┐
│  Page  [+]                 │
├─────────┬──────────────────┤
│         │ ┌────┐┌────┐     │
│  edit   │ │ a  ││ b  │     │
│         │ └────┘└────┘     │
│         │                  │
│         │   (tabs)         │
└─────────┴──────────────────┘
```

This is also the shape `new_dock_board()` gives you by default when both
blocks and extensions are present, and you pass neither slot: extensions
on the left, blocks on the right.

# Multiple views (pages)

Give `grids` more than one entry; each list name becomes a separate page in
the view-nav dropdown, and each becomes its own view:

```{r}
new_dock_board(
  blocks = c(a = new_dataset_block(), b = new_head_block()),
  extensions = new_edit_board_extension(),
  grids = list(
    Analysis = dock_grid("a", "b"),
    Editor = dock_grid("edit_board")
  )
)
```

`Analysis` view (active by default --- the first view wins):

```
┌────────────────────────────┐
│ Analysis  [+]   ← view nav │
├────────────────────────────┤
│      a       │      b      │
└────────────────────────────┘
```

Switching to `Editor` via the dropdown:

```
┌────────────────────────────┐
│ Editor  [+]                │
├────────────────────────────┤
│                            │
│           edit             │
│                            │
└────────────────────────────┘
```

Each grid follows exactly the same syntax as the single-view form:
character vectors for tabs, nested lists for splits.

## Display names distinct from the id

A grid's list name is the view *id*, and it doubles as the display label.
When you want a label that differs from the id --- or a view with no
geometry at all --- fill the `views` slot too. A `dock_view()` carries the
membership and the display name; align it to the grid by id:

```{r}
new_dock_board(
  blocks = c(a = new_dataset_block(), b = new_head_block()),
  views = list(
    main = dock_view(c("a", "b"), name = "Analysis")
  ),
  grids = list(
    main = dock_grid("a", "b")
  )
)
```

The nav shows **Analysis**; the stable id `main` is what `active`, renames
and updates address.

## Choosing the initially active view

The first view is active by default. To start elsewhere, name it with
`new_dock_board(active = )`, using its view id:

```{r}
new_dock_board(
  blocks = c(a = new_dataset_block(), b = new_head_block()),
  extensions = new_edit_board_extension(),
  grids = list(
    Analysis = dock_grid("a", "b"),
    Editor = dock_grid("edit_board")
  ),
  active = "Editor"
)
```

```
┌────────────────────────────┐
│ Editor  [+]   ← starts here│
├────────────────────────────┤
│           edit             │
└────────────────────────────┘
```

Which view is active is a single field on the views collection, keyed by
id --- it belongs to the collection, not to any one view, so it is always
exactly one. Change it at runtime with `active_view(board) <- id`, and read
it back with `active_view(board)`; index `board_grids(board)[[id]]` if you
then need that view's geometry.

## Views without geometry

Pass `views` alone (bare member vectors coerce to `dock_view`s) for
structure with no explicit arrangement. Each such view renders a flat
default over its members until the client arranges it (which the board then
records):

```{r}
new_dock_board(
  blocks = c(a = new_dataset_block(), b = new_head_block()),
  views = list(
    Analysis = c("a", "b"),
    Empty = character()
  )
)
```

An empty view shows the same watermark prompt as an empty board, scoped to
that tab.

# Putting it all together

A pot-pourri: multiple views, nested grids, tabbed panels, an extension
sidebar, and an explicit active view.

```{r}
new_dock_board(
  blocks = c(
    raw = new_dataset_block(),
    cleaned = new_head_block(),
    summary = new_head_block(),
    plot1 = new_scatter_block(),
    plot2 = new_scatter_block()
  ),
  extensions = new_edit_board_extension(),
  links = list(
    new_link("raw", "cleaned", "data"),
    new_link("cleaned", "summary", "data"),
    new_link("cleaned", "plot1", "data"),
    new_link("cleaned", "plot2", "data")
  ),
  grids = list(
    Data = dock_grid(
      "edit_board",
      panels("raw", "cleaned", active = "cleaned"),
      sizes = c(0.25, 0.75)
    ),
    Analysis = dock_grid(
      group("summary", "plot1", sizes = c(0.4, 0.6)),
      "plot2",
      sizes = c(0.55, 0.45)
    ),
    Charts = dock_grid(panels("plot1", "plot2"))
  ),
  active = "Charts"
)
```

Three views; `Charts` is active, so the user lands there first.

**Charts** (active on load): one tabbed panel, `plot1` shown, `plot2` a tab.

```
┌────────────────────────────┐
│  Charts  [+]               │
├────────────────────────────┤
│ ┌───────┐┌───────┐         │
│ │ plot1 ││ plot2 │         │
│ └───────┘└───────┘         │
│  (plot1 shown, plot2 tab)  │
└────────────────────────────┘
```

**Data**: a slim extension sidebar on the left, a wide right column with
`raw` / `cleaned` tabbed. `active = "cleaned"` opens the cleaned tab;
`sizes = c(0.25, 0.75)` carves out the narrow sidebar.

```
┌────────────────────────────┐
│  Data  [+]                 │
├──────┬─────────────────────┤
│      │┌─────┐┌─────────┐   │
│ edit ││ raw ││ cleaned │   │
│      │└─────┘└─────────┘   │
│      │  (cleaned shown)    │
└──────┴─────────────────────┘
  25%          75%
```

**Analysis**: two columns at 55/45. The left is a nested `group()` with a
40/60 vertical split (summary over plot1); the right is `plot2`.

```
┌────────────────────────────┐
│  Analysis  [+]             │
├─────────────┬──────────────┤
│   summary   │              │   40% / 55%
├─────────────┤    plot2     │
│    plot1    │              │   60% / 45%
└─────────────┴──────────────┘
     55%             45%
```

# Custom split ratios and active tabs

The bare list-of-IDs form splits space evenly and opens the first tab. For
non-default ratios or a non-default open tab, use the typed helpers:

* `dock_grid(..., sizes = c(...))` --- sizes parallel to the root children.
  They are relative; they need not sum to `1` (they are normalised for you).
* `group(..., sizes = c(...))` --- the same, for a nested branch below the
  root.
* `panels(..., active = "...")` --- a tabbed leaf with an explicit open tab.

## A 30/70 split

`sizes` runs parallel to the children in `...`. Two children split
horizontally; two sizes make the split uneven:

```{r}
new_dock_board(
  blocks = c(a = new_dataset_block(), b = new_head_block()),
  grids = list(
    Main = dock_grid("a", "b", sizes = c(0.3, 0.7))
  )
)
```

```
┌────────────────────────────┐
│  Main  [+]                 │
├──────────┬─────────────────┤
│          │                 │
│    a     │        b        │
│          │                 │
└──────────┴─────────────────┘
   30%             70%
```

## Stacking with sizes

Same idea, vertical: `orientation = "vertical"` makes the root split run
top-to-bottom.

```{r}
new_dock_board(
  blocks = c(a = new_dataset_block(), b = new_head_block()),
  grids = list(
    Main = dock_grid("a", "b",
                     orientation = "vertical",
                     sizes = c(0.25, 0.75))
  )
)
```

```
┌────────────────────────────┐
│  Main  [+]                 │
├────────────────────────────┤
│            a               │   25%
├────────────────────────────┤
│                            │
│            b               │   75%
│                            │
└────────────────────────────┘
```

## Choosing the open tab

`panels()` builds a tabbed leaf. Without `active`, the first ID opens ---
same as a bare character vector. Pass `active` to open a different tab:

```{r}
new_dock_board(
  blocks = c(
    a = new_dataset_block(),
    b = new_head_block(),
    c = new_head_block()
  ),
  grids = list(
    Main = dock_grid(panels("a", "b", "c", active = "b"))
  )
)
```

```
┌────────────────────────────┐
│  Main  [+]                 │
├────────────────────────────┤
│ ┌────┐┌──────┐┌────┐       │
│ │ a  ││  b   ││ c  │       │
│ └────┘└──────┘└────┘       │
│         ↑                  │
│   b is open by default     │
└────────────────────────────┘
```

## Sizes inside a nested branch

`group()` is an inner `list(...)` with its own `sizes` --- use it for
ratios on any non-root branch:

```{r}
new_dock_board(
  blocks = c(
    a = new_dataset_block(),
    b = new_head_block(),
    c = new_head_block()
  ),
  grids = list(
    Main = dock_grid(
      "a",
      group("b", "c", sizes = c(0.6, 0.4)),
      sizes = c(0.3, 0.7)
    )
  )
)
```

```
┌────────────────────────────┐
│  Main  [+]                 │
├────────┬───────────────────┤
│        │      b            │   inner: 60% top
│        ├───────────────────┤
│   a    │      c            │   inner: 40% bottom
│        │                   │
└────────┴───────────────────┘
   30%          70%
```

Outer `sizes = c(0.3, 0.7)` controls the root split; inner
`group(..., sizes = c(0.6, 0.4))` controls the right column's stack.

# Cheat-sheet

Geometry (`dock_grid(...)`):

| Goal | Syntax |
| --- | --- |
| One panel | `dock_grid("a")` |
| Two side-by-side | `dock_grid("a", "b")` |
| Two stacked | `dock_grid(list("a", "b"))` |
| Tabbed panel | `dock_grid(c("a", "b"))` |
| Sidebar + main | `dock_grid("ext", "main")` |
| Two columns, both stacked | `dock_grid(list("a", "b"), list("c", "d"))` |
| Custom split ratio | `dock_grid("a", "b", sizes = c(0.3, 0.7))` |
| Custom open tab | `dock_grid(panels("a", "b", active = "b"))` |
| Vertical top-level split | `dock_grid("a", "b", orientation = "vertical")` |

Board (`new_dock_board(...)`):

| Goal | Syntax |
| --- | --- |
| One view with geometry | `grids = list(Page = dock_grid(...))` |
| Several views | `grids = list(A = dock_grid(...), B = dock_grid(...))` |
| A display name != id | `views = list(id = dock_view(members, name = "..."))` |
| Structure, no geometry | `views = list(Page = c("a", "b"))` |
| Start on view `B` | `new_dock_board(grids = list(A = ..., B = ...), active = "B")` |
| Empty starter view | `views = list(Page = character())` |

# Where to go from here

- `?dock_grid` for the geometry syntax (with `panels()` and `group()`).
- `?dock_view` for the structure type, and `?active_view` for switching the
  active view at runtime.
- `?dock_layout` for the dockView boundary form, and `as_dock_grid()` /
  `as_dock_layout()` for the casts across it.
