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,200 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "json"
5
+
6
+ module Zui
7
+ class Distribution
8
+ attr_reader :platform
9
+
10
+ def initialize(host: Host.new, platform: Platform.current, framework_root: FRAMEWORK_ROOT)
11
+ @host = host
12
+ @platform = platform.assert_supported!
13
+ @framework_root = framework_root
14
+ end
15
+
16
+ def bundle(source, name: nil, destination: nil)
17
+ project = File.expand_path(source)
18
+ raise ArgumentError, "project directory not found: #{project}" unless File.directory?(project)
19
+ raise ArgumentError, "main.rb not found: #{project}" unless File.file?(File.join(project, "main.rb"))
20
+ app_name = name || titleize(File.basename(project))
21
+ destination ||= default_destination(project, app_name)
22
+ destination = File.expand_path(destination)
23
+ raise ArgumentError, "bundle destination already exists: #{destination}" if File.exist?(destination)
24
+
25
+ case platform.os
26
+ when :linux then bundle_linux(project, destination, app_name)
27
+ when :macos then bundle_macos(project, destination, app_name)
28
+ when :windows then bundle_windows(project, destination, app_name)
29
+ end
30
+ destination
31
+ rescue StandardError
32
+ FileUtils.remove_entry(destination) if destination && File.exist?(destination)
33
+ raise
34
+ end
35
+
36
+ private
37
+
38
+ def bundle_linux(project, destination, app_name)
39
+ FileUtils.mkdir_p([File.join(destination, "app"), File.join(destination, "bin"),
40
+ File.join(destination, "runtime"), File.join(destination, "share", "applications")])
41
+ install_application(project, File.join(destination, "app"))
42
+ install_runtime(File.join(destination, "runtime"))
43
+ FileUtils.cp(@host.executable, File.join(destination, "bin", "zui-host"))
44
+ FileUtils.chmod(0o755, File.join(destination, "bin", "zui-host"))
45
+ write_linux_launcher(destination, app_name)
46
+ desktop_name = "#{slug(app_name)}.desktop"
47
+ File.write(File.join(destination, "share", "applications", desktop_name), <<~DESKTOP)
48
+ [Desktop Entry]
49
+ Type=Application
50
+ Name=#{app_name}
51
+ Exec=#{File.join(destination, "run")}
52
+ Terminal=false
53
+ Categories=Utility;
54
+ DESKTOP
55
+ write_manifest(destination, app_name)
56
+ end
57
+
58
+ def bundle_macos(project, destination, app_name)
59
+ contents = File.join(destination, "Contents")
60
+ macos = File.join(contents, "MacOS")
61
+ resources = File.join(contents, "Resources")
62
+ FileUtils.mkdir_p([macos, File.join(resources, "app"), File.join(resources, "runtime")])
63
+ install_application(project, File.join(resources, "app"))
64
+ install_runtime(File.join(resources, "runtime"))
65
+ FileUtils.cp(@host.executable, File.join(macos, "zui-host"))
66
+ FileUtils.chmod(0o755, File.join(macos, "zui-host"))
67
+ File.write(File.join(macos, "run"), macos_launcher(app_name))
68
+ FileUtils.chmod(0o755, File.join(macos, "run"))
69
+ File.write(File.join(contents, "Info.plist"), info_plist(app_name))
70
+ write_manifest(resources, app_name)
71
+ end
72
+
73
+ def bundle_windows(project, destination, app_name)
74
+ FileUtils.mkdir_p([File.join(destination, "app"), File.join(destination, "bin"),
75
+ File.join(destination, "runtime")])
76
+ install_application(project, File.join(destination, "app"))
77
+ install_runtime(File.join(destination, "runtime"))
78
+ FileUtils.cp(@host.executable, File.join(destination, "bin", "zui-host.exe"))
79
+ File.write(File.join(destination, "run.rb"), windows_ruby_launcher(app_name))
80
+ File.write(File.join(destination, "run.cmd"), windows_command_launcher)
81
+ write_manifest(destination, app_name)
82
+ end
83
+
84
+ def install_application(source, destination)
85
+ entries = Dir.children(source).reject do |entry|
86
+ entry_path = File.join(source, entry)
87
+ %w[.git dist].include?(entry) || destination.start_with?("#{entry_path}#{File::SEPARATOR}")
88
+ end
89
+ FileUtils.cp_r(entries.map { |entry| File.join(source, entry) }, destination) unless entries.empty?
90
+ end
91
+
92
+ def install_runtime(destination)
93
+ Runtime.install_qml(File.join(destination, "qml"), framework_root: @framework_root)
94
+ FileUtils.mkdir_p(File.join(destination, "lib"))
95
+ FileUtils.cp(File.join(@framework_root, "lib", "zui.rb"), File.join(destination, "lib", "zui.rb"))
96
+ FileUtils.cp_r(File.join(@framework_root, "lib", "zui"), File.join(destination, "lib", "zui"))
97
+ end
98
+
99
+ def write_linux_launcher(destination, app_name)
100
+ File.write(File.join(destination, "run"), <<~SH)
101
+ #!/bin/sh
102
+ set -eu
103
+ bundle_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
104
+ ruby_command=${ZUI_RUBY:-ruby}
105
+ exec "$bundle_dir/bin/zui-host" \
106
+ --qml-root "$bundle_dir/runtime/qml" \
107
+ --project "$bundle_dir/app" \
108
+ --program "$bundle_dir/app/main.rb" \
109
+ --ruby "$ruby_command" \
110
+ --load-path "$bundle_dir/runtime/lib" \
111
+ --name #{shell_quote(app_name)}
112
+ SH
113
+ FileUtils.chmod(0o755, File.join(destination, "run"))
114
+ end
115
+
116
+ def macos_launcher(app_name)
117
+ <<~SH
118
+ #!/bin/sh
119
+ set -eu
120
+ contents=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
121
+ resources="$contents/Resources"
122
+ ruby_command=${ZUI_RUBY:-ruby}
123
+ exec "$contents/MacOS/zui-host" \
124
+ --qml-root "$resources/runtime/qml" \
125
+ --project "$resources/app" \
126
+ --program "$resources/app/main.rb" \
127
+ --ruby "$ruby_command" \
128
+ --load-path "$resources/runtime/lib" \
129
+ --name #{shell_quote(app_name)}
130
+ SH
131
+ end
132
+
133
+ def windows_ruby_launcher(app_name)
134
+ <<~RUBY
135
+ # frozen_string_literal: true
136
+
137
+ require "rbconfig"
138
+
139
+ bundle_dir = File.expand_path(__dir__)
140
+ arguments = [
141
+ File.join(bundle_dir, "bin", "zui-host.exe"),
142
+ "--qml-root", File.join(bundle_dir, "runtime", "qml"),
143
+ "--project", File.join(bundle_dir, "app"),
144
+ "--program", File.join(bundle_dir, "app", "main.rb"),
145
+ "--ruby", RbConfig.ruby,
146
+ "--load-path", File.join(bundle_dir, "runtime", "lib"),
147
+ "--name", #{app_name.to_s.dump}
148
+ ]
149
+ exec(*arguments)
150
+ RUBY
151
+ end
152
+
153
+ def windows_command_launcher
154
+ <<~CMD.gsub("\n", "\r\n")
155
+ @echo off
156
+ setlocal
157
+ if defined ZUI_RUBY (
158
+ "%ZUI_RUBY%" "%~dp0run.rb"
159
+ ) else (
160
+ ruby "%~dp0run.rb"
161
+ )
162
+ exit /b %ERRORLEVEL%
163
+ CMD
164
+ end
165
+
166
+ def info_plist(app_name)
167
+ identifier = "dev.zui.#{slug(app_name).tr("-", ".")}"
168
+ <<~PLIST
169
+ <?xml version="1.0" encoding="UTF-8"?>
170
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
171
+ <plist version="1.0"><dict>
172
+ <key>CFBundleExecutable</key><string>run</string>
173
+ <key>CFBundleIdentifier</key><string>#{identifier}</string>
174
+ <key>CFBundleName</key><string>#{xml_escape(app_name)}</string>
175
+ <key>CFBundlePackageType</key><string>APPL</string>
176
+ <key>CFBundleShortVersionString</key><string>#{VERSION}</string>
177
+ <key>NSHighResolutionCapable</key><true/>
178
+ </dict></plist>
179
+ PLIST
180
+ end
181
+
182
+ def write_manifest(directory, app_name)
183
+ File.write(File.join(directory, "zui-bundle.json"), JSON.pretty_generate(
184
+ "format" => 1, "framework" => "zui", "version" => VERSION,
185
+ "platform" => platform.os.to_s, "architecture" => platform.arch.to_s,
186
+ "name" => app_name
187
+ ))
188
+ end
189
+
190
+ def default_destination(project, app_name)
191
+ base = File.join(project, "dist")
192
+ platform.macos? ? File.join(base, "#{app_name}.app") : File.join(base, "#{slug(app_name)}-#{platform.id}")
193
+ end
194
+
195
+ def titleize(value) = value.split(/[-_]/).map(&:capitalize).join(" ")
196
+ def slug(value) = value.downcase.gsub(/[^a-z0-9]+/, "-").gsub(/\A-|-\z/, "")
197
+ def shell_quote(value) = "'#{value.to_s.gsub("'", %q('"'"'))}'"
198
+ def xml_escape(value) = value.to_s.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;")
199
+ end
200
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module Zui
6
+ class Generator
7
+ def initialize(path:, name: nil)
8
+ @path = File.expand_path(path)
9
+ @name = name || File.basename(@path).split(/[-_]/).map(&:capitalize).join(" ")
10
+ end
11
+
12
+ def create
13
+ raise ArgumentError, "destination already exists: #{@path}" if File.exist?(@path)
14
+ created = true
15
+ FileUtils.mkdir_p(File.join(@path, "components"))
16
+ File.write(File.join(@path, "main.rb"), main_program)
17
+ File.write(File.join(@path, "components", "welcome.rb"), welcome_component)
18
+ File.write(File.join(@path, "README.md"), readme)
19
+ @path
20
+ rescue StandardError
21
+ FileUtils.remove_entry(@path) if created && File.directory?(@path)
22
+ raise
23
+ end
24
+
25
+ private
26
+
27
+ def main_program
28
+ <<~RUBY
29
+ # frozen_string_literal: true
30
+
31
+ require "zui"
32
+ require_relative "components/welcome"
33
+
34
+ module #{constant_name}
35
+ def self.build
36
+ Zui::Application.new(ui: WelcomeComponent) do
37
+ app :main, title: "#{@name}", width: 760, height: 520 do
38
+ welcome_card(title: "Welcome to #{@name}", message: "A native cross-platform Zui application.")
39
+ end
40
+ end
41
+ end
42
+
43
+ def self.run = build.run
44
+ end
45
+
46
+ #{constant_name}.run
47
+ RUBY
48
+ end
49
+
50
+ def welcome_component
51
+ <<~RUBY
52
+ # frozen_string_literal: true
53
+
54
+ module WelcomeComponent
55
+ def welcome_card(title:, message:)
56
+ card padding: 24, spacing: 12 do
57
+ text title, style: :heading
58
+ text message, wrap: true
59
+ end
60
+ end
61
+ end
62
+ RUBY
63
+ end
64
+
65
+ def readme
66
+ <<~MARKDOWN
67
+ # #{@name}
68
+
69
+ A native Linux, macOS, and Windows desktop application written in Ruby with Zui.
70
+
71
+ ```bash
72
+ zui run main.rb
73
+ zui bundle
74
+ ```
75
+ MARKDOWN
76
+ end
77
+
78
+ def constant_name
79
+ value = @name.scan(/[a-z0-9]+/i).map { |part| part[0].upcase + part[1..].to_s }.join
80
+ value = "Application#{value}" if value.match?(/\A\d/)
81
+ value.empty? ? "ZuiApplication" : value
82
+ end
83
+ end
84
+ end
data/lib/zui/host.rb ADDED
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "tmpdir"
5
+
6
+ module Zui
7
+ class Host
8
+ attr_reader :platform
9
+
10
+ def initialize(platform: Platform.current, framework_root: FRAMEWORK_ROOT, environment: ENV)
11
+ @platform = platform.assert_supported!
12
+ @framework_root = framework_root
13
+ @environment = environment
14
+ end
15
+
16
+ def executable(build: true)
17
+ override = @environment["ZUI_HOST"]
18
+ return checked_executable(override) if override && !override.empty?
19
+ return bundled if File.executable?(bundled)
20
+ return cached if File.executable?(cached)
21
+ return nil unless build
22
+
23
+ build!(cached)
24
+ end
25
+
26
+ def available? = !executable(build: false).nil?
27
+
28
+ def build!(destination = cached)
29
+ cmake = find_command("cmake")
30
+ raise ArgumentError, platform_help unless cmake
31
+
32
+ FileUtils.mkdir_p(File.dirname(destination))
33
+ Dir.mktmpdir("zui-host-build-") do |build_dir|
34
+ configure = Command.run([
35
+ cmake, "-S", File.join(@framework_root, "native"), "-B", build_dir,
36
+ "-DCMAKE_BUILD_TYPE=Release"
37
+ ], timeout: 180, max_output_bytes: 4_194_304)
38
+ raise ArgumentError, "Zui host configuration failed:\n#{configure.stderr}" unless configure.success?
39
+
40
+ compile = Command.run([cmake, "--build", build_dir, "--config", "Release", "--parallel"],
41
+ timeout: 600, max_output_bytes: 8_388_608)
42
+ raise ArgumentError, "Zui host build failed:\n#{compile.stderr}" unless compile.success?
43
+
44
+ built = locate_build(build_dir)
45
+ raise ArgumentError, "Zui host build produced no executable" unless built
46
+ temporary = "#{destination}.install-#{Process.pid}"
47
+ FileUtils.cp(built, temporary)
48
+ FileUtils.chmod(0o755, temporary)
49
+ File.rename(temporary, destination)
50
+ ensure
51
+ FileUtils.rm_f(temporary) if temporary && File.exist?(temporary)
52
+ end
53
+ destination
54
+ end
55
+
56
+ def platform_help
57
+ if platform.macos?
58
+ "building Zui on macOS requires CMake and Qt 6 (for example: brew install cmake qt)"
59
+ elsif platform.windows?
60
+ "building Zui on Windows requires CMake, Qt 6, and a C++17 toolchain (Visual Studio Build Tools or LLVM/MinGW)"
61
+ else
62
+ "building Zui on Linux requires CMake, a C++17 compiler, and Qt 6 Core/Gui/Qml/Quick development packages"
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def bundled
69
+ File.join(@framework_root, "vendor", "host", platform.id, executable_name)
70
+ end
71
+
72
+ def cached
73
+ cache_root = @environment["XDG_CACHE_HOME"]
74
+ cache_root = default_cache_root if cache_root.nil? || cache_root.empty?
75
+ File.join(cache_root, "zui", "host", VERSION, platform.id, executable_name)
76
+ end
77
+
78
+ def default_cache_root
79
+ home = @environment["HOME"] || @environment["USERPROFILE"] || Dir.home
80
+ if platform.windows?
81
+ @environment["LOCALAPPDATA"] || @environment["APPDATA"] || File.join(home, "AppData", "Local")
82
+ elsif platform.macos?
83
+ File.join(home, "Library", "Caches")
84
+ else
85
+ File.join(home, ".cache")
86
+ end
87
+ end
88
+
89
+ def executable_name = platform.os == :windows ? "zui-host.exe" : "zui-host"
90
+
91
+ def checked_executable(path)
92
+ expanded = File.expand_path(path)
93
+ raise ArgumentError, "ZUI_HOST is not executable: #{expanded}" unless File.executable?(expanded)
94
+ expanded
95
+ end
96
+
97
+ def find_command(name)
98
+ @environment.fetch("PATH", "").split(File::PATH_SEPARATOR).each do |directory|
99
+ command_names(name).each do |command_name|
100
+ path = File.join(directory, command_name)
101
+ return path if File.executable?(path) && !File.directory?(path)
102
+ end
103
+ end
104
+ nil
105
+ end
106
+
107
+ def command_names(name)
108
+ return [name] unless platform.windows? && File.extname(name).empty?
109
+
110
+ extensions = @environment.fetch("PATHEXT", ".COM;.EXE;.BAT;.CMD").split(";")
111
+ [name] + extensions.flat_map { |extension| ["#{name}#{extension.downcase}", "#{name}#{extension.upcase}"] }.uniq
112
+ end
113
+
114
+ def locate_build(build_dir)
115
+ candidates = [
116
+ File.join(build_dir, executable_name),
117
+ File.join(build_dir, "Release", executable_name),
118
+ File.join(build_dir, "zui-host.app", "Contents", "MacOS", "zui-host")
119
+ ]
120
+ candidates.find { |path| File.executable?(path) }
121
+ end
122
+ end
123
+ end
data/lib/zui/node.rb ADDED
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zui
4
+ class Node
5
+ attr_reader :type, :id, :props, :children, :events
6
+
7
+ def initialize(type:, id:, props: {})
8
+ @type = type.to_s
9
+ @id = id.to_s
10
+ @props = props.transform_keys(&:to_s)
11
+ @children = []
12
+ @events = []
13
+ end
14
+
15
+ def to_h
16
+ result = { "type" => type, "id" => id }
17
+ result["props"] = props unless props.empty?
18
+ result["children"] = children.map(&:to_h) unless children.empty?
19
+ result["events"] = events unless events.empty?
20
+ result
21
+ end
22
+
23
+ def subscribe(event)
24
+ @events << event.to_s unless @events.include?(event.to_s)
25
+ end
26
+ end
27
+
28
+ Binding = Struct.new(:node, :property, :reader, :last_value, :animation, keyword_init: true)
29
+ StructuralBinding = Struct.new(:node, :renderer, :last_children, keyword_init: true)
30
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbconfig"
4
+
5
+ module Zui
6
+ Platform = Struct.new(:os, :arch, keyword_init: true) do
7
+ SUPPORTED_OSES = %i[linux macos windows].freeze
8
+
9
+ def self.current
10
+ detect(RbConfig::CONFIG.fetch("host_os"), RbConfig::CONFIG.fetch("host_cpu"))
11
+ end
12
+
13
+ def self.detect(host_os, host_cpu)
14
+ os = case host_os.to_s
15
+ when /darwin|mac\s?os/i then :macos
16
+ when /linux/i then :linux
17
+ when /mswin|mingw|cygwin|windows/i then :windows
18
+ else :unsupported
19
+ end
20
+ arch = case host_cpu.to_s
21
+ when /aarch64|arm64/i then :arm64
22
+ when /x86_64|amd64|x64/i then :x86_64
23
+ else host_cpu.to_s.downcase.gsub(/[^a-z0-9_]+/, "_").to_sym
24
+ end
25
+ new(os:, arch:)
26
+ end
27
+
28
+ def supported? = SUPPORTED_OSES.include?(os)
29
+ def id = "#{os}-#{arch}"
30
+ def linux? = os == :linux
31
+ def macos? = os == :macos
32
+ def windows? = os == :windows
33
+
34
+ def assert_supported!
35
+ return self if supported?
36
+
37
+ raise ArgumentError, "Zui desktop applications support Linux, macOS, and Windows; detected #{os}/#{arch}"
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zui
4
+ PROTOCOL_VERSION = 1
5
+ MAX_MESSAGE_BYTES = 1_048_576
6
+ class AsciiPattern
7
+ def initialize(min:, max:, first: nil, rest:)
8
+ @min = min
9
+ @max = max
10
+ @first = first
11
+ @rest = rest
12
+ end
13
+
14
+ def match?(value)
15
+ text = value.to_s
16
+ return false if text.length < @min || text.length > @max
17
+ return false if @first && !@first.include?(text[0])
18
+ text.each_char.all? { |character| @rest.include?(character) }
19
+ end
20
+ end
21
+
22
+ LOWER = ("a".."z").to_a.join.freeze
23
+ UPPER = ("A".."Z").to_a.join.freeze
24
+ DIGITS = ("0".."9").to_a.join.freeze
25
+ VALID_ID = AsciiPattern.new(min: 1, max: 128, rest: LOWER + UPPER + DIGITS + "_.:-")
26
+ VALID_EVENT = AsciiPattern.new(min: 1, max: 64, first: LOWER, rest: LOWER + DIGITS + "_")
27
+
28
+ class ProtocolError < StandardError; end
29
+ end
data/lib/zui/runner.rb ADDED
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbconfig"
4
+
5
+ module Zui
6
+ class Runner
7
+ def initialize(host: Host.new, framework_root: FRAMEWORK_ROOT, ruby: RbConfig.ruby)
8
+ @host = host
9
+ @framework_root = framework_root
10
+ @ruby = ruby
11
+ end
12
+
13
+ def run(file, name: nil, environment: ENV.to_h)
14
+ program = File.expand_path(file)
15
+ raise ArgumentError, "Ruby file not found: #{program}" unless File.file?(program)
16
+ executable = @host.executable
17
+ project = File.dirname(program)
18
+ arguments = [
19
+ executable,
20
+ "--qml-root", @framework_root,
21
+ "--project", project,
22
+ "--program", program,
23
+ "--ruby", @ruby,
24
+ "--load-path", File.join(@framework_root, "lib"),
25
+ "--name", name || project_name(project)
26
+ ]
27
+ system(environment, *arguments)
28
+ $?&.exitstatus || 1
29
+ end
30
+
31
+ private
32
+
33
+ def project_name(path)
34
+ File.basename(path).split(/[-_]/).map(&:capitalize).join(" ")
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module Zui
6
+ module Runtime
7
+ module_function
8
+
9
+ QML_FILES = %w[Desktop.qml Service.qml ControlNode.qml].freeze
10
+ QML_DIRECTORIES = %w[Components Controls Theme].freeze
11
+
12
+ def install_qml(destination, framework_root: FRAMEWORK_ROOT)
13
+ FileUtils.mkdir_p(destination)
14
+ QML_FILES.each do |name|
15
+ FileUtils.cp(File.join(framework_root, name), File.join(destination, name))
16
+ end
17
+ QML_DIRECTORIES.each do |name|
18
+ FileUtils.cp_r(File.join(framework_root, name), File.join(destination, name))
19
+ end
20
+ destination
21
+ end
22
+ end
23
+ end