---
title: "Defining dock layouts"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Defining dock layouts}
  %\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` exposes a single argument that controls panel arrangement:
the `layouts`. This vignette walks through every shape `layouts` accepts,
shows what UI each shape produces, and ends with patterns for the layouts
you'll build most often.

# The big picture

Internally, every board carries a `dock_layouts` collection. This is a
named list of one or more **views** (the global tabs you see at the top
of the app). A view's content is a `dock_layout`: the per-view panel
arrangement, stored as a tree of block / extension IDs. Panel content is
derived from the board's blocks and extensions on demand — only the
arrangement is stored.

Single-page boards are the degenerate case: a `dock_layouts` with one
auto-named view called `"Page"`. The view-nav dropdown is always present;
it just has one entry until you add more.

When you pass a `layouts =` argument to `new_dock_board()`, the constructor
normalises it to a `dock_layouts`:

| You pass | Treated as | Final shape |
| --- | --- | --- |
| A named `list(A = ..., B = ...)` | multi-view (each name is a view) | `dock_layouts` collection |
| A `dock_layout` | single page | wrapped as `Page` slot |
| A raw list (grid spec) | single-page raw grid, resolved on the way in | wrapped as `Page` slot |

```{r coercion-flow, eval=TRUE, echo=FALSE}
blockr.core::include_mermaid("coercion-flow")
```

The dispatch is name-driven: a list with top-level names becomes
multi-view (one slot per name); an unnamed list (or a `dock_layout`) is
treated as a single-page grid spec. To get multiple views, name the
top-level entries.

So you only ever need to think about two things: the **grid syntax** for
arranging panels inside a view, and **named-list syntax** for having
more than one view.

# Starting from scratch

The simplest possible board: no blocks, no extensions, no `layouts`. Since `new_dock_board()` defaults to `layouts = default_layout(blocks, extensions)` — which returns an empty `dock_layout` here — and the empty layout gets wrapped as a single `Page` view, the resulting collection is a `dock_layouts` with one empty `Page` slot:

```{r}
new_dock_board()
```

```
┌────────────────────────────┐
│  Page  [+]                 │
├────────────────────────────┤
│                            │
│             ⊕              │
│                            │
│   Start by adding a panel  │
│                            │
│       [ Add panel ]        │
│                            │
└────────────────────────────┘
```

You get a single auto-named `Page` view holding the watermark prompt.
Clicking **Add panel** would normally open the panel picker, but here
the board has no blocks or extensions to choose from, so the picker
says so and points you to add a block first. From this empty starting
point you can grow the board interactively.

When you do supply blocks or extensions but still no `layouts`, the
default `Page` is auto-populated by `default_layout()`: blocks alone
become a single row of panels; blocks together with extensions become
the familiar sidebar-and-main shape (extensions on the left, blocks on
the right). Pass `layouts = ` only to override that.

# Single-page raw grid

The simplest layout: a list (or character vector) of block/extension
IDs. The shape of the list determines the grid.

The two rules to internalise:

1. **List nesting alternates orientation.** The top level lays its
   children out horizontally; one level of nesting flips to vertical;
   another level flips back to horizontal, and so on.
2. **Character vectors create tabs.** A vector of IDs lives inside one
   DockView panel; a list of IDs gets split into multiple panels.

## One panel

A single ID gives one panel filling the whole view:

```{r}
new_dock_board(
  blocks = c(a = new_dataset_block()),
  layouts = list("a")
)
```

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

## Two panels side by side

Two top-level entries → two columns split horizontally:

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

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

## Two panels stacked vertically

Wrap the entries 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()),
  layouts = list(list("a", "b"))
)
```

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

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

## Tabs (multiple views in one panel)

Use a **character vector** (not a list) to put multiple panels in the
same DockView panel as tabs:

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

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

`list("a", "b")` (panels split) and `list(c("a", "b"))` (panels
tabbed) look almost identical in source 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 layout.

## Two columns, the right one stacked

```{r}
new_dock_board(
  blocks = c(
    a = new_dataset_block(),
    b = new_head_block(),
    c = new_head_block()
  ),
  layouts = list("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()
  ),
  layouts = list(list("a", "b"), list("c", "d"))
)
```

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

## Three rows in one column

Add a third level of nesting to flip back to horizontal inside the
vertical stack. Useful when you want a row that holds two panels
side-by-side:

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

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

## Sidebar + tabs

