omarchy-ui 0.0.1-x86_64-linux
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.
- checksums.yaml +7 -0
- data/App.qml +112 -0
- data/BarWidget.qml +47 -0
- data/Components/README.md +40 -0
- data/Components/Sparkline.qml +36 -0
- data/ControlNode.qml +745 -0
- data/LICENSE +22 -0
- data/Panel.qml +106 -0
- data/README.md +362 -0
- data/Service.qml +422 -0
- data/bin/omarchy_ui +7 -0
- data/lib/omarchy_ui/animation.rb +24 -0
- data/lib/omarchy_ui/application.rb +282 -0
- data/lib/omarchy_ui/builder.rb +230 -0
- data/lib/omarchy_ui/cli.rb +199 -0
- data/lib/omarchy_ui/command.rb +67 -0
- data/lib/omarchy_ui/component_registry.rb +87 -0
- data/lib/omarchy_ui/components.rb +43 -0
- data/lib/omarchy_ui/node.rb +30 -0
- data/lib/omarchy_ui/project.rb +127 -0
- data/lib/omarchy_ui/protocol.rb +29 -0
- data/lib/omarchy_ui/runtime.rb +31 -0
- data/lib/omarchy_ui/scheduler.rb +136 -0
- data/lib/omarchy_ui/state_store.rb +100 -0
- data/lib/omarchy_ui/value.rb +44 -0
- data/lib/omarchy_ui.rb +29 -0
- data/manifest.json +27 -0
- data/vendor/runtime/x86_64-linux/omarchy-ui-runtime +0 -0
- metadata +71 -0
data/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Adam Moussa Ali
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
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.
|
|
22
|
+
|
data/Panel.qml
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import QtQuick
|
|
2
|
+
import Quickshell
|
|
3
|
+
import Quickshell.Wayland
|
|
4
|
+
import qs.Commons
|
|
5
|
+
import qs.Ui as OmarchyUi
|
|
6
|
+
|
|
7
|
+
Item {
|
|
8
|
+
id: root
|
|
9
|
+
|
|
10
|
+
property var shell: null
|
|
11
|
+
property var manifest: null
|
|
12
|
+
property var service: null
|
|
13
|
+
property bool opened: false
|
|
14
|
+
property string surfaceName: "counter"
|
|
15
|
+
|
|
16
|
+
readonly property string rootControlId: service ? service.rootId(surfaceName) : ""
|
|
17
|
+
|
|
18
|
+
function open(payloadJson) {
|
|
19
|
+
var payload = {}
|
|
20
|
+
try { payload = JSON.parse(payloadJson || "{}") || {} }
|
|
21
|
+
catch (error) { payload = {} }
|
|
22
|
+
if (typeof payload.surface === "string" && payload.surface !== "")
|
|
23
|
+
surfaceName = payload.surface
|
|
24
|
+
opened = true
|
|
25
|
+
Qt.callLater(function() { keyCatcher.forceActiveFocus() })
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function close() {
|
|
29
|
+
opened = false
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function dismiss() {
|
|
33
|
+
if (shell && manifest) shell.hide(manifest.id)
|
|
34
|
+
else close()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
PanelWindow {
|
|
38
|
+
id: window
|
|
39
|
+
visible: root.opened
|
|
40
|
+
anchors { top: true; right: true; bottom: true; left: true }
|
|
41
|
+
color: "transparent"
|
|
42
|
+
exclusionMode: ExclusionMode.Ignore
|
|
43
|
+
WlrLayershell.namespace: "omarchy-ruby-ui-poc"
|
|
44
|
+
WlrLayershell.layer: WlrLayer.Overlay
|
|
45
|
+
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
|
|
46
|
+
|
|
47
|
+
Rectangle {
|
|
48
|
+
anchors.fill: parent
|
|
49
|
+
color: Qt.rgba(0, 0, 0, 0.62)
|
|
50
|
+
|
|
51
|
+
MouseArea {
|
|
52
|
+
anchors.fill: parent
|
|
53
|
+
onClicked: root.dismiss()
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
Item {
|
|
58
|
+
id: keyCatcher
|
|
59
|
+
anchors.fill: parent
|
|
60
|
+
focus: true
|
|
61
|
+
Keys.onEscapePressed: root.dismiss()
|
|
62
|
+
|
|
63
|
+
OmarchyUi.BorderSurface {
|
|
64
|
+
id: card
|
|
65
|
+
anchors.centerIn: parent
|
|
66
|
+
width: Math.min(parent.width - Style.space(32), Math.max(Style.space(320), renderer.implicitWidth + Style.space(48)))
|
|
67
|
+
height: Math.min(parent.height - Style.space(32), Math.max(Style.space(180), renderer.implicitHeight + Style.space(48)))
|
|
68
|
+
color: Color.popups.background
|
|
69
|
+
borderSpec: Border.localOrSurfaceSpec("popups", "border", Color.popups.border, Color.popups.border, Math.max(1, Style.normalBorderWidth))
|
|
70
|
+
radius: Style.cornerRadius
|
|
71
|
+
|
|
72
|
+
MouseArea {
|
|
73
|
+
anchors.fill: parent
|
|
74
|
+
onClicked: {}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
ControlNode {
|
|
78
|
+
id: renderer
|
|
79
|
+
anchors.centerIn: parent
|
|
80
|
+
visible: root.service && root.rootControlId !== ""
|
|
81
|
+
bridge: root.service
|
|
82
|
+
surfaceName: root.surfaceName
|
|
83
|
+
controlId: root.rootControlId
|
|
84
|
+
foreground: Color.foreground
|
|
85
|
+
fontFamily: Style.font.family
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
Column {
|
|
89
|
+
anchors.centerIn: parent
|
|
90
|
+
spacing: Style.space(8)
|
|
91
|
+
visible: !renderer.visible
|
|
92
|
+
|
|
93
|
+
Text {
|
|
94
|
+
anchors.horizontalCenter: parent.horizontalCenter
|
|
95
|
+
text: root.service && root.service.lastError !== ""
|
|
96
|
+
? root.service.lastError
|
|
97
|
+
: "Starting Ruby UI…"
|
|
98
|
+
color: Color.foreground
|
|
99
|
+
font.family: Style.font.family
|
|
100
|
+
font.pixelSize: Style.font.body
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
data/README.md
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
# Omarchy UI
|
|
2
|
+
|
|
3
|
+
> **Experimental:** the API and packaging format are being validated with real Omarchy apps.
|
|
4
|
+
> Pin the gem version for production projects and review release notes before upgrading.
|
|
5
|
+
|
|
6
|
+
Omarchy UI is the official-style Ruby application framework for building native Omarchy
|
|
7
|
+
interfaces. Ruby owns application state, events, tasks, commands, and models; a shared mruby
|
|
8
|
+
runtime communicates with QML over a validated protocol. Applications do not need system Ruby
|
|
9
|
+
and do not copy the framework runtime into every project.
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
require "omarchy_ui" unless Object.const_defined?(:OmarchyUI)
|
|
13
|
+
|
|
14
|
+
OmarchyUI.plugin do
|
|
15
|
+
state :count, 0
|
|
16
|
+
|
|
17
|
+
app :main, title: "Counter", width: 640, height: 420 do
|
|
18
|
+
column spacing: 12 do
|
|
19
|
+
text "Official Omarchy UI", style: :heading
|
|
20
|
+
text { "Count: #{state.count}" }
|
|
21
|
+
button("Increment") { state.count += 1 }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
On Omarchy x86-64, install the gem and start building:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
gem install omarchy-ui
|
|
33
|
+
omarchy_ui new "My App"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The gem includes the CLI, QML bridge, and a prebuilt mruby runtime with Omarchy UI embedded.
|
|
37
|
+
Developers do not install mruby, compile the runtime, copy framework files, or require Ruby on
|
|
38
|
+
machines that run a bundled application. Omarchy with Quickshell is the only host requirement.
|
|
39
|
+
|
|
40
|
+
Framework maintainers can rebuild the pinned mruby 4.0 binary with:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
./scripts/build-mruby-runtime.sh
|
|
44
|
+
install -Dm755 build/runtime/omarchy-ui-runtime ~/.local/bin/omarchy-ui-runtime
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The stripped prebuilt runtime is approximately 1.8 MB and embeds the Ruby framework, JSON, regular
|
|
48
|
+
expressions, process support, and the native safe-command bridge.
|
|
49
|
+
|
|
50
|
+
## Create and run an application
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
omarchy_ui new "My App"
|
|
54
|
+
cd my-app
|
|
55
|
+
omarchy_ui launch main.rb
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The standalone generator creates no plugin manifest or copied runtime files:
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
my-app/
|
|
62
|
+
├── Components/
|
|
63
|
+
│ └── Welcome.qml
|
|
64
|
+
├── README.md
|
|
65
|
+
└── main.rb
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`launch` opens a compositor-managed window. Drag its title bar or use Super+drag, and close it
|
|
69
|
+
with Super+W. For protocol debugging without a window, use `omarchy_ui run main.rb`.
|
|
70
|
+
|
|
71
|
+
## Bundle for another Omarchy computer
|
|
72
|
+
|
|
73
|
+
From an application directory:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
omarchy_ui bundle
|
|
77
|
+
./dist/my-app/run
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`bundle` copies the working application, framework QML bridge, and prebuilt mruby executable into
|
|
81
|
+
`dist/<project-name>/`. The generated `run` launcher invokes Quickshell directly with the bundled
|
|
82
|
+
runtime, so the destination computer does not need Ruby or the `omarchy-ui` gem.
|
|
83
|
+
|
|
84
|
+
An Omarchy Shell plugin is a separate packaging mode. It requires `manifest.json` so the shell
|
|
85
|
+
can discover its ID, entry points, bar placement, and lifecycle. For a project that intentionally
|
|
86
|
+
contains a manifest:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
omarchy_ui validate path/to/plugin
|
|
90
|
+
omarchy_ui push path/to/plugin
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`push` stages and validates the project, injects the shared QML bridge files, backs up an existing
|
|
94
|
+
installation, installs atomically, optionally enables it, and restarts Omarchy Shell. Use
|
|
95
|
+
`--no-enable` or `--no-restart` when needed.
|
|
96
|
+
|
|
97
|
+
## Surfaces and windows
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
bar_widget do
|
|
101
|
+
text "Weather"
|
|
102
|
+
on_click { open_panel :weather }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
panel :weather do
|
|
106
|
+
text "Panel content"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
app :main,
|
|
110
|
+
title: "Weather",
|
|
111
|
+
width: 900,
|
|
112
|
+
height: 600,
|
|
113
|
+
min_width: 480,
|
|
114
|
+
min_height: 320,
|
|
115
|
+
max_width: 1600,
|
|
116
|
+
max_height: 1200,
|
|
117
|
+
color: "#101713",
|
|
118
|
+
visible: true,
|
|
119
|
+
maximized: false,
|
|
120
|
+
fullscreen: false do
|
|
121
|
+
text "Application content"
|
|
122
|
+
end
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
`app` supports `title`, `width`, `height`, `min_width`, `min_height`, `max_width`, `max_height`,
|
|
126
|
+
`color`, `visible`, `maximized`, and `fullscreen`.
|
|
127
|
+
|
|
128
|
+
## State and reactivity
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
state :enabled, false
|
|
132
|
+
state :profile, { "name" => "Ada" }
|
|
133
|
+
|
|
134
|
+
toggle_control = toggle "Enabled", checked: state.enabled do |event|
|
|
135
|
+
state.enabled = event.fetch("value")
|
|
136
|
+
end
|
|
137
|
+
bind(toggle_control, :checked) { state.enabled }
|
|
138
|
+
|
|
139
|
+
text { state.enabled ? "Enabled" : "Disabled" }
|
|
140
|
+
|
|
141
|
+
transaction do
|
|
142
|
+
state.enabled = true
|
|
143
|
+
state.profile = { "name" => "Grace" }
|
|
144
|
+
end
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
State accepts protocol-safe values: `nil`, booleans, finite numbers, strings, arrays, and hashes
|
|
148
|
+
with string/symbol keys. Bindings are reevaluated after changes and emit small property patches.
|
|
149
|
+
`transaction` batches related state writes.
|
|
150
|
+
|
|
151
|
+
Use `dynamic` when state changes the structure rather than only a property:
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
dynamic id: :results, spacing: 8 do
|
|
155
|
+
if state.items.empty?
|
|
156
|
+
text "Nothing found"
|
|
157
|
+
else
|
|
158
|
+
state.items.each { |item| text item.fetch("name") }
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Common properties
|
|
164
|
+
|
|
165
|
+
Every component supports `visible`, `enabled`, `opacity`, `scale`, `rotation`, `z`, `width`, and
|
|
166
|
+
`height`. Component-specific properties are listed below. Names use Ruby `snake_case`; the QML
|
|
167
|
+
bridge maps them to native property names.
|
|
168
|
+
|
|
169
|
+
## Built-in component reference
|
|
170
|
+
|
|
171
|
+
### Layout and display
|
|
172
|
+
|
|
173
|
+
| Ruby component | Component-specific properties | Events | Container |
|
|
174
|
+
| --- | --- | --- | --- |
|
|
175
|
+
| `container` | `spacing`, `padding`, `bordered` | `click` | yes |
|
|
176
|
+
| `row` | `spacing`, `alignment` (`start`, `center`, `end`) | `click` | yes |
|
|
177
|
+
| `column` | `spacing`, `alignment` (`start`, `center`, `end`) | `click` | yes |
|
|
178
|
+
| `grid` | `columns`, `rows`, `spacing`, `row_spacing`, `column_spacing` | `click` | yes |
|
|
179
|
+
| `stack` | — | `click` | yes |
|
|
180
|
+
| `scroll` | `clip` | `click` | yes |
|
|
181
|
+
| `rectangle` | `color`, `radius`, `border_color`, `border_width`, `padding` | `click` | yes |
|
|
182
|
+
| `text` | `text`, `style`, `size`, `bold`, `color`, `wrap` | — | no |
|
|
183
|
+
| `icon` | `name`, `text`, `size`, `color` | — | no |
|
|
184
|
+
| `image` | `source`, `fill_mode` | — | no |
|
|
185
|
+
| `spacer` | — | — | no |
|
|
186
|
+
| `progress` | `value`, `minimum`, `maximum`, `color` | — | no |
|
|
187
|
+
| `separator` | `strength` | — | no |
|
|
188
|
+
| `section_header` | `text` | — | no |
|
|
189
|
+
| `panel_hero` | `title`, `meta`, `detail`, `foreground`, `font_family`, `icon_size`, `icon_opacity`, `meta_opacity` | — | no |
|
|
190
|
+
| `optical_glyph` | `text`, `size`, `color`, `debug_bounds` | — | no |
|
|
191
|
+
|
|
192
|
+
### Inputs and actions
|
|
193
|
+
|
|
194
|
+
| Ruby component | Component-specific properties | Events |
|
|
195
|
+
| --- | --- | --- |
|
|
196
|
+
| `button` | `text`, `icon`, `tooltip`, `selected`, `active`, `cursor`, `focusable`, `bordered`, colors, font/icon sizes, padding, `left_align` | `click`, `right_click`, `hover` |
|
|
197
|
+
| `action_button` | `icon`, `tooltip`, `foreground`, `hover_color`, font/size, `focusable`, `cursor`, `bordered` | `click`, `hover` |
|
|
198
|
+
| `toggle` | `label`, `description`, `checked`, `cursor`, `rounded`, colors, font/title/description sizes | `change`, `hover` |
|
|
199
|
+
| `toggle_switch` | `checked`, `busy`, `interactive`, `cursor`, `cursor_ring`, `cursor_pad`, `rounded`, colors, track/knob geometry | `change`, `hover` |
|
|
200
|
+
| `text_field` | `text`, `placeholder`, `password`, colors, selection tint, padding, `cursor` | `input`, `change`, `submit`, `focus`, `blur` |
|
|
201
|
+
| `number_field` | `label`, `value`, `from`, `to`, `step`, colors, font/field width, `cursor` | `change`, `hover` |
|
|
202
|
+
| `slider` | `value`, `minimum`, `maximum`, `step`, `integer`, track/fill/knob colors and sizes, `ticks`, `tick_color` | `input`, `change`, `right_click` |
|
|
203
|
+
| `dropdown` | `label`, `value`, `options`, colors, font, row sizes, `show_label`, `cursor` | `change`, `hover` |
|
|
204
|
+
| `searchable_dropdown` | dropdown fields plus `placeholder`, `empty_text`, `trigger_label`, popup sizing | `change`, `hover` |
|
|
205
|
+
| `multi_select` | `label`, `values`, `options`, command options, placeholder/empty labels, popup sizing, colors | `change`, `hover` |
|
|
206
|
+
| `button_group` | `value`, `options`, colors, font, `focusable`, `cursor_index` | `change`, `hover` |
|
|
207
|
+
| `confirm_dialog` | `opened`, `message`, cancel/confirm labels, `selected_index`, colors, font, `corner_radius` | `cancel`, `confirm` |
|
|
208
|
+
| `cursor_surface` | `cursor`, `current`, `outline`, `bordered`, `foreground`, `accent`, `fill`, `current_fill` | `click` |
|
|
209
|
+
| `widget_button` | text/font/colors, active state, dimensions, rotation, visibility states, interaction flags, tooltip | `click`, `right_click`, `middle_click`, `wheel` |
|
|
210
|
+
| `list_view` | `items`, key/label/description/icon fields, `selected`, `orientation`, `spacing`, `empty_text` | `change`, `activate`, `scroll` |
|
|
211
|
+
|
|
212
|
+
Convenience methods return their node, so it can be bound, animated, or passed to `on`:
|
|
213
|
+
|
|
214
|
+
```ruby
|
|
215
|
+
field = text_field "", id: :query, placeholder: "Search" do |event|
|
|
216
|
+
state.query = event.fetch("value")
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
on(field, :submit) { |event| state.query = event.fetch("value") }
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Typical event payloads are:
|
|
223
|
+
|
|
224
|
+
- `click`, `right_click`, `confirm`, `cancel`: `{}`
|
|
225
|
+
- `change`, `input`, `submit`, `hover`: `{ "value" => ... }`
|
|
226
|
+
- `wheel`: `{ "delta" => number }`
|
|
227
|
+
- `list_view` change/activate: value, index, and original item
|
|
228
|
+
- `list_view` scroll: x and y offsets
|
|
229
|
+
|
|
230
|
+
Only declared and subscribed events cross the QML/Ruby boundary.
|
|
231
|
+
|
|
232
|
+
## Bindings and properties
|
|
233
|
+
|
|
234
|
+
```ruby
|
|
235
|
+
label = text "", id: :status
|
|
236
|
+
bind(label, :text) { state.message }
|
|
237
|
+
|
|
238
|
+
container do
|
|
239
|
+
property :opacity, 0.8
|
|
240
|
+
end
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
`text { ... }` is shorthand for a reactive text binding. `property` binds or sets a property on
|
|
244
|
+
the current component. Explicit IDs are recommended for controls targeted by tests or external
|
|
245
|
+
effects; generated IDs are stable for the lifetime of one render.
|
|
246
|
+
|
|
247
|
+
## Animation
|
|
248
|
+
|
|
249
|
+
Reactive binding transition:
|
|
250
|
+
|
|
251
|
+
```ruby
|
|
252
|
+
card = rectangle width: 240, height: 120, opacity: 1.0
|
|
253
|
+
bind(card, :opacity, animation: animation(duration: 180, easing: :out_cubic)) do
|
|
254
|
+
state.visible ? 1.0 : 0.0
|
|
255
|
+
end
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Animate one or several properties immediately:
|
|
259
|
+
|
|
260
|
+
```ruby
|
|
261
|
+
animate card,
|
|
262
|
+
{ opacity: 0.25, scale: 1.08, rotation: 2 },
|
|
263
|
+
duration: 220,
|
|
264
|
+
easing: :in_out_quad,
|
|
265
|
+
delay: 40
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Sequential animation:
|
|
269
|
+
|
|
270
|
+
```ruby
|
|
271
|
+
animate_sequence card, [
|
|
272
|
+
{ to: { scale: 1.12 }, duration: 120, easing: :out_back },
|
|
273
|
+
{ to: { scale: 1.0 }, duration: 160, easing: :out_cubic, pause: 30 }
|
|
274
|
+
]
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Animation durations and delays are milliseconds from `0` to `60_000`. Supported easing names:
|
|
278
|
+
|
|
279
|
+
```text
|
|
280
|
+
linear
|
|
281
|
+
in_quad, out_quad, in_out_quad
|
|
282
|
+
in_cubic, out_cubic, in_out_cubic
|
|
283
|
+
in_back, out_back, in_out_back
|
|
284
|
+
in_elastic, out_elastic, in_out_elastic
|
|
285
|
+
in_bounce, out_bounce, in_out_bounce
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
All common numeric visual properties and declared numeric custom-adapter properties can be
|
|
289
|
+
animated. Parallel property hashes become parallel QML animation tracks.
|
|
290
|
+
|
|
291
|
+
## Tasks and commands
|
|
292
|
+
|
|
293
|
+
```ruby
|
|
294
|
+
after(0.5) { state.message = "Ready" }
|
|
295
|
+
every(5, immediate: true) { state.updated_at = Time.now.to_i }
|
|
296
|
+
async { state.result = run_command(["uname", "-r"], timeout: 2).stdout.strip }
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
`after`, `every`, and `async` return cancellable task objects. MRI uses worker threads; mruby
|
|
300
|
+
uses cooperative ticks from the QML host. Command execution always takes an argv array and does
|
|
301
|
+
not invoke a shell. Results expose `stdout`, `stderr`, `exitstatus`, and `success?`; timeout raises
|
|
302
|
+
`OmarchyUI::CommandTimeout`.
|
|
303
|
+
|
|
304
|
+
## Custom QML components
|
|
305
|
+
|
|
306
|
+
Any QtQuick, QtQuick.Controls, Quickshell, Omarchy `qs.Ui`, Canvas, shader, particle, or
|
|
307
|
+
third-party QML component can be exposed through a validated adapter contract:
|
|
308
|
+
|
|
309
|
+
```ruby
|
|
310
|
+
register_component :sparkline,
|
|
311
|
+
qml: "Sparkline.qml",
|
|
312
|
+
properties: %i[values color line_width],
|
|
313
|
+
property_map: { color: :strokeColor, line_width: :lineWidth },
|
|
314
|
+
events: %i[click point_hover],
|
|
315
|
+
event_map: { point_hover: :pointHovered },
|
|
316
|
+
container: false,
|
|
317
|
+
auto_bind: true
|
|
318
|
+
|
|
319
|
+
chart = component :sparkline, values: [2, 8, 5], color: "#ff6655"
|
|
320
|
+
on(chart, :point_hover) { |event| state.hovered = event.fetch("index") }
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Place adapter files under `Components/`. Declared properties are assigned to the QML root and
|
|
324
|
+
declared signals are forwarded to Ruby. A container adapter can expose an `Item` property named
|
|
325
|
+
`contentHost`; framework children are parented into it automatically. See
|
|
326
|
+
[the QML support matrix](docs/qml-support.md) and [Sparkline.qml](Components/Sparkline.qml).
|
|
327
|
+
|
|
328
|
+
## Architecture and safety
|
|
329
|
+
|
|
330
|
+
`Service.qml` supervises one long-lived mruby process and exchanges versioned NDJSON through
|
|
331
|
+
stdin/stdout. `ControlNode.qml` recursively renders validated component nodes. Property changes
|
|
332
|
+
send incremental patches; dynamic branches replace only affected children; animations run in
|
|
333
|
+
QML. Closing a window or panel does not evaluate Ruby or QML received over the protocol.
|
|
334
|
+
|
|
335
|
+
Component names, QML filenames, properties, events, IDs, effects, values, message sizes, and
|
|
336
|
+
animation limits are validated. Commands use argv arrays without a shell. Applications and
|
|
337
|
+
plugins still run with the current user's permissions.
|
|
338
|
+
|
|
339
|
+
## Omarchy Phone example
|
|
340
|
+
|
|
341
|
+
`examples/omarchy-phone` demonstrates reactive controls, background discovery, safe commands,
|
|
342
|
+
ADB pairing and connection, scrcpy launching, iPhone discovery, and UxPlay AirPlay mirroring.
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
omarchy_ui launch examples/omarchy-phone/main.rb
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## Development and verification
|
|
349
|
+
|
|
350
|
+
```bash
|
|
351
|
+
./scripts/test.sh
|
|
352
|
+
ruby script/benchmark.rb
|
|
353
|
+
./scripts/smoke-test.sh # live Omarchy session
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
The suite covers state, bindings, repeated structures, event persistence, component schemas,
|
|
357
|
+
animation tracks and sequences, tasks, command safety, mruby compatibility, standalone project
|
|
358
|
+
generation, packaging, manifests, QML contracts, QML lint, and the phone backend.
|
|
359
|
+
|
|
360
|
+
## License
|
|
361
|
+
|
|
362
|
+
MIT. See [LICENSE](LICENSE).
|