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
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OmarchyUI
|
|
4
|
+
class Task
|
|
5
|
+
attr_reader :kind, :interval, :block
|
|
6
|
+
|
|
7
|
+
def initialize(kind:, interval:, immediate:, block:)
|
|
8
|
+
@kind = kind
|
|
9
|
+
@interval = interval
|
|
10
|
+
@immediate = immediate
|
|
11
|
+
@block = block
|
|
12
|
+
@mutex = Mutex.new
|
|
13
|
+
@condition = ConditionVariable.new
|
|
14
|
+
@cancelled = false
|
|
15
|
+
@thread = nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def start(evaluator, on_error)
|
|
19
|
+
@mutex.synchronize do
|
|
20
|
+
return self if @cancelled || @thread
|
|
21
|
+
if Object.const_defined?(:Thread)
|
|
22
|
+
@thread = Thread.new { run(evaluator, on_error) }
|
|
23
|
+
@thread.name = "omarchy-ui-#{kind}" if @thread.respond_to?(:name=)
|
|
24
|
+
else
|
|
25
|
+
@thread = :cooperative
|
|
26
|
+
@evaluator = evaluator
|
|
27
|
+
@on_error = on_error
|
|
28
|
+
@next_due = Time.now.to_f + ((kind == :async || @immediate) ? 0 : interval)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
self
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def cancel
|
|
35
|
+
@mutex.synchronize do
|
|
36
|
+
@cancelled = true
|
|
37
|
+
@condition.broadcast
|
|
38
|
+
end
|
|
39
|
+
self
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def join(timeout = nil)
|
|
43
|
+
thread = @mutex.synchronize { @thread }
|
|
44
|
+
thread.join(timeout) if thread && thread != :cooperative
|
|
45
|
+
self
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def tick(now = Time.now.to_f)
|
|
49
|
+
return unless @thread == :cooperative || !Object.const_defined?(:Thread)
|
|
50
|
+
return if cancelled? || now < @next_due
|
|
51
|
+
invoke(@evaluator, @on_error)
|
|
52
|
+
if kind == :every
|
|
53
|
+
@next_due = now + interval
|
|
54
|
+
else
|
|
55
|
+
cancel
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def cancelled?
|
|
60
|
+
@mutex.synchronize { @cancelled }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def run(evaluator, on_error)
|
|
66
|
+
case kind
|
|
67
|
+
when :async then invoke(evaluator, on_error)
|
|
68
|
+
when :after then invoke(evaluator, on_error) if wait(interval)
|
|
69
|
+
when :every
|
|
70
|
+
invoke(evaluator, on_error) if @immediate && !cancelled?
|
|
71
|
+
while wait(interval)
|
|
72
|
+
invoke(evaluator, on_error)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def wait(seconds)
|
|
78
|
+
@mutex.synchronize do
|
|
79
|
+
@condition.wait(@mutex, seconds) unless @cancelled
|
|
80
|
+
!@cancelled
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def invoke(evaluator, on_error)
|
|
85
|
+
evaluator.call(block) unless cancelled?
|
|
86
|
+
rescue StandardError => error
|
|
87
|
+
on_error.call(error)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
class Scheduler
|
|
92
|
+
def initialize(evaluator:, on_error:)
|
|
93
|
+
@evaluator = evaluator
|
|
94
|
+
@on_error = on_error
|
|
95
|
+
@tasks = []
|
|
96
|
+
@lock = Mutex.new
|
|
97
|
+
@started = false
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def schedule(kind, interval: 0, immediate: false, &block)
|
|
101
|
+
raise ArgumentError, "task requires a block" unless block
|
|
102
|
+
seconds = Float(interval)
|
|
103
|
+
raise ArgumentError, "task interval must be positive" if kind != :async && !seconds.positive?
|
|
104
|
+
task = Task.new(kind:, interval: seconds, immediate:, block:)
|
|
105
|
+
@lock.synchronize do
|
|
106
|
+
@tasks << task
|
|
107
|
+
task.start(@evaluator, @on_error) if @started
|
|
108
|
+
end
|
|
109
|
+
task
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def start
|
|
113
|
+
@lock.synchronize do
|
|
114
|
+
@started = true
|
|
115
|
+
@tasks.each { |task| task.start(@evaluator, @on_error) }
|
|
116
|
+
end
|
|
117
|
+
self
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def stop
|
|
121
|
+
tasks = @lock.synchronize do
|
|
122
|
+
@started = false
|
|
123
|
+
@tasks.dup
|
|
124
|
+
end
|
|
125
|
+
tasks.each(&:cancel)
|
|
126
|
+
tasks.each { |task| task.join(1) }
|
|
127
|
+
self
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def tick(now = Time.now.to_f)
|
|
131
|
+
tasks = @lock.synchronize { @tasks.dup }
|
|
132
|
+
tasks.each { |task| task.tick(now) }
|
|
133
|
+
self
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OmarchyUI
|
|
4
|
+
class StateStore
|
|
5
|
+
def initialize(on_change)
|
|
6
|
+
@values = {}
|
|
7
|
+
@on_change = on_change
|
|
8
|
+
@lock = Mutex.new
|
|
9
|
+
@transaction_depth = 0
|
|
10
|
+
@pending = {}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def define(name, initial)
|
|
14
|
+
key = name.to_sym
|
|
15
|
+
@lock.synchronize do
|
|
16
|
+
raise ArgumentError, "state already defined: #{key}" if @values.key?(key)
|
|
17
|
+
@values[key] = initial
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def [](name)
|
|
22
|
+
@lock.synchronize { @values.fetch(name.to_sym) }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def []=(name, value)
|
|
26
|
+
write(name.to_sym, value)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def update(name)
|
|
30
|
+
raise ArgumentError, "update requires a block" unless block_given?
|
|
31
|
+
key = name.to_sym
|
|
32
|
+
change, value = @lock.synchronize do
|
|
33
|
+
raise NoMethodError, "unknown state: #{key}" unless @values.key?(key)
|
|
34
|
+
next_value = yield(@values[key])
|
|
35
|
+
[store_locked(key, next_value), next_value]
|
|
36
|
+
end
|
|
37
|
+
@on_change.call(*change) if change
|
|
38
|
+
value
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def method_missing(name, *arguments)
|
|
42
|
+
raw = name.to_s
|
|
43
|
+
if raw.end_with?("=")
|
|
44
|
+
raise ArgumentError, "expected one value" unless arguments.length == 1
|
|
45
|
+
|
|
46
|
+
return write(raw.delete_suffix("=").to_sym, arguments.first)
|
|
47
|
+
end
|
|
48
|
+
if arguments.empty?
|
|
49
|
+
found, value = @lock.synchronize { [@values.key?(name), @values[name]] }
|
|
50
|
+
return value if found
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
super
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def respond_to_missing?(name, include_private = false)
|
|
57
|
+
key = name.to_s.delete_suffix("=").to_sym
|
|
58
|
+
@lock.synchronize { @values.key?(key) } || super
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def transaction
|
|
62
|
+
raise ArgumentError, "transaction requires a block" unless block_given?
|
|
63
|
+
@lock.synchronize { @transaction_depth += 1 }
|
|
64
|
+
yield self
|
|
65
|
+
ensure
|
|
66
|
+
changes = @lock.synchronize do
|
|
67
|
+
@transaction_depth -= 1
|
|
68
|
+
next [] unless @transaction_depth.zero?
|
|
69
|
+
flushed = @pending.values
|
|
70
|
+
@pending.clear
|
|
71
|
+
flushed
|
|
72
|
+
end
|
|
73
|
+
changes.each { |change| @on_change.call(*change) }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def write(key, value)
|
|
79
|
+
change = @lock.synchronize do
|
|
80
|
+
raise NoMethodError, "unknown state: #{key}" unless @values.key?(key)
|
|
81
|
+
store_locked(key, value)
|
|
82
|
+
end
|
|
83
|
+
@on_change.call(*change) if change
|
|
84
|
+
value
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def store_locked(key, value)
|
|
88
|
+
return nil if @values[key] == value
|
|
89
|
+
previous = @values[key]
|
|
90
|
+
@values[key] = value
|
|
91
|
+
if @transaction_depth.positive?
|
|
92
|
+
@pending[key] ||= [key, previous, value]
|
|
93
|
+
@pending[key][2] = value
|
|
94
|
+
nil
|
|
95
|
+
else
|
|
96
|
+
[key, previous, value]
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OmarchyUI
|
|
4
|
+
module Value
|
|
5
|
+
MAX_DEPTH = 32
|
|
6
|
+
MAX_ITEMS = 10_000
|
|
7
|
+
def normalize(value, property: nil, depth: 0, seen: {}, count: [0])
|
|
8
|
+
raise ArgumentError, "property value exceeds #{MAX_DEPTH} levels: #{property}" if depth > MAX_DEPTH
|
|
9
|
+
count[0] += 1
|
|
10
|
+
raise ArgumentError, "property value exceeds #{MAX_ITEMS} items: #{property}" if count[0] > MAX_ITEMS
|
|
11
|
+
|
|
12
|
+
case value
|
|
13
|
+
when Symbol then value.to_s
|
|
14
|
+
when String, TrueClass, FalseClass, NilClass, Integer then value
|
|
15
|
+
when Float
|
|
16
|
+
raise ArgumentError, "property number must be finite: #{property}" unless value.finite?
|
|
17
|
+
value
|
|
18
|
+
when Array
|
|
19
|
+
guard_cycle(value, property, seen) do
|
|
20
|
+
value.map { |item| normalize(item, property:, depth: depth + 1, seen:, count:) }
|
|
21
|
+
end
|
|
22
|
+
when Hash
|
|
23
|
+
guard_cycle(value, property, seen) do
|
|
24
|
+
value.each_with_object({}) do |(key, item), result|
|
|
25
|
+
result[key.to_s] = normalize(item, property:, depth: depth + 1, seen:, count:)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
else
|
|
29
|
+
raise ArgumentError, "unsupported property value for #{property}: #{value.class}"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def guard_cycle(value, property, seen)
|
|
34
|
+
identity = value.object_id
|
|
35
|
+
raise ArgumentError, "cyclic property value: #{property}" if seen[identity]
|
|
36
|
+
seen[identity] = true
|
|
37
|
+
yield
|
|
38
|
+
ensure
|
|
39
|
+
seen.delete(identity)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
module_function :normalize, :guard_cycle
|
|
43
|
+
end
|
|
44
|
+
end
|
data/lib/omarchy_ui.rb
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "omarchy_ui/protocol"
|
|
4
|
+
require_relative "omarchy_ui/value"
|
|
5
|
+
require_relative "omarchy_ui/state_store"
|
|
6
|
+
require_relative "omarchy_ui/node"
|
|
7
|
+
require_relative "omarchy_ui/animation"
|
|
8
|
+
require_relative "omarchy_ui/scheduler"
|
|
9
|
+
require_relative "omarchy_ui/command"
|
|
10
|
+
require_relative "omarchy_ui/component_registry"
|
|
11
|
+
require_relative "omarchy_ui/components"
|
|
12
|
+
require_relative "omarchy_ui/builder"
|
|
13
|
+
require_relative "omarchy_ui/application"
|
|
14
|
+
require_relative "omarchy_ui/project"
|
|
15
|
+
require_relative "omarchy_ui/runtime"
|
|
16
|
+
|
|
17
|
+
module OmarchyUI
|
|
18
|
+
VERSION = "0.0.1"
|
|
19
|
+
FRAMEWORK_ROOT = File.expand_path("..", __dir__)
|
|
20
|
+
|
|
21
|
+
def self.plugin(&definition)
|
|
22
|
+
Application.new(&definition).run
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class << self
|
|
26
|
+
alias application plugin
|
|
27
|
+
alias app plugin
|
|
28
|
+
end
|
|
29
|
+
end
|
data/manifest.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"id": "izeesoft.omarchy-ui-poc",
|
|
4
|
+
"name": "Omarchy UI Ruby Framework",
|
|
5
|
+
"version": "0.3.0",
|
|
6
|
+
"author": "Adam Moussa Ali",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"description": "Persistent Ruby state and event handlers rendered by a generic Omarchy QML bridge.",
|
|
9
|
+
"kinds": [
|
|
10
|
+
"service",
|
|
11
|
+
"bar-widget",
|
|
12
|
+
"panel"
|
|
13
|
+
],
|
|
14
|
+
"keepLoaded": true,
|
|
15
|
+
"entryPoints": {
|
|
16
|
+
"service": "Service.qml",
|
|
17
|
+
"barWidget": "BarWidget.qml",
|
|
18
|
+
"panel": "Panel.qml"
|
|
19
|
+
},
|
|
20
|
+
"barWidget": {
|
|
21
|
+
"displayName": "Ruby UI Framework",
|
|
22
|
+
"description": "Open the Ruby-driven counter panel.",
|
|
23
|
+
"category": "Development",
|
|
24
|
+
"allowMultiple": false,
|
|
25
|
+
"defaultSection": "right"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
Binary file
|
metadata
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: omarchy-ui
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: x86_64-linux
|
|
6
|
+
authors:
|
|
7
|
+
- Adam Moussa Ali
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: A persistent Ruby runtime, reactive UI model, and safe QML renderer for
|
|
13
|
+
Omarchy.
|
|
14
|
+
executables:
|
|
15
|
+
- omarchy_ui
|
|
16
|
+
extensions: []
|
|
17
|
+
extra_rdoc_files: []
|
|
18
|
+
files:
|
|
19
|
+
- App.qml
|
|
20
|
+
- BarWidget.qml
|
|
21
|
+
- Components/README.md
|
|
22
|
+
- Components/Sparkline.qml
|
|
23
|
+
- ControlNode.qml
|
|
24
|
+
- LICENSE
|
|
25
|
+
- Panel.qml
|
|
26
|
+
- README.md
|
|
27
|
+
- Service.qml
|
|
28
|
+
- bin/omarchy_ui
|
|
29
|
+
- lib/omarchy_ui.rb
|
|
30
|
+
- lib/omarchy_ui/animation.rb
|
|
31
|
+
- lib/omarchy_ui/application.rb
|
|
32
|
+
- lib/omarchy_ui/builder.rb
|
|
33
|
+
- lib/omarchy_ui/cli.rb
|
|
34
|
+
- lib/omarchy_ui/command.rb
|
|
35
|
+
- lib/omarchy_ui/component_registry.rb
|
|
36
|
+
- lib/omarchy_ui/components.rb
|
|
37
|
+
- lib/omarchy_ui/node.rb
|
|
38
|
+
- lib/omarchy_ui/project.rb
|
|
39
|
+
- lib/omarchy_ui/protocol.rb
|
|
40
|
+
- lib/omarchy_ui/runtime.rb
|
|
41
|
+
- lib/omarchy_ui/scheduler.rb
|
|
42
|
+
- lib/omarchy_ui/state_store.rb
|
|
43
|
+
- lib/omarchy_ui/value.rb
|
|
44
|
+
- manifest.json
|
|
45
|
+
- vendor/runtime/x86_64-linux/omarchy-ui-runtime
|
|
46
|
+
homepage: https://github.com/AdamMusa/omarchy-ui
|
|
47
|
+
licenses:
|
|
48
|
+
- MIT
|
|
49
|
+
metadata:
|
|
50
|
+
source_code_uri: https://github.com/AdamMusa/omarchy-ui
|
|
51
|
+
documentation_uri: https://github.com/AdamMusa/omarchy-ui#readme
|
|
52
|
+
release_status: experimental
|
|
53
|
+
rubygems_mfa_required: 'true'
|
|
54
|
+
rdoc_options: []
|
|
55
|
+
require_paths:
|
|
56
|
+
- lib
|
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '3.1'
|
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - ">="
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '0'
|
|
67
|
+
requirements: []
|
|
68
|
+
rubygems_version: 4.0.18
|
|
69
|
+
specification_version: 4
|
|
70
|
+
summary: Build native Omarchy plugins and applications in Ruby
|
|
71
|
+
test_files: []
|