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/Service.qml
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import QtQuick
|
|
2
|
+
import Quickshell
|
|
3
|
+
import Quickshell.Io
|
|
4
|
+
|
|
5
|
+
Item {
|
|
6
|
+
id: root
|
|
7
|
+
|
|
8
|
+
property var shell: null
|
|
9
|
+
property var manifest: null
|
|
10
|
+
readonly property string pluginDir: manifest && manifest.__sourceDir
|
|
11
|
+
? String(manifest.__sourceDir)
|
|
12
|
+
: ""
|
|
13
|
+
readonly property string rubyProgram: pluginDir + "/main.rb"
|
|
14
|
+
property string program: ""
|
|
15
|
+
readonly property string effectiveRubyProgram: program !== "" ? program : rubyProgram
|
|
16
|
+
|
|
17
|
+
property bool ready: false
|
|
18
|
+
property bool stopping: false
|
|
19
|
+
property string lastError: ""
|
|
20
|
+
property var surfaces: ({})
|
|
21
|
+
property var surfaceOptions: ({})
|
|
22
|
+
property var nodeIndex: ({})
|
|
23
|
+
property var componentDefinitions: ({})
|
|
24
|
+
property int revision: 0
|
|
25
|
+
property int restartDelayMs: 500
|
|
26
|
+
property int eventSequence: 0
|
|
27
|
+
|
|
28
|
+
property var allowedTypes: ({})
|
|
29
|
+
property var allowedProperties: ({})
|
|
30
|
+
|
|
31
|
+
signal effectReceived(string name, var payload)
|
|
32
|
+
|
|
33
|
+
function validId(value) {
|
|
34
|
+
return typeof value === "string"
|
|
35
|
+
&& value.length > 0
|
|
36
|
+
&& value.length <= 128
|
|
37
|
+
&& /^[a-zA-Z0-9_.:-]+$/.test(value)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function plainObject(value) {
|
|
41
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function validateNode(node, index, depth, count) {
|
|
45
|
+
if (!plainObject(node) || depth > 32 || count.value >= 2000) return false
|
|
46
|
+
if (!allowedTypes[String(node.type || "")] || !validId(node.id)) return false
|
|
47
|
+
if (index[node.id] !== undefined) return false
|
|
48
|
+
|
|
49
|
+
var props = node.props === undefined ? {} : node.props
|
|
50
|
+
if (!plainObject(props)) return false
|
|
51
|
+
var whitelist = allowedProperties[node.type]
|
|
52
|
+
for (var key in props) {
|
|
53
|
+
if (!whitelist[key]) return false
|
|
54
|
+
var value = props[key]
|
|
55
|
+
if (value !== null && typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean" && !Array.isArray(value) && !plainObject(value))
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
var children = node.children === undefined ? [] : node.children
|
|
60
|
+
if (!Array.isArray(children)) return false
|
|
61
|
+
if (!componentDefinitions[node.type].container && children.length !== 0)
|
|
62
|
+
return false
|
|
63
|
+
var subscriptions = node.events === undefined ? [] : node.events
|
|
64
|
+
if (!Array.isArray(subscriptions)) return false
|
|
65
|
+
for (var e = 0; e < subscriptions.length; e++) {
|
|
66
|
+
var eventName = String(subscriptions[e])
|
|
67
|
+
if (eventName !== "mount" && eventName !== "unmount" && componentDefinitions[node.type].events.indexOf(eventName) < 0)
|
|
68
|
+
return false
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
index[node.id] = node
|
|
72
|
+
count.value += 1
|
|
73
|
+
for (var i = 0; i < children.length; i++)
|
|
74
|
+
if (!validateNode(children[i], index, depth + 1, count)) return false
|
|
75
|
+
return true
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function validateComponents(components) {
|
|
79
|
+
if (!plainObject(components)) return false
|
|
80
|
+
var names = Object.keys(components)
|
|
81
|
+
if (names.length === 0 || names.length > 256) return false
|
|
82
|
+
var validated = ({})
|
|
83
|
+
for (var i = 0; i < names.length; i++) {
|
|
84
|
+
var name = names[i]
|
|
85
|
+
var definition = components[name]
|
|
86
|
+
if (!/^[a-z][a-z0-9_]*$/.test(name) || !plainObject(definition)) return false
|
|
87
|
+
if (!/^[A-Z][A-Za-z0-9]*\.qml$/.test(String(definition.qml || ""))) return false
|
|
88
|
+
if (!Array.isArray(definition.properties) || !Array.isArray(definition.events)
|
|
89
|
+
|| !plainObject(definition.property_map || {}) || !plainObject(definition.event_map || {})) return false
|
|
90
|
+
var propertyMap = ({})
|
|
91
|
+
for (var p = 0; p < definition.properties.length; p++) {
|
|
92
|
+
var propertyName = String(definition.properties[p])
|
|
93
|
+
if (!/^[a-z][a-z0-9_]*$/.test(propertyName)) return false
|
|
94
|
+
propertyMap[propertyName] = true
|
|
95
|
+
}
|
|
96
|
+
validated[name] = {
|
|
97
|
+
qml: definition.qml,
|
|
98
|
+
properties: propertyMap,
|
|
99
|
+
events: definition.events,
|
|
100
|
+
propertyMap: definition.property_map || {},
|
|
101
|
+
eventMap: definition.event_map || {},
|
|
102
|
+
container: definition.container === true,
|
|
103
|
+
autoBind: definition.auto_bind !== false
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
componentDefinitions = validated
|
|
107
|
+
return true
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function installRender(message) {
|
|
111
|
+
if (!plainObject(message.surfaces)) return reject("render surfaces must be an object")
|
|
112
|
+
if (message.surface_options !== undefined && !validateSurfaceOptions(message.surface_options))
|
|
113
|
+
return reject("invalid surface options")
|
|
114
|
+
if (!validateComponents(message.components)) return reject("invalid component registry")
|
|
115
|
+
var dynamicTypes = ({})
|
|
116
|
+
var dynamicProperties = ({})
|
|
117
|
+
for (var componentName in componentDefinitions) {
|
|
118
|
+
dynamicTypes[componentName] = true
|
|
119
|
+
dynamicProperties[componentName] = componentDefinitions[componentName].properties
|
|
120
|
+
}
|
|
121
|
+
allowedTypes = dynamicTypes
|
|
122
|
+
allowedProperties = dynamicProperties
|
|
123
|
+
var nextIndex = ({})
|
|
124
|
+
var count = { value: 0 }
|
|
125
|
+
var names = Object.keys(message.surfaces)
|
|
126
|
+
if (names.length === 0 || names.length > 32) return reject("invalid surface count")
|
|
127
|
+
|
|
128
|
+
for (var i = 0; i < names.length; i++) {
|
|
129
|
+
if (!validId(names[i]) || !validateNode(message.surfaces[names[i]], nextIndex, 0, count))
|
|
130
|
+
return reject("invalid control tree")
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
surfaces = message.surfaces
|
|
134
|
+
surfaceOptions = message.surface_options || ({})
|
|
135
|
+
nodeIndex = nextIndex
|
|
136
|
+
revision += 1
|
|
137
|
+
lastError = ""
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function validateSurfaceOptions(options) {
|
|
141
|
+
if (!plainObject(options)) return false
|
|
142
|
+
var allowed = {
|
|
143
|
+
title: true, width: true, height: true, min_width: true, min_height: true,
|
|
144
|
+
max_width: true, max_height: true, color: true, visible: true,
|
|
145
|
+
maximized: true, fullscreen: true
|
|
146
|
+
}
|
|
147
|
+
for (var surfaceName in options) {
|
|
148
|
+
if (!validId(surfaceName) || !plainObject(options[surfaceName])) return false
|
|
149
|
+
for (var key in options[surfaceName]) {
|
|
150
|
+
if (!allowed[key]) return false
|
|
151
|
+
var value = options[surfaceName][key]
|
|
152
|
+
if (value !== null && typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean")
|
|
153
|
+
return false
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return true
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function applyPatch(message) {
|
|
160
|
+
if (!validId(message.id))
|
|
161
|
+
return reject("invalid patch")
|
|
162
|
+
var node = nodeIndex[message.id]
|
|
163
|
+
if (!node) return reject("patch target rejected")
|
|
164
|
+
|
|
165
|
+
if (message.op === "replace_children") {
|
|
166
|
+
if (!componentDefinitions[node.type].container || !Array.isArray(message.children))
|
|
167
|
+
return reject("children patch rejected")
|
|
168
|
+
var removed = ({})
|
|
169
|
+
function markRemoved(child) {
|
|
170
|
+
removed[child.id] = true
|
|
171
|
+
var nested = Array.isArray(child.children) ? child.children : []
|
|
172
|
+
for (var r = 0; r < nested.length; r++) markRemoved(nested[r])
|
|
173
|
+
}
|
|
174
|
+
var oldChildren = Array.isArray(node.children) ? node.children : []
|
|
175
|
+
for (var oldIndex = 0; oldIndex < oldChildren.length; oldIndex++) markRemoved(oldChildren[oldIndex])
|
|
176
|
+
|
|
177
|
+
var childrenIndex = ({})
|
|
178
|
+
for (var existingId in nodeIndex)
|
|
179
|
+
if (!removed[existingId] && existingId !== node.id) childrenIndex[existingId] = nodeIndex[existingId]
|
|
180
|
+
var count = { value: Object.keys(childrenIndex).length }
|
|
181
|
+
for (var childIndex = 0; childIndex < message.children.length; childIndex++)
|
|
182
|
+
if (!validateNode(message.children[childIndex], childrenIndex, 0, count)) return reject("invalid children patch")
|
|
183
|
+
|
|
184
|
+
var containerReplacement = ({ type: node.type, id: node.id, props: node.props || {}, children: message.children })
|
|
185
|
+
if (node.events !== undefined) containerReplacement.events = node.events
|
|
186
|
+
childrenIndex[node.id] = containerReplacement
|
|
187
|
+
nodeIndex = childrenIndex
|
|
188
|
+
revision += 1
|
|
189
|
+
return true
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (message.op === "animate") {
|
|
193
|
+
if (!Array.isArray(message.tracks) || message.tracks.length === 0 || message.tracks.length > 64)
|
|
194
|
+
return reject("animation tracks rejected")
|
|
195
|
+
var animatedProps = ({})
|
|
196
|
+
var validatedTracks = []
|
|
197
|
+
var sourceProps = node.props || {}
|
|
198
|
+
for (var trackIndex = 0; trackIndex < message.tracks.length; trackIndex++) {
|
|
199
|
+
var track = message.tracks[trackIndex]
|
|
200
|
+
if (!plainObject(track) || typeof track.property !== "string"
|
|
201
|
+
|| !allowedProperties[node.type][track.property] || !validAnimation(track))
|
|
202
|
+
return reject("animation track rejected")
|
|
203
|
+
var targetValue = track.to
|
|
204
|
+
if (targetValue !== null && typeof targetValue !== "string" && typeof targetValue !== "number")
|
|
205
|
+
return reject("animation target rejected")
|
|
206
|
+
animatedProps[track.property] = targetValue
|
|
207
|
+
validatedTracks.push(track)
|
|
208
|
+
}
|
|
209
|
+
var animatedReplacement = ({ type: node.type, id: node.id })
|
|
210
|
+
var finalProps = ({})
|
|
211
|
+
for (var sourceKey in sourceProps) finalProps[sourceKey] = sourceProps[sourceKey]
|
|
212
|
+
for (var animatedKey in animatedProps) finalProps[animatedKey] = animatedProps[animatedKey]
|
|
213
|
+
animatedReplacement.props = finalProps
|
|
214
|
+
if (node.children !== undefined) animatedReplacement.children = node.children
|
|
215
|
+
if (node.events !== undefined) animatedReplacement.events = node.events
|
|
216
|
+
animatedReplacement.transitions = validatedTracks
|
|
217
|
+
var animatedIndex = ({})
|
|
218
|
+
for (var animatedId in nodeIndex) animatedIndex[animatedId] = nodeIndex[animatedId]
|
|
219
|
+
animatedIndex[node.id] = animatedReplacement
|
|
220
|
+
nodeIndex = animatedIndex
|
|
221
|
+
revision += 1
|
|
222
|
+
return true
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (message.op !== "set" || typeof message.property !== "string") return reject("invalid patch")
|
|
226
|
+
if (!allowedProperties[node.type][message.property]) return reject("patch target rejected")
|
|
227
|
+
var value = message.value
|
|
228
|
+
if (value !== null && typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean" && !Array.isArray(value) && !plainObject(value))
|
|
229
|
+
return reject("patch value rejected")
|
|
230
|
+
|
|
231
|
+
var animation = message.animation
|
|
232
|
+
if (animation !== undefined && !validAnimation(animation)) return reject("patch animation rejected")
|
|
233
|
+
|
|
234
|
+
var replacement = ({ type: node.type, id: node.id })
|
|
235
|
+
var props = ({})
|
|
236
|
+
var oldProps = node.props || {}
|
|
237
|
+
for (var key in oldProps) props[key] = oldProps[key]
|
|
238
|
+
props[message.property] = value
|
|
239
|
+
replacement.props = props
|
|
240
|
+
if (node.children !== undefined) replacement.children = node.children
|
|
241
|
+
if (node.events !== undefined) replacement.events = node.events
|
|
242
|
+
if (animation !== undefined) {
|
|
243
|
+
replacement.transition = {
|
|
244
|
+
property: message.property,
|
|
245
|
+
from: oldProps[message.property],
|
|
246
|
+
to: value,
|
|
247
|
+
duration: animation.duration,
|
|
248
|
+
delay: animation.delay,
|
|
249
|
+
easing: animation.easing,
|
|
250
|
+
sequence: revision + 1
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
var nextIndex = ({})
|
|
255
|
+
for (var id in nodeIndex) nextIndex[id] = nodeIndex[id]
|
|
256
|
+
nextIndex[node.id] = replacement
|
|
257
|
+
nodeIndex = nextIndex
|
|
258
|
+
revision += 1
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function validAnimation(animation) {
|
|
262
|
+
if (!plainObject(animation)
|
|
263
|
+
|| typeof animation.duration !== "number" || animation.duration < 0 || animation.duration > 60000
|
|
264
|
+
|| typeof animation.delay !== "number" || animation.delay < 0 || animation.delay > 60000
|
|
265
|
+
|| typeof animation.easing !== "string") return false
|
|
266
|
+
var easings = ["linear", "in_quad", "out_quad", "in_out_quad", "in_cubic", "out_cubic", "in_out_cubic",
|
|
267
|
+
"in_back", "out_back", "in_out_back", "in_elastic", "out_elastic", "in_out_elastic",
|
|
268
|
+
"in_bounce", "out_bounce", "in_out_bounce"]
|
|
269
|
+
return easings.indexOf(animation.easing) >= 0
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function handleLine(line) {
|
|
273
|
+
var raw = String(line || "").trim()
|
|
274
|
+
if (raw === "" || raw.length > 1048576) return reject("invalid message size")
|
|
275
|
+
|
|
276
|
+
var message
|
|
277
|
+
try { message = JSON.parse(raw) }
|
|
278
|
+
catch (error) { return reject("invalid JSON from Ruby") }
|
|
279
|
+
if (!plainObject(message) || message.v !== 1 || typeof message.type !== "string")
|
|
280
|
+
return reject("invalid protocol envelope")
|
|
281
|
+
|
|
282
|
+
if (message.type === "ready") {
|
|
283
|
+
ready = true
|
|
284
|
+
restartDelayMs = 500
|
|
285
|
+
lastError = ""
|
|
286
|
+
} else if (message.type === "render") {
|
|
287
|
+
installRender(message)
|
|
288
|
+
} else if (message.type === "patch") {
|
|
289
|
+
applyPatch(message)
|
|
290
|
+
} else if (message.type === "effect") {
|
|
291
|
+
handleEffect(message)
|
|
292
|
+
} else if (message.type === "ack") {
|
|
293
|
+
// Acknowledgements are consumed by benchmarks and future diagnostics.
|
|
294
|
+
} else if (message.type === "handler_error" || message.type === "runtime_error" || message.type === "protocol_error") {
|
|
295
|
+
lastError = String(message.message || message.code || "Ruby runtime error")
|
|
296
|
+
} else {
|
|
297
|
+
reject("unknown message type")
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function handleEffect(message) {
|
|
302
|
+
if (typeof message.name !== "string" || !plainObject(message.payload || {}))
|
|
303
|
+
return reject("invalid effect")
|
|
304
|
+
var payload = message.payload || {}
|
|
305
|
+
effectReceived(message.name, payload)
|
|
306
|
+
if (!shell || !manifest) return
|
|
307
|
+
if (message.name === "open_panel")
|
|
308
|
+
shell.summon(manifest.id, JSON.stringify(payload))
|
|
309
|
+
else if (message.name === "close_panel")
|
|
310
|
+
shell.hide(manifest.id)
|
|
311
|
+
else
|
|
312
|
+
reject("effect not allowed")
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function reject(reason) {
|
|
316
|
+
lastError = String(reason || "protocol error")
|
|
317
|
+
console.warn("omarchy-ui bridge:", lastError)
|
|
318
|
+
return false
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function rootId(surfaceName) {
|
|
322
|
+
var surface = surfaces[String(surfaceName || "")]
|
|
323
|
+
return surface && validId(surface.id) ? surface.id : ""
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function optionsFor(surfaceName) {
|
|
327
|
+
var options = surfaceOptions[String(surfaceName || "")]
|
|
328
|
+
return plainObject(options) ? options : ({})
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function nodeFor(controlId) {
|
|
332
|
+
return nodeIndex[String(controlId || "")] || null
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function componentSource(typeName) {
|
|
336
|
+
var definition = componentDefinitions[String(typeName || "")]
|
|
337
|
+
return definition ? pluginDir + "/Components/" + definition.qml : ""
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function componentDefinition(typeName) {
|
|
341
|
+
return componentDefinitions[String(typeName || "")] || null
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function sendEvent(surfaceName, controlId, eventName, payload) {
|
|
345
|
+
if (!rubyProcess.running || !validId(controlId) || !/^[a-z][a-z0-9_]{0,63}$/.test(eventName)) return false
|
|
346
|
+
var target = nodeIndex[String(controlId)]
|
|
347
|
+
var definition = target ? componentDefinitions[String(target.type)] : null
|
|
348
|
+
var subscriptions = target && Array.isArray(target.events) ? target.events : []
|
|
349
|
+
if (!definition || subscriptions.indexOf(eventName) < 0) return false
|
|
350
|
+
eventSequence += 1
|
|
351
|
+
rubyProcess.write(JSON.stringify({
|
|
352
|
+
v: 1,
|
|
353
|
+
type: "event",
|
|
354
|
+
surface: String(surfaceName || ""),
|
|
355
|
+
id: String(controlId),
|
|
356
|
+
event: eventName,
|
|
357
|
+
seq: eventSequence,
|
|
358
|
+
payload: plainObject(payload) ? payload : {}
|
|
359
|
+
}) + "\n")
|
|
360
|
+
return true
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function startRuby() {
|
|
364
|
+
if (stopping || pluginDir === "" || rubyProcess.running) return
|
|
365
|
+
var configuredRuntime = String(Quickshell.env("OMARCHY_UI_RUNTIME") || "")
|
|
366
|
+
rubyProcess.command = [
|
|
367
|
+
configuredRuntime !== "" ? configuredRuntime : pluginDir + "/omarchy-ui-runtime",
|
|
368
|
+
effectiveRubyProgram
|
|
369
|
+
]
|
|
370
|
+
rubyProcess.workingDirectory = pluginDir
|
|
371
|
+
rubyProcess.running = true
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
onPluginDirChanged: Qt.callLater(startRuby)
|
|
375
|
+
|
|
376
|
+
Component.onDestruction: {
|
|
377
|
+
stopping = true
|
|
378
|
+
restartTimer.stop()
|
|
379
|
+
if (rubyProcess.running) rubyProcess.running = false
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
Process {
|
|
383
|
+
id: rubyProcess
|
|
384
|
+
stdinEnabled: true
|
|
385
|
+
|
|
386
|
+
stdout: SplitParser {
|
|
387
|
+
onRead: function(line) { root.handleLine(line) }
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
stderr: SplitParser {
|
|
391
|
+
onRead: function(line) {
|
|
392
|
+
var message = String(line || "").trim()
|
|
393
|
+
if (message !== "") console.warn("omarchy-ui ruby:", message)
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
onExited: function(exitCode) {
|
|
398
|
+
root.ready = false
|
|
399
|
+
if (root.stopping || root.pluginDir === "") return
|
|
400
|
+
root.lastError = exitCode === 0
|
|
401
|
+
? "Ruby UI runtime stopped"
|
|
402
|
+
: "Ruby UI runtime crashed (exit " + exitCode + ")"
|
|
403
|
+
restartTimer.interval = root.restartDelayMs
|
|
404
|
+
restartTimer.start()
|
|
405
|
+
root.restartDelayMs = Math.min(30000, root.restartDelayMs * 2)
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
Timer {
|
|
410
|
+
interval: 100
|
|
411
|
+
repeat: true
|
|
412
|
+
running: rubyProcess.running
|
|
413
|
+
onTriggered: rubyProcess.write('{"v":1,"type":"tick"}\n')
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
Timer {
|
|
417
|
+
id: restartTimer
|
|
418
|
+
interval: 500
|
|
419
|
+
repeat: false
|
|
420
|
+
onTriggered: root.startRuby()
|
|
421
|
+
}
|
|
422
|
+
}
|
data/bin/omarchy_ui
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OmarchyUI
|
|
4
|
+
class Animation
|
|
5
|
+
EASINGS = %w[linear in_quad out_quad in_out_quad in_cubic out_cubic in_out_cubic
|
|
6
|
+
in_back out_back in_out_back in_elastic out_elastic in_out_elastic
|
|
7
|
+
in_bounce out_bounce in_out_bounce].freeze
|
|
8
|
+
|
|
9
|
+
attr_reader :duration, :easing, :delay
|
|
10
|
+
|
|
11
|
+
def initialize(duration: 200, easing: :in_out_quad, delay: 0)
|
|
12
|
+
@duration = Integer(duration)
|
|
13
|
+
@delay = Integer(delay)
|
|
14
|
+
@easing = easing.to_s
|
|
15
|
+
raise ArgumentError, "animation duration must be between 0 and 60 seconds" unless (0..60_000).cover?(@duration)
|
|
16
|
+
raise ArgumentError, "animation delay must be between 0 and 60 seconds" unless (0..60_000).cover?(@delay)
|
|
17
|
+
raise ArgumentError, "unsupported easing: #{@easing}" unless EASINGS.include?(@easing)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def to_h
|
|
21
|
+
{ "duration" => duration, "easing" => easing, "delay" => delay }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|