plushie

Build native desktop apps in Elixir. Pre-1.0

Plushie is a desktop GUI framework that allows you to write your entire application in Elixir -- state, events, UI -- and get native windows on Linux, macOS, and Windows. Rendering is powered by iced, a cross-platform GUI library for Rust, which plushie drives as a precompiled binary behind the scenes.

defmodule Counter do
  use Plushie.App
  alias Plushie.Event.Widget
  import Plushie.UI

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

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

  def update(model, %Widget{type: :click, id: "dec"}),
    do: %{model | count: model.count - 1}

  def update(model, _event), do: model

  def view(model) do
    window "main", title: "Counter" do
      column padding: 16, spacing: 8 do
        text("count", "Count: #{model.count}")
        row spacing: 8 do
          button("inc", "+")
          button("dec", "-")
        end
      end
    end
  end
end
mix plushie.gui Counter

Or from IEx:

iex> Plushie.start_link(Counter)

This is one of 8 examples included in the repo, from a minimal counter to a full widget catalog. Edit them while the GUI is running and see changes instantly. For multi-file projects (extensions, collaborative transports, real project scaffolding), see the plushie-demos repo.

Getting started

Add plushie to your dependencies:

# mix.exs
{:plushie, "== 0.5.0"}

Then:

mix deps.get
mix plushie.download                    # download precompiled binary
mix plushie.gui Counter                 # run the counter example

Pin to an exact version (==, not ~>) and read the CHANGELOG carefully when upgrading.

The precompiled binary requires no Rust toolchain. To build from source instead, install rustup and run mix plushie.build. See the getting started guide for the full walkthrough.

Features

Testing

Plushie ships a test framework with three interchangeable backends. Write your tests once, run them at whatever fidelity you need:

defmodule TodoTest do
  use Plushie.Test.Case, app: Todo

  test "add and complete a todo" do
    type_text("#new_todo", "Buy milk")
    submit("#new_todo")

    assert_text "#todo_count", "1 item"
    assert_exists "#todo:1"

    toggle("#todo:1")
    click("#filter_completed")

    assert_text "#todo_count", "0 items"
    assert_not_exists "#todo:1"
  end
end

See the testing guide for the full API, backend details, and CI configuration.

How it works

Under the hood, a renderer built on iced handles window drawing and platform integration. Your Elixir code sends widget trees to the renderer over stdin; the renderer draws native windows and sends user events back over stdout.

You don't need Rust to use plushie. The renderer is a precompiled binary, similar to how your app talks to a database without you writing C. If you ever need custom native rendering, the extension system lets you write Rust for just those parts.

The same protocol works over a local pipe, an SSH connection, or any bidirectional byte stream -- your code doesn't need to change. See the running guide for deployment options.

Status

Pre-1.0. The core works -- 38 widget types, event system, 22 themes, multi-window, testing framework, accessibility -- but the API is still evolving:

Documentation

Guides are in docs/ and will be on hexdocs once published:

Development

mix preflight                         # run all CI checks locally

Mirrors CI and stops on first failure: format, compile (warnings as errors), credo, test, dialyzer.

System requirements

The precompiled binary (mix plushie.download) has no additional dependencies. To build from source, install a Rust toolchain via rustup and the platform-specific libraries:

Links

Elixir SDK github.com/plushie-ui/plushie-elixir
Demos github.com/plushie-ui/plushie-demos
Renderer github.com/plushie-ui/plushie
Rust crate crates.io/crates/plushie

License

MIT