A common dashboard shape: a narrow column on the left holding an
extension, and 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(),
  layouts = list("edit_board_extension", c("a", "b"))
)
```

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

This is also what `default_layout()` produces when both blocks and
extensions are present: extensions on the left, blocks on the right.

# Multiple views (pages)

Pass a named list. Each named entry becomes a separate page in the
view-nav dropdown:

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

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

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

Switching to `Editor` via the dropdown:

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

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

## Choosing the initially active view

By default, the first layout is active. To start on a different one,
name it with `new_dock_board(active = )`, using its view id (the
`layouts` key):

```{r}
new_dock_board(
  blocks = c(a = new_dataset_block(), b = new_head_block()),
  extensions = new_edit_board_extension(),
  layouts = list(
    Analysis = list("a", "b"),
    Editor = dock_layout("edit_board_extension")
  ),
  active = "Editor"
)
```

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

Which view is active is tracked as a single field on the `dock_layouts`
collection, keyed by view id — like the id itself, it belongs to the
collection, not to any one layout. So it is always exactly one: the id
named in `active` (defaulting to the first view). Change it at runtime
with `active_view(board) <- id`.

## Empty views

A view with no panels is fine. Switching to it shows the same
watermark prompt as the empty default board (see `Starting from
scratch`), scoped to that tab.

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

# Putting it all together

A pot-pourri exercising every feature: multiple views, nested grids,
tabbed panels, an extension as a 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")
  ),
  layouts = list(
    Data = dock_layout(
      "edit_board_extension",
      panels("raw", "cleaned", active = "cleaned"),
      sizes = c(0.25, 0.75)
    ),
    Analysis = dock_layout(
      group("summary", "plot1", sizes = c(0.4, 0.6)),
      "plot2",
      sizes = c(0.55, 0.45)
    ),
    Charts = dock_layout(panels("plot1", "plot2"))
  ),
  active = "Charts"
)
```

The board has three views. `Charts` is marked active, so the user lands
there first.

**Charts** (active on load): one tabbed panel with `plot1` shown and
`plot2` selectable as a tab.

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

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

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

**Analysis**: two top-level columns at 55/45. The left column is a
nested `group()` with a 40/60 vertical split (summary on top, plot1
below); the right column is a single panel (`plot2`).

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

# Custom split ratios and active tabs

The list-of-IDs sugar splits space evenly and opens the first panel in
a tabbed group. For non-default ratios or a non-default open tab,
escalate to the typed constructors:

* `dock_layout(..., sizes = c(...))` — sizes parallel to the root
  children. The numbers are relative; they don't have to sum to `1`.
* `group(..., sizes = c(...))` — same, for a nested branch (any level
  below the root).
* `panels(..., active = "...")` — tabbed leaf with an explicit open
  tab. Equivalent to a character-vector child, except you also get to
  pick the active tab.

## A 30/70 split

`dock_layout(..., sizes =)` runs parallel to the children passed in
`...`. With two children the layout splits horizontally; with two
sizes it does so unevenly:

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

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

## Stacking with sizes

Same idea, vertical layout: pass `orientation = "vertical"` so the
root split runs top-to-bottom instead of left-to-right.

```{r}
new_dock_board(
  blocks = c(a = new_dataset_block(), b = new_head_block()),
  layouts = list(
    Main = dock_layout("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
by default — same behaviour as a bare character vector. Pass `active`
to open a different tab on load:

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

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

## Sizes inside a nested branch

`group()` is the equivalent of an inner `list(...)` but with its own
`sizes`. Use it whenever you need ratios on a non-root branch:

```{r}
new_dock_board(
  blocks = c(
    a = new_dataset_block(),
    b = new_head_block(),
    c = new_head_block()
  ),
  layouts = list(
    Main = dock_layout(
      "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 (left / right);
inner `group(..., sizes = c(0.6, 0.4))` controls the right column's
internal stack.

# Cheat-sheet

| Goal | Syntax |
| --- | --- |
| One panel | `list("a")` |
| Two side-by-side panels | `list("a", "b")` |
| Two stacked panels | `list(list("a", "b"))` |
| Tabbed single panel | `list(c("a", "b"))` |
| Sidebar + main | `list("ext", "main")` |
| Two columns, both stacked | `list(list("a", "b"), list("c", "d"))` |
| Custom split ratio | `dock_layout("a", "b", sizes = c(0.3, 0.7))` |
| Custom open tab | `dock_layout(panels("a", "b", active = "b"))` |
| Vertical top-level split | `dock_layout("a", "b", orientation = "vertical")` |
| Multiple views | `list(A = ..., B = ...)` |
| Start on view `B` | `new_dock_board(layouts = list(A = ..., B = ...), active = "B")` |
| Empty starter view | `list(Page = list())` |

# Where to go from here

- See `?dock_layout` for the per-view layout type (including
  `panels()` and `group()`) and `?active_view` for switching the
  active view at runtime.
