# Placement and layouts

> Reference for Placement, Placeable, PlacementHandle, the collide modes, PlaceCheck, MarkerOptions, areas, zones and snap points, and useLayout with LayoutStorage and fileStorage: every prop, type and default.

```ts
import { fileStorage, Placeable, Placement, useLayout } from "@apricot/react";
import type {
  Collide, Layout, LayoutItem, LayoutOptions, LayoutStorage, MarkerOptions, Place, PlaceCheck, PlaceOn,
  PlacementArea, PlacementBounds, PlacementHandle, PlacementProps, PlacementSnap, PlacementZone, PlaceableProps, SnapPoint,
} from "@apricot/react";
```

## `Placement`

Things to pick up, move and turn by hand. Tap a `<Placeable>` to pick it up, drag it, twist two fingers to turn it, tap empty space to put it down. Drags elsewhere still reach `<CameraRig orbit>`. A drag that starts on the marker moves the picked-up thing. Put it inside `<Scene>`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `bounds` | `[minX, minZ, maxX, maxZ]` | anywhere | The floor area things stay inside, in metres: a room's walls |
| `wallHeight` | metres | `2.4` | Height of the walls round `bounds`, for things hung on them |
| `areas` | `PlacementArea[]` | anywhere (or `bounds`) | Ground and water of any shape, each at its level |
| `zones` | `PlacementZone[]` | none | Places nothing may stand |
| `snaps` | `SnapPoint[]` | none | Spots that pull things into place and turn them |
| `snap` | `PlacementSnap` | free | `{ grid, turn, wall }` steps while moving |
| `collide` | `Collide` | `"block"` | What a moved thing does when it meets another |
| `canPlace` | `(id, place: Place, check: PlaceCheck) => boolean` | | Your own rule, called while a thing moves or turns |
| `selected` | `string \| null` | uncontrolled | The picked-up thing's id, to control it yourself |
| `onSelect` | `(id: string \| null) => void` | | Picked up (its id) or put down (`null`) |
| `onChange` | `(id: string, place: Place) => void` | | A thing was moved or turned and let go: its new place. Things standing on it report theirs too |
| `onFitChange` | `(id: string, fits: boolean) => void` | | Whether the picked-up thing fits where it is |
| `lift` | metres | `0.03` | How high a picked-up thing floats while moved |
| `marker` | colour, `MarkerOptions` or `false` | `"#4f8cff"` | The patch under (or behind) the picked-up thing |
| `ref` | `Ref<PlacementHandle>` | | |
| `children` | `ReactNode` | | `<Placeable>`s, possibly inside your own components |

Places come from your state (`position`, `turn` on each `<Placeable>`) and go back through `onChange`. Store them (`useLayout` does).

### `Collide`

| Value | A moved thing that meets another |
| --- | --- |
| `"block"` | Stops against it and slides along it. `canPlace` false: it stays where it last fitted |
| `"warn"` | Passes through it with the marker in its `blocked` colour; let go there, it goes back to where it last fitted. Also passes over the wrong kind of ground, water and zones, marked |
| `"off"` | Ignores it |

### `PlacementSnap`

| Field | Default | Description |
| --- | --- | --- |
| `grid` | `0` (free) | Grid step for positions, metres |
| `turn` | `0` (free) | Step for turns, degrees |
| `wall` | `0` (off) | Moves things flush against a wall within this distance, metres |

### `Place` and `PlaceCheck`

`Place` is `{ position: Vec3, turn: number }` (turn in degrees).

`PlaceCheck`, given to `canPlace`:

| Field | Type | Description |
| --- | --- | --- |
| `on` | `PlaceOn` | Where it goes |
| `standingOn` | `string \| null` | The id of what it would stand on (a desk), or `null` (the floor, a wall) |
| `overlapping` | `string[]` | Solid things it would overlap (always empty with `"block"` or `"off"`) |
| `area` | `string \| null` | The id of the area it would stand or float in |
| `zones` | `string[]` | The ids of the no-go zones it would touch |

### `MarkerOptions`

| Field | Default | Description |
| --- | --- | --- |
| `color` | `"#4f8cff"` | Where it fits |
| `blocked` | `"#ff5a4f"` | Where it doesn't (overlap, zone, wrong ground, or `canPlace` false) |
| `opacity` | `0.8` | 0 to 1 |
| `margin` | `0.12` | How far it reaches past the thing on each side, metres |
| `radius` | `0.06` | Corner rounding, metres. Large values make a pill or a disc |
| `outline` | `0` (filled) | Draws only a ring this thick, metres |

### Areas, zones and snap points

`PlacementArea`:

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `id` | `string` | | What `canPlace` is told |
| `outline` | `Outline` | required | `[x, z]` corners in order, metres |
| `level` | metres | `0` | Height of its top, where things stand |
| `water` | `Waves \| true` | | Water: only `on="water"` things go in it, bobbing on these waves |

Things stay wholly inside one area, sliding along its edge. Later areas lie on earlier ones (the sand on the sea, a deck on the sand).

