plushie

Native desktop GUIs from Gleam, powered by iced.

Plushie implements the Elm architecture (init/update/view) with commands and subscriptions. It communicates with a Rust binary over stdin/stdout using MessagePack, driving native windows via iced.

Quick start

import plushie
import plushie/app
import plushie/command
import plushie/event.{type Event, WidgetClick}
import plushie/ui
import gleam/int

type Model { Model(count: Int) }

pub fn main() {
  let counter = app.simple(
    fn() { #(Model(0), command.none()) },
    fn(model, event) {
      case event {
        WidgetClick(id: "inc", ..) ->
          #(Model(model.count + 1), command.none())
        _ -> #(model, command.none())
      }
    },
    fn(model) {
      ui.window("main", [ui.title("Counter")], [
        ui.text_("count", "Count: " <> int.to_string(model.count)),
        ui.button_("inc", "+"),
      ])
    },
  )
  let assert Ok(_) = plushie.start(counter, plushie.default_start_opts())
}

Types

A running plushie application instance.

Wraps the supervisor pid. Use wait to block until the application exits, or stop to shut it down.

pub opaque type Instance

Errors that can occur when starting a plushie application.

pub type StartError {
  BinaryNotFound(binary.BinaryError)
  SupervisorStartFailed(actor.StartError)
}

Constructors

Options for starting a plushie application.

pub type StartOpts {
  StartOpts(
    binary_path: option.Option(String),
    format: protocol.Format,
    daemon: Bool,
    session: String,
    app_opts: dynamic.Dynamic,
    renderer_args: List(String),
    transport: Transport,
    dev: Bool,
  )
}

Constructors

  • StartOpts(
      binary_path: option.Option(String),
      format: protocol.Format,
      daemon: Bool,
      session: String,
      app_opts: dynamic.Dynamic,
      renderer_args: List(String),
      transport: Transport,
      dev: Bool,
    )

    Arguments

    binary_path

    Path to the plushie binary. None = auto-resolve.

    format

    Wire format. Default: MessagePack.

    daemon

    Keep running after all windows close. Default: False.

    session

    Session identifier. Default: “” (single-session).

    app_opts

    Application options passed to init/1. Default: dynamic.nil().

    renderer_args

    Extra CLI arguments prepended to the renderer command.

    transport

    Transport mode. Default: Spawn.

    dev

    Enable dev-mode live reload. Default: False. When True, starts a file watcher that recompiles on source changes and triggers a force re-render without losing state.

Transport mode for communicating with the renderer.

  • Spawn (default): spawns the renderer binary as a child process using an Erlang Port.
  • Stdio: reads/writes the BEAM’s own stdin/stdout. Used when the renderer spawns the Gleam process (e.g. plushie --exec).
  • Iostream: sends and receives protocol messages via an external process. Used for custom transports like SSH channels, TCP sockets, or WebSockets where an adapter process handles the underlying I/O.
pub type Transport {
  Spawn
  Stdio
  Iostream(adapter: process.Subject(bridge.IoStreamMessage))
}

Constructors

Values

pub fn default_start_opts() -> StartOpts

Default start options.

pub fn start(
  app: app.App(model, msg),
  opts: StartOpts,
) -> Result(Instance, StartError)

Start a plushie application under an OTP supervisor.

Creates a RestForOne supervisor with Bridge and Runtime as children. Bridge starts first and opens the port to the renderer binary. Runtime starts second, registers with the bridge, and enters the Elm update loop. If dev mode is enabled, a DevServer child is added.

Returns an Instance that can be used with wait and stop.

pub fn start_error_to_string(err: StartError) -> String

Format a start error as a human-readable string.

pub fn stop(instance: Instance) -> Nil

Stop a running plushie application.

Sends a shutdown exit to the supervisor, which terminates all children (bridge, runtime, dev server) in reverse start order.

pub fn supervisor_pid(instance: Instance) -> process.Pid

Get the supervisor pid from a running instance. Useful for linking, monitoring, or integration with other OTP code.

pub fn wait(instance: Instance) -> Nil

Block the caller until the plushie application exits.

Monitors the supervisor process and returns when it stops. Use this instead of process.sleep_forever() so that the caller exits cleanly when the user closes all windows.

case plushie.start(app(), plushie.default_start_opts()) {
  Ok(instance) -> plushie.wait(instance)
  Error(err) -> io.println_error(plushie.start_error_to_string(err))
}
Search Document