plushie 0.0.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +22 -0
  3. data/CHANGELOG.md +65 -0
  4. data/CONTRIBUTING.md +115 -0
  5. data/Gemfile +15 -0
  6. data/{LICENSE → LICENSE.txt} +6 -6
  7. data/README.md +297 -5
  8. data/Rakefile +21 -0
  9. data/Steepfile +8 -0
  10. data/docs/accessibility.md +489 -0
  11. data/docs/app-behaviour.md +614 -0
  12. data/docs/commands.md +882 -0
  13. data/docs/composition-patterns.md +1037 -0
  14. data/docs/dsl-internals.md +343 -0
  15. data/docs/effects.md +108 -0
  16. data/docs/events.md +653 -0
  17. data/docs/extensions.md +1791 -0
  18. data/docs/getting-started.md +237 -0
  19. data/docs/layout.md +266 -0
  20. data/docs/running.md +356 -0
  21. data/docs/scoped-ids.md +200 -0
  22. data/docs/testing.md +844 -0
  23. data/docs/theming.md +292 -0
  24. data/docs/tutorial.md +369 -0
  25. data/examples/README.md +163 -0
  26. data/examples/async_fetch.rb +71 -0
  27. data/examples/catalog.rb +448 -0
  28. data/examples/clock.rb +43 -0
  29. data/examples/color_picker.rb +146 -0
  30. data/examples/counter.rb +34 -0
  31. data/examples/notes.rb +219 -0
  32. data/examples/rate_plushie.rb +256 -0
  33. data/examples/shortcuts.rb +79 -0
  34. data/examples/todo.rb +102 -0
  35. data/examples/widgets/color_picker_widget.rb +133 -0
  36. data/examples/widgets/star_rating.rb +107 -0
  37. data/examples/widgets/theme_toggle.rb +95 -0
  38. data/lib/plushie/animation.rb +250 -0
  39. data/lib/plushie/app.rb +75 -0
  40. data/lib/plushie/binary.rb +282 -0
  41. data/lib/plushie/bridge.rb +163 -0
  42. data/lib/plushie/canvas/shape/canvas_image.rb +29 -0
  43. data/lib/plushie/canvas/shape/canvas_svg.rb +25 -0
  44. data/lib/plushie/canvas/shape/canvas_text.rb +31 -0
  45. data/lib/plushie/canvas/shape/circle.rb +31 -0
  46. data/lib/plushie/canvas/shape/clip.rb +22 -0
  47. data/lib/plushie/canvas/shape/dash.rb +26 -0
  48. data/lib/plushie/canvas/shape/drag_bounds.rb +31 -0
  49. data/lib/plushie/canvas/shape/group.rb +64 -0
  50. data/lib/plushie/canvas/shape/hit_rect.rb +25 -0
  51. data/lib/plushie/canvas/shape/line.rb +30 -0
  52. data/lib/plushie/canvas/shape/linear_gradient.rb +26 -0
  53. data/lib/plushie/canvas/shape/path.rb +31 -0
  54. data/lib/plushie/canvas/shape/rect.rb +31 -0
  55. data/lib/plushie/canvas/shape/shape_style.rb +30 -0
  56. data/lib/plushie/canvas/shape/stroke.rb +31 -0
  57. data/lib/plushie/canvas/shape/transform.rb +60 -0
  58. data/lib/plushie/canvas/shape.rb +166 -0
  59. data/lib/plushie/command.rb +448 -0
  60. data/lib/plushie/connection.rb +330 -0
  61. data/lib/plushie/data.rb +106 -0
  62. data/lib/plushie/dev_server.rb +86 -0
  63. data/lib/plushie/dsl/buildable.rb +48 -0
  64. data/lib/plushie/effects.rb +169 -0
  65. data/lib/plushie/encode.rb +51 -0
  66. data/lib/plushie/event.rb +322 -0
  67. data/lib/plushie/extension/build.rb +293 -0
  68. data/lib/plushie/extension.rb +339 -0
  69. data/lib/plushie/key_modifiers.rb +99 -0
  70. data/lib/plushie/model.rb +34 -0
  71. data/lib/plushie/node.rb +44 -0
  72. data/lib/plushie/protocol/decode.rb +638 -0
  73. data/lib/plushie/protocol/encode.rb +362 -0
  74. data/lib/plushie/protocol/keys.rb +295 -0
  75. data/lib/plushie/protocol/parsers.rb +66 -0
  76. data/lib/plushie/protocol.rb +23 -0
  77. data/lib/plushie/rake.rb +254 -0
  78. data/lib/plushie/renderer_env.rb +113 -0
  79. data/lib/plushie/route.rb +86 -0
  80. data/lib/plushie/runtime/commands.rb +216 -0
  81. data/lib/plushie/runtime/subscriptions.rb +134 -0
  82. data/lib/plushie/runtime.rb +303 -0
  83. data/lib/plushie/selection.rb +135 -0
  84. data/lib/plushie/state.rb +136 -0
  85. data/lib/plushie/subscription.rb +268 -0
  86. data/lib/plushie/test/case.rb +61 -0
  87. data/lib/plushie/test/event_decoder.rb +118 -0
  88. data/lib/plushie/test/helpers.rb +214 -0
  89. data/lib/plushie/test/rspec.rb +78 -0
  90. data/lib/plushie/test/script/runner.rb +109 -0
  91. data/lib/plushie/test/script.rb +129 -0
  92. data/lib/plushie/test/session.rb +480 -0
  93. data/lib/plushie/test/session_pool.rb +179 -0
  94. data/lib/plushie/test/snapshot.rb +99 -0
  95. data/lib/plushie/test.rb +73 -0
  96. data/lib/plushie/thread_pool.rb +83 -0
  97. data/lib/plushie/transport/framing.rb +73 -0
  98. data/lib/plushie/transport/tcp_adapter.rb +68 -0
  99. data/lib/plushie/tree.rb +292 -0
  100. data/lib/plushie/type/a11y.rb +87 -0
  101. data/lib/plushie/type/alignment.rb +29 -0
  102. data/lib/plushie/type/anchor.rb +22 -0
  103. data/lib/plushie/type/border.rb +89 -0
  104. data/lib/plushie/type/color.rb +175 -0
  105. data/lib/plushie/type/content_fit.rb +22 -0
  106. data/lib/plushie/type/direction.rb +23 -0
  107. data/lib/plushie/type/filter_method.rb +22 -0
  108. data/lib/plushie/type/font.rb +87 -0
  109. data/lib/plushie/type/gradient.rb +41 -0
  110. data/lib/plushie/type/length.rb +41 -0
  111. data/lib/plushie/type/padding.rb +91 -0
  112. data/lib/plushie/type/position.rb +22 -0
  113. data/lib/plushie/type/shadow.rb +79 -0
  114. data/lib/plushie/type/shaping.rb +22 -0
  115. data/lib/plushie/type/style_map.rb +116 -0
  116. data/lib/plushie/type/theme.rb +49 -0
  117. data/lib/plushie/type/wrapping.rb +22 -0
  118. data/lib/plushie/ui.rb +904 -0
  119. data/lib/plushie/undo.rb +174 -0
  120. data/lib/plushie/version.rb +9 -0
  121. data/lib/plushie/widget/build.rb +34 -0
  122. data/lib/plushie/widget/button.rb +61 -0
  123. data/lib/plushie/widget/canvas.rb +73 -0
  124. data/lib/plushie/widget/checkbox.rb +56 -0
  125. data/lib/plushie/widget/column.rb +52 -0
  126. data/lib/plushie/widget/combo_box.rb +69 -0
  127. data/lib/plushie/widget/container.rb +73 -0
  128. data/lib/plushie/widget/floating.rb +61 -0
  129. data/lib/plushie/widget/grid.rb +66 -0
  130. data/lib/plushie/widget/image.rb +64 -0
  131. data/lib/plushie/widget/keyed_column.rb +61 -0
  132. data/lib/plushie/widget/markdown.rb +61 -0
  133. data/lib/plushie/widget/mouse_area.rb +72 -0
  134. data/lib/plushie/widget/overlay.rb +65 -0
  135. data/lib/plushie/widget/pane_grid.rb +67 -0
  136. data/lib/plushie/widget/pick_list.rb +68 -0
  137. data/lib/plushie/widget/pin.rb +60 -0
  138. data/lib/plushie/widget/progress_bar.rb +60 -0
  139. data/lib/plushie/widget/qr_code.rb +58 -0
  140. data/lib/plushie/widget/radio.rb +67 -0
  141. data/lib/plushie/widget/responsive.rb +58 -0
  142. data/lib/plushie/widget/rich_text.rb +57 -0
  143. data/lib/plushie/widget/row.rb +52 -0
  144. data/lib/plushie/widget/rule.rb +50 -0
  145. data/lib/plushie/widget/scrollable.rb +73 -0
  146. data/lib/plushie/widget/sensor.rb +60 -0
  147. data/lib/plushie/widget/slider.rb +66 -0
  148. data/lib/plushie/widget/space.rb +48 -0
  149. data/lib/plushie/widget/stack.rb +60 -0
  150. data/lib/plushie/widget/svg.rb +61 -0
  151. data/lib/plushie/widget/table.rb +76 -0
  152. data/lib/plushie/widget/text.rb +55 -0
  153. data/lib/plushie/widget/text_editor.rb +68 -0
  154. data/lib/plushie/widget/text_input.rb +57 -0
  155. data/lib/plushie/widget/themer.rb +60 -0
  156. data/lib/plushie/widget/toggler.rb +63 -0
  157. data/lib/plushie/widget/tooltip.rb +67 -0
  158. data/lib/plushie/widget/vertical_slider.rb +65 -0
  159. data/lib/plushie/widget/window.rb +54 -0
  160. data/lib/plushie.rb +235 -5
  161. data/sig/plushie/animation.rbs +38 -0
  162. data/sig/plushie/app.rbs +29 -0
  163. data/sig/plushie/binary.rbs +21 -0
  164. data/sig/plushie/bridge.rbs +43 -0
  165. data/sig/plushie/canvas/shape.rbs +303 -0
  166. data/sig/plushie/command.rbs +111 -0
  167. data/sig/plushie/connection.rbs +63 -0
  168. data/sig/plushie/data.rbs +13 -0
  169. data/sig/plushie/dev_server.rbs +15 -0
  170. data/sig/plushie/dsl/buildable.rbs +13 -0
  171. data/sig/plushie/effects.rbs +31 -0
  172. data/sig/plushie/encode.rbs +5 -0
  173. data/sig/plushie/event.rbs +170 -0
  174. data/sig/plushie/extension.rbs +40 -0
  175. data/sig/plushie/key_modifiers.rbs +30 -0
  176. data/sig/plushie/model.rbs +11 -0
  177. data/sig/plushie/node.rbs +12 -0
  178. data/sig/plushie/protocol.rbs +82 -0
  179. data/sig/plushie/renderer_env.rbs +11 -0
  180. data/sig/plushie/route.rbs +19 -0
  181. data/sig/plushie/runtime.rbs +114 -0
  182. data/sig/plushie/selection.rbs +23 -0
  183. data/sig/plushie/state.rbs +23 -0
  184. data/sig/plushie/subscription.rbs +35 -0
  185. data/sig/plushie/thread_pool.rbs +19 -0
  186. data/sig/plushie/transport/framing.rbs +13 -0
  187. data/sig/plushie/tree.rbs +11 -0
  188. data/sig/plushie/ui.rbs +73 -0
  189. data/sig/plushie/undo.rbs +35 -0
  190. data/sig/plushie.rbs +13 -0
  191. metadata +244 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b62b68b40590aee927eee8df2cf4e0f94d3459b9ddbeb2ac15e13c5c73098e96
