---
title: Layout
description: "fitIconSize, centerBoard, createGrid, distribute: sizing and point math for boards made of icons."
---

Desktop icons are placed at absolute `x/y` points, so layout in filedom means
computing points: pick an icon size, center a board, then map cells to points.
These helpers do that math.

## fitIconSize

Returns the largest icon size that fits `cols × rows` icon units inside a
fraction of the desktop, clamped to `[min, max]`. It takes the smaller of the
width-limited and height-limited sizes, so the board fits on both axes.

```ts
import { fitIconSize, MAX_DESKTOP_ICON_SIZE } from 'filedom';

const iconSize = fitIconSize({
  metrics,               // SystemMetrics (width/height used)
  widthFraction: 0.6,    // board may occupy 60% of screen width…
  heightFraction: 0.8,   // …and 80% of its height
  cols: 10,
  rows: 20,
  min: 24,
  // max defaults to MAX_DESKTOP_ICON_SIZE (128)
  // gap: px shaved off so the *icon* (not the cell) is the fitted size
});
```

`cols` and `rows` are icon units and may be fractional. Solitaire passes
`cols * cardAspect * 1.18` to allow for non-square cards and spacing.

:::info[The 128px limit]
macOS caps desktop icons at 128px, and icon size is one global Finder setting.
`MAX_DESKTOP_ICON_SIZE` holds that limit and is the default `max`.
:::

## centerBoard

Returns the top-left offset that centers a `boardW × boardH` box on the desktop.
`top` reserves that many pixels at the top (for the menu bar or a HUD row) and
excludes them from centering. Results are floored to whole pixels.

```ts
const { offsetX, offsetY } = centerBoard({ metrics, boardW, boardH, top: 64 });
```

## createGrid

A uniform grid addressed by cell. `cellCenter(col, row)` gives the point to move
a sprite to, and `pointToCell(x, y)` reverses it for click hit-testing. Both are
pure functions. The grid has no knowledge of icons, stages or parking, and
computing a point never mounts or unmounts anything.

```ts
import { createGrid } from 'filedom';

const grid = createGrid({
  cols: 8, rows: 8,
  cell: iconSize + gap,             // per-cell pitch, not the icon size
  origin: { x: offsetX, y: offsetY }, // top-left of cell (0,0)'s box
});

grid.cellCenter(3, 4);       // -> { x, y } for a sprite's `at`
grid.indexCenter(12);        // row-major flat index variant
grid.pointToCell(px, py);    // -> { col, row } | null (outside the board)
grid.boardRect;              // { x, y, width, height } for frames/backgrounds
```

`boardRect` spans `cols × rows` whole cells. If `cell` includes a gap, the
rectangle includes the gap after the last cell too.

## distribute

Returns `count` evenly spaced points centered on `center` along one axis, each
at the fixed `cross` coordinate on the other axis. Spacing is center to center,
so a `step` smaller than the item width gives an overlapping fan, such as a hand
of cards. Flexbox can't express this because its gap is positive only.

```ts
import { distribute } from 'filedom';

const handSlots = distribute({ count: 5, step: iconSize * 0.55, center: cx, cross: handY });
// axis: 'x' (default) or 'y'
```
