# Drawing Apps With LivePanels

Use this track when the canvas is primarily a whiteboard, sketch surface,
annotation tool, diagramming app, review board, or drawing-heavy editor.

The shorter `Add Drawing Tools` guide shows how drawing support fits into a
canvas. This track goes deeper: toolbars, cursor modes, browser drafts,
committed vector items, vector item rendering, point editing, erasing, drawing-owned content
elements, persistence, shared canvases, import/export, and recipes.

## What LivePanels Gives A Drawing App

LivePanels gives drawing apps a server-authoritative canvas model:

- in-progress vector items are browser-owned drafts;
- completed drafts become drawing commands;
- accepted drawing commands create canvas items;
- committed vector items render in the world vector layer;
- drawing elements use the same item placement model;
- selection, history, clipboard, persistence, and shared mode keep working
  because drawings become canvas state.

Your application still owns the product:

- the toolbar and palette;
- which tools are available;
- selected-vector-item inspectors;
- import/export screens;
- annotation meaning;
- document, asset, or domain records associated with drawings;
- any custom SVG or HTML presentation around vector items and elements.

## The Drawing App Loop

A drawing app usually follows this loop:

```text
use the drawing toolkit
  -> render a canvas runtime
  -> render application toolbar controls
  -> set browser cursor mode and draft style
  -> let the browser own the active draft
  -> commit the finished draft as a drawing command
  -> render committed vector items from canvas state
  -> edit, erase, persist, copy, and share committed vector items as items
```

The important part is that drawing does not become a separate client-side
document store. The drawing surface is a capability on the canvas.

## Minimum Drawing Canvas

Start with the drawing toolkit:

```elixir
defmodule MyAppWeb.SketchLive do
  use MyAppWeb, :live_view

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

  use LivePanels.Toolkit.Drawing

  @default_style %{
    color: "#111827",
    width: 3,
    fill_color: "none",
    stroke_style: "solid",
    opacity: 100,
    roundness: 0
  }

  @impl true
  def mount(_params, _session, socket) do
    {:ok,
     socket
     |> assign(:cursor_mode, :drag)
     |> assign(:draw_tool, :pen)
     |> assign(:draw_style, @default_style)}
  end

  @impl true
  def render(assigns) do
    ~H"""
    <main class="sketch">
      <nav class="sketch-toolbar">
        <button phx-click={set_cursor_mode_js(@livepanels.canvas_id, :drag, push: "set_cursor_mode")}>
          Select
        </button>

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

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

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

      <.canvas_runtime {canvas_runtime_attrs(assigns, cursor_mode: @cursor_mode)} />
    </main>
    """
  end

  @impl true
  def handle_event("set_cursor_mode", %{"mode" => mode} = params, socket) do
    cursor_mode =
      case mode do
        "draw" -> :draw
        "erase" -> :erase
        "marquee" -> :marquee
        _ -> :drag
      end

    draw_tool =
      case Map.get(params, "tool") do
        "pen" -> :pen
        "freehand" -> :pen
        "rectangle" -> :rectangle
        "arrow" -> :arrow
        "line" -> :line
        "polyline" -> :polyline
        "ellipse" -> :ellipse
        "diamond" -> :diamond
        "triangle" -> :triangle
        "text" -> :text
        _ -> socket.assigns.draw_tool
      end

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

The runtime renders committed vector items automatically. The toolbar tells the
browser what to do next.

## Track Map

Read the guides in this order when building a drawing app:

- `Tools, Cursor Modes, And Drafts` for the browser interaction model.
- `Commit Vector Items To Canvas State` for vector item creation and server commands.
- `Render And Style Vector Items` for the vector layer and application presentation.
- `Edit Points, Labels, Text, And Arrowheads` for vector item inspectors and geometry
  editing.
- `Erase, Select, Move, And Resize` for item behavior around committed vector items.
- `Images, Frames, Embeds, And Drawing Elements` for drawing-owned content.
- `Persist, Share, Import, And Export` for recovery and collaboration.
- `Drawing App Recipes` for common application patterns.

## Drawing And Other Capabilities

Drawing can coexist with ordinary item content, graph connectors, shared mode, and
custom application UI because committed drawings are canvas items.

For example, a review app can use vector items for annotations, ordinary
content items for comments, shared mode for collaboration, and graph connectors for
relationships between frames. LivePanels does not need a separate runtime for
each capability. The application decides which capabilities are useful for the
product.

## What To Keep Out Of The Core Runtime

Do not put product meaning into the drawing runtime.

A rectangle might mean a highlight, a frame, a bounding box, a selected region,
or a step in a diagram. LivePanels only needs to know that a committed vector item has
kind, placement, points, style, and vector state.

Keep domain records in your app. Use LivePanels to make the drawing behavior
consistent.