4
- data.tar.gz: 5178af8c1b52021bd7046e6381db61538323dfb74d58784db3557610f9da08b4
3
+ metadata.gz: c2f963b103814479cb494a52823c3c9ff0e5e07ba98bc306f5a1682204550869
4
+ data.tar.gz: 448aad5c90d158c3984bc6d8a2ebfa27f9bb6cdb2509b0bea643df35c11c39a4
5
5
  SHA512:
6
- metadata.gz: d7355f1468b38e58343072335cc1207b412ec891c6088cc3e81a9b45c3c262ef908367470c19f0526a22c5adc51afa37b9f71f96a1016f335c56a5aadd221ca2
7
- data.tar.gz: a2a365e21640dc3d2525b1df4f4124a3ad84267e97d8ae0b77fc1451d66889f72c81e3bcce08fe21c0fcdead6ee0fd12fc77a0a9bf27ea0c9741536d85ccd437
6
+ metadata.gz: b91eca16110d4f22964cfc17515bc15ce4310119b61947a726f672ad655a9102a3bdc783fd959903546a7b74417188d59ee5c15b3eb7ed8f8ca4e3fb5ed876ac
7
+ data.tar.gz: ae52385d5fba80f69bf48c06618e848af49ec59c3b016cf61f3937543f6655b7a236d55e60e629ac2f1b15ff0f343af08108775f7ccedf3d3dbc6a8dbb164dde
data/.yardopts ADDED
@@ -0,0 +1,22 @@
1
+ --markup markdown
2
+ --output-dir doc
3
+ --protected
4
+ --no-private
5
+ --hide-void-return
6
+ --embed-mixins
7
+ -
8
+ docs/getting-started.md
9
+ docs/tutorial.md
10
+ docs/app-behaviour.md
11
+ docs/layout.md
12
+ docs/events.md
13
+ docs/commands.md
14
+ docs/effects.md
15
+ docs/scoped-ids.md
16
+ docs/theming.md
17
+ docs/testing.md
18
+ docs/running.md
19
+ docs/composition-patterns.md
20
+ docs/accessibility.md
21
+ docs/extensions.md
22
+ docs/dsl-internals.md
data/CHANGELOG.md ADDED
@@ -0,0 +1,65 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/).
6
+
7
+ ## [0.5.0] - 2026-03-23
8
+
9
+ Initial release. Targets plushie-renderer 0.5.0.
10
+
11
+ ### Added
12
+
13
+ - Elm architecture (init/update/view/subscribe) via `include Plushie::App`
14
+ - Immutable models via `Plushie::Model.define` (Data.define + #with)
15
+ - Block-based UI DSL with 39 widget types
16
+ - Canvas shape DSL with typed structs (Rect, Circle, Line, Text, Path, Group)
17
+ - Canvas Group with transforms array, clip field, and top-level
18
+ interactive properties (on_click, on_hover, focus_style, focusable, a11y)
19
+ - Canvas widget `role` and `arrow_mode` props for accessible containers
20
+ - Complete wire protocol encode/decode (MessagePack + JSONL)
21
+ - Tree diffing with incremental patch generation
22
+ - 72+ command constructors (async, focus, scroll, window ops, effects,
23
+ focus_element for canvas, etc.)
24
+ - Platform effects (file dialogs, clipboard, notifications)
25
+ - Subscription system (timers, keyboard, mouse, window events)
26
+ - Three transport modes: spawn, stdio, iostream
27
+ - Renderer lifecycle management with exponential backoff restart
28
+ - Error recovery: StandardError rescue in update/view with model
29
+ preservation and log throttling
30
+ - 18 property type modules with wire encoding
31
+ - State helpers: Animation, Route, Selection, Undo, DataQuery, State,
32
+ KeyModifiers
33
+ - Widget extension system (pure Ruby composites + native Rust-backed)
34
+ - Native Rust extension build pipeline via `rake plushie:build` --
35
+ generates Cargo workspace, validates crate paths and constructors,
36
+ detects type name and crate collisions, builds custom renderer binary
37
+ - `Plushie.configure` block for SDK-wide configuration: `binary_path`,
38
+ `source_path`, `build_name`, `extensions`, `extension_config`,
39
+ `test_backend`
40
+ - `extension_config` runtime configuration passed to Rust extensions
41
+ via the Settings wire message and `InitCtx`
42
+ - WASM renderer download via `rake plushie:download[wasm]`
43
+ - `PLUSHIE_BIN_FILE` and `PLUSHIE_WASM_DIR` env vars for overriding
44
+ download and build output paths
45
+ - `rake plushie:connect` task for stdio transport (plushie --exec)
46
+ - Token authentication for --exec and remote rendering
47
+ - `RendererEnv` to filter sensitive environment variables from renderer
48
+ subprocess
49
+ - Dev server with hot code reloading
50
+ - Test framework with three backends (mock, headless, windowed)
51
+ - Session pooling for parallel test execution
52
+ - Snapshot and screenshot assertion helpers
53
+ - .plushie script format parser and runner
54
+ - Minitest and RSpec integration
55
+ - 100% YARD documentation coverage with zero warnings
56
+ - RBS type signatures for all modules
57
+ - GitHub Actions CI workflow (Ruby 3.2 + 3.3 matrix)
58
+ - CONTRIBUTING.md with commit conventions and development guide
59
+ - Rake tasks: download, build, run, connect, inspect, script, replay,
60
+ preflight
61
+ - Binary download with SHA-256 checksum verification
62
+ - 9 examples: counter, clock, todo, async_fetch, notes, shortcuts,
63
+ color_picker, catalog, rate_plushie
64
+ - Extracted reusable canvas widgets: StarRating, ThemeToggle,
65
+ ColorPickerWidget (in examples/widgets/)
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,115 @@
1
+ # Contributing to plushie-ruby
2
+
3
+ ## Setup
4
+
5
+ ```bash
6
+ git clone https://github.com/plushie-ui/plushie-ruby.git
7
+ cd plushie-ruby
8
+ bundle install
9
+ rake plushie:download # precompiled renderer binary
10
+ ```
11
+
12
+ Requires Ruby 3.2+. No Rust toolchain needed unless building from
13
+ source or writing native extensions.
14
+
15
+ ## Running checks
16
+
17
+ ```bash
18
+ bundle exec rake # tests + linter + type check
19
+ bundle exec rake test # tests only
20
+ bundle exec rake standard # linter only
21
+ bundle exec rake steep # type check only
22
+ bundle exec rake yard # generate API docs to doc/
23
+ rake plushie:preflight # pre-push gate (standard, test, steep, yard)
24
+ ```
25
+
26
+ ### Test backends
27
+
28
+ Tests run against the renderer binary. Three interchangeable backends:
29
+
30
+ ```bash
31
+ bundle exec rake test # mock (default, fastest)
32
+ PLUSHIE_TEST_BACKEND=headless bundle exec rake test # real rendering, no display
33
+ PLUSHIE_TEST_BACKEND=windowed bundle exec rake test # real windows (needs display)
34
+ ```
35
+
36
+ Mock is fast enough for TDD loops. CI runs both mock and headless.
37
+
38
+ ## Commits
39
+
40
+ Run `bundle exec rake` before committing. CI runs the same checks.
41
+
42
+ ### Message format
43
+
44
+ Use imperative mood. Describe what changed and why, not how.
45
+
46
+ ```
47
+ feat: add on_resize subscription for window resize events
48
+
49
+ The renderer already emits resize events but the SDK had no
50
+ subscription type for them. Apps had to poll window dimensions
51
+ on a timer, which was wasteful and laggy.
52
+ ```
53
+
54
+ Prefix with a category when it clarifies intent:
55
+
56
+ | Prefix | Use for |
57
+ |------------|--------------------------------------------|
58
+ | `feat:` | new user-facing functionality |
59
+ | `fix:` | bug fix |
60
+ | `docs:` | documentation only |
61
+ | `test:` | test additions or corrections |
62
+ | `refactor:`| restructuring without behaviour change |
63
+ | `chore:` | deps, CI, tooling, release prep |
64
+
65
+ # Pull requests
66
+
67
+ - One logical change per PR. If a refactor enables a feature, consider
68
+ splitting them unless the refactor is small and tightly coupled.
69
+ - PR title follows the same format as commit messages.
70
+ - Include a brief description of what and why. If there's a visual
71
+ change, a screenshot or before/after helps.
72
+ - All CI checks must pass.
73
+
74
+ ## Code style
75
+
76
+ [Standard](https://github.com/standardrb/standard) handles formatting
77
+ and linting. No configuration to argue about.
78
+
79
+ Beyond what Standard enforces:
80
+
81
+ - **Let the code speak.** Only comment when intent isn't obvious from
82
+ the code itself.
83
+ - **Prefer real implementations over mocks in tests.** The mock backend
84
+ is already fast; mocking Ruby internals hides real bugs.
85
+ - **Tests are documentation.** Write them so the next person
86
+ understands the behaviour, not just that it "passes".
87
+ - **ID is always the first argument** for widget builders.
88
+
89
+ ## Type signatures
90
+
91
+ RBS signatures live in `sig/`. When adding or changing public API
92
+ methods, update the corresponding `.rbs` file. Run `bundle exec rake
93
+ steep` to verify.
94
+
95
+ ## Documentation
96
+
97
+ Public API methods use YARD-style doc comments (`@param`, `@return`,
98
+ `@example`). Guides live in `docs/` as Markdown. Generate and browse
99
+ locally:
100
+
101
+ ```bash
102
+ bundle exec rake yard
103
+ open doc/index.html
104
+ ```
105
+
106
+ ## Architecture overview
107
+
108
+ See the [project layout and architecture](README.md#how-it-works) in
109
+ the README, or the detailed guide docs:
110
+
111
+ - [Getting started](docs/getting-started.md)
112
+ - [App behaviour](docs/app-behaviour.md)
113
+ - [Events](docs/events.md)
114
+ - [Commands](docs/commands.md)
115
+ - [Testing](docs/testing.md)
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
6
+
7
+ gem "irb"
8
+ gem "rake", "~> 13.0"
9
+ gem "minitest", "~> 5.16"
10
+ gem "standard", "~> 1.3"
11
+ gem "steep", require: false
12
+ gem "yard", require: false
13
+
14
+ # Optional: hot reload in dev mode (Plushie.run(MyApp, dev: true))
15
+ gem "listen", "~> 3.0", require: false
@@ -1,6 +1,6 @@
1
- MIT License
1
+ The MIT License (MIT)
2
2
 
3
- Copyright (c) 2026 Daniel Hedlund <daniel@digitree.org>
3
+ Copyright (c) 2026 Daniel Hedlund
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -9,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
9
  copies of the Software, and to permit persons to whom the Software is
10
10
  furnished to do so, subject to the following conditions:
11
11
 
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
14
 
15
15
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
16
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
17
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
18
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md CHANGED
@@ -1,8 +1,300 @@
1
- # Plushie for Ruby
1
+ # plushie
2
2
 
3
- Native GUI toolkit for Ruby, powered by native rendering via Iced.
3
+ Build native desktop apps in Ruby. **Pre-1.0**
4
4
 
5
- This is a placeholder release to reserve the package name. The first
6
- functional release is coming soon.
5
+ Plushie is a desktop GUI framework that allows you to write your entire
6
+ application in Ruby -- state, events, UI -- and get native windows
7
+ on Linux, macOS, and Windows. Rendering is powered by
8
+ [iced](https://github.com/iced-rs/iced), a cross-platform GUI library
9
+ for Rust, which plushie drives as a precompiled binary behind the scenes.
7
10
 
8
- Source: <https://github.com/plushie-ui/plushie>
11
+ ```ruby
12
+ class Counter
13
+ include Plushie::App
14
+
15
+ Model = Plushie::Model.define(:count)
16
+
17
+ def init(_opts) = Model.new(count: 0)
18
+
19
+ def update(model, event)
20
+ case event
21
+ in Event::Widget[type: :click, id: "inc"]
22
+ model.with(count: model.count + 1)
23
+ in Event::Widget[type: :click, id: "dec"]
24
+ model.with(count: model.count - 1)
25
+ else
26
+ model
27
+ end
28
+ end
29
+
30
+ def view(model)
31
+ window("main", title: "Counter") do
32
+ column(padding: 16, spacing: 8) do
33
+ text("count", "Count: #{model.count}")
34
+ row(spacing: 8) do
35
+ button("inc", "+")
36
+ button("dec", "-")
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ Plushie.run(Counter)
44
+ ```
45
+
46
+ This is one of [8 examples](examples/) included in the repo, from a
47
+ minimal counter to a full widget catalog. For complete project demos,
48
+ including native Rust extensions, see the
49
+ [plushie-demos](https://github.com/plushie-ui/plushie-demos/tree/main/ruby)
50
+ repository.
51
+
52
+ ## Getting started
53
+
54
+ Add plushie to your Gemfile:
55
+
56
+ ```ruby
57
+ gem "plushie", "== 0.1.0"
58
+ ```
59
+
60
+ Then:
61
+
62
+ ```bash
63
+ bundle install
64
+ rake plushie:download # download precompiled renderer binary
65
+ ```
66
+
67
+ Requires Ruby 3.2+. The precompiled binary requires no Rust toolchain.
68
+
69
+ ### Your first app
70
+
71
+ Create `lib/counter.rb`:
72
+
73
+ ```ruby
74
+ require "plushie"
75
+
76
+ class Counter
77
+ include Plushie::App
78
+
79
+ Model = Plushie::Model.define(:count)
80
+
81
+ def init(_opts) = Model.new(count: 0)
82
+
83
+ def update(model, event)
84
+ case event
85
+ in Event::Widget[type: :click, id: "increment"]
86
+ model.with(count: model.count + 1)
87
+ in Event::Widget[type: :click, id: "decrement"]
88
+ model.with(count: model.count - 1)
89
+ else
90
+ model
91
+ end
92
+ end
93
+
94
+ def view(model)
95
+ window("main", title: "Counter") do
96
+ column(padding: 16, spacing: 8) do
97
+ text("count", "Count: #{model.count}", size: 20)
98
+ row(spacing: 8) do
99
+ button("increment", "+")
100
+ button("decrement", "-")
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ Plushie.run(Counter)
108
+ ```
109
+
110
+ Run it:
111
+
112
+ ```bash
113
+ ruby lib/counter.rb
114
+ ```
115
+
116
+ ## The Elm architecture
117
+
118
+ Plushie follows the Elm architecture. Your app implements four callbacks:
119
+
120
+ - **`init(opts)`** -- returns the initial model (any Ruby object, ideally
121
+ immutable via `Plushie::Model.define`).
122
+ - **`update(model, event)`** -- receives the current model and an event,
123
+ returns the new model. Pure function. Return `[model, command]` for
124
+ side effects.
125
+ - **`view(model)`** -- receives the model, returns a UI tree using the
126
+ block DSL. The runtime diffs trees and sends patches to the renderer.
127
+ - **`subscribe(model)`** (optional) -- returns active subscriptions
128
+ (timers, keyboard/mouse events).
129
+
130
+ ## Features
131
+
132
+ - **39 built-in widget types** -- buttons, text inputs, sliders, tables,
133
+ markdown, canvas, pane grids, and more.
134
+ - **22 built-in themes** -- light, dark, dracula, nord, catppuccin,
135
+ tokyo night, kanagawa, and more.
136
+ - **Multi-window** -- declare window nodes in your widget tree; the
137
+ framework manages them automatically.
138
+ - **Platform effects** -- native file dialogs, clipboard, OS
139
+ notifications.
140
+ - **Accessibility** -- screen reader support via accesskit.
141
+ - **Live reload** -- `Plushie.run(MyApp, dev: true)` watches lib/ and
142
+ reloads on file changes. Model state is preserved.
143
+ - **Remote rendering** -- native desktop UI for server-side Ruby apps
144
+ over SSH. Your init/update/view code doesn't change.
145
+ - **Widget extensions** -- pure Ruby composites or native Rust-backed
146
+ custom widgets via `include Plushie::Extension`.
147
+ - **Configuration system** -- `Plushie.configure` for binary paths,
148
+ extensions, test backends, and extension runtime config.
149
+ - **WASM renderer** -- `rake plushie:download[wasm]` downloads a WASM
150
+ build of the renderer for browser targets.
151
+
152
+ ## Documentation
153
+
154
+ **Guides:**
155
+
156
+ - [Getting started](docs/getting-started.md) -- setup, first app, rake tasks, dev mode
157
+ - [Tutorial: building a todo app](docs/tutorial.md) -- step-by-step walkthrough
158
+ - [App behaviour](docs/app-behaviour.md) -- init, update, view, subscribe callbacks
159
+ - [Layout](docs/layout.md) -- column, row, container, spacing, alignment
160
+ - [Events](docs/events.md) -- widget, keyboard, mouse, window, canvas events
161
+ - [Commands](docs/commands.md) -- async, timers, widget ops, effects, batching
162
+ - [Effects](docs/effects.md) -- file dialogs, clipboard, notifications
163
+ - [Scoped IDs](docs/scoped-ids.md) -- how container nesting scopes widget IDs
164
+
165
+ **Advanced:**
166
+
167
+ - [Running](docs/running.md) -- transports, remote rendering, WASM
168
+ - [Theming](docs/theming.md) -- built-in themes and custom styling
169
+ - [Testing](docs/testing.md) -- mock, headless, and windowed backends
170
+ - [Composition patterns](docs/composition-patterns.md) -- tabs, modals, forms, lists
171
+ - [Accessibility](docs/accessibility.md) -- screen reader support, roles, labels
172
+ - [Extensions](docs/extensions.md) -- Ruby composites and native Rust widgets
173
+ - [DSL internals](docs/dsl-internals.md) -- how the UI builder works under the hood
174
+
175
+ **API reference:** [rubydoc.info/gems/plushie](https://www.rubydoc.info/gems/plushie)
176
+
177
+ ## Testing
178
+
179
+ All testing goes through the renderer binary. No Ruby-side mocks.
180
+ The mock backend runs at millisecond speed.
181
+
182
+ Add `require "plushie/test"` to your test helper:
183
+
184
+ ```ruby
185
+ # test/test_helper.rb
186
+ require "plushie"
187
+ require "plushie/test"
188
+ require "minitest/autorun"
189
+ ```
190
+
191
+ Then write tests:
192
+
193
+ ```ruby
194
+ class CounterTest < Plushie::Test::Case
195
+ app Counter
196
+
197
+ def test_clicking_increment_updates_counter
198
+ click("#increment")
199
+ assert_text "#count", "Count: 1"
200
+ end
201
+ end
202
+ ```
203
+
204
+ Three interchangeable backends:
205
+
206
+ - **Mock** (`PLUSHIE_TEST_BACKEND=mock`) -- millisecond tests, no display.
207
+ Default.
208
+ - **Headless** (`PLUSHIE_TEST_BACKEND=headless`) -- real rendering via
209
+ tiny-skia, no display server. Pixel screenshots.
210
+ - **Windowed** (`PLUSHIE_TEST_BACKEND=windowed`) -- real windows with GPU.
211
+ Needs display server (Xvfb in CI).
212
+
213
+ ## How it works
214
+
215
+ Under the hood, a renderer built on iced handles window drawing and
216
+ platform integration. Your Ruby code sends widget trees to the
217
+ renderer over stdin; the renderer draws native windows and sends user
218
+ events back over stdout.
219
+
220
+ You don't need Rust to use plushie. The renderer is a precompiled
221
+ binary, similar to how your app talks to a database without you
222
+ writing C. If you need custom native rendering, the extension system
223
+ lets you write Rust for just those parts.
224
+
225
+ The same protocol works over a local pipe, an SSH connection, or any
226
+ bidirectional byte stream.
227
+
228
+ ## State helpers
229
+
230
+ Plushie ships optional state management utilities:
231
+
232
+ - **`Plushie::Animation`** -- easing functions and interpolation
233
+ - **`Plushie::Route`** -- navigation stack for multi-view apps
234
+ - **`Plushie::Selection`** -- single, multi, and range selection
235
+ - **`Plushie::Undo`** -- undo/redo stacks with coalescing
236
+ - **`Plushie::DataQuery`** -- filter, search, sort, paginate collections
237
+ - **`Plushie::State`** -- path-based state with revision tracking
238
+
239
+ ## Development
240
+
241
+ ```bash
242
+ bundle exec rake # tests + linter + type check
243
+ bundle exec rake test # tests only
244
+ bundle exec rake standard # linter only
245
+ bundle exec rake steep # type check only
246
+ bundle exec rake yard # generate API docs to doc/
247
+ ```
248
+
249
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for the full development guide.
250
+
251
+ Rake tasks (add `require "plushie/rake"` to your Rakefile):
252
+
253
+ ```bash
254
+ rake plushie:download # download precompiled binary
255
+ rake plushie:download[wasm] # download WASM renderer
256
+ rake plushie:build # build from Rust source (with extensions if configured)
257
+ rake plushie:run[Counter] # run an app
258
+ rake plushie:connect[Counter] # connect to renderer via stdio
259
+ rake plushie:inspect[Counter] # print UI tree as JSON
260
+ rake plushie:script # run .plushie test scripts
261
+ rake plushie:replay[path] # replay a script with real windows
262
+ rake plushie:preflight # run all CI checks
263
+ ```
264
+
265
+ Configure the SDK programmatically:
266
+
267
+ ```ruby
268
+ Plushie.configure do |config|
269
+ config.binary_path = "/opt/plushie/bin/plushie"
270
+ config.extensions = [MyGauge]
271
+ config.test_backend = :headless
272
+ end
273
+ ```
274
+
275
+ ## System requirements
276
+
277
+ The precompiled binary has no additional dependencies. To build from
278
+ source, install a Rust toolchain via [rustup](https://rustup.rs/) and
279
+ the platform-specific libraries:
280
+
281
+ - **Linux (Debian/Ubuntu):**
282
+ `sudo apt-get install libxkbcommon-dev libwayland-dev libx11-dev cmake fontconfig pkg-config`
283
+ - **Linux (Arch):**
284
+ `sudo pacman -S libxkbcommon wayland libx11 cmake fontconfig pkgconf`
285
+ - **macOS:** `xcode-select --install`
286
+ - **Windows:**
287
+ [Visual Studio Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/)
288
+ with "Desktop development with C++"
289
+
290
+ ## Links
291
+
292
+ | | |
293
+ |---|---|
294
+ | Ruby SDK | [github.com/plushie-ui/plushie-ruby](https://github.com/plushie-ui/plushie-ruby) |
295
+ | Elixir SDK | [github.com/plushie-ui/plushie-elixir](https://github.com/plushie-ui/plushie-elixir) |
296
+ | Renderer | [github.com/plushie-ui/plushie](https://github.com/plushie-ui/plushie) |
297
+
298
+ ## License
299
+
300
+ MIT
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "minitest/test_task"
5
+
6
+ Minitest::TestTask.create
7
+
8
+ require "standard/rake"
9
+ require "plushie/rake"
10
+
11
+ desc "Run Steep type checker"
12
+ task :steep do
13
+ sh "bundle exec steep check"
14
+ end
15
+
16
+ desc "Generate YARD documentation"
17
+ task :yard do
18
+ sh "bundle exec yard doc"
19
+ end
20
+
21
+ task default: %i[test standard steep]
data/Steepfile ADDED
@@ -0,0 +1,8 @@
1
+ # Steepfile
2
+ target :lib do
3
+ signature "sig"
4
+ check "lib"
5
+ library "msgpack"
6
+ library "json"
7
+ library "logger"
8
+ end