# `Mob.UI`
[🔗](https://github.com/genericjam/mob/blob/master/lib/mob/ui.ex#L1)

UI component constructors for the Mob framework.

Each function returns a node map compatible with `Mob.Renderer`. These can
be used directly, via the `~MOB` sigil, or mixed freely — they produce the
same map format.

    # Native map literal
    %{type: :text, props: %{text: "Hello"}, children: []}

    # Component function (keyword list or map)
    Mob.UI.text(text: "Hello")

    # Sigil (import Mob.Sigil or use Mob.Screen)
    ~MOB(<Text text="Hello" />)

All three forms produce identical output and are accepted by `Mob.Renderer`.

# `camera_preview`

```elixir
@spec camera_preview(keyword() | map()) :: map()
```

Returns a `:camera_preview` component node. Renders a live camera feed inline.

Call `Mob.Camera.start_preview/2` before mounting this component, and
`Mob.Camera.stop_preview/1` when done.

Props:
  * `:facing` — `:back` (default) or `:front`
  * `:width`, `:height` — dimensions in dp/pts; omit to fill parent

# `canvas`

```elixir
@spec canvas(keyword() | map()) :: map()
```

Returns a `:canvas` leaf node — declarative 2D drawing surface backed
by SwiftUI `Canvas` on iOS and Jetpack Compose `Canvas` on Android.

Coordinates are canvas-local in points/dp, top-left origin.

## Props

  * `:width` — canvas width in pt/dp (required)
  * `:height` — canvas height in pt/dp (required)
  * `:draw` — list of op maps (required); construct via `Mob.Canvas.line/5`,
    `Mob.Canvas.circle/4`, etc., or as raw maps with an `:op` key

Color tokens inside draw ops are resolved against the active theme
by `Mob.Renderer` before serialisation, exactly like top-level color
props on text/button/etc.

## Example

    import Mob.UI
    import Mob.Canvas

    canvas(width: 240, height: 240, draw: [
      circle(120, 120, 115, color: :surface_outline, width: 2),
      line(60, 60, 60, 180, color: :primary, width: 8, cap: :round),
      line(60, 180, 180, 180, color: :primary, width: 8, cap: :round),
      line(60, 60, 180, 180, color: :primary, width: 8, cap: :round)
    ])

See `Mob.Canvas` for the full op list and modifier reference.

# `gpu_view`

```elixir
@spec gpu_view(keyword() | map()) :: map()
```

Returns a `:gpu_view` leaf node — a fragment-shader-driven GPU surface
backed by `MTKView` + Metal on iOS. The native side compiles the
supplied shader (Metal Shading Language) into a render pipeline, binds
the supplied uniforms in declaration order at fragment buffer slot 0,
and renders a full-screen quad at the display refresh rate.

Android support (`GLSurfaceView` + GLES 3.0) is not in v1.

## Props

  * `:id` — required atom that identifies the GPU view across re-renders
    (so the native side keeps the same Metal pipeline / texture cache).
  * `:width` / `:height` — pt/dp, required.
  * `:shader` — either a string of Metal Shading Language source (iOS),
    or a map `%{ios: "...MSL..."}` (escape hatch — same as the string
    form; the map form exists so future platforms can be added without
    breaking the API).
  * `:uniforms` — an **ordered list of values** packed into the shader's
    `Uniforms` struct in declaration order. Each element is one of:
    * a number — `float` (or `uint` if integer-typed at the BEAM level)
    * a 2-element list `[a, b]` — `float2`
    * a 4-element list `[a, b, c, d]` — `float4`
    (`float3` deliberately not supported in v1 — its 16-byte
    alignment with 12-byte size makes the layout API messier than
    it's worth here.)

Shader compile errors are caught natively and surfaced as a translucent
overlay on top of the GpuView with the error message.

## Why a list, not a map

Elixir map iteration order is **not stable** across runtimes or map
sizes — `%{a: 1, b: 2, c: 3}` can iterate in any order. The natural
MSL layout for a `Uniforms` struct is positional, so we mirror that
on the BEAM side. List position 0 → first struct member, etc.

A map form is still accepted as a backward-compat fallback but will
pack in whatever order the runtime decides, so the shader-side struct
has to match an unstable order — not recommended.

## Example — Mandelbrot at the display's refresh rate

    @shader File.read!("priv/shaders/mandelbrot.metal")

    Mob.UI.gpu_view(
      id: :mandelbrot,
      width: 350,
      height: 350,
      shader: @shader,
      # MSL: struct Uniforms { float2 center; float zoom; uint max_iter; };
      uniforms: [[cx, cy], zoom, max_iter]
    )

## What the framework auto-provides

The host emits a built-in vertex shader that draws a full-screen quad
and produces a `VertexOut { float4 position [[position]]; float2 uv; }`.
Your fragment shader receives that as `[[stage_in]]` and reads
`in.uv` (0..1 across the view) plus the user uniforms at buffer slot 0.
Don't redeclare `VertexOut`, `vertex_main`, or the metal_stdlib include
in your shader — the host prepends them.

## Required fragment entry point

Your shader must export `fragment_main`:

    fragment half4 fragment_main(VertexOut in [[stage_in]],
                                 constant Uniforms& u [[buffer(0)]]) { ... }

# `native_view`

```elixir
@spec native_view(module(), keyword() | map()) :: map()
```

Returns a `:native_view` node that renders a platform-native component.

`module` must implement the `Mob.Component` behaviour and be registered
on the native side via `MobNativeViewRegistry`. The `:id` must be unique
per screen — a duplicate raises at render time.

All other props are passed to `mount/2` and `update/2` on the component.

## Example

    Mob.UI.native_view(MyApp.ChartComponent, id: :revenue_chart, data: @points)

# `text`

```elixir
@spec text(keyword() | map()) :: map()
```

Returns a `:text` leaf node.

## Props

  * `:text` — the string to display (required)
  * `:text_color` — color value passed to `set_text_color/2` in the NIF
  * `:text_size` — font size in sp passed to `set_text_size/2` in the NIF

## Examples

    Mob.UI.text(text: "Hello")
    #=> %{type: :text, props: %{text: "Hello"}, children: []}

    Mob.UI.text(text: "Hello", text_color: "#ffffff", text_size: 18)
    #=> %{type: :text, props: %{text: "Hello", text_color: "#ffffff", text_size: 18}, children: []}

# `webview`

```elixir
@spec webview(keyword() | map()) :: map()
```

Returns a `:webview` component node. Renders a native web view inline.

The JS bridge is injected automatically — the page can call `window.mob.send(data)`
to deliver messages to `handle_info({:webview, :message, data}, socket)`, and
Elixir can push to JS via `Mob.WebView.post_message/2`.

Props:
  * `:url` — URL to load (required)
  * `:allow` — list of URL prefixes that navigation is permitted to (default: allow all).
    Blocked attempts arrive as `{:webview, :blocked, url}` in `handle_info`.
  * `:show_url` — show a native URL label above the WebView (default: false)
  * `:title` — static title label above the WebView; overrides `:show_url`
  * `:width`, `:height` — dimensions in dp/pts; omit to fill parent

---

*Consult [api-reference.md](api-reference.md) for complete listing*
