Search documentation of Plushie

# Plushie.App behaviour (Plushie v0.7.2)

Copy Markdown View Source

Behaviour for Plushie apps following the Elm architecture.

Implement this behaviour to define a desktop application. The runtime calls `init/1` once at startup, then repeatedly calls `update/2` and `view/1` as events arrive from the renderer and subscriptions.

## Minimal example

    defmodule MyApp do
      use Plushie.App

      alias Plushie.Event.WidgetEvent

      def init(_opts), do: %{count: 0}

      def update(model, %WidgetEvent{type: :click, id: "increment"}),
        do: %{model | count: model.count + 1}

      def update(model, _event), do: model

      def view(model) do
        import Plushie.UI

        window "counter", title: "Counter" do
          column do
            text("count", "Count: #{model.count}")
            button("increment", "Increment")
          end
        end
      end
    end

## Return values

`init/1` and `update/2` both accept two return shapes:

- Bare model: no side effects needed.
- `{model, command}` or `{model, [command]}`: schedule async work, widget operations, window management, or timers. See `Plushie.Command` for the full command API.

## Commands

Return a `{model, command}` tuple from `update/2` or `init/1` to schedule side effects:

    def init(_opts) do
      model = %{data: nil, loading: true}
      {model, Command.task(fn -> load_initial_data() end, :loaded)}
    end

    def update(model, %WidgetEvent{type: :click, id: "save"}) do
      {model, Command.task(fn -> save_to_disk(model) end, :save_result)}
    end

See `Plushie.Command` for the full command API and result delivery.

## Subscriptions

Override `subscribe/1` to receive ongoing events. The runtime diffs the subscription list after each update and starts or stops subscriptions as needed.

    def subscribe(model) do
      subs = [Plushie.Subscription.on_key(:key_events)]

      if model.auto_refresh do
        [Plushie.Subscription.every(5000, :refresh) | subs]
      else
        subs
      end
    end

See `Plushie.Subscription` for all subscription types.

## Error handling

Exceptions in `update/2` and `view/1` are caught by the runtime and logged; the model reverts to its pre-exception state. You do not need to wrap your callbacks in try/rescue. After 100 consecutive errors, the runtime suppresses further log messages to prevent log flooding.

## Optional callbacks

`subscribe/1`, `handle_renderer_exit/2`, `window_config/1`, and `settings/0` are optional. `use Plushie.App` provides default implementations that can be overridden.

## Summary

### Types

command()
event()

Events delivered to `update/2`.

model()
window_view()

### Callbacks

handle_renderer_exit(model, reason)

Called when the renderer process exits unexpectedly. Receives a `%Plushie.RendererExitError{}` struct with `:type`, `:message`, and `:details`. Return the model to use when the renderer restarts. Default: return model unchanged.

init(opts)

Called once at startup. Returns the initial model, optionally with commands for loading initial data or configuring the UI.

settings()

Called once at startup to provide application-level settings to the renderer.

subscribe(model)

Called after every update. Returns the list of active subscriptions. The runtime diffs this list and starts or stops subscriptions as needed. Default: `[]`.

update(model, event)

Called on every event. Returns the next model, optionally with commands.

view(model)

Called after every update. Returns a window node or a list of window nodes.

window_config(model)

Called on runtime startup to configure window properties. Also called on renderer restart whenever windows need re-opening. Per-window props set in the view tree override these defaults.

## Types

# command()

    @type command() :: Plushie.Command.t() | [Plushie.Command.t()]

# event()

    @type event() :: Plushie.Event.t() | term()

Events delivered to `update/2`.

Most events are `Plushie.Event` structs from the renderer and subscriptions. The `term()` case covers bare values delivered by `Command.send_after/2` and `Command.dispatch/2`.

# model()

    @type model() :: term()

# window_view()

    @type window_view() ::
      Plushie.Widget.Window.t() | map() | [Plushie.Widget.Window.t() | map()] | nil

## Callbacks

# handle_renderer_exit(model, reason)

(optional)

    @callback handle_renderer_exit(model(), reason :: Plushie.RendererExitError.t()) ::
      model()

Called when the renderer process exits unexpectedly. Receives a `%Plushie.RendererExitError{}` struct with `:type`, `:message`, and `:details`. Return the model to use when the renderer restarts. Default: return model unchanged.

# init(opts)

    @callback init(opts :: keyword()) :: model() | {model(), command()}

Called once at startup. Returns the initial model, optionally with commands for loading initial data or configuring the UI.

# settings()

(optional)

    @callback settings() :: map()

Called once at startup to provide application-level settings to the renderer.

## Typography

- `default_font`: font specification map (same format as font props)
- `default_text_size`: base text size in pixels
- `fonts`: list of font file paths to load at startup

## Theming

- `theme`: built-in theme atom (e.g. `:dark`, `:nord`), `:system` to follow the OS light/dark preference, or a custom palette map. See `Plushie.Type.Theme`.

## Rendering

- `antialiasing`: boolean
- `vsync`: boolean (default `true`). Synchronizes frame presentation with the display refresh rate. Disabling can improve performance on some platforms.
- `scale_factor`: number (default `1.0`). Multiplier on top of OS DPI scaling. Per-window overrides via the `scale_factor` window prop.

## Performance

- `default_event_rate`: integer, max events per second for coalescable event types (mouse moves, scroll, slider drags, etc.). Omit for unlimited. Set to 60 for most apps, lower for dashboards or remote rendering. Per-subscription `max_rate` and per-widget `event_rate` override this default.

## Native widgets

- `widget_config`: map of namespace -> config for native widgets. Each key matches a widget's `namespace()` on the Rust side. The config is passed to `PlushieWidget::init()` via `InitCtx.config`. Also configurable via `config :plushie, :widget_config`.
- `required_widgets`: list of native widget type names this app requires the renderer to have registered. The renderer emits a `required_widgets_missing` diagnostic on the Settings handshake for any names it does not recognize (non-fatal; the app keeps running).

Default: `%{}` (renderer uses its own defaults).

# subscribe(model)

(optional)

    @callback subscribe(model()) :: [Plushie.Subscription.t()]

Called after every update. Returns the list of active subscriptions. The runtime diffs this list and starts or stops subscriptions as needed. Default: `[]`.

# update(model, event)

    @callback update(model(), event()) :: model() | {model(), command()}

Called on every event. Returns the next model, optionally with commands.

Events are `Plushie.Event` structs (widget interactions, key presses, window lifecycle, async results, timer ticks, etc.) or bare terms from `Command.send_after/2`. Include a catch-all clause for unhandled events.

# view(model)

    @callback view(model()) :: window_view()

Called after every update. Returns a window node or a list of window nodes.

The top level must be explicit windows. Single-window apps return one `window(...)` node. Multi-window apps return a list of `window(...)` nodes. `nil` or `[]` is allowed when the app intentionally has no open windows.

# window_config(model)

(optional)

    @callback window_config(model()) :: map()

Called on runtime startup to configure window properties. Also called on renderer restart whenever windows need re-opening. Per-window props set in the view tree override these defaults.

## Supported keys

`title`, `size`, `width`, `height`, `position`, `min_size`, `max_size`, `maximized`, `fullscreen`, `visible`, `resizable`, `closeable`, `minimizable`, `decorations`, `transparent`, `blur`, `level`, `exit_on_close_request`.

See `Plushie.Widget.Window` for details on each property.

## Example

    def window_config(_model) do
      %{title: "My App", width: 800, height: 600, min_size: {400, 300}}
    end

Default: empty config (the renderer uses its own defaults).