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
data/Desktop.qml ADDED
@@ -0,0 +1,76 @@
1
+ import QtQuick
2
+ import QtQuick.Controls as QQC
3
+ import "Theme"
4
+
5
+ QQC.ApplicationWindow {
6
+ id: window
7
+
8
+ readonly property string activeSurface: applicationSurface()
9
+ readonly property var windowOptions: service.optionsFor(activeSurface)
10
+
11
+ function option(name, fallback) {
12
+ var value = windowOptions ? windowOptions[name] : undefined
13
+ return value === undefined || value === null ? fallback : value
14
+ }
15
+
16
+ function applicationSurface() {
17
+ if (service.rootId("main") !== "") return "main"
18
+ var names = Object.keys(service.surfaces)
19
+ return names.length > 0 ? names[0] : ""
20
+ }
21
+
22
+ visible: option("visible", true) !== false
23
+ title: String(option("title", zuiApplicationName))
24
+ width: Number(option("width", 760))
25
+ height: Number(option("height", 520))
26
+ minimumWidth: Number(option("min_width", 320))
27
+ minimumHeight: Number(option("min_height", 220))
28
+ maximumWidth: Number(option("max_width", 16777215))
29
+ maximumHeight: Number(option("max_height", 16777215))
30
+ visibility: option("fullscreen", false) === true
31
+ ? Window.FullScreen
32
+ : (option("maximized", false) === true ? Window.Maximized : Window.Windowed)
33
+ color: option("color", Color.background)
34
+
35
+ Service {
36
+ id: service
37
+ transport: zuiProcess
38
+ projectDir: zuiProjectDir
39
+ componentDir: zuiComponentDir
40
+ runtimeExecutable: zuiRubyExecutable
41
+ program: zuiRubyProgram
42
+ rubyLoadPath: zuiRubyLoadPath
43
+ }
44
+
45
+ ControlNode {
46
+ id: renderer
47
+ anchors.fill: parent
48
+ anchors.margins: Number(window.option("padding", 20))
49
+ visible: window.activeSurface !== "" && service.rootId(window.activeSurface) !== ""
50
+ bridge: service
51
+ surfaceName: window.activeSurface
52
+ controlId: service.rootId(surfaceName)
53
+ foreground: Color.foreground
54
+ fontFamily: Style.font.family
55
+ }
56
+
57
+ Column {
58
+ anchors.centerIn: parent
59
+ spacing: Style.spacing.sm
60
+ visible: !renderer.visible
61
+ QQC.BusyIndicator { anchors.horizontalCenter: parent.horizontalCenter; running: !service.ready && service.lastError === "" }
62
+ Text {
63
+ anchors.horizontalCenter: parent.horizontalCenter
64
+ text: service.lastError !== "" ? service.lastError : "Starting Zui…"
65
+ color: Color.foreground
66
+ font.family: Style.font.family
67
+ font.pixelSize: Style.font.body
68
+ }
69
+ }
70
+
71
+ onClosing: function(close) {
72
+ service.stopping = true
73
+ if (zuiProcess.running) zuiProcess.stop()
74
+ close.accepted = true
75
+ }
76
+ }
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/README.md ADDED
@@ -0,0 +1,112 @@
1
+ # Zui
2
+
3
+ Zui is a platform-neutral Ruby framework for native desktop interfaces powered by Qt and QML.
4
+ Ruby owns the application, component tree, state, events, bindings, animation descriptions, and
5
+ business logic. QML is the rendering backend, not application code.
6
+
7
+ This repository is the reusable core. Distribution integrations live at its edges:
8
+
9
+ - Linux and Windows desktop applications use the standalone Qt host.
10
+ - macOS applications use the same host inside a standard application bundle.
11
+ - Omarchy plugins and applications use the separate
12
+ [`omarchy-ui`](https://github.com/AdamMusa/omarchy-ui) adapter.
13
+
14
+ Zui ships a standard Qt `ApplicationWindow`, a bidirectional `QProcess` bridge, its own neutral
15
+ theme and controls, and the full built-in component catalog. It has no runtime dependency on
16
+ Quickshell or an Omarchy installation.
17
+
18
+ ## Core API
19
+
20
+ ```ruby
21
+ require "zui"
22
+
23
+ Zui.app do
24
+ state :count, 0
25
+
26
+ app :main, title: "Counter", width: 640, height: 420 do
27
+ column spacing: 16 do
28
+ label { "Count: #{state.count}" }
29
+ button "Increment" do
30
+ state.count += 1
31
+ end
32
+ end
33
+ end
34
+ end
35
+ ```
36
+
37
+ ## Developer workflow
38
+
39
+ ```bash
40
+ gem install zui
41
+ zui new telemetry-console
42
+ cd telemetry-console
43
+ zui run main.rb
44
+ zui bundle
45
+ ```
46
+
47
+ Build and install the gem directly from a checkout:
48
+
49
+ ```bash
50
+ gem build zui.gemspec
51
+ gem install ./zui-0.0.1.gem
52
+ ```
53
+
54
+ `zui run` uses the bundled host for supported release targets. If a matching host is not
55
+ bundled, Zui builds and caches it from the checked-in C++ source with CMake and Qt 6.8 or newer.
56
+
57
+ `zui bundle` produces an application directory on Linux and Windows and a standard `.app` bundle
58
+ on macOS. Each package includes the application, Zui Ruby runtime, QML renderer, component catalog,
59
+ theme, controls, and native host. Ruby and the required Qt runtime libraries must be available on
60
+ the destination machine; native installers can add them with the platform's normal deployment
61
+ tools.
62
+
63
+ See [Platform support](docs/platforms.md) for host requirements and bundle layouts.
64
+
65
+ Zui has no separate validation command. `run` opens the app directly, and
66
+ `bundle` packages the project directly. Ruby, DSL, protocol, resource, and QML
67
+ errors are reported by the operation that actually encounters them.
68
+
69
+ ## Reusable application UI
70
+
71
+ Application UI modules are scoped to one builder instead of being mixed into Zui globally:
72
+
73
+ ```ruby
74
+ module TelemetryConsole
75
+ module UI
76
+ def dashboard
77
+ card { text "System online" }
78
+ end
79
+ end
80
+
81
+ def self.build
82
+ Zui::Application.new(ui: UI) do
83
+ app(:main, title: "Telemetry Console") { dashboard }
84
+ end
85
+ end
86
+
87
+ def self.run = build.run
88
+ end
89
+
90
+ TelemetryConsole.run
91
+ ```
92
+
93
+ This keeps `main.rb` at one domain-level call and prevents components from one application leaking
94
+ into another application's DSL.
95
+
96
+ ## Architecture
97
+
98
+ ```text
99
+ Ruby application
100
+ → Zui DSL/state/events
101
+ → versioned JSON protocol
102
+ → native Qt host and process transport
103
+ → platform-neutral QML renderer
104
+ → Qt Quick / Controls / Multimedia / optional modules
105
+ ```
106
+
107
+ Every declared component renders as that component or reports an explicit component error. Zui
108
+ does not silently substitute images, alternate controls, or application-specific fallbacks.
109
+
110
+ ## License
111
+
112
+ MIT
data/Service.qml ADDED
@@ -0,0 +1,430 @@
1
+ import QtQuick
2
+
3
+ Item {
4
+ id: root
5
+
6
+ required property var transport
7
+ property string projectDir: ""
8
+ property string componentDir: ""
9
+ property string runtimeExecutable: ""
10
+ property string program: ""
11
+ property string rubyLoadPath: ""
12
+
13
+ property bool ready: false
14
+ property bool stopping: false
15
+ property string lastError: ""
16
+ property var surfaces: ({})
17
+ property var surfaceOptions: ({})
18
+ property var nodeIndex: ({})
19
+ property var componentDefinitions: ({})
20
+ property int revision: 0
21
+ property int restartDelayMs: 500
22
+ property int eventSequence: 0
23
+ readonly property int maxStringLength: 16384
24
+ readonly property int maxCollectionItems: 256
25
+
26
+ property var allowedTypes: ({})
27
+ property var allowedProperties: ({})
28
+
29
+ signal effectReceived(string name, var payload)
30
+
31
+ function reportComponentError(surfaceName, controlId, componentType, code, message) {
32
+ var location = String(surfaceName || "") + "/" + String(controlId || "")
33
+ var detail = String(componentType || "component") + " error at " + location
34
+ if (code) detail += " [" + String(code) + "]"
35
+ if (message) detail += ": " + String(message)
36
+ lastError = detail.slice(0, 512)
37
+ console.warn("zui component:", lastError)
38
+ }
39
+
40
+ function validId(value) {
41
+ return typeof value === "string"
42
+ && value.length > 0
43
+ && value.length <= 128
44
+ && /^[a-zA-Z0-9_.:-]+$/.test(value)
45
+ }
46
+
47
+ function plainObject(value) {
48
+ return value !== null && typeof value === "object" && !Array.isArray(value)
49
+ }
50
+
51
+ function boundedValue(value, depth) {
52
+ if (depth > 8) return false
53
+ if (value === null || typeof value === "number" || typeof value === "boolean") return true
54
+ if (typeof value === "string") return value.length <= maxStringLength
55
+ if (Array.isArray(value)) {
56
+ if (value.length > maxCollectionItems) return false
57
+ for (var i = 0; i < value.length; i++) if (!boundedValue(value[i], depth + 1)) return false
58
+ return true
59
+ }
60
+ if (plainObject(value)) {
61
+ var keys = Object.keys(value)
62
+ if (keys.length > maxCollectionItems) return false
63
+ for (var k = 0; k < keys.length; k++) {
64
+ if (keys[k].length > 128 || !boundedValue(value[keys[k]], depth + 1)) return false
65
+ }
66
+ return true
67
+ }
68
+ return false
69
+ }
70
+
71
+ function validateNode(node, index, depth, count) {
72
+ if (!plainObject(node) || depth > 32 || count.value >= 2000) return false
73
+ if (!allowedTypes[String(node.type || "")] || !validId(node.id)) return false
74
+ if (index[node.id] !== undefined) return false
75
+
76
+ var props = node.props === undefined ? {} : node.props
77
+ if (!plainObject(props)) return false
78
+ var whitelist = allowedProperties[node.type]
79
+ for (var key in props) {
80
+ if (!whitelist[key]) return false
81
+ var value = props[key]
82
+ if (!boundedValue(value, 0)) return false
83
+ }
84
+
85
+ var children = node.children === undefined ? [] : node.children
86
+ if (!Array.isArray(children)) return false
87
+ if (!componentDefinitions[node.type].container && children.length !== 0)
88
+ return false
89
+ var subscriptions = node.events === undefined ? [] : node.events
90
+ if (!Array.isArray(subscriptions)) return false
91
+ for (var e = 0; e < subscriptions.length; e++) {
92
+ var eventName = String(subscriptions[e])
93
+ if (eventName !== "mount" && eventName !== "unmount" && componentDefinitions[node.type].events.indexOf(eventName) < 0)
94
+ return false
95
+ }
96
+
97
+ index[node.id] = node
98
+ count.value += 1
99
+ for (var i = 0; i < children.length; i++)
100
+ if (!validateNode(children[i], index, depth + 1, count)) return false
101
+ return true
102
+ }
103
+
104
+ function validateComponents(components) {
105
+ if (!plainObject(components)) return false
106
+ var names = Object.keys(components)
107
+ if (names.length === 0 || names.length > 256) return false
108
+ var validated = ({})
109
+ for (var i = 0; i < names.length; i++) {
110
+ var name = names[i]
111
+ var definition = components[name]
112
+ if (!/^[a-z][a-z0-9_]*$/.test(name) || !plainObject(definition)) return false
113
+ if (!/^[A-Z][A-Za-z0-9]*\.qml$/.test(String(definition.qml || ""))) return false
114
+ if (!Array.isArray(definition.properties) || !Array.isArray(definition.events)
115
+ || !plainObject(definition.property_map || {}) || !plainObject(definition.event_map || {})) return false
116
+ var propertyMap = ({})
117
+ for (var p = 0; p < definition.properties.length; p++) {
118
+ var propertyName = String(definition.properties[p])
119
+ if (!/^[a-z][a-z0-9_]*$/.test(propertyName)) return false
120
+ propertyMap[propertyName] = true
121
+ }
122
+ validated[name] = {
123
+ qml: definition.qml,
124
+ properties: propertyMap,
125
+ events: definition.events,
126
+ propertyMap: definition.property_map || {},
127
+ eventMap: definition.event_map || {},
128
+ container: definition.container === true,
129
+ autoBind: definition.auto_bind !== false
130
+ }
131
+ }
132
+ componentDefinitions = validated
133
+ return true
134
+ }
135
+
136
+ function installRender(message) {
137
+ if (!plainObject(message.surfaces)) return reject("render surfaces must be an object")
138
+ if (message.surface_options !== undefined && !validateSurfaceOptions(message.surface_options))
139
+ return reject("invalid surface options")
140
+ if (!validateComponents(message.components)) return reject("invalid component registry")
141
+ var dynamicTypes = ({})
142
+ var dynamicProperties = ({})
143
+ for (var componentName in componentDefinitions) {
144
+ dynamicTypes[componentName] = true
145
+ dynamicProperties[componentName] = componentDefinitions[componentName].properties
146
+ }
147
+ allowedTypes = dynamicTypes
148
+ allowedProperties = dynamicProperties
149
+ var nextIndex = ({})
150
+ var count = { value: 0 }
151
+ var names = Object.keys(message.surfaces)
152
+ if (names.length === 0 || names.length > 32) return reject("invalid surface count")
153
+
154
+ for (var i = 0; i < names.length; i++) {
155
+ if (!validId(names[i]) || !validateNode(message.surfaces[names[i]], nextIndex, 0, count))
156
+ return reject("invalid control tree")
157
+ }
158
+
159
+ surfaces = message.surfaces
160
+ surfaceOptions = message.surface_options || ({})
161
+ nodeIndex = nextIndex
162
+ revision += 1
163
+ lastError = ""
164
+ }
165
+
166
+ function validateSurfaceOptions(options) {
167
+ if (!plainObject(options)) return false
168
+ var allowed = {
169
+ title: true, width: true, height: true, min_width: true, min_height: true,
170
+ max_width: true, max_height: true, color: true, visible: true,
171
+ maximized: true, fullscreen: true
172
+ }
173
+ for (var surfaceName in options) {
174
+ if (!validId(surfaceName) || !plainObject(options[surfaceName])) return false
175
+ for (var key in options[surfaceName]) {
176
+ if (!allowed[key]) return false
177
+ var value = options[surfaceName][key]
178
+ if (value !== null && typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean")
179
+ return false
180
+ }
181
+ }
182
+ return true
183
+ }
184
+
185
+ function applyPatch(message) {
186
+ if (!validId(message.id))
187
+ return reject("invalid patch")
188
+ var node = nodeIndex[message.id]
189
+ if (!node) return reject("patch target rejected")
190
+
191
+ if (message.op === "replace_children") {
192
+ if (!componentDefinitions[node.type].container || !Array.isArray(message.children))
193
+ return reject("children patch rejected")
194
+ var removed = ({})
195
+ function markRemoved(child) {
196
+ removed[child.id] = true
197
+ var nested = Array.isArray(child.children) ? child.children : []
198
+ for (var r = 0; r < nested.length; r++) markRemoved(nested[r])
199
+ }
200
+ var oldChildren = Array.isArray(node.children) ? node.children : []
201
+ for (var oldIndex = 0; oldIndex < oldChildren.length; oldIndex++) markRemoved(oldChildren[oldIndex])
202
+
203
+ var childrenIndex = ({})
204
+ for (var existingId in nodeIndex)
205
+ if (!removed[existingId] && existingId !== node.id) childrenIndex[existingId] = nodeIndex[existingId]
206
+ var count = { value: Object.keys(childrenIndex).length }
207
+ for (var childIndex = 0; childIndex < message.children.length; childIndex++)
208
+ if (!validateNode(message.children[childIndex], childrenIndex, 0, count)) return reject("invalid children patch")
209
+
210
+ var containerReplacement = ({ type: node.type, id: node.id, props: node.props || {}, children: message.children })
211
+ if (node.events !== undefined) containerReplacement.events = node.events
212
+ childrenIndex[node.id] = containerReplacement
213
+ nodeIndex = childrenIndex
214
+ revision += 1
215
+ return true
216
+ }
217
+
218
+ if (message.op === "animate") {
219
+ if (!Array.isArray(message.tracks) || message.tracks.length === 0 || message.tracks.length > 64)
220
+ return reject("animation tracks rejected")
221
+ var animatedProps = ({})
222
+ var validatedTracks = []
223
+ var sourceProps = node.props || {}
224
+ for (var trackIndex = 0; trackIndex < message.tracks.length; trackIndex++) {
225
+ var track = message.tracks[trackIndex]
226
+ if (!plainObject(track) || typeof track.property !== "string"
227
+ || !allowedProperties[node.type][track.property] || !validAnimation(track))
228
+ return reject("animation track rejected")
229
+ var targetValue = track.to
230
+ if (targetValue !== null && typeof targetValue !== "string" && typeof targetValue !== "number")
231
+ return reject("animation target rejected")
232
+ animatedProps[track.property] = targetValue
233
+ validatedTracks.push(track)
234
+ }
235
+ var animatedReplacement = ({ type: node.type, id: node.id })
236
+ var finalProps = ({})
237
+ for (var sourceKey in sourceProps) finalProps[sourceKey] = sourceProps[sourceKey]
238
+ for (var animatedKey in animatedProps) finalProps[animatedKey] = animatedProps[animatedKey]
239
+ animatedReplacement.props = finalProps
240
+ if (node.children !== undefined) animatedReplacement.children = node.children
241
+ if (node.events !== undefined) animatedReplacement.events = node.events
242
+ animatedReplacement.transitions = validatedTracks
243
+ var animatedIndex = ({})
244
+ for (var animatedId in nodeIndex) animatedIndex[animatedId] = nodeIndex[animatedId]
245
+ animatedIndex[node.id] = animatedReplacement
246
+ nodeIndex = animatedIndex
247
+ revision += 1
248
+ return true
249
+ }
250
+
251
+ if (message.op !== "set" || typeof message.property !== "string") return reject("invalid patch")
252
+ if (!allowedProperties[node.type][message.property]) return reject("patch target rejected")
253
+ var value = message.value
254
+ if (!boundedValue(value, 0)) return reject("patch value rejected")
255
+
256
+ var animation = message.animation
257
+ if (animation !== undefined && !validAnimation(animation)) return reject("patch animation rejected")
258
+
259
+ var replacement = ({ type: node.type, id: node.id })
260
+ var props = ({})
261
+ var oldProps = node.props || {}
262
+ for (var key in oldProps) props[key] = oldProps[key]
263
+ props[message.property] = value
264
+ replacement.props = props
265
+ if (node.children !== undefined) replacement.children = node.children
266
+ if (node.events !== undefined) replacement.events = node.events
267
+ if (animation !== undefined) {
268
+ replacement.transition = {
269
+ property: message.property,
270
+ from: oldProps[message.property],
271
+ to: value,
272
+ duration: animation.duration,
273
+ delay: animation.delay,
274
+ easing: animation.easing,
275
+ sequence: revision + 1
276
+ }
277
+ }
278
+
279
+ var nextIndex = ({})
280
+ for (var id in nodeIndex) nextIndex[id] = nodeIndex[id]
281
+ nextIndex[node.id] = replacement
282
+ nodeIndex = nextIndex
283
+ revision += 1
284
+ }
285
+
286
+ function validAnimation(animation) {
287
+ if (!plainObject(animation)
288
+ || typeof animation.duration !== "number" || animation.duration < 0 || animation.duration > 60000
289
+ || typeof animation.delay !== "number" || animation.delay < 0 || animation.delay > 60000
290
+ || typeof animation.easing !== "string") return false
291
+ var easings = ["linear", "in_quad", "out_quad", "in_out_quad", "in_cubic", "out_cubic", "in_out_cubic",
292
+ "in_back", "out_back", "in_out_back", "in_elastic", "out_elastic", "in_out_elastic",
293
+ "in_bounce", "out_bounce", "in_out_bounce"]
294
+ return easings.indexOf(animation.easing) >= 0
295
+ }
296
+
297
+ function handleLine(line) {
298
+ var raw = String(line || "").trim()
299
+ if (raw === "" || raw.length > 1048576) return reject("invalid message size")
300
+
301
+ var message
302
+ try { message = JSON.parse(raw) }
303
+ catch (error) { return reject("invalid JSON from Ruby") }
304
+ if (!plainObject(message) || message.v !== 1 || typeof message.type !== "string")
305
+ return reject("invalid protocol envelope")
306
+
307
+ if (message.type === "ready") {
308
+ ready = true
309
+ restartDelayMs = 500
310
+ lastError = ""
311
+ } else if (message.type === "render") {
312
+ installRender(message)
313
+ } else if (message.type === "patch") {
314
+ applyPatch(message)
315
+ } else if (message.type === "effect") {
316
+ handleEffect(message)
317
+ } else if (message.type === "ack") {
318
+ // Acknowledgements are consumed by benchmarks and future diagnostics.
319
+ } else if (message.type === "handler_error" || message.type === "runtime_error" || message.type === "protocol_error") {
320
+ lastError = String(message.message || message.code || "Ruby runtime error").slice(0, 512)
321
+ } else {
322
+ reject("unknown message type")
323
+ }
324
+ }
325
+
326
+ function handleEffect(message) {
327
+ if (typeof message.name !== "string" || message.name.length > 64
328
+ || !plainObject(message.payload || {}) || !boundedValue(message.payload || {}, 0))
329
+ return reject("invalid effect")
330
+ var payload = message.payload || {}
331
+ effectReceived(message.name, payload)
332
+ }
333
+
334
+ function reject(reason) {
335
+ lastError = String(reason || "protocol error").slice(0, 512)
336
+ console.warn("zui bridge:", lastError)
337
+ return false
338
+ }
339
+
340
+ function rootId(surfaceName) {
341
+ var surface = surfaces[String(surfaceName || "")]
342
+ return surface && validId(surface.id) ? surface.id : ""
343
+ }
344
+
345
+ function optionsFor(surfaceName) {
346
+ var options = surfaceOptions[String(surfaceName || "")]
347
+ return plainObject(options) ? options : ({})
348
+ }
349
+
350
+ function nodeFor(controlId) {
351
+ return nodeIndex[String(controlId || "")] || null
352
+ }
353
+
354
+ function componentSource(typeName) {
355
+ var definition = componentDefinitions[String(typeName || "")]
356
+ return definition ? componentDir + "/" + definition.qml : ""
357
+ }
358
+
359
+ function componentDefinition(typeName) {
360
+ return componentDefinitions[String(typeName || "")] || null
361
+ }
362
+
363
+ function sendEvent(surfaceName, controlId, eventName, payload) {
364
+ if (!transport.running || !validId(controlId) || !/^[a-z][a-z0-9_]{0,63}$/.test(eventName)
365
+ || !plainObject(payload || {}) || !boundedValue(payload || {}, 0)) return false
366
+ var target = nodeIndex[String(controlId)]
367
+ var definition = target ? componentDefinitions[String(target.type)] : null
368
+ var subscriptions = target && Array.isArray(target.events) ? target.events : []
369
+ if (!definition || subscriptions.indexOf(eventName) < 0) return false
370
+ eventSequence += 1
371
+ transport.write(JSON.stringify({
372
+ v: 1,
373
+ type: "event",
374
+ surface: String(surfaceName || ""),
375
+ id: String(controlId),
376
+ event: eventName,
377
+ seq: eventSequence,
378
+ payload: plainObject(payload) ? payload : {}
379
+ }) + "\n")
380
+ return true
381
+ }
382
+
383
+ function startRuby() {
384
+ if (stopping || projectDir === "" || program === "" || transport.running) return
385
+ transport.start(runtimeExecutable, program, projectDir, rubyLoadPath)
386
+ }
387
+
388
+ onProjectDirChanged: Qt.callLater(startRuby)
389
+ onProgramChanged: Qt.callLater(startRuby)
390
+ Component.onCompleted: Qt.callLater(startRuby)
391
+
392
+ Component.onDestruction: {
393
+ stopping = true
394
+ restartTimer.stop()
395
+ if (transport.running) transport.stop()
396
+ }
397
+
398
+ Connections {
399
+ target: transport
400
+ function onLineReceived(line) { root.handleLine(line) }
401
+ function onErrorLineReceived(line) {
402
+ var message = String(line || "").trim()
403
+ if (message !== "") console.warn("zui ruby:", message)
404
+ }
405
+ function onExited(exitCode) {
406
+ root.ready = false
407
+ if (root.stopping || root.projectDir === "") return
408
+ root.lastError = exitCode === 0
409
+ ? "Zui runtime stopped"
410
+ : "Zui runtime crashed (exit " + exitCode + ")"
411
+ restartTimer.interval = root.restartDelayMs
412
+ restartTimer.start()
413
+ root.restartDelayMs = Math.min(30000, root.restartDelayMs * 2)
414
+ }
415
+ }
416
+
417
+ Timer {
418
+ interval: 100
419
+ repeat: true
420
+ running: transport.running
421
+ onTriggered: transport.write('{"v":1,"type":"tick"}\n')
422
+ }
423
+
424
+ Timer {
425
+ id: restartTimer
426
+ interval: 500
427
+ repeat: false
428
+ onTriggered: root.startRuby()
429
+ }
430
+ }
data/Theme/Border.qml ADDED
@@ -0,0 +1,22 @@
1
+ pragma Singleton
2
+ import QtQuick
3
+
4
+ QtObject {
5
+ function none() {
6
+ return ({ color: "transparent", widths: ({ top: 0, right: 0, bottom: 0, left: 0 }) })
7
+ }
8
+
9
+ function widths(value) {
10
+ if (typeof value === "object") return value
11
+ var width = Number(value || 0)
12
+ return ({ top: width, right: width, bottom: width, left: width })
13
+ }
14
+
15
+ function flat(color, width) {
16
+ return ({ color: color, widths: widths(width), gradient: ({ enabled: false, colors: [] }) })
17
+ }
18
+
19
+ function controlSpec(_kind, color, _accent) {
20
+ return flat(color, 1)
21
+ }
22
+ }
data/Theme/Color.qml ADDED
@@ -0,0 +1,22 @@
1
+ pragma Singleton
2
+ import QtQuick
3
+
4
+ QtObject {
5
+ readonly property color background: "#0b1118"
6
+ readonly property color foreground: "#edf5f7"
7
+ readonly property color accent: "#59e1ff"
8
+ readonly property color muted: "#78909c"
9
+ readonly property color urgent: "#ff5d7a"
10
+ readonly property color border: "#29404c"
11
+
12
+ readonly property QtObject popups: QtObject {
13
+ readonly property color background: "#111c26"
14
+ readonly property color border: "#2c4654"
15
+ }
16
+
17
+ readonly property QtObject tooltip: QtObject {
18
+ readonly property color background: "#172631"
19
+ readonly property color text: "#f4fbfd"
20
+ readonly property color border: "#355363"
21
+ }
22
+ }