Project

plushie

0.0
No release in over 3 years
Build native desktop apps in Ruby using the Elm architecture. Rendering is handled by a precompiled binary built on iced.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

>= 0
>= 0
~> 1.7
 Project Readme

plushie

Build native desktop apps in Ruby. Pre-1.0

Plushie is a desktop GUI framework that allows you to write your entire application in Ruby -- 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.

class Counter
  include Plushie::App

  Model = Plushie::Model.define(:count)

  def init(_opts) = Model.new(count: 0)

  def update(model, event)
    case event
    in Event::Widget[type: :click, id: "inc"]
      model.with(count: model.count + 1)
    in Event::Widget[type: :click, id: "dec"]
      model.with(count: model.count - 1)
    else
      model
    end
  end

  def view(model)
    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

Plushie.run(Counter)

This is one of 8 examples included in the repo, from a minimal counter to a full widget catalog. For complete project demos, including native Rust extensions, see the plushie-demos repository.

Getting started

Add plushie to your Gemfile:

gem "plushie", "== 0.1.0"

Then:

bundle install
rake plushie:download   # download precompiled renderer binary

Requires Ruby 3.2+. The precompiled binary requires no Rust toolchain.

Your first app

Create lib/counter.rb:

require "plushie"

class Counter
  include Plushie::App

  Model = Plushie::Model.define(:count)

  def init(_opts) = Model.new(count: 0)

  def update(model, event)
    case event
    in Event::Widget[type: :click, id: "increment"]
      model.with(count: model.count + 1)
    in Event::Widget[type: :click, id: "decrement"]
      model.with(count: model.count - 1)
    else
      model
    end
  end

  def view(model)
    window("main", title: "Counter") do
      column(padding: 16, spacing: 8) do
        text("count", "Count: #{model.count}", size: 20)
        row(spacing: 8) do
          button("increment", "+")
          button("decrement", "-")
        end
      end
    end
  end
end

Plushie.run(Counter)

Run it:

ruby lib/counter.rb

The Elm architecture

Plushie follows the Elm architecture. Your app implements four callbacks:

  • init(opts) -- returns the initial model (any Ruby object, ideally immutable via Plushie::Model.define).
  • update(model, event) -- receives the current model and an event, returns the new model. Pure function. Return [model, command] for side effects.
  • view(model) -- receives the model, returns a UI tree using the block DSL. The runtime diffs trees and sends patches to the renderer.
  • subscribe(model) (optional) -- returns active subscriptions (timers, keyboard/mouse events).

Features

  • 39 built-in widget types -- buttons, text inputs, sliders, tables, markdown, canvas, pane grids, and more.
  • 22 built-in themes -- light, dark, dracula, nord, catppuccin, tokyo night, kanagawa, and more.
  • Multi-window -- declare window nodes in your widget tree; the framework manages them automatically.
  • Platform effects -- native file dialogs, clipboard, OS notifications.
  • Accessibility -- screen reader support via accesskit.
  • Live reload -- Plushie.run(MyApp, dev: true) watches lib/ and reloads on file changes. Model state is preserved.
  • Remote rendering -- native desktop UI for server-side Ruby apps over SSH. Your init/update/view code doesn't change.
  • Widget extensions -- pure Ruby composites or native Rust-backed custom widgets via include Plushie::Extension.
  • Configuration system -- Plushie.configure for binary paths, extensions, test backends, and extension runtime config.
  • WASM renderer -- rake plushie:download[wasm] downloads a WASM build of the renderer for browser targets.

Documentation

Guides:

Advanced:

API reference: rubydoc.info/gems/plushie

Testing

All testing goes through the renderer binary. No Ruby-side mocks. The mock backend runs at millisecond speed.

Add require "plushie/test" to your test helper:

# test/test_helper.rb
require "plushie"
require "plushie/test"
require "minitest/autorun"

Then write tests:

class CounterTest < Plushie::Test::Case
  app Counter

  def test_clicking_increment_updates_counter
    click("#increment")
    assert_text "#count", "Count: 1"
  end
end

Three interchangeable backends:

  • Mock (PLUSHIE_TEST_BACKEND=mock) -- millisecond tests, no display. Default.
  • Headless (PLUSHIE_TEST_BACKEND=headless) -- real rendering via tiny-skia, no display server. Pixel screenshots.
  • Windowed (PLUSHIE_TEST_BACKEND=windowed) -- real windows with GPU. Needs display server (Xvfb in CI).

How it works

Under the hood, a renderer built on iced handles window drawing and platform integration. Your Ruby 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 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.

State helpers

Plushie ships optional state management utilities:

  • Plushie::Animation -- easing functions and interpolation
  • Plushie::Route -- navigation stack for multi-view apps
  • Plushie::Selection -- single, multi, and range selection
  • Plushie::Undo -- undo/redo stacks with coalescing
  • Plushie::DataQuery -- filter, search, sort, paginate collections
  • Plushie::State -- path-based state with revision tracking

Development

bundle exec rake              # tests + linter + type check
bundle exec rake test         # tests only
bundle exec rake standard     # linter only
bundle exec rake steep        # type check only
bundle exec rake yard         # generate API docs to doc/

See CONTRIBUTING.md for the full development guide.

Rake tasks (add require "plushie/rake" to your Rakefile):

rake plushie:download          # download precompiled binary
rake plushie:download[wasm]    # download WASM renderer
rake plushie:build             # build from Rust source (with extensions if configured)
rake plushie:run[Counter]      # run an app
rake plushie:connect[Counter]  # connect to renderer via stdio
rake plushie:inspect[Counter]  # print UI tree as JSON
rake plushie:script            # run .plushie test scripts
rake plushie:replay[path]      # replay a script with real windows
rake plushie:preflight         # run all CI checks

Configure the SDK programmatically:

Plushie.configure do |config|
  config.binary_path = "/opt/plushie/bin/plushie"
  config.extensions = [MyGauge]
  config.test_backend = :headless
end

System requirements

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

  • Linux (Debian/Ubuntu): sudo apt-get install libxkbcommon-dev libwayland-dev libx11-dev cmake fontconfig pkg-config
  • Linux (Arch): sudo pacman -S libxkbcommon wayland libx11 cmake fontconfig pkgconf
  • macOS: xcode-select --install
  • Windows: Visual Studio Build Tools with "Desktop development with C++"

Links

Ruby SDK github.com/plushie-ui/plushie-ruby
Elixir SDK github.com/plushie-ui/plushie-elixir
Renderer github.com/plushie-ui/plushie

License

MIT