`PlacementZone`: `{ id?, outline }`. A thing touching one doesn't fit.

`SnapPoint`:

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `at` | `[x, z]` | required | Where, metres |
| `turn` | degrees | as it was | The turn it gives what snaps to it |
| `radius` | metres | `0.3` | How close it pulls from |
| `accepts` | `(id: string) => boolean` | all | Which things it takes |

### `PlacementHandle`

| Method | Description |
| --- | --- |
| `turnBy(id, degrees)` | Turns a thing (snapped to `snap.turn`), carrying what stands on it. Returns `false` if it isn't there or hangs on a wall; with `"block"`, also if it has no room or `canPlace` says no. Otherwise it turns even where it doesn't fit (marked) |
| `standingOn(id)` | The ids of the things standing on it |

## `Placeable`

Something to place by hand. Must be inside a `<Placement>` (it throws otherwise).

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `id` | `string` | required | Unique within its `<Placement>`: what `onSelect` and `onChange` report |
| `position` | `Vec3` | `[0, 0, 0]` | Metres. On the floor, `y` is the level it moves along; on a surface, the surface's top; on a wall, `[along, height, wall]` |
| `turn` | degrees | `0` | Round the vertical axis. On a wall, set by the wall: `0` back (min z), `90` left (min x), `180` front (max z), `270` right (max x) |
| `on` | `PlaceOn` | `"floor"` | `"floor"`, `"surface"` (on things marked `surface`, or the floor), `"wall"` (hung on the walls round `bounds`, facing in), `"water"` (floats in water areas) |
| `surface` | `boolean \| number` | `false` | Things with `on="surface"` can stand on it and move with it: `true` for its top, or its top's height in its own space |
| `solid` | `boolean` | `true` | Others can't overlap it, nor it them. `false` for a rug |
| `locked` | `boolean` | `false` | Can't be picked up, but still blocks others |
| `children` | `ReactNode` | | What it is: shapes, `<Merged>`, `<Model>`, a `<Lamp>` |

Build children standing on their origin and facing +z; hung things with their back at z = 0. The footprint is the children's bounding box (a `<Lamp>`'s glow is left out). A `<Model>` still loading has no size: it fits anywhere until it arrives, then it is checked.

While a thing moves (dragged, easing back, or bobbing on water) it casts no shadow, so the shadow map is not redrawn every frame.

## `useLayout`

```ts
const layout = useLayout<T extends LayoutItem>(initial: T[], options?: LayoutOptions): Layout<T>;
```

`LayoutItem` is `{ id: string; position: Vec3; turn: number }`; add your own fields (`kind`, a colour).

### `LayoutOptions`

| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `save` | `string` | not kept | Keep it between runs under this name, and start from what was kept |
| `version` | `number` | `1` | The version of your items' shape. A layout kept by another version is ignored |
| `history` | `number` | `100` | How many steps undo goes back |
| `storage` | `LayoutStorage` | `fileStorage()` | Where it is kept |

### `Layout<T>`

| Member | Description |
| --- | --- |
| `items` | The layout now |
| `place(id, place)` | Moves an item. Pass it to `<Placement onChange>`. Calls in the same moment (one gesture) are one step |
| `add(item)` | Adds an item |
| `update(id, change)` | Changes some of an item's fields |
| `remove(ids)` | Removes one id or several, as one step |
| `set(items)` | Replaces every item (a reset), as one step |
| `undo()`, `redo()` | |
| `canUndo`, `canRedo` | |
| `newId(prefix)` | An unused id: `prefix-1`, `prefix-2`… |
| `loaded` | With `save`: true once the kept layout was read. True at once without `save` |

A kept layout is written about 0.3 s after the last change, and at once when the component unmounts.

### `LayoutStorage` and `fileStorage`

`LayoutStorage` is `{ read(name): Promise<string | null>; write(name, text): Promise<void> | void }`.

`fileStorage()`, the default:

- on iOS and Android, a `<name>.json` file in an `apricot` folder in the app's documents (`expo-file-system`). Without `expo-file-system` installed, nothing is kept and a warning says so once;
- on the web, `localStorage` under `apricot:<name>`.

Your own, for example with AsyncStorage or a server:

```ts
import AsyncStorage from "@react-native-async-storage/async-storage";
import type { LayoutStorage } from "@apricot/react";

export const asyncStorage: LayoutStorage = {
  read: (name) => AsyncStorage.getItem(`layout:${name}`),
  write: (name, text) => AsyncStorage.setItem(`layout:${name}`, text),
};
```

Core has the pieces underneath: `LayoutHistory`, `writeLayout(items, version)`, `readLayout(text, version)` and `freeId(prefix, items)`.

## Related

- [Placing things by hand](https://matthiasbrandolin.dev/apricot/guides/placement).
- [Ground and water](https://matthiasbrandolin.dev/apricot/guides/ground-and-water).
- [Room builder recipe](https://matthiasbrandolin.dev/apricot/recipes/room-builder).
