# Tools, Cursor Modes, And Drafts

Drawing starts in the browser.

The server owns committed canvas state, but the browser owns the active pointer
gesture. That split is what makes drawing feel immediate without turning every
pointer move into a LiveView event.

In shared canvases the browser sends throttled draw intent while the gesture is
active so peers can see live draft previews. The server still treats the final
`add_vector_item` command as the durable source of truth. The browser keeps
a pending draw commit locally until that command ack resolves; applications can
replace or disable the default draft persistence through
`createLivePanelsCanvasHook` runtime options.

## Use The Drawing Toolkit

Drawing canvases should import the drawing toolkit:

```elixir
use LivePanels.Canvas,
  pubsub: MyApp.PubSub,
  domains: [:drawing]

use LivePanels.Toolkit.Drawing
```

The drawing preset includes the canvas preset plus drawing controls and
viewport JS helpers:

- `set_cursor_mode_js/3`;
- `set_draw_style_js/3`;
- drawing element attrs;
- committed vector item style attrs;
- erase target attrs;
- point handle attrs.

## Cursor Modes

The browser runtime recognizes these cursor modes:

| Mode | Purpose |
|---|---|
| `:drag` | Select, drag, and resize committed canvas items. |
| `:marquee` | Draw a selection marquee. |
| `:pan` | Pan the viewport. |
| `:draw` | Create a drawing draft with the selected tool. |
| `:erase` | Remove committed vector items through erase targets. |

Set a mode with `set_cursor_mode_js/3`:

```heex
<button phx-click={set_cursor_mode_js(@livepanels.canvas_id, :drag)}>
  Select
</button>

<button phx-click={set_cursor_mode_js(@livepanels.canvas_id, :marquee)}>
  Marquee
</button>

<button phx-click={set_cursor_mode_js(@livepanels.canvas_id, :erase)}>
  Erase
</button>
```

## Drawing Tools

When the mode is `:draw`, pass a drawing tool and a complete style map:

```heex
<button
  phx-click={
    set_cursor_mode_js(@livepanels.canvas_id, :draw,
      tool: :arrow,
      style: %{
        color: "#111827",
        width: 3,
        fill_color: "none",
        stroke_style: "solid",
        opacity: 100,
        roundness: 0
      }
    )
  }
>
  Arrow
</button>
```

Supported drawing tools are:

- `:pen`;
- `:freehand`;
- `:polyline`;
- `:arrow`;
- `:line`;
- `:rectangle`;
- `:ellipse`;
- `:diamond`;
- `:triangle`;
- `:text`.

`pen` is the toolbar-friendly name for freehand drawing.

## Draft Style

Drawing style is explicit. The style map must include all fields:

```elixir
%{
  color: "#2563eb",
  width: 4,
  fill_color: "none",
  stroke_style: "solid",
  opacity: 100,
  roundness: 0
}
```

`stroke_style` must be `"solid"`, `"dashed"`, or `"dotted"`. `opacity` is an
integer from `0` through `100`. `roundness` is a non-negative integer.

Update draft style without changing mode:

```heex
<button
  phx-click={
    set_draw_style_js(@livepanels.canvas_id, %{
      color: "#dc2626",
      width: 4,
      fill_color: "none",
      stroke_style: "dashed",
      opacity: 100,
      roundness: 0
    })
  }
>
  Red dashed
</button>
```

Draft style affects the current and next browser draft. It does not restyle
already committed vector items unless the application also sends committed vector item updates.

## Mirror Mode In LiveView Assigns

The browser can own mode and style without any LiveView assigns. Most products
still mirror them so toolbar buttons can show active state.

Pass `push: "event_name"` to append a LiveView event after the browser dispatch:

```heex
<button
  phx-click={
    set_cursor_mode_js(@livepanels.canvas_id, :draw,
      tool: :rectangle,
      style: @draw_style,
      push: "set_cursor_mode"
    )
  }
>
  Rectangle
</button>
```

Handle the event in the application:

```elixir
def handle_event("set_cursor_mode", params, socket) do
  params = Map.get(params, "value", params)

  cursor_mode =
    case params["mode"] do
      "draw" -> :draw
      "erase" -> :erase
      "marquee" -> :marquee
      "pan" -> :pan
      _ -> :drag
    end

  {:noreply, assign(socket, :cursor_mode, cursor_mode)}
end
```

Pass the mirrored mode into the runtime:

```heex
<.canvas_runtime {canvas_runtime_attrs(assigns, cursor_mode: @cursor_mode)} />
```

This keeps LiveView patches from snapping visible toolbar state back to an old
mode.

## Mirror Style In LiveView Assigns

Style controls use the same pattern:

```heex
<button
  phx-click={
    set_draw_style_js(@livepanels.canvas_id, Map.put(@draw_style, :color, "#16a34a"),
      push: "set_draw_style"
    )
  }
>
  Green
</button>
```

Handle the pushed style:

```elixir
def handle_event("set_draw_style", style, socket) do
  {:noreply, assign(socket, :draw_style, normalize_draw_style(style))}
end
```

If your product applies palette changes to selected committed vector items, do that
explicitly in the same event. Draft style and committed vector item style are separate
state.

## Marquee Selection

Drawing canvases often need both drag selection and marquee selection. Pass the
marquee attrs through `canvas_runtime_attrs/2`:

```heex
<.canvas_runtime
  {canvas_runtime_attrs(
    assigns,
    marquee_selection_attrs(:items, :overlap, cursor_mode: @cursor_mode)
  )}
/>
```

Use `:overlap` when touching the selection rectangle should select an item. Use
`:contain` when the item must be fully enclosed.

## Draft Lifecycle

During a drawing gesture:

1. the browser creates and updates a draft locally;
2. the canvas renders the draft immediately;
3. the finished gesture becomes one drawing command;
4. the server validates the command;
5. the accepted command creates a committed canvas item.

The active draft is not persisted, copied, undone, redone, or broadcast to
other participants. The committed vector item is.

That boundary keeps collaboration and persistence quiet while the pointer is
moving.
