zui 0.0.1

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 (325) hide show
  1. checksums.yaml +7 -0
  2. data/Components/Builtins/Accordion.qml +208 -0
  3. data/Components/Builtins/Action.qml +43 -0
  4. data/Components/Builtins/ActionButton.qml +19 -0
  5. data/Components/Builtins/ActionGroup.qml +81 -0
  6. data/Components/Builtins/AlertDialog.qml +398 -0
  7. data/Components/Builtins/AnchorAnimation.qml +3 -0
  8. data/Components/Builtins/AnchorChanges.qml +11 -0
  9. data/Components/Builtins/AnimatedImage.qml +39 -0
  10. data/Components/Builtins/Animation.qml +3 -0
  11. data/Components/Builtins/AnimationController.qml +11 -0
  12. data/Components/Builtins/ApplicationWindow.qml +103 -0
  13. data/Components/Builtins/AreaChart.qml +70 -0
  14. data/Components/Builtins/AspectRatio.qml +30 -0
  15. data/Components/Builtins/Audio.qml +85 -0
  16. data/Components/Builtins/AudioInput.qml +10 -0
  17. data/Components/Builtins/AudioOutput.qml +10 -0
  18. data/Components/Builtins/Avatar.qml +46 -0
  19. data/Components/Builtins/Badge.qml +38 -0
  20. data/Components/Builtins/Banner.qml +133 -0
  21. data/Components/Builtins/BarChart.qml +67 -0
  22. data/Components/Builtins/BarIconButton.qml +30 -0
  23. data/Components/Builtins/BarIndicator.qml +26 -0
  24. data/Components/Builtins/Behavior.qml +13 -0
  25. data/Components/Builtins/Blur.qml +14 -0
  26. data/Components/Builtins/BorderImage.qml +38 -0
  27. data/Components/Builtins/BorderOverlay.qml +25 -0
  28. data/Components/Builtins/BottomSheet.qml +211 -0
  29. data/Components/Builtins/Breadcrumb.qml +169 -0
  30. data/Components/Builtins/BubbleChart.qml +3 -0
  31. data/Components/Builtins/BusyIndicator.qml +24 -0
  32. data/Components/Builtins/Button.qml +55 -0
  33. data/Components/Builtins/ButtonGroup.qml +18 -0
  34. data/Components/Builtins/Calendar.qml +55 -0
  35. data/Components/Builtins/Camera.qml +18 -0
  36. data/Components/Builtins/CandlestickChart.qml +3 -0
  37. data/Components/Builtins/Canvas.qml +159 -0
  38. data/Components/Builtins/CaptureSession.qml +19 -0
  39. data/Components/Builtins/Card.qml +23 -0
  40. data/Components/Builtins/Carousel.qml +72 -0
  41. data/Components/Builtins/Center.qml +20 -0
  42. data/Components/Builtins/CheckDelegate.qml +132 -0
  43. data/Components/Builtins/Checkbox.qml +62 -0
  44. data/Components/Builtins/Chip.qml +65 -0
  45. data/Components/Builtins/Circle.qml +35 -0
  46. data/Components/Builtins/Clipboard.qml +29 -0
  47. data/Components/Builtins/ColorAnimation.qml +3 -0
  48. data/Components/Builtins/ColorPicker.qml +70 -0
  49. data/Components/Builtins/Colorize.qml +14 -0
  50. data/Components/Builtins/Column.qml +17 -0
  51. data/Components/Builtins/ColumnLayout.qml +13 -0
  52. data/Components/Builtins/ConfirmDialog.qml +20 -0
  53. data/Components/Builtins/ConstrainedBox.qml +28 -0
  54. data/Components/Builtins/Container.qml +30 -0
  55. data/Components/Builtins/ContextMenu.qml +140 -0
  56. data/Components/Builtins/CursorSurface.qml +18 -0
  57. data/Components/Builtins/DataTable.qml +69 -0
  58. data/Components/Builtins/DatePicker.qml +180 -0
  59. data/Components/Builtins/DayOfWeekRow.qml +11 -0
  60. data/Components/Builtins/DelayButton.qml +47 -0
  61. data/Components/Builtins/DelegateModel.qml +10 -0
  62. data/Components/Builtins/DelegateModelGroup.qml +10 -0
  63. data/Components/Builtins/Dial.qml +42 -0
  64. data/Components/Builtins/Dialog.qml +128 -0
  65. data/Components/Builtins/DialogButtonBox.qml +137 -0
  66. data/Components/Builtins/Divider.qml +26 -0
  67. data/Components/Builtins/DonutChart.qml +3 -0
  68. data/Components/Builtins/DoubleSpinBox.qml +58 -0
  69. data/Components/Builtins/DragArea.qml +43 -0
  70. data/Components/Builtins/Drawer.qml +107 -0
  71. data/Components/Builtins/DropArea.qml +19 -0
  72. data/Components/Builtins/DropShadow.qml +16 -0
  73. data/Components/Builtins/Dropdown.qml +19 -0
  74. data/Components/Builtins/ExpansionPanel.qml +157 -0
  75. data/Components/Builtins/FilePicker.qml +140 -0
  76. data/Components/Builtins/FittedBox.qml +40 -0
  77. data/Components/Builtins/Flickable.qml +40 -0
  78. data/Components/Builtins/Flipable.qml +45 -0
  79. data/Components/Builtins/Flow.qml +16 -0
  80. data/Components/Builtins/FocusScope.qml +29 -0
  81. data/Components/Builtins/FolderListModel.qml +9 -0
  82. data/Components/Builtins/FolderPicker.qml +93 -0
  83. data/Components/Builtins/FontLoader.qml +21 -0
  84. data/Components/Builtins/FontPicker.qml +91 -0
  85. data/Components/Builtins/Frame.qml +71 -0
  86. data/Components/Builtins/FrameAnimation.qml +13 -0
  87. data/Components/Builtins/Gauge.qml +3 -0
  88. data/Components/Builtins/Glow.qml +15 -0
  89. data/Components/Builtins/Gradient.qml +93 -0
  90. data/Components/Builtins/Grid.qml +16 -0
  91. data/Components/Builtins/GridLayout.qml +16 -0
  92. data/Components/Builtins/GridView.qml +226 -0
  93. data/Components/Builtins/GroupBox.qml +95 -0
  94. data/Components/Builtins/Heatmap.qml +3 -0
  95. data/Components/Builtins/Histogram.qml +3 -0
  96. data/Components/Builtins/HorizontalHeader.qml +23 -0
  97. data/Components/Builtins/HorizontalHeaderDelegate.qml +39 -0
  98. data/Components/Builtins/HoverArea.qml +19 -0
  99. data/Components/Builtins/Icon.qml +16 -0
  100. data/Components/Builtins/Image.qml +110 -0
  101. data/Components/Builtins/ImageCapture.qml +14 -0
  102. data/Components/Builtins/ItemDelegate.qml +152 -0
  103. data/Components/Builtins/KeyCatcher.qml +25 -0
  104. data/Components/Builtins/Label.qml +43 -0
  105. data/Components/Builtins/LayoutItemProxy.qml +51 -0
  106. data/Components/Builtins/Legend.qml +86 -0
  107. data/Components/Builtins/Line.qml +28 -0
  108. data/Components/Builtins/LineChart.qml +87 -0
  109. data/Components/Builtins/ListModel.qml +8 -0
  110. data/Components/Builtins/ListView.qml +65 -0
  111. data/Components/Builtins/Loader.qml +33 -0
  112. data/Components/Builtins/Markdown.qml +23 -0
  113. data/Components/Builtins/MediaDevices.qml +10 -0
  114. data/Components/Builtins/MediaPlayer.qml +27 -0
  115. data/Components/Builtins/MediaRecorder.qml +16 -0
  116. data/Components/Builtins/Menu.qml +119 -0
  117. data/Components/Builtins/MenuBar.qml +128 -0
  118. data/Components/Builtins/MenuItem.qml +77 -0
  119. data/Components/Builtins/MenuSeparator.qml +30 -0
  120. data/Components/Builtins/MessageDialog.qml +104 -0
  121. data/Components/Builtins/ModalSheet.qml +222 -0
  122. data/Components/Builtins/ModelView3d.qml +50 -0
  123. data/Components/Builtins/MonthGrid.qml +23 -0
  124. data/Components/Builtins/MultiEffect.qml +48 -0
  125. data/Components/Builtins/MultiSelect.qml +23 -0
  126. data/Components/Builtins/NavigationRail.qml +154 -0
  127. data/Components/Builtins/NumberAnimation.qml +3 -0
  128. data/Components/Builtins/NumberField.qml +18 -0
  129. data/Components/Builtins/OpacityAnimator.qml +3 -0
  130. data/Components/Builtins/OpacityMask.qml +21 -0
  131. data/Components/Builtins/OpticalGlyph.qml +16 -0
  132. data/Components/Builtins/Page.qml +111 -0
  133. data/Components/Builtins/PageIndicator.qml +63 -0
  134. data/Components/Builtins/Pagination.qml +158 -0
  135. data/Components/Builtins/Pane.qml +72 -0
  136. data/Components/Builtins/PanelHero.qml +15 -0
  137. data/Components/Builtins/ParallelAnimation.qml +3 -0
  138. data/Components/Builtins/ParentAnimation.qml +3 -0
  139. data/Components/Builtins/ParentChange.qml +10 -0
  140. data/Components/Builtins/ParticleSystem.qml +81 -0
  141. data/Components/Builtins/PasswordField.qml +58 -0
  142. data/Components/Builtins/Path.qml +35 -0
  143. data/Components/Builtins/PathAnimation.qml +3 -0
  144. data/Components/Builtins/PauseAnimation.qml +3 -0
  145. data/Components/Builtins/PieChart.qml +3 -0
  146. data/Components/Builtins/PinchArea.qml +23 -0
  147. data/Components/Builtins/Popup.qml +130 -0
  148. data/Components/Builtins/Progress.qml +15 -0
  149. data/Components/Builtins/ProgressRing.qml +120 -0
  150. data/Components/Builtins/PropertyAction.qml +11 -0
  151. data/Components/Builtins/PropertyAnimation.qml +3 -0
  152. data/Components/Builtins/PropertyChanges.qml +10 -0
  153. data/Components/Builtins/RadarChart.qml +3 -0
  154. data/Components/Builtins/RadialGauge.qml +3 -0
  155. data/Components/Builtins/RadioButton.qml +46 -0
  156. data/Components/Builtins/RadioDelegate.qml +115 -0
  157. data/Components/Builtins/RadioGroup.qml +58 -0
  158. data/Components/Builtins/RangeSlider.qml +39 -0
  159. data/Components/Builtins/Rectangle.qml +23 -0
  160. data/Components/Builtins/RectangularShadow.qml +23 -0
  161. data/Components/Builtins/ReorderableList.qml +204 -0
  162. data/Components/Builtins/RichText.qml +23 -0
  163. data/Components/Builtins/RotationAnimation.qml +3 -0
  164. data/Components/Builtins/RotationAnimator.qml +3 -0
  165. data/Components/Builtins/RoundButton.qml +42 -0
  166. data/Components/Builtins/Row.qml +17 -0
  167. data/Components/Builtins/RowLayout.qml +13 -0
  168. data/Components/Builtins/ScaleAnimator.qml +3 -0
  169. data/Components/Builtins/ScatterChart.qml +3 -0
  170. data/Components/Builtins/ScreenCapture.qml +78 -0
  171. data/Components/Builtins/ScriptAction.qml +15 -0
  172. data/Components/Builtins/Scroll.qml +18 -0
  173. data/Components/Builtins/ScrollBar.qml +18 -0
  174. data/Components/Builtins/ScrollIndicator.qml +15 -0
  175. data/Components/Builtins/SearchField.qml +178 -0
  176. data/Components/Builtins/SearchableDropdown.qml +21 -0
  177. data/Components/Builtins/SectionHeader.qml +14 -0
  178. data/Components/Builtins/SelectableText.qml +33 -0
  179. data/Components/Builtins/SelectionRectangle.qml +17 -0
  180. data/Components/Builtins/Separator.qml +13 -0
  181. data/Components/Builtins/SequentialAnimation.qml +3 -0
  182. data/Components/Builtins/Settings.qml +10 -0
  183. data/Components/Builtins/ShaderEffect.qml +115 -0
  184. data/Components/Builtins/ShaderEffectSource.qml +61 -0
  185. data/Components/Builtins/Shaders/grayscale.frag +14 -0
  186. data/Components/Builtins/Shaders/grayscale.frag.qsb +0 -0
  187. data/Components/Builtins/Shaders/passthrough.frag +11 -0
  188. data/Components/Builtins/Shaders/passthrough.frag.qsb +0 -0
  189. data/Components/Builtins/Shaders/pixelate.frag +16 -0
  190. data/Components/Builtins/Shaders/pixelate.frag.qsb +0 -0
  191. data/Components/Builtins/Shaders/vignette.frag +18 -0
  192. data/Components/Builtins/Shaders/vignette.frag.qsb +0 -0
  193. data/Components/Builtins/Shaders/wave.frag +18 -0
  194. data/Components/Builtins/Shaders/wave.frag.qsb +0 -0
  195. data/Components/Builtins/Shape.qml +53 -0
  196. data/Components/Builtins/Skeleton.qml +78 -0
  197. data/Components/Builtins/Slider.qml +21 -0
  198. data/Components/Builtins/SmoothedAnimation.qml +3 -0
  199. data/Components/Builtins/Snackbar.qml +181 -0
  200. data/Components/Builtins/SortFilterProxyModel.qml +16 -0
  201. data/Components/Builtins/SoundEffect.qml +10 -0
  202. data/Components/Builtins/Spacer.qml +13 -0
  203. data/Components/Builtins/Sparkline.qml +3 -0
  204. data/Components/Builtins/SpinBox.qml +44 -0
  205. data/Components/Builtins/SplitView.qml +31 -0
  206. data/Components/Builtins/SpringAnimation.qml +3 -0
  207. data/Components/Builtins/Stack.qml +14 -0
  208. data/Components/Builtins/StackLayout.qml +19 -0
  209. data/Components/Builtins/StackView.qml +122 -0
  210. data/Components/Builtins/StackedBarChart.qml +3 -0
  211. data/Components/Builtins/StandardPaths.qml +9 -0
  212. data/Components/Builtins/State.qml +13 -0
  213. data/Components/Builtins/StateGroup.qml +11 -0
  214. data/Components/Builtins/Support/AnimationDriver.qml +79 -0
  215. data/Components/Builtins/Support/AnimationGroupDriver.qml +32 -0
  216. data/Components/Builtins/Support/ChartCanvas.qml +629 -0
  217. data/Components/Builtins/Support/ModelView3dScene.qml +292 -0
  218. data/Components/Builtins/Support/OptionalModuleState.js +5 -0
  219. data/Components/Builtins/SwipeDelegate.qml +231 -0
  220. data/Components/Builtins/SwipeView.qml +69 -0
  221. data/Components/Builtins/SwitchDelegate.qml +140 -0
  222. data/Components/Builtins/TabBar.qml +117 -0
  223. data/Components/Builtins/TabButton.qml +105 -0
  224. data/Components/Builtins/TableView.qml +388 -0
  225. data/Components/Builtins/TableViewDelegate.qml +17 -0
  226. data/Components/Builtins/Tabs.qml +138 -0
  227. data/Components/Builtins/Text.qml +24 -0
  228. data/Components/Builtins/TextArea.qml +59 -0
  229. data/Components/Builtins/TextField.qml +21 -0
  230. data/Components/Builtins/TextMetrics.qml +39 -0
  231. data/Components/Builtins/TimePicker.qml +213 -0
  232. data/Components/Builtins/Timer.qml +13 -0
  233. data/Components/Builtins/Toast.qml +213 -0
  234. data/Components/Builtins/Toggle.qml +19 -0
  235. data/Components/Builtins/ToggleSwitch.qml +22 -0
  236. data/Components/Builtins/ToolBar.qml +61 -0
  237. data/Components/Builtins/ToolButton.qml +41 -0
  238. data/Components/Builtins/ToolSeparator.qml +36 -0
  239. data/Components/Builtins/Tooltip.qml +20 -0
  240. data/Components/Builtins/Transition.qml +3 -0
  241. data/Components/Builtins/TreeView.qml +423 -0
  242. data/Components/Builtins/TreeViewDelegate.qml +46 -0
  243. data/Components/Builtins/Tumbler.qml +18 -0
  244. data/Components/Builtins/UniformAnimator.qml +3 -0
  245. data/Components/Builtins/VectorAnimation.qml +3 -0
  246. data/Components/Builtins/VectorImage.qml +62 -0
  247. data/Components/Builtins/VerticalHeader.qml +21 -0
  248. data/Components/Builtins/VerticalHeaderDelegate.qml +35 -0
  249. data/Components/Builtins/Video.qml +54 -0
  250. data/Components/Builtins/VideoOutput.qml +54 -0
  251. data/Components/Builtins/WebView.qml +57 -0
  252. data/Components/Builtins/WeekNumberColumn.qml +13 -0
  253. data/Components/Builtins/WidgetButton.qml +26 -0
  254. data/Components/Builtins/Window.qml +90 -0
  255. data/Components/Builtins/WindowCapture.qml +11 -0
  256. data/Components/Builtins/Wrap.qml +18 -0
  257. data/Components/Builtins/XAnimator.qml +3 -0
  258. data/Components/Builtins/YAnimator.qml +3 -0
  259. data/Components/README.md +45 -0
  260. data/ControlNode.qml +1369 -0
  261. data/Controls/BarIconButton.qml +27 -0
  262. data/Controls/BarIndicator.qml +22 -0
  263. data/Controls/BorderOverlay.qml +10 -0
  264. data/Controls/BorderSurface.qml +10 -0
  265. data/Controls/Button.qml +78 -0
  266. data/Controls/ButtonGroup.qml +19 -0
  267. data/Controls/ConfirmDialog.qml +37 -0
  268. data/Controls/CursorSurface.qml +29 -0
  269. data/Controls/Dropdown.qml +59 -0
  270. data/Controls/MultiSelect.qml +32 -0
  271. data/Controls/NumberField.qml +24 -0
  272. data/Controls/OpticalGlyph.qml +14 -0
  273. data/Controls/PanelActionButton.qml +10 -0
  274. data/Controls/PanelHero.qml +18 -0
  275. data/Controls/PanelKeyCatcher.qml +25 -0
  276. data/Controls/PanelSectionHeader.qml +11 -0
  277. data/Controls/PanelSeparator.qml +10 -0
  278. data/Controls/PanelSlider.qml +36 -0
  279. data/Controls/PanelToolTip.qml +13 -0
  280. data/Controls/SearchableDropdown.qml +11 -0
  281. data/Controls/TextField.qml +25 -0
  282. data/Controls/Toggle.qml +22 -0
  283. data/Controls/ToggleSwitch.qml +26 -0
  284. data/Controls/WidgetButton.qml +35 -0
  285. data/Controls/qmldir +25 -0
  286. data/Desktop.qml +76 -0
  287. data/LICENSE +22 -0
  288. data/README.md +112 -0
  289. data/Service.qml +430 -0
  290. data/Theme/Border.qml +22 -0
  291. data/Theme/Color.qml +22 -0
  292. data/Theme/Style.qml +47 -0
  293. data/Theme/Util.qml +6 -0
  294. data/Theme/qmldir +5 -0
  295. data/bin/zui +7 -0
  296. data/lib/zui/animation.rb +24 -0
  297. data/lib/zui/application.rb +327 -0
  298. data/lib/zui/builder.rb +725 -0
  299. data/lib/zui/cli.rb +91 -0
  300. data/lib/zui/command.rb +101 -0
  301. data/lib/zui/component_registry.rb +91 -0
  302. data/lib/zui/components.rb +262 -0
  303. data/lib/zui/distribution.rb +200 -0
  304. data/lib/zui/generator.rb +84 -0
  305. data/lib/zui/host.rb +123 -0
  306. data/lib/zui/node.rb +30 -0
  307. data/lib/zui/platform.rb +40 -0
  308. data/lib/zui/protocol.rb +29 -0
  309. data/lib/zui/runner.rb +37 -0
  310. data/lib/zui/runtime.rb +23 -0
  311. data/lib/zui/scheduler.rb +136 -0
  312. data/lib/zui/source_bundle.rb +63 -0
  313. data/lib/zui/state_store.rb +100 -0
  314. data/lib/zui/value.rb +44 -0
  315. data/lib/zui.rb +33 -0
  316. data/native/CMakeLists.txt +45 -0
  317. data/native/ZuiClipboard.cpp +16 -0
  318. data/native/ZuiClipboard.h +16 -0
  319. data/native/ZuiProcess.cpp +84 -0
  320. data/native/ZuiProcess.h +34 -0
  321. data/native/main.cpp +64 -0
  322. data/vendor/host/linux-x86_64/PROVENANCE.md +15 -0
  323. data/vendor/host/linux-x86_64/zui-host +0 -0
  324. data/vendor/host/linux-x86_64/zui-host.sha256 +1 -0
  325. metadata +365 -0
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zui
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 = "zui-#{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,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zui
4
+ class SourceBundle
5
+ REQUIRE_RELATIVE = /\A\s*require_relative\s*(?:\(\s*)?["']([^"']+)["']\s*\)?\s*(?:#.*)?\z/
6
+
7
+ def initialize(entrypoint, root: File.dirname(entrypoint), embedded_frameworks: ["zui"])
8
+ @entrypoint = File.expand_path(entrypoint)
9
+ @root = File.expand_path(root)
10
+ frameworks = Array(embedded_frameworks).map(&:to_s)
11
+ raise ArgumentError, "embedded framework names cannot be empty" if frameworks.any?(&:empty?)
12
+
13
+ names = frameworks.map { |name| Regexp.escape(name) }.join("|")
14
+ @embedded_framework_require = /\A\s*require\s*(?:\(\s*)?["'](?:#{names})["']\s*\)?\s*(?:#.*)?\z/
15
+ @loaded = {}
16
+ @loading = []
17
+ end
18
+
19
+ def call
20
+ expand(@entrypoint)
21
+ end
22
+
23
+ private
24
+
25
+ def expand(path)
26
+ path = ruby_path(File.expand_path(path))
27
+ ensure_inside_root!(path)
28
+ return "" if @loaded[path]
29
+ raise ArgumentError, "circular require_relative: #{relative(path)}" if @loading.include?(path)
30
+ raise ArgumentError, "required Ruby file not found: #{relative(path)}" unless File.file?(path)
31
+
32
+ @loading << path
33
+ source = File.readlines(path, chomp: true).map do |line|
34
+ match = REQUIRE_RELATIVE.match(line)
35
+ if match
36
+ expand(File.expand_path(match[1], File.dirname(path)))
37
+ elsif @embedded_framework_require.match?(line)
38
+ ""
39
+ else
40
+ line
41
+ end
42
+ end.join("\n")
43
+ @loaded[path] = true
44
+ "# source: #{relative(path)}\n#{source}\n"
45
+ ensure
46
+ @loading.pop if @loading.last == path
47
+ end
48
+
49
+ def ruby_path(path)
50
+ File.extname(path).empty? ? "#{path}.rb" : path
51
+ end
52
+
53
+ def ensure_inside_root!(path)
54
+ return if path == @root || path.start_with?("#{@root}#{File::SEPARATOR}")
55
+
56
+ raise ArgumentError, "require_relative escapes the application directory: #{path}"
57
+ end
58
+
59
+ def relative(path)
60
+ path.delete_prefix("#{@root}#{File::SEPARATOR}")
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zui
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
data/lib/zui/value.rb ADDED
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zui
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/zui.rb ADDED
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "zui/protocol"
4
+ require_relative "zui/value"
5
+ require_relative "zui/state_store"
6
+ require_relative "zui/node"
7
+ require_relative "zui/animation"
8
+ require_relative "zui/scheduler"
9
+ require_relative "zui/command"
10
+ require_relative "zui/component_registry"
11
+ require_relative "zui/components"
12
+ require_relative "zui/builder"
13
+ require_relative "zui/application"
14
+ require_relative "zui/source_bundle"
15
+ require_relative "zui/platform"
16
+ require_relative "zui/runtime"
17
+ require_relative "zui/host"
18
+ require_relative "zui/runner"
19
+ require_relative "zui/generator"
20
+ require_relative "zui/distribution"
21
+
22
+ module Zui
23
+ VERSION = "0.0.1"
24
+ FRAMEWORK_ROOT = File.expand_path("..", __dir__)
25
+
26
+ def self.app(&definition)
27
+ Application.new(&definition).run
28
+ end
29
+
30
+ class << self
31
+ alias application app
32
+ end
33
+ end
@@ -0,0 +1,45 @@
1
+ cmake_minimum_required(VERSION 3.21)
2
+ project(zui-host VERSION 0.1.0 LANGUAGES CXX)
3
+
4
+ set(CMAKE_CXX_STANDARD 17)
5
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
6
+ set(CMAKE_AUTOMOC ON)
7
+
8
+ find_package(Qt6 6.8 REQUIRED COMPONENTS
9
+ Core
10
+ Gui
11
+ Multimedia
12
+ Qml
13
+ Quick
14
+ QuickControls2
15
+ QuickVectorImage
16
+ )
17
+ find_package(Qt6 6.8 QUIET COMPONENTS WebEngineQuick)
18
+
19
+ qt_add_executable(zui-host
20
+ main.cpp
21
+ ZuiClipboard.cpp
22
+ ZuiClipboard.h
23
+ ZuiProcess.cpp
24
+ ZuiProcess.h
25
+ )
26
+
27
+ target_link_libraries(zui-host PRIVATE
28
+ Qt6::Core
29
+ Qt6::Gui
30
+ Qt6::Multimedia
31
+ Qt6::Qml
32
+ Qt6::Quick
33
+ Qt6::QuickControls2
34
+ Qt6::QuickVectorImage
35
+ )
36
+
37
+ if(TARGET Qt6::WebEngineQuick)
38
+ target_link_libraries(zui-host PRIVATE Qt6::WebEngineQuick)
39
+ target_compile_definitions(zui-host PRIVATE ZUI_HAS_WEBENGINE=1)
40
+ endif()
41
+
42
+ install(TARGETS zui-host
43
+ BUNDLE DESTINATION .
44
+ RUNTIME DESTINATION bin
45
+ )
@@ -0,0 +1,16 @@
1
+ #include "ZuiClipboard.h"
2
+
3
+ #include <QGuiApplication>
4
+ #include <QClipboard>
5
+
6
+ ZuiClipboard::ZuiClipboard(QObject *parent) : QObject(parent) {
7
+ connect(QGuiApplication::clipboard(), &QClipboard::dataChanged, this, &ZuiClipboard::textChanged);
8
+ }
9
+
10
+ QString ZuiClipboard::text() const { return QGuiApplication::clipboard()->text(); }
11
+
12
+ void ZuiClipboard::setText(const QString &text) {
13
+ if (this->text() == text)
14
+ return;
15
+ QGuiApplication::clipboard()->setText(text);
16
+ }
@@ -0,0 +1,16 @@
1
+ #pragma once
2
+
3
+ #include <QObject>
4
+
5
+ class ZuiClipboard final : public QObject {
6
+ Q_OBJECT
7
+ Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
8
+
9
+ public:
10
+ explicit ZuiClipboard(QObject *parent = nullptr);
11
+ QString text() const;
12
+ void setText(const QString &text);
13
+
14
+ signals:
15
+ void textChanged();
16
+ };
@@ -0,0 +1,84 @@
1
+ #include "ZuiProcess.h"
2
+
3
+ #include <QProcessEnvironment>
4
+ #include <QTimer>
5
+
6
+ ZuiProcess::ZuiProcess(QObject *parent) : QObject(parent) {
7
+ connect(&m_process, &QProcess::stateChanged, this, [this] { emit runningChanged(); });
8
+ connect(&m_process, &QProcess::readyReadStandardOutput, this, [this] {
9
+ m_stdoutBuffer += m_process.readAllStandardOutput();
10
+ consume(m_stdoutBuffer, false);
11
+ });
12
+ connect(&m_process, &QProcess::readyReadStandardError, this, [this] {
13
+ m_stderrBuffer += m_process.readAllStandardError();
14
+ consume(m_stderrBuffer, true);
15
+ });
16
+ connect(&m_process, &QProcess::errorOccurred, this, [this](QProcess::ProcessError) {
17
+ emit errorLineReceived(m_process.errorString());
18
+ });
19
+ connect(&m_process, qOverload<int, QProcess::ExitStatus>(&QProcess::finished), this,
20
+ [this](int exitCode, QProcess::ExitStatus) {
21
+ m_stdoutBuffer += m_process.readAllStandardOutput();
22
+ m_stderrBuffer += m_process.readAllStandardError();
23
+ flush(m_stdoutBuffer, false);
24
+ flush(m_stderrBuffer, true);
25
+ emit exited(exitCode);
26
+ });
27
+ }
28
+
29
+ ZuiProcess::~ZuiProcess() { stop(); }
30
+
31
+ bool ZuiProcess::running() const { return m_process.state() != QProcess::NotRunning; }
32
+
33
+ void ZuiProcess::start(const QString &executable, const QString &program,
34
+ const QString &workingDirectory, const QString &loadPath) {
35
+ if (running() || executable.isEmpty() || program.isEmpty())
36
+ return;
37
+
38
+ QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
39
+ environment.insert(QStringLiteral("ZUI_PROJECT_DIR"), workingDirectory);
40
+ m_process.setProcessEnvironment(environment);
41
+ m_process.setWorkingDirectory(workingDirectory);
42
+ m_process.setProcessChannelMode(QProcess::SeparateChannels);
43
+
44
+ QStringList arguments;
45
+ if (!loadPath.isEmpty())
46
+ arguments << QStringLiteral("-I") << loadPath;
47
+ arguments << program;
48
+ m_process.start(executable, arguments, QIODevice::ReadWrite);
49
+ }
50
+
51
+ void ZuiProcess::write(const QString &data) {
52
+ if (!running())
53
+ return;
54
+ m_process.write(data.toUtf8());
55
+ }
56
+
57
+ void ZuiProcess::stop() {
58
+ if (!running())
59
+ return;
60
+ m_process.closeWriteChannel();
61
+ m_process.terminate();
62
+ if (!m_process.waitForFinished(1000)) {
63
+ m_process.kill();
64
+ m_process.waitForFinished(1000);
65
+ }
66
+ }
67
+
68
+ void ZuiProcess::consume(QByteArray &buffer, bool errorStream) {
69
+ qsizetype newline = -1;
70
+ while ((newline = buffer.indexOf('\n')) >= 0) {
71
+ const QString line = QString::fromUtf8(buffer.left(newline));
72
+ buffer.remove(0, newline + 1);
73
+ errorStream ? emit errorLineReceived(line) : emit lineReceived(line);
74
+ }
75
+ }
76
+
77
+ void ZuiProcess::flush(QByteArray &buffer, bool errorStream) {
78
+ consume(buffer, errorStream);
79
+ if (buffer.isEmpty())
80
+ return;
81
+ const QString line = QString::fromUtf8(buffer);
82
+ buffer.clear();
83
+ errorStream ? emit errorLineReceived(line) : emit lineReceived(line);
84
+ }
@@ -0,0 +1,34 @@
1
+ #pragma once
2
+
3
+ #include <QObject>
4
+ #include <QProcess>
5
+
6
+ class ZuiProcess final : public QObject {
7
+ Q_OBJECT
8
+ Q_PROPERTY(bool running READ running NOTIFY runningChanged)
9
+
10
+ public:
11
+ explicit ZuiProcess(QObject *parent = nullptr);
12
+ ~ZuiProcess() override;
13
+
14
+ bool running() const;
15
+
16
+ Q_INVOKABLE void start(const QString &executable, const QString &program,
17
+ const QString &workingDirectory, const QString &loadPath);
18
+ Q_INVOKABLE void write(const QString &data);
19
+ Q_INVOKABLE void stop();
20
+
21
+ signals:
22
+ void runningChanged();
23
+ void lineReceived(const QString &line);
24
+ void errorLineReceived(const QString &line);
25
+ void exited(int exitCode);
26
+
27
+ private:
28
+ void consume(QByteArray &buffer, bool errorStream);
29
+ void flush(QByteArray &buffer, bool errorStream);
30
+
31
+ QProcess m_process;
32
+ QByteArray m_stdoutBuffer;
33
+ QByteArray m_stderrBuffer;
34
+ };
data/native/main.cpp ADDED
@@ -0,0 +1,64 @@
1
+ #include "ZuiClipboard.h"
2
+ #include "ZuiProcess.h"
3
+
4
+ #include <QCommandLineParser>
5
+ #include <QDir>
6
+ #include <QFileInfo>
7
+ #include <QGuiApplication>
8
+ #include <QQmlApplicationEngine>
9
+ #include <QQmlContext>
10
+ #include <QQuickStyle>
11
+
12
+ #ifdef ZUI_HAS_WEBENGINE
13
+ #include <QtWebEngineQuick/qtwebenginequickglobal.h>
14
+ #endif
15
+
16
+ int main(int argc, char *argv[]) {
17
+ #ifdef ZUI_HAS_WEBENGINE
18
+ QtWebEngineQuick::initialize();
19
+ #endif
20
+ QGuiApplication application(argc, argv);
21
+ QCoreApplication::setApplicationName(QStringLiteral("Zui"));
22
+ QCoreApplication::setOrganizationName(QStringLiteral("Zui"));
23
+ QQuickStyle::setStyle(QStringLiteral("Fusion"));
24
+
25
+ QCommandLineParser parser;
26
+ parser.setApplicationDescription(QStringLiteral("Zui cross-platform Qt host"));
27
+ parser.addHelpOption();
28
+ parser.addOption({QStringLiteral("qml-root"), QStringLiteral("Zui QML runtime directory"), QStringLiteral("path")});
29
+ parser.addOption({QStringLiteral("project"), QStringLiteral("Application project directory"), QStringLiteral("path")});
30
+ parser.addOption({QStringLiteral("program"), QStringLiteral("Ruby application entrypoint"), QStringLiteral("path")});
31
+ parser.addOption({QStringLiteral("ruby"), QStringLiteral("Ruby executable"), QStringLiteral("path"), QStringLiteral("ruby")});
32
+ parser.addOption({QStringLiteral("load-path"), QStringLiteral("Ruby framework load path"), QStringLiteral("path")});
33
+ parser.addOption({QStringLiteral("name"), QStringLiteral("Application name"), QStringLiteral("name"), QStringLiteral("Zui Application")});
34
+ parser.process(application);
35
+
36
+ const QString qmlRootValue = parser.value(QStringLiteral("qml-root"));
37
+ const QString projectValue = parser.value(QStringLiteral("project"));
38
+ const QString programValue = parser.value(QStringLiteral("program"));
39
+ const QString qmlRoot = QFileInfo(qmlRootValue).absoluteFilePath();
40
+ const QString project = QFileInfo(projectValue).absoluteFilePath();
41
+ const QString program = QFileInfo(programValue).absoluteFilePath();
42
+ const QFileInfo desktopFile(QDir(qmlRoot).filePath(QStringLiteral("Desktop.qml")));
43
+ if (qmlRootValue.isEmpty() || projectValue.isEmpty() || programValue.isEmpty()
44
+ || !desktopFile.isFile() || !QFileInfo(project).isDir() || !QFileInfo(program).isFile())
45
+ parser.showHelp(64);
46
+
47
+ ZuiProcess process;
48
+ ZuiClipboard clipboard;
49
+ QQmlApplicationEngine engine;
50
+ engine.rootContext()->setContextProperty(QStringLiteral("zuiProcess"), &process);
51
+ engine.rootContext()->setContextProperty(QStringLiteral("zuiClipboard"), &clipboard);
52
+ engine.rootContext()->setContextProperty(QStringLiteral("zuiProjectDir"), project);
53
+ engine.rootContext()->setContextProperty(QStringLiteral("zuiComponentDir"), QDir(qmlRoot).filePath(QStringLiteral("Components/Builtins")));
54
+ engine.rootContext()->setContextProperty(QStringLiteral("zuiRubyExecutable"), parser.value(QStringLiteral("ruby")));
55
+ engine.rootContext()->setContextProperty(QStringLiteral("zuiRubyProgram"), program);
56
+ engine.rootContext()->setContextProperty(QStringLiteral("zuiRubyLoadPath"), parser.value(QStringLiteral("load-path")));
57
+ engine.rootContext()->setContextProperty(QStringLiteral("zuiApplicationName"), parser.value(QStringLiteral("name")));
58
+ engine.addImportPath(qmlRoot);
59
+
60
+ QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed, &application,
61
+ [] { QCoreApplication::exit(1); }, Qt::QueuedConnection);
62
+ engine.load(QUrl::fromLocalFile(QDir(qmlRoot).filePath(QStringLiteral("Desktop.qml"))));
63
+ return application.exec();
64
+ }
@@ -0,0 +1,15 @@
1
+ # Zui Linux host provenance
2
+
3
+ - Target: Linux x86-64
4
+ - Source: `native/` in this repository
5
+ - Framework version: Zui 0.1.0
6
+ - Compiler: GNU C++ 16.2.1
7
+ - Qt: 6.11.2
8
+ - Build type: CMake `Release`
9
+
10
+ The binary is reproducible from the checked-in C++ sources with:
11
+
12
+ ```bash
13
+ cmake -S native -B build -DCMAKE_BUILD_TYPE=Release
14
+ cmake --build build --parallel
15
+ ```
Binary file
@@ -0,0 +1 @@
1
+ de446bb598f922a7a58fe30127d88cae9602cf54bd819aa2b5c84447a51da7a4 zui-host