Emerge behaviour (Emerge v0.4.0-beta.1)

Copy Markdown View Source

Defines Emerge viewport modules.

A viewport owns a rendered UI and receives its events. Use Emerge in a module, implement mount/1, and implement either render/0 or render/1.

use Emerge imports the common Emerge.UI helpers and aliases Emerge as Viewport inside your module.

Example

defmodule MyApp.CounterViewport do
  use Emerge

  @impl Viewport
  def mount(opts) do
    {:ok, %{count: 0}, Keyword.merge([title: "Counter"], opts)}
  end

  @impl Viewport
  def render(%{count: count}) do
    row([spacing(12), padding(12)], [
      Input.button([Event.on_press(:decrement)], text("-")),
      text("Count: #{count}"),
      Input.button([Event.on_press(:increment)], text("+"))
    ])
  end

  @impl Viewport
  def handle_info(:increment, state) do
    {:noreply, Viewport.rerender(%{state | count: state.count + 1})}
  end

  def handle_info(:decrement, state) do
    {:noreply, Viewport.rerender(%{state | count: state.count - 1})}
  end
end

Start the viewport directly:

{:ok, viewport} = MyApp.CounterViewport.start_link()

Or supervise it:

children = [MyApp.CounterViewport]
Supervisor.start_link(children, strategy: :one_for_one)

use Emerge adds start_link/1 and child_spec/1 to the viewport module.

Callbacks

mount/1 receives the options passed to start_link/1. Return one of:

  • {:ok, viewport_options} and implement render/0;
  • {:ok, state, viewport_options} and implement render/1;
  • {:stop, reason} to stop startup.

State must be a map. The :__emerge__ key is reserved.

render/0 or render/1 returns an Emerge.tree() built with Emerge.UI.

Implement handle_info/2 for messages and element events. Implement handle_input/2 only when you need raw renderer input. Implement handle_close/2 to customize window-close behavior; the default stops the viewport normally.

Callback handlers return {:noreply, state} or {:stop, reason, state}.

Rerendering

Call rerender/1 after changing viewport state:

{:noreply, Viewport.rerender(%{state | count: state.count + 1})}

Return the state produced by rerender/1. Calling it schedules a new render; it does not call render/1 immediately.

Element events

Event helpers send ordinary Elixir messages. Passing only a message sends it to the viewport process:

Input.button([Event.on_press(:save)], text("Save"))

@impl Viewport
def handle_info(:save, state) do
  {:noreply, state}
end

To send elsewhere, pass {pid, message}. Payload events append the payload to a tuple message or send {message, payload} for a non-tuple message. Override wrap_payload/3 when an integration needs another shape.

Configuration and output modes

Viewport options choose the window/display backend, rendering API, size, assets, diagnostics, and headless output. Emerge.Runtime.Viewport documents these user-facing options and includes desktop, DRM, and headless examples.

Options can be returned at the top level or under emerge_skia: [...]. Runtime-only viewport options use viewport: [...].

Video elements use viewport-local atom targets. Submit owned binary or borrowed DMA-BUF frames with submit_video_frame/3. Vulkan rendering and Vulkan video import are supported where compiled and available.

Summary

Types

Viewport state map passed to render/1 and callback functions.

Public tree type built with Emerge.UI and rendered by Emerge backends.

Functions

Applies Emerge's default payload-event message shape.

Requests a rerender from viewports after application source is reloaded.

Returns the renderer handle owned by a running viewport.

Marks viewport state for rerendering and returns the updated state.

Submits one frame to a viewport-local video target.

Types

state()

@type state() :: map()

Viewport state map passed to render/1 and callback functions.

tree()

@type tree() :: Emerge.Engine.Element.t()

Public tree type built with Emerge.UI and rendered by Emerge backends.

Callbacks

handle_close(term, state)

(optional)
@callback handle_close(term(), state()) :: {:noreply, state()} | {:stop, term(), state()}

handle_info(term, state)

(optional)
@callback handle_info(term(), state()) :: {:noreply, state()} | {:stop, term(), state()}

handle_input(term, state)

(optional)
@callback handle_input(term(), state()) :: {:noreply, state()} | {:stop, term(), state()}

mount(keyword)

@callback mount(keyword()) ::
  {:ok, state(), keyword()} | {:ok, keyword()} | {:stop, term()}

render()

(optional)
@callback render() :: tree()

render(state)

(optional)
@callback render(state()) :: tree()

wrap_payload(term, term, term)

(optional)
@callback wrap_payload(term(), term(), term()) :: term()

Functions

default_wrap_payload(message, payload, event_type)

@spec default_wrap_payload(term(), term(), term()) :: term()

Applies Emerge's default payload-event message shape.

Tuple messages receive the payload as a final tuple element. Other messages become {message, payload}. Viewports normally use this through the default wrap_payload/3 callback.

notify_source_reloaded(meta \\ %{})

@spec notify_source_reloaded(term()) :: :ok

Requests a rerender from viewports after application source is reloaded.

Emerge.Runtime.CodeReloader calls this automatically. Custom development reloaders may pass metadata for their own logging or coordination.

renderer(pid)

@spec renderer(pid()) :: term()

Returns the renderer handle owned by a running viewport.

Use this handle with renderer-specific APIs such as EmergeSkia.renderer_info/1, EmergeSkia.stats/2, and capture.

renderer = Emerge.renderer(viewport)
{:ok, info} = EmergeSkia.renderer_info(renderer)

rerender(state)

@spec rerender(state()) :: state()

Marks viewport state for rerendering and returns the updated state.

Call this after changing local state and return its result from the callback:

{:noreply, Viewport.rerender(%{state | count: state.count + 1})}

submit_video_frame(viewport, target, frame)

@spec submit_video_frame(pid(), atom(), VideoInterop.Frame.t()) ::
  :ok | {:error, term()}

Submits one frame to a viewport-local video target.

The target is the same atom passed to Emerge.UI.video/2. Hidden targets consume and drop frames. Visible targets retain only the latest frame.

Every normal return consumes the supplied frame: immutable binary frames need no further action, while borrowed storage is released by Emerge when retired.

:ok = Emerge.submit_video_frame(viewport, :camera, frame)