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
-
BinaryNotFound(binary.BinaryError)The plushie binary could not be found.
-
SupervisorStartFailed(actor.StartError)The supervisor failed to start.
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
-
Spawn -
Stdio -
Iostream(adapter: process.Subject(bridge.IoStreamMessage))
Values
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))
}