lacci 0.2.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 (559) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +85 -0
  3. data/.ruby-version +1 -0
  4. data/.vscode/extensions.json +5 -0
  5. data/.yardopts +12 -0
  6. data/CHANGELOG.md +19 -0
  7. data/CODE_OF_CONDUCT.md +84 -0
  8. data/CONTRIBUTING.md +20 -0
  9. data/Gemfile +24 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +178 -0
  12. data/Rakefile +15 -0
  13. data/bin/console +15 -0
  14. data/bin/setup +8 -0
  15. data/dev.yml +7 -0
  16. data/docs/static/PKGBUILD +47 -0
  17. data/docs/static/Shoes.icns +0 -0
  18. data/docs/static/avatar.png +0 -0
  19. data/docs/static/code_highlighter.js +188 -0
  20. data/docs/static/code_highlighter_ruby.js +26 -0
  21. data/docs/static/icon-debug.png +0 -0
  22. data/docs/static/icon-error.png +0 -0
  23. data/docs/static/icon-info.png +0 -0
  24. data/docs/static/icon-warn.png +0 -0
  25. data/docs/static/listbox_button1.png +0 -0
  26. data/docs/static/listbox_button2.png +0 -0
  27. data/docs/static/man-app.png +0 -0
  28. data/docs/static/man-builds.png +0 -0
  29. data/docs/static/man-builds1.png +0 -0
  30. data/docs/static/man-editor-notepad.png +0 -0
  31. data/docs/static/man-editor-osx.png +0 -0
  32. data/docs/static/man-ele-background.png +0 -0
  33. data/docs/static/man-ele-border.png +0 -0
  34. data/docs/static/man-ele-button.png +0 -0
  35. data/docs/static/man-ele-check.png +0 -0
  36. data/docs/static/man-ele-editbox.png +0 -0
  37. data/docs/static/man-ele-editline.png +0 -0
  38. data/docs/static/man-ele-image.png +0 -0
  39. data/docs/static/man-ele-listbox.png +0 -0
  40. data/docs/static/man-ele-progress.png +0 -0
  41. data/docs/static/man-ele-radio.png +0 -0
  42. data/docs/static/man-ele-shape.png +0 -0
  43. data/docs/static/man-ele-textblock.png +0 -0
  44. data/docs/static/man-ele-video.png +0 -0
  45. data/docs/static/man-intro-dmg.png +0 -0
  46. data/docs/static/man-intro-exe.png +0 -0
  47. data/docs/static/man-look-tiger.png +0 -0
  48. data/docs/static/man-look-ubuntu.png +0 -0
  49. data/docs/static/man-look-vista.png +0 -0
  50. data/docs/static/man-run-osx.png +0 -0
  51. data/docs/static/man-run-vista.png +0 -0
  52. data/docs/static/man-run-xp.png +0 -0
  53. data/docs/static/man-shot1.png +0 -0
  54. data/docs/static/manual-en.txt +3529 -0
  55. data/docs/static/manual-ja.txt +2829 -0
  56. data/docs/static/manual.css +171 -0
  57. data/docs/static/manual.md +3528 -0
  58. data/docs/static/menu-corner1.png +0 -0
  59. data/docs/static/menu-corner2.png +0 -0
  60. data/docs/static/menu-gray.png +0 -0
  61. data/docs/static/menu-left.png +0 -0
  62. data/docs/static/menu-right.png +0 -0
  63. data/docs/static/menu-top.png +0 -0
  64. data/docs/static/shoes-dmg.jpg +0 -0
  65. data/docs/static/shoes-icon-blue.png +0 -0
  66. data/docs/static/shoes-icon.png +0 -0
  67. data/docs/static/shoes-manual-apps.gif +0 -0
  68. data/docs/static/shoes_main_window.png +0 -0
  69. data/docs/static/stripe.png +0 -0
  70. data/docs/static/stubs/blank.exe +0 -0
  71. data/docs/static/stubs/blank.hfz +0 -0
  72. data/docs/static/stubs/blank.run +375 -0
  73. data/docs/static/stubs/cocoa-install +0 -0
  74. data/docs/static/stubs/sh-install +49 -0
  75. data/docs/static/stubs/shoes-stub-inject.exe +0 -0
  76. data/docs/static/stubs/shoes-stub.exe +0 -0
  77. data/docs/static/tutor-back.png +0 -0
  78. data/docs/yard/catscradle.md +44 -0
  79. data/docs/yard/template/default/fulldoc/html/setup.rb +13 -0
  80. data/docs/yard/template/default/layout/html/setup.rb +9 -0
  81. data/examples/backdround_with_image.rb +16 -0
  82. data/examples/bloopsaphone/working/1901_by_Aanand_Prasad.rb +478 -0
  83. data/examples/bloopsaphone/working/b1.rb +64 -0
  84. data/examples/bloopsaphone/working/b2.rb +36 -0
  85. data/examples/bloopsaphone/working/bloops_test.rb +41 -0
  86. data/examples/bloopsaphone/working/bloopsaphone_theme_song_by_why.rb +31 -0
  87. data/examples/bloopsaphone/working/feepogram.rb +67 -0
  88. data/examples/bloopsaphone/working/simpsons_theme_song_by_why.rb +14 -0
  89. data/examples/bloopsaphone/working/tune_cheeky_drat.rb +210 -0
  90. data/examples/button.rb +10 -0
  91. data/examples/button_alert.rb +6 -0
  92. data/examples/button_go_away.rb +6 -0
  93. data/examples/button_with_position_and_size.rb +7 -0
  94. data/examples/coffee.rb +14 -0
  95. data/examples/download.rb +5 -0
  96. data/examples/download_and_show_image.rb +28 -0
  97. data/examples/edit_box.rb +8 -0
  98. data/examples/edit_line.rb +8 -0
  99. data/examples/fill.rb +25 -0
  100. data/examples/flow.rb +11 -0
  101. data/examples/fonts.rb +6 -0
  102. data/examples/gen.rb +140 -0
  103. data/examples/get_headers.rb +10 -0
  104. data/examples/hello_world.rb +3 -0
  105. data/examples/highlander.rb +87 -0
  106. data/examples/image/clickable_image.rb +5 -0
  107. data/examples/image/image.rb +3 -0
  108. data/examples/image/image_size.rb +7 -0
  109. data/examples/image/image_with_position_and_size.rb +3 -0
  110. data/examples/info.rb +9 -0
  111. data/examples/legacy/not_checked/expert/colours.rb +105 -0
  112. data/examples/legacy/not_checked/expert/curve-animation.rb +31 -0
  113. data/examples/legacy/not_checked/expert/curve-control-point.rb +51 -0
  114. data/examples/legacy/not_checked/expert/custom-list-box.rb +53 -0
  115. data/examples/legacy/not_checked/expert/definr.rb +23 -0
  116. data/examples/legacy/not_checked/expert/funnies.rb +56 -0
  117. data/examples/legacy/not_checked/expert/minesweeper.rb +267 -0
  118. data/examples/legacy/not_checked/expert/othello.rb +318 -0
  119. data/examples/legacy/not_checked/expert/pong.rb +62 -0
  120. data/examples/legacy/not_checked/expert/tankspank.rb +385 -0
  121. data/examples/legacy/not_checked/expert/tooltips.rb +45 -0
  122. data/examples/legacy/not_checked/expert/url.rb +37 -0
  123. data/examples/legacy/not_checked/expert/video-player.rb +256 -0
  124. data/examples/legacy/not_checked/good/_why-stories.rb +44 -0
  125. data/examples/legacy/not_checked/good/_why-stories.yaml +387 -0
  126. data/examples/legacy/not_checked/good/arc.rb +37 -0
  127. data/examples/legacy/not_checked/good/cardflip.rb +141 -0
  128. data/examples/legacy/not_checked/good/clock.rb +51 -0
  129. data/examples/legacy/not_checked/good/console.rb +21 -0
  130. data/examples/legacy/not_checked/good/follow.rb +26 -0
  131. data/examples/legacy/not_checked/good/image-rotate.rb +14 -0
  132. data/examples/legacy/not_checked/good/paris.svg +7236 -0
  133. data/examples/legacy/not_checked/good/path-animation.rb +46 -0
  134. data/examples/legacy/not_checked/good/plots.rb +100 -0
  135. data/examples/legacy/not_checked/good/reminder.rb +174 -0
  136. data/examples/legacy/not_checked/good/svgview.rb +113 -0
  137. data/examples/legacy/not_checked/good/vjot.rb +56 -0
  138. data/examples/legacy/not_checked/shoes-contrib/.gitignore +2 -0
  139. data/examples/legacy/not_checked/shoes-contrib/README.md +34 -0
  140. data/examples/legacy/not_checked/shoes-contrib/animation/animate-ovals.rb +21 -0
  141. data/examples/legacy/not_checked/shoes-contrib/animation/flowers.rb +59 -0
  142. data/examples/legacy/not_checked/shoes-contrib/animation/happy-trails.rb +31 -0
  143. data/examples/legacy/not_checked/shoes-contrib/animation/mice-satellites.rb +27 -0
  144. data/examples/legacy/not_checked/shoes-contrib/animation/oval-motion.rb +12 -0
  145. data/examples/legacy/not_checked/shoes-contrib/animation/pink-bubbles.rb +34 -0
  146. data/examples/legacy/not_checked/shoes-contrib/animation/pulsate.rb +18 -0
  147. data/examples/legacy/not_checked/shoes-contrib/animation/rotating-star.rb +18 -0
  148. data/examples/legacy/not_checked/shoes-contrib/app/download-and-save.rb +11 -0
  149. data/examples/legacy/not_checked/shoes-contrib/app/download.rb +10 -0
  150. data/examples/legacy/not_checked/shoes-contrib/app/get-google.rb +11 -0
  151. data/examples/legacy/not_checked/shoes-contrib/app/mouse-detection.rb +7 -0
  152. data/examples/legacy/not_checked/shoes-contrib/app/resizeable-false.rb +6 -0
  153. data/examples/legacy/not_checked/shoes-contrib/art/bubble-bullseye.rb +23 -0
  154. data/examples/legacy/not_checked/shoes-contrib/art/faded.rb +17 -0
  155. data/examples/legacy/not_checked/shoes-contrib/art/fill-oval.rb +5 -0
  156. data/examples/legacy/not_checked/shoes-contrib/art/mask.rb +15 -0
  157. data/examples/legacy/not_checked/shoes-contrib/art/oval-gradient.rb +6 -0
  158. data/examples/legacy/not_checked/shoes-contrib/art/star-gradient.rb +4 -0
  159. data/examples/legacy/not_checked/shoes-contrib/basic/basic-edit-box.rb +8 -0
  160. data/examples/legacy/not_checked/shoes-contrib/basic/basic-new-window.rb +8 -0
  161. data/examples/legacy/not_checked/shoes-contrib/basic/basic-oval-image.rb +8 -0
  162. data/examples/legacy/not_checked/shoes-contrib/basic/basic-oval-shape.rb +8 -0
  163. data/examples/legacy/not_checked/shoes-contrib/basic/basic-oval.rb +6 -0
  164. data/examples/legacy/not_checked/shoes-contrib/basic/class-book.rb +43 -0
  165. data/examples/legacy/not_checked/shoes-contrib/basic/class-book.yaml +387 -0
  166. data/examples/legacy/not_checked/shoes-contrib/basic/clock.rb +6 -0
  167. data/examples/legacy/not_checked/shoes-contrib/basic/edit-stack +14 -0
  168. data/examples/legacy/not_checked/shoes-contrib/basic/gradient-shoes.rb +7 -0
  169. data/examples/legacy/not_checked/shoes-contrib/basic/list_box-shape-report.rb +19 -0
  170. data/examples/legacy/not_checked/shoes-contrib/basic/rect-arrow.rb +7 -0
  171. data/examples/legacy/not_checked/shoes-contrib/basic/scribble.rb +8 -0
  172. data/examples/legacy/not_checked/shoes-contrib/basic/search.rb +32 -0
  173. data/examples/legacy/not_checked/shoes-contrib/basic/shoes-notes.rb +16 -0
  174. data/examples/legacy/not_checked/shoes-contrib/basic/url-shoes-subclassing.rb +24 -0
  175. data/examples/legacy/not_checked/shoes-contrib/browser.rb +21 -0
  176. data/examples/legacy/not_checked/shoes-contrib/elements/background-column.rb +3 -0
  177. data/examples/legacy/not_checked/shoes-contrib/elements/background.rb +4 -0
  178. data/examples/legacy/not_checked/shoes-contrib/elements/basic-fps.rb +6 -0
  179. data/examples/legacy/not_checked/shoes-contrib/elements/border-cat.rb +6 -0
  180. data/examples/legacy/not_checked/shoes-contrib/elements/button-block.rb +8 -0
  181. data/examples/legacy/not_checked/shoes-contrib/elements/check-mate.rb +15 -0
  182. data/examples/legacy/not_checked/shoes-contrib/elements/common-styles.rb +13 -0
  183. data/examples/legacy/not_checked/shoes-contrib/elements/displace-animation.rb +13 -0
  184. data/examples/legacy/not_checked/shoes-contrib/elements/edit_box-character-count.rb +7 -0
  185. data/examples/legacy/not_checked/shoes-contrib/elements/edit_line-character-count.rb +7 -0
  186. data/examples/legacy/not_checked/shoes-contrib/elements/image-icon.rb +3 -0
  187. data/examples/legacy/not_checked/shoes-contrib/elements/list_box-select-class.rb +13 -0
  188. data/examples/legacy/not_checked/shoes-contrib/elements/list_box.rb +6 -0
  189. data/examples/legacy/not_checked/shoes-contrib/elements/move-flow-animate.rb +13 -0
  190. data/examples/legacy/not_checked/shoes-contrib/elements/phat-button.rb +7 -0
  191. data/examples/legacy/not_checked/shoes-contrib/elements/popup.rb +7 -0
  192. data/examples/legacy/not_checked/shoes-contrib/elements/progress-bar.rb +10 -0
  193. data/examples/legacy/not_checked/shoes-contrib/elements/radio-dreamcast-favs.rb +17 -0
  194. data/examples/legacy/not_checked/shoes-contrib/elements/timer.rb +6 -0
  195. data/examples/legacy/not_checked/shoes-contrib/elements/width-introspec.rb +8 -0
  196. data/examples/legacy/not_checked/shoes-contrib/events/background-hover.rb +11 -0
  197. data/examples/legacy/not_checked/shoes-contrib/events/motion-detect.rb +9 -0
  198. data/examples/legacy/not_checked/shoes-contrib/events/pressed-key.rb +6 -0
  199. data/examples/legacy/not_checked/shoes-contrib/expert/expert-funnies.rb +51 -0
  200. data/examples/legacy/not_checked/shoes-contrib/expert/expert-irb.rb +112 -0
  201. data/examples/legacy/not_checked/shoes-contrib/expert/expert-othello.rb +319 -0
  202. data/examples/legacy/not_checked/shoes-contrib/good/good-arc.rb +37 -0
  203. data/examples/legacy/not_checked/shoes-contrib/good/good-clock.rb +51 -0
  204. data/examples/legacy/not_checked/shoes-contrib/good/good-follow.rb +26 -0
  205. data/examples/legacy/not_checked/shoes-contrib/good/good-reminder.rb +174 -0
  206. data/examples/legacy/not_checked/shoes-contrib/good/good-vjot.rb +56 -0
  207. data/examples/legacy/not_checked/shoes-contrib/kernel/ask.rb +4 -0
  208. data/examples/legacy/not_checked/shoes-contrib/kernel/ask_color.rb +4 -0
  209. data/examples/legacy/not_checked/shoes-contrib/kernel/ask_open_file.rb +4 -0
  210. data/examples/legacy/not_checked/shoes-contrib/kernel/ask_save_file.rb +7 -0
  211. data/examples/legacy/not_checked/shoes-contrib/kernel/confirm.rb +3 -0
  212. data/examples/legacy/not_checked/shoes-contrib/kernel/debug.rb +4 -0
  213. data/examples/legacy/not_checked/shoes-contrib/kernel/exit.rb +5 -0
  214. data/examples/legacy/not_checked/shoes-contrib/kernel/font.rb +5 -0
  215. data/examples/legacy/not_checked/shoes-contrib/kernel/fonts.rb +3 -0
  216. data/examples/legacy/not_checked/shoes-contrib/manipulation/append-slot.rb +9 -0
  217. data/examples/legacy/not_checked/shoes-contrib/manipulation/clear-slot.rb +6 -0
  218. data/examples/legacy/not_checked/shoes-contrib/manipulation/prepend-slot.rb +6 -0
  219. data/examples/legacy/not_checked/shoes-contrib/manipulation/roll.rb +17 -0
  220. data/examples/legacy/not_checked/shoes-contrib/position/gutter-margin.rb +5 -0
  221. data/examples/legacy/not_checked/shoes-contrib/position/stack-width.rb +3 -0
  222. data/examples/legacy/not_checked/shoes-contrib/simple/simple-accordion.rb +75 -0
  223. data/examples/legacy/not_checked/shoes-contrib/simple/simple-anim-shapes.rb +17 -0
  224. data/examples/legacy/not_checked/shoes-contrib/simple/simple-anim-text.rb +13 -0
  225. data/examples/legacy/not_checked/shoes-contrib/simple/simple-arc.rb +23 -0
  226. data/examples/legacy/not_checked/shoes-contrib/simple/simple-bounce.rb +24 -0
  227. data/examples/legacy/not_checked/shoes-contrib/simple/simple-calc.rb +70 -0
  228. data/examples/legacy/not_checked/shoes-contrib/simple/simple-chipmunk.rb +26 -0
  229. data/examples/legacy/not_checked/shoes-contrib/simple/simple-control-sizes.rb +24 -0
  230. data/examples/legacy/not_checked/shoes-contrib/simple/simple-curve.rb +26 -0
  231. data/examples/legacy/not_checked/shoes-contrib/simple/simple-dialogs.rb +29 -0
  232. data/examples/legacy/not_checked/shoes-contrib/simple/simple-downloader.rb +27 -0
  233. data/examples/legacy/not_checked/shoes-contrib/simple/simple-draw.rb +13 -0
  234. data/examples/legacy/not_checked/shoes-contrib/simple/simple-editor.rb +28 -0
  235. data/examples/legacy/not_checked/shoes-contrib/simple/simple-form.rb +28 -0
  236. data/examples/legacy/not_checked/shoes-contrib/simple/simple-form.shy +0 -0
  237. data/examples/legacy/not_checked/shoes-contrib/simple/simple-mask.rb +21 -0
  238. data/examples/legacy/not_checked/shoes-contrib/simple/simple-menu.rb +31 -0
  239. data/examples/legacy/not_checked/shoes-contrib/simple/simple-menu1.rb +35 -0
  240. data/examples/legacy/not_checked/shoes-contrib/simple/simple-rubygems.rb +29 -0
  241. data/examples/legacy/not_checked/shoes-contrib/simple/simple-slide.rb +45 -0
  242. data/examples/legacy/not_checked/shoes-contrib/simple/simple-sphere.rb +28 -0
  243. data/examples/legacy/not_checked/shoes-contrib/simple/simple-sqlite3.rb +13 -0
  244. data/examples/legacy/not_checked/shoes-contrib/simple/simple-timer.rb +13 -0
  245. data/examples/legacy/not_checked/shoes-contrib/simple/simple-video.rb +13 -0
  246. data/examples/legacy/not_checked/shoes-contrib/styles/alignment.rb +4 -0
  247. data/examples/legacy/not_checked/shoes-contrib/styles/attachment.rb +5 -0
  248. data/examples/legacy/not_checked/shoes-contrib/styles/emphasis.rb +3 -0
  249. data/examples/legacy/not_checked/shoes-contrib/styles/gradient-angle.rb +3 -0
  250. data/examples/legacy/not_checked/shoes-contrib/styles/para-methods.rb +5 -0
  251. data/examples/legacy/not_checked/shoes-contrib/styles/para-red-underlined.rb +3 -0
  252. data/examples/legacy/not_checked/shoes-contrib/styles/para-style-method.rb +4 -0
  253. data/examples/legacy/not_checked/shoes-dep-samples/expert-game-of-life.rb +243 -0
  254. data/examples/legacy/not_checked/shoes-dep-samples/expert-othello.rb +319 -0
  255. data/examples/legacy/not_checked/shoes-dep-samples/good-clock.rb +51 -0
  256. data/examples/legacy/not_checked/shoes-dep-samples/good-follow.rb +26 -0
  257. data/examples/legacy/not_checked/shoes-dep-samples/good-reminder.rb +174 -0
  258. data/examples/legacy/not_checked/shoes-dep-samples/good-vjot.rb +56 -0
  259. data/examples/legacy/not_checked/shoes-dep-samples/simple-accordion.rb +75 -0
  260. data/examples/legacy/not_checked/shoes-dep-samples/simple-anim-shapes.rb +17 -0
  261. data/examples/legacy/not_checked/shoes-dep-samples/simple-anim-text.rb +13 -0
  262. data/examples/legacy/not_checked/shoes-dep-samples/simple-arc.rb +23 -0
  263. data/examples/legacy/not_checked/shoes-dep-samples/simple-bounce.rb +24 -0
  264. data/examples/legacy/not_checked/shoes-dep-samples/simple-calc.rb +70 -0
  265. data/examples/legacy/not_checked/shoes-dep-samples/simple-chipmunk.rb +26 -0
  266. data/examples/legacy/not_checked/shoes-dep-samples/simple-control-sizes.rb +24 -0
  267. data/examples/legacy/not_checked/shoes-dep-samples/simple-curve.rb +26 -0
  268. data/examples/legacy/not_checked/shoes-dep-samples/simple-dialogs.rb +29 -0
  269. data/examples/legacy/not_checked/shoes-dep-samples/simple-draw.rb +13 -0
  270. data/examples/legacy/not_checked/shoes-dep-samples/simple-editor.rb +28 -0
  271. data/examples/legacy/not_checked/shoes-dep-samples/simple-form.rb +28 -0
  272. data/examples/legacy/not_checked/shoes-dep-samples/simple-form.shy +0 -0
  273. data/examples/legacy/not_checked/shoes-dep-samples/simple-mask.rb +21 -0
  274. data/examples/legacy/not_checked/shoes-dep-samples/simple-menu.rb +31 -0
  275. data/examples/legacy/not_checked/shoes-dep-samples/simple-menu1.rb +35 -0
  276. data/examples/legacy/not_checked/shoes-dep-samples/simple-rubygems.rb +29 -0
  277. data/examples/legacy/not_checked/shoes-dep-samples/simple-slide.rb +45 -0
  278. data/examples/legacy/not_checked/shoes-dep-samples/simple-sphere.rb +28 -0
  279. data/examples/legacy/not_checked/shoes-dep-samples/simple-sqlite3.rb +13 -0
  280. data/examples/legacy/not_checked/shoes-dep-samples/simple-timer.rb +13 -0
  281. data/examples/legacy/not_checked/shoes-dep-samples/simple-video.rb +13 -0
  282. data/examples/legacy/not_checked/shoes3-tests/AnemicCinema1926marcelDuchampCut.mp4 +0 -0
  283. data/examples/legacy/not_checked/shoes3-tests/button/button.rb +53 -0
  284. data/examples/legacy/not_checked/shoes3-tests/cache/cache.rb +41 -0
  285. data/examples/legacy/not_checked/shoes3-tests/combo/combo.rb +24 -0
  286. data/examples/legacy/not_checked/shoes3-tests/curl/bare.rb +34 -0
  287. data/examples/legacy/not_checked/shoes3-tests/curl/downloader.rb +40 -0
  288. data/examples/legacy/not_checked/shoes3-tests/curl/m1.rb +11 -0
  289. data/examples/legacy/not_checked/shoes3-tests/curl/m2.rb +12 -0
  290. data/examples/legacy/not_checked/shoes3-tests/curl/m3.rb +11 -0
  291. data/examples/legacy/not_checked/shoes3-tests/curl/m4.rb +5 -0
  292. data/examples/legacy/not_checked/shoes3-tests/curl/typ.rb +30 -0
  293. data/examples/legacy/not_checked/shoes3-tests/cursor/c1.rb +21 -0
  294. data/examples/legacy/not_checked/shoes3-tests/decoration.rb +14 -0
  295. data/examples/legacy/not_checked/shoes3-tests/dialogs/ask.rb +6 -0
  296. data/examples/legacy/not_checked/shoes3-tests/dialogs/confirm.rb +6 -0
  297. data/examples/legacy/not_checked/shoes3-tests/editbox/editbox.rb +36 -0
  298. data/examples/legacy/not_checked/shoes3-tests/editline/editline.rb +27 -0
  299. data/examples/legacy/not_checked/shoes3-tests/events/button.rb +32 -0
  300. data/examples/legacy/not_checked/shoes3-tests/events/button.yaml +40 -0
  301. data/examples/legacy/not_checked/shoes3-tests/events/capture.rb +57 -0
  302. data/examples/legacy/not_checked/shoes3-tests/events/chipmunk.yaml +47 -0
  303. data/examples/legacy/not_checked/shoes3-tests/events/event0.rb +8 -0
  304. data/examples/legacy/not_checked/shoes3-tests/events/event1.rb +51 -0
  305. data/examples/legacy/not_checked/shoes3-tests/events/event1.yaml +26 -0
  306. data/examples/legacy/not_checked/shoes3-tests/events/event2.rb +46 -0
  307. data/examples/legacy/not_checked/shoes3-tests/events/event3.rb +44 -0
  308. data/examples/legacy/not_checked/shoes3-tests/events/event4.rb +65 -0
  309. data/examples/legacy/not_checked/shoes3-tests/events/event5.rb +17 -0
  310. data/examples/legacy/not_checked/shoes3-tests/events/event6.rb +51 -0
  311. data/examples/legacy/not_checked/shoes3-tests/events/replay.rb +60 -0
  312. data/examples/legacy/not_checked/shoes3-tests/gapp/fullscreen.rb +38 -0
  313. data/examples/legacy/not_checked/shoes3-tests/gapp/icon.rb +56 -0
  314. data/examples/legacy/not_checked/shoes3-tests/gapp/mon1.rb +46 -0
  315. data/examples/legacy/not_checked/shoes3-tests/gapp/settings1.rb +22 -0
  316. data/examples/legacy/not_checked/shoes3-tests/gapp/title1.rb +34 -0
  317. data/examples/legacy/not_checked/shoes3-tests/indian.m4a +0 -0
  318. data/examples/legacy/not_checked/shoes3-tests/menus/event.rb +33 -0
  319. data/examples/legacy/not_checked/shoes3-tests/menus/menu1.rb +8 -0
  320. data/examples/legacy/not_checked/shoes3-tests/menus/menu2.rb +25 -0
  321. data/examples/legacy/not_checked/shoes3-tests/menus/menu3.rb +103 -0
  322. data/examples/legacy/not_checked/shoes3-tests/menus/menu4.rb +41 -0
  323. data/examples/legacy/not_checked/shoes3-tests/menus/tests.txt +42 -0
  324. data/examples/legacy/not_checked/shoes3-tests/opacity.rb +13 -0
  325. data/examples/legacy/not_checked/shoes3-tests/plot/^IRX.R +5219 -0
  326. data/examples/legacy/not_checked/shoes3-tests/plot/^SPX +5219 -0
  327. data/examples/legacy/not_checked/shoes3-tests/plot/cstest.rb +51 -0
  328. data/examples/legacy/not_checked/shoes3-tests/plot/gr1.rb +73 -0
  329. data/examples/legacy/not_checked/shoes3-tests/plot/gr2.rb +40 -0
  330. data/examples/legacy/not_checked/shoes3-tests/plot/gr3.rb +47 -0
  331. data/examples/legacy/not_checked/shoes3-tests/plot/gr4.rb +35 -0
  332. data/examples/legacy/not_checked/shoes3-tests/plot/gr5.rb +31 -0
  333. data/examples/legacy/not_checked/shoes3-tests/plot/gr6.rb +35 -0
  334. data/examples/legacy/not_checked/shoes3-tests/plot/gr7.rb +37 -0
  335. data/examples/legacy/not_checked/shoes3-tests/plot/grcsv.rb +219 -0
  336. data/examples/legacy/not_checked/shoes3-tests/plot/manual.rb +40 -0
  337. data/examples/legacy/not_checked/shoes3-tests/progress/progress.rb +29 -0
  338. data/examples/legacy/not_checked/shoes3-tests/radio/clear.rb +20 -0
  339. data/examples/legacy/not_checked/shoes3-tests/radio/group.rb +18 -0
  340. data/examples/legacy/not_checked/shoes3-tests/radio/multiple.rb +32 -0
  341. data/examples/legacy/not_checked/shoes3-tests/radio/nogroup.rb +9 -0
  342. data/examples/legacy/not_checked/shoes3-tests/simpletest.svg +14 -0
  343. data/examples/legacy/not_checked/shoes3-tests/spinner.rb +15 -0
  344. data/examples/legacy/not_checked/shoes3-tests/svg.rb +208 -0
  345. data/examples/legacy/not_checked/shoes3-tests/switch/switch.rb +28 -0
  346. data/examples/legacy/not_checked/shoes3-tests/systray/back.rb +20 -0
  347. data/examples/legacy/not_checked/shoes3-tests/systray/note.rb +17 -0
  348. data/examples/legacy/not_checked/shoes3-tests/terminal/cursor.rb +27 -0
  349. data/examples/legacy/not_checked/shoes3-tests/terminal/ed.rb +65 -0
  350. data/examples/legacy/not_checked/shoes3-tests/terminal/el.rb +50 -0
  351. data/examples/legacy/not_checked/shoes3-tests/terminal/logattr.rb +31 -0
  352. data/examples/legacy/not_checked/shoes3-tests/tests_color.rb +138 -0
  353. data/examples/legacy/not_checked/shoes3-tests/tests_svg.rb +228 -0
  354. data/examples/legacy/not_checked/shoes3-tests/tests_video_vlc.rb +186 -0
  355. data/examples/legacy/not_checked/shoes3-tests/tooltips.rb +20 -0
  356. data/examples/legacy/not_checked/shoes3-tests/video_vlc.rb +242 -0
  357. data/examples/legacy/not_checked/shoes3-tests/wheel/wheel1.rb +21 -0
  358. data/examples/legacy/not_checked/simple/accordion.rb +81 -0
  359. data/examples/legacy/not_checked/simple/anim-shapes.rb +17 -0
  360. data/examples/legacy/not_checked/simple/anim-text.rb +13 -0
  361. data/examples/legacy/not_checked/simple/arc.rb +23 -0
  362. data/examples/legacy/not_checked/simple/bounce.rb +24 -0
  363. data/examples/legacy/not_checked/simple/chipmunk.rb +26 -0
  364. data/examples/legacy/not_checked/simple/control-sizes.rb +24 -0
  365. data/examples/legacy/not_checked/simple/curve-control-points.rb +24 -0
  366. data/examples/legacy/not_checked/simple/curve.rb +26 -0
  367. data/examples/legacy/not_checked/simple/dialogs.rb +29 -0
  368. data/examples/legacy/not_checked/simple/downloader.rb +40 -0
  369. data/examples/legacy/not_checked/simple/downloader1.rb +31 -0
  370. data/examples/legacy/not_checked/simple/draw.rb +13 -0
  371. data/examples/legacy/not_checked/simple/editor.rb +28 -0
  372. data/examples/legacy/not_checked/simple/form.rb +30 -0
  373. data/examples/legacy/not_checked/simple/image.rb +21 -0
  374. data/examples/legacy/not_checked/simple/info.rb +78 -0
  375. data/examples/legacy/not_checked/simple/mask.rb +21 -0
  376. data/examples/legacy/not_checked/simple/mask2.rb +27 -0
  377. data/examples/legacy/not_checked/simple/menu.rb +31 -0
  378. data/examples/legacy/not_checked/simple/menu1.rb +35 -0
  379. data/examples/legacy/not_checked/simple/slide.rb +45 -0
  380. data/examples/legacy/not_checked/simple/sphere.rb +28 -0
  381. data/examples/legacy/not_checked/superleg.rb +244 -0
  382. data/examples/legacy/working/shoes-contrib/basic/a-poem.rb +24 -0
  383. data/examples/legacy/working/shoes-contrib/basic/basic-blocks-instances.rb +7 -0
  384. data/examples/legacy/working/shoes-contrib/basic/basic-blocks.rb +7 -0
  385. data/examples/legacy/working/shoes-contrib/basic/basic-slot.rb +8 -0
  386. data/examples/legacy/working/shoes-contrib/basic/intro.rb +20 -0
  387. data/examples/legacy/working/shoes-contrib/basic/two-column.rb +12 -0
  388. data/examples/legacy/working/shoes-contrib/elements/edit_box.rb +6 -0
  389. data/examples/legacy/working/shoes-contrib/kernel/alert.rb +3 -0
  390. data/examples/legacy/working/simple/calc.rb +71 -0
  391. data/examples/legacy/working/simple/sqlite3.rb +27 -0
  392. data/examples/legacy/working/simple/timer.rb +13 -0
  393. data/examples/line.rb +5 -0
  394. data/examples/link.rb +20 -0
  395. data/examples/list_box.rb +8 -0
  396. data/examples/local_fonts.rb +4 -0
  397. data/examples/local_images.rb +4 -0
  398. data/examples/motion_events.rb +20 -0
  399. data/examples/para/collection_of_arguments.rb +9 -0
  400. data/examples/para/hello_world.rb +5 -0
  401. data/examples/para/hide_and_show.rb +9 -0
  402. data/examples/para/rainbow.rb +11 -0
  403. data/examples/para/rainbow_2.rb +11 -0
  404. data/examples/para/sizes.rb +11 -0
  405. data/examples/para/sizes_2.rb +12 -0
  406. data/examples/para/strong.rb +3 -0
  407. data/examples/para_text_widgets.rb +4 -0
  408. data/examples/parse_xl_funnies.rb +58 -0
  409. data/examples/pirate.png +0 -0
  410. data/examples/raw_flow.rb +11 -0
  411. data/examples/ruby_racer.rb +50 -0
  412. data/examples/selfitude.rb +18 -0
  413. data/examples/shapes/arc.rb +8 -0
  414. data/examples/shapes/shapes.rb +8 -0
  415. data/examples/shapes/shapes_fill.rb +10 -0
  416. data/examples/shapes/star.rb +5 -0
  417. data/examples/shoes_school.rb +66 -0
  418. data/examples/shoes_splorer.rb +150 -0
  419. data/examples/simple_slides.rb +73 -0
  420. data/examples/skip_ci/parrot.rb +22 -0
  421. data/examples/skip_ci/say.rb +20 -0
  422. data/examples/sleepless.rb +26 -0
  423. data/examples/slots.rb +10 -0
  424. data/examples/spacing.rb +18 -0
  425. data/examples/span.rb +6 -0
  426. data/examples/stack/background.rb +35 -0
  427. data/examples/stack/border.rb +11 -0
  428. data/examples/stack/gradients.rb +6 -0
  429. data/examples/stack/raw_stack.rb +8 -0
  430. data/examples/stack/stack.rb +27 -0
  431. data/examples/text_change.rb +4 -0
  432. data/examples/text_sizes.rb +10 -0
  433. data/examples/timmy.rb +109 -0
  434. data/examples/title_and_resize.rb +3 -0
  435. data/examples/video.rb +10 -0
  436. data/exe/scarpe +107 -0
  437. data/fonts/Pacifico.ttf +0 -0
  438. data/lib/scarpe/alert.rb +19 -0
  439. data/lib/scarpe/arc.rb +49 -0
  440. data/lib/scarpe/background.rb +14 -0
  441. data/lib/scarpe/base64.rb +27 -0
  442. data/lib/scarpe/border.rb +16 -0
  443. data/lib/scarpe/button.rb +35 -0
  444. data/lib/scarpe/cats_cradle.rb +255 -0
  445. data/lib/scarpe/colors.rb +215 -0
  446. data/lib/scarpe/display_service.rb +185 -0
  447. data/lib/scarpe/document_root.rb +20 -0
  448. data/lib/scarpe/download.rb +123 -0
  449. data/lib/scarpe/edit_box.rb +29 -0
  450. data/lib/scarpe/edit_line.rb +25 -0
  451. data/lib/scarpe/fill.rb +23 -0
  452. data/lib/scarpe/flow.rb +18 -0
  453. data/lib/scarpe/font.rb +14 -0
  454. data/lib/scarpe/glibui/README.md +34 -0
  455. data/lib/scarpe/glibui/alert.rb +65 -0
  456. data/lib/scarpe/glibui/app.rb +27 -0
  457. data/lib/scarpe/glibui/background.rb +18 -0
  458. data/lib/scarpe/glibui/border.rb +22 -0
  459. data/lib/scarpe/glibui/button.rb +38 -0
  460. data/lib/scarpe/glibui/dimensions.rb +22 -0
  461. data/lib/scarpe/glibui/document_root.rb +33 -0
  462. data/lib/scarpe/glibui/edit_box.rb +44 -0
  463. data/lib/scarpe/glibui/edit_line.rb +43 -0
  464. data/lib/scarpe/glibui/flow.rb +33 -0
  465. data/lib/scarpe/glibui/html.rb +77 -0
  466. data/lib/scarpe/glibui/image.rb +36 -0
  467. data/lib/scarpe/glibui/link.rb +29 -0
  468. data/lib/scarpe/glibui/local_display.rb +62 -0
  469. data/lib/scarpe/glibui/para.rb +84 -0
  470. data/lib/scarpe/glibui/spacing.rb +41 -0
  471. data/lib/scarpe/glibui/stack.rb +33 -0
  472. data/lib/scarpe/glibui/text_widget.rb +30 -0
  473. data/lib/scarpe/glibui/widget.rb +81 -0
  474. data/lib/scarpe/glibui.rb +29 -0
  475. data/lib/scarpe/image.rb +35 -0
  476. data/lib/scarpe/libui/alert.rb +9 -0
  477. data/lib/scarpe/libui/button.rb +18 -0
  478. data/lib/scarpe/libui/colors.rb +157 -0
  479. data/lib/scarpe/libui/core.rb +66 -0
  480. data/lib/scarpe/libui/flow.rb +16 -0
  481. data/lib/scarpe/libui/libui.rb +45 -0
  482. data/lib/scarpe/libui/notepad.md +26 -0
  483. data/lib/scarpe/libui/para.rb +154 -0
  484. data/lib/scarpe/libui/stack.rb +34 -0
  485. data/lib/scarpe/line.rb +25 -0
  486. data/lib/scarpe/link.rb +25 -0
  487. data/lib/scarpe/list_box.rb +25 -0
  488. data/lib/scarpe/logger.rb +159 -0
  489. data/lib/scarpe/para.rb +92 -0
  490. data/lib/scarpe/promises.rb +405 -0
  491. data/lib/scarpe/shape.rb +20 -0
  492. data/lib/scarpe/slot.rb +7 -0
  493. data/lib/scarpe/spacing.rb +9 -0
  494. data/lib/scarpe/span.rb +26 -0
  495. data/lib/scarpe/stack.rb +20 -0
  496. data/lib/scarpe/star.rb +47 -0
  497. data/lib/scarpe/subscription_item.rb +60 -0
  498. data/lib/scarpe/unit_test_helpers.rb +236 -0
  499. data/lib/scarpe/version.rb +5 -0
  500. data/lib/scarpe/video.rb +15 -0
  501. data/lib/scarpe/widgets.rb +35 -0
  502. data/lib/scarpe/wv/alert.rb +65 -0
  503. data/lib/scarpe/wv/app.rb +104 -0
  504. data/lib/scarpe/wv/arc.rb +57 -0
  505. data/lib/scarpe/wv/background.rb +27 -0
  506. data/lib/scarpe/wv/border.rb +24 -0
  507. data/lib/scarpe/wv/button.rb +50 -0
  508. data/lib/scarpe/wv/control_interface.rb +147 -0
  509. data/lib/scarpe/wv/control_interface_test.rb +234 -0
  510. data/lib/scarpe/wv/dimensions.rb +22 -0
  511. data/lib/scarpe/wv/document_root.rb +8 -0
  512. data/lib/scarpe/wv/edit_box.rb +44 -0
  513. data/lib/scarpe/wv/edit_line.rb +43 -0
  514. data/lib/scarpe/wv/fill.rb +30 -0
  515. data/lib/scarpe/wv/flow.rb +24 -0
  516. data/lib/scarpe/wv/font.rb +36 -0
  517. data/lib/scarpe/wv/html.rb +108 -0
  518. data/lib/scarpe/wv/image.rb +41 -0
  519. data/lib/scarpe/wv/line.rb +38 -0
  520. data/lib/scarpe/wv/link.rb +29 -0
  521. data/lib/scarpe/wv/list_box.rb +50 -0
  522. data/lib/scarpe/wv/para.rb +90 -0
  523. data/lib/scarpe/wv/shape.rb +35 -0
  524. data/lib/scarpe/wv/shape_helper.rb +44 -0
  525. data/lib/scarpe/wv/slot.rb +81 -0
  526. data/lib/scarpe/wv/spacing.rb +41 -0
  527. data/lib/scarpe/wv/span.rb +66 -0
  528. data/lib/scarpe/wv/stack.rb +24 -0
  529. data/lib/scarpe/wv/star.rb +64 -0
  530. data/lib/scarpe/wv/subscription_item.rb +50 -0
  531. data/lib/scarpe/wv/text_widget.rb +30 -0
  532. data/lib/scarpe/wv/video.rb +42 -0
  533. data/lib/scarpe/wv/web_wrangler.rb +683 -0
  534. data/lib/scarpe/wv/webview_local_display.rb +73 -0
  535. data/lib/scarpe/wv/webview_relay_display.rb +185 -0
  536. data/lib/scarpe/wv/widget.rb +185 -0
  537. data/lib/scarpe/wv/wv_display_worker.rb +65 -0
  538. data/lib/scarpe/wv.rb +43 -0
  539. data/lib/scarpe/wv_local.rb +6 -0
  540. data/lib/scarpe/wv_relay.rb +6 -0
  541. data/lib/scarpe.rb +29 -0
  542. data/lib/shoes/app.rb +187 -0
  543. data/lib/shoes/constants.rb +26 -0
  544. data/lib/shoes/text_widget.rb +52 -0
  545. data/lib/shoes/widget.rb +209 -0
  546. data/lib/shoes.rb +65 -0
  547. data/logger/debug_web_wrangler.json +4 -0
  548. data/logger/scarpe_wv_test.json +4 -0
  549. data/scarpe-0.2.0.gem +0 -0
  550. data/scarpe.gemspec +44 -0
  551. data/scarpegen.rb +193 -0
  552. data/sig/scarpe.rbs +4 -0
  553. data/templates/basic_class_template.erb +38 -0
  554. data/templates/class_template_with_event_bind.erb +27 -0
  555. data/templates/class_template_with_shapes.erb +43 -0
  556. data/templates/example_template.erb +3 -0
  557. data/templates/module_template.erb +12 -0
  558. data/templates/webview_template.erb +27 -0
  559. metadata +606 -0
@@ -0,0 +1,683 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webview_ruby"
4
+ require "cgi"
5
+
6
+ # WebWrangler operates in multiple phases: setup and running.
7
+
8
+ # After creation, it starts in setup mode, and you can
9
+ # use setup-mode callbacks.
10
+
11
+ class Scarpe
12
+ class WebWrangler
13
+ include Scarpe::Log
14
+
15
+ attr_reader :is_running
16
+ attr_reader :is_terminated
17
+ attr_reader :heartbeat # This is the heartbeat duration in seconds, usually fractional
18
+ attr_reader :control_interface
19
+
20
+ # This error indicates a problem when running ConfirmedEval
21
+ class JSEvalError < Scarpe::Error
22
+ def initialize(data)
23
+ @data = data
24
+ super(data[:msg] || (self.class.name + "!"))
25
+ end
26
+ end
27
+
28
+ # We got an error running the supplied JS code string in confirmed_eval
29
+ class JSRuntimeError < JSEvalError
30
+ end
31
+
32
+ # The code timed out for some reason
33
+ class JSTimeoutError < JSEvalError
34
+ end
35
+
36
+ # We got weird or nonsensical results that seem like an error on WebWrangler's part
37
+ class InternalError < JSEvalError
38
+ end
39
+
40
+ # This is the JS function name for eval results
41
+ EVAL_RESULT = "scarpeAsyncEvalResult"
42
+
43
+ # Allow a half-second for Webview to finish our JS eval before we decide it's not going to
44
+ EVAL_DEFAULT_TIMEOUT = 0.5
45
+
46
+ def initialize(title:, width:, height:, resizable: false, debug: false, heartbeat: 0.1)
47
+ log_init("WV::WebWrangler")
48
+
49
+ @log.debug("Creating WebWrangler...")
50
+
51
+ # For now, always allow inspect element
52
+ @webview = WebviewRuby::Webview.new debug: true
53
+ @webview = Scarpe::LoggedWrapper.new(@webview, "WebviewAPI") if debug
54
+ @init_refs = {} # Inits don't go away so keep a reference to them to prevent GC
55
+
56
+ @title = title
57
+ @width = width
58
+ @height = height
59
+ @resizable = resizable
60
+ @heartbeat = heartbeat
61
+
62
+ # Better to have a single setInterval than many when we don't care too much
63
+ # about the timing.
64
+ @heartbeat_handlers = []
65
+
66
+ # Need to keep track of which WebView Javascript evals are still pending,
67
+ # what handlers to call when they return, etc.
68
+ @pending_evals = {}
69
+ @eval_counter = 0
70
+
71
+ @dom_wrangler = DOMWrangler.new(self)
72
+
73
+ bind("puts") do |*args|
74
+ puts(*args)
75
+ end
76
+
77
+ @webview.bind(EVAL_RESULT) do |*results|
78
+ receive_eval_result(*results)
79
+ end
80
+
81
+ # Ruby receives scarpeHeartbeat messages via the window library's main loop.
82
+ # So this is a way for Ruby to be notified periodically, in time with that loop.
83
+ @webview.bind("scarpeHeartbeat") do
84
+ return unless @webview # I think GTK+ may continue to deliver events after shutdown
85
+
86
+ periodic_js_callback
87
+ @heartbeat_handlers.each(&:call)
88
+ @control_interface.dispatch_event(:heartbeat)
89
+ end
90
+ js_interval = (heartbeat.to_f * 1_000.0).to_i
91
+ @webview.init("setInterval(scarpeHeartbeat,#{js_interval})")
92
+ end
93
+
94
+ # Shorter name for better stack trace messages
95
+ def inspect
96
+ "Scarpe::WebWrangler:#{object_id}"
97
+ end
98
+
99
+ attr_writer :control_interface
100
+
101
+ ### Setup-mode Callbacks
102
+
103
+ def bind(name, &block)
104
+ raise "App is running, javascript binding no longer works because it uses WebView init!" if @is_running
105
+
106
+ @webview.bind(name, &block)
107
+ end
108
+
109
+ def init_code(name, &block)
110
+ raise "App is running, javascript init no longer works!" if @is_running
111
+
112
+ # Save a reference to the init string so that it goesn't get GC'd
113
+ code_str = "#{name}();"
114
+ @init_refs[name] = code_str
115
+
116
+ bind(name, &block)
117
+ @webview.init(code_str)
118
+ end
119
+
120
+ # Run the specified code periodically, every "interval" seconds.
121
+ # If interface is unspecified, run per-heartbeat, which is very
122
+ # slightly more efficient.
123
+ def periodic_code(name, interval = heartbeat, &block)
124
+ if interval == heartbeat
125
+ @heartbeat_handlers << block
126
+ else
127
+ if @is_running
128
+ # I *think* we need to use init because we want this done for every
129
+ # new window. But will there ever be a new page/window? Can we just
130
+ # use eval instead of init to set up a periodic handler and call it
131
+ # good?
132
+ raise "App is running, can't set up new periodic handlers with init!"
133
+ end
134
+
135
+ js_interval = (interval.to_f * 1_000.0).to_i
136
+ code_str = "setInterval(#{name}, #{js_interval});"
137
+ @init_refs[name] = code_str
138
+
139
+ bind(name, &block)
140
+ @webview.init(code_str)
141
+ end
142
+ end
143
+
144
+ # Running callbacks
145
+
146
+ # js_eventually is a simple JS evaluation. On syntax error, nothing happens.
147
+ # On runtime error, execution stops at the error with no further
148
+ # effect or notification. This is rarely what you want.
149
+ # The js_eventually code is run asynchronously, returning neither error
150
+ # nor value.
151
+ #
152
+ # This method does *not* return a promise, and there is no way to track
153
+ # its progress or its success or failure.
154
+ def js_eventually(code)
155
+ raise "WebWrangler isn't running, eval doesn't work!" unless @is_running
156
+
157
+ @log.warning "Deprecated: please do NOT use js_eventually, it's basically never what you want!" unless ENV["CI"]
158
+
159
+ @webview.eval(code)
160
+ end
161
+
162
+ # Eval a chunk of JS code asynchronously. This method returns a
163
+ # promise which will be fulfilled or rejected after the JS executes
164
+ # or times out.
165
+ #
166
+ # Note that we *both* care whether the JS has finished after it was
167
+ # scheduled *and* whether it ever got scheduled at all. If it
168
+ # depends on tasks that never fulfill or reject then it may wait
169
+ # in limbo, potentially forever.
170
+ #
171
+ # Right now we can't/don't handle arguments from previous fulfilled
172
+ # promises. To do that, we'd probably need to know we were passing
173
+ # in a JS function.
174
+ EVAL_OPTS = [:timeout, :wait_for]
175
+ def eval_js_async(code, opts = {})
176
+ bad_opts = opts.keys - EVAL_OPTS
177
+ raise("Bad options given to eval_with_handler! #{bad_opts.inspect}") unless bad_opts.empty?
178
+
179
+ unless @is_running
180
+ raise "WebWrangler isn't running, so evaluating JS won't work!"
181
+ end
182
+
183
+ this_eval_serial = @eval_counter
184
+ @eval_counter += 1
185
+
186
+ @pending_evals[this_eval_serial] = {
187
+ id: this_eval_serial,
188
+ code: code,
189
+ start_time: Time.now,
190
+ timeout_if_not_scheduled: Time.now + EVAL_DEFAULT_TIMEOUT,
191
+ }
192
+
193
+ # We'll need this inside the promise-scheduling block
194
+ pending_evals = @pending_evals
195
+ timeout = opts[:timeout] || EVAL_DEFAULT_TIMEOUT
196
+
197
+ promise = Scarpe::Promise.new(parents: (opts[:wait_for] || [])) do
198
+ # Are we mid-shutdown?
199
+ if @webview
200
+ wrapped_code = WebWrangler.js_wrapped_code(code, this_eval_serial)
201
+
202
+ # We've been scheduled!
203
+ t_now = Time.now
204
+ # Hard to be sure Webview keeps a proper reference to this, so we will
205
+ pending_evals[this_eval_serial][:wrapped_code] = wrapped_code
206
+
207
+ pending_evals[this_eval_serial][:scheduled_time] = t_now
208
+ pending_evals[this_eval_serial].delete(:timeout_if_not_scheduled)
209
+
210
+ pending_evals[this_eval_serial][:timeout_if_not_finished] = t_now + timeout
211
+ @webview.eval(wrapped_code)
212
+ @log.debug("Scheduled JS: (#{this_eval_serial})\n#{wrapped_code}")
213
+ else
214
+ # We're mid-shutdown. No more scheduling things.
215
+ end
216
+ end
217
+
218
+ @pending_evals[this_eval_serial][:promise] = promise
219
+
220
+ promise
221
+ end
222
+
223
+ def self.js_wrapped_code(code, eval_id)
224
+ <<~JS_CODE
225
+ (function() {
226
+ var code_string = #{JSON.dump code};
227
+ try {
228
+ result = eval(code_string);
229
+ #{EVAL_RESULT}("success", #{eval_id}, result);
230
+ } catch(error) {
231
+ #{EVAL_RESULT}("error", #{eval_id}, error.message);
232
+ }
233
+ })();
234
+ JS_CODE
235
+ end
236
+
237
+ private
238
+
239
+ def periodic_js_callback
240
+ time_out_eval_results
241
+ end
242
+
243
+ def receive_eval_result(r_type, id, val)
244
+ entry = @pending_evals.delete(id)
245
+ unless entry
246
+ raise "Received an eval result for a nonexistent ID #{id.inspect}!"
247
+ end
248
+
249
+ @log.debug("Got JS value: #{r_type} / #{id} / #{val.inspect}")
250
+
251
+ promise = entry[:promise]
252
+
253
+ case r_type
254
+ when "success"
255
+ promise.fulfilled!(val)
256
+ when "error"
257
+ promise.rejected! JSRuntimeError.new(
258
+ msg: "JS runtime error: #{val.inspect}!",
259
+ code: entry[:code],
260
+ ret_value: val,
261
+ )
262
+ else
263
+ promise.rejected! InternalError.new(
264
+ msg: "JS eval internal error! r_type: #{r_type.inspect}",
265
+ code: entry[:code],
266
+ ret_value: val,
267
+ )
268
+ end
269
+ end
270
+
271
+ # TODO: would be good to keep 'tombstone' results for awhile after timeout, maybe up to around a minute,
272
+ # so we can detect if we're timing things out and then having them return successfully after a delay.
273
+ # Then we could adjust the timeouts. We could also check if later serial numbers have returned, and time
274
+ # out earlier serial numbers... *if* we're sure Webview will always execute JS evals in order.
275
+ # This all adds complexity, though. For now, do timeouts on a simple max duration.
276
+ def time_out_eval_results
277
+ t_now = Time.now
278
+ timed_out_from_scheduling = @pending_evals.keys.select do |id|
279
+ t = @pending_evals[id][:timeout_if_not_scheduled]
280
+ t && t_now >= t
281
+ end
282
+ timed_out_from_finish = @pending_evals.keys.select do |id|
283
+ t = @pending_evals[id][:timeout_if_not_finished]
284
+ t && t_now >= t
285
+ end
286
+ timed_out_from_scheduling.each do |id|
287
+ @log.debug("JS timed out because it was never scheduled: (#{id}) #{@pending_evals[id][:code].inspect}")
288
+ end
289
+ timed_out_from_finish.each do |id|
290
+ @log.debug("JS timed out because it never finished: (#{id}) #{@pending_evals[id][:code].inspect}")
291
+ end
292
+
293
+ # A plus *should* be fine since nothing should ever be on both lists. But let's be safe.
294
+ timed_out_ids = timed_out_from_scheduling | timed_out_from_finish
295
+
296
+ timed_out_ids.each do |id|
297
+ @log.error "Timing out JS eval! #{@pending_evals[id][:code]}"
298
+ entry = @pending_evals.delete(id)
299
+ err = JSTimeoutError.new(msg: "JS timeout error!", code: entry[:code], ret_value: nil)
300
+ entry[:promise].rejected!(err)
301
+ end
302
+ end
303
+
304
+ public
305
+
306
+ # After setup, we call run to go to "running" mode.
307
+ # No more setup callbacks, only running callbacks.
308
+
309
+ def run
310
+ @log.debug("Run...")
311
+
312
+ # From webview:
313
+ # 0 - Width and height are default size
314
+ # 1 - Width and height are minimum bonds
315
+ # 2 - Width and height are maximum bonds
316
+ # 3 - Window size can not be changed by a user
317
+ hint = @resizable ? 0 : 3
318
+
319
+ @webview.set_title(@title)
320
+ @webview.set_size(@width, @height, hint)
321
+ @webview.navigate("data:text/html, #{empty}")
322
+
323
+ monkey_patch_console(@webview)
324
+
325
+ @is_running = true
326
+ @webview.run
327
+ @is_running = false
328
+ @webview.destroy
329
+ @webview = nil
330
+ end
331
+
332
+ def destroy
333
+ @log.debug("Destroying WebWrangler...")
334
+ @log.debug(" (WebWrangler was already terminated)") if @is_terminated
335
+ @log.debug(" (WebWrangler was already destroyed)") unless @webview
336
+ if @webview && !@is_terminated
337
+ @bindings = {}
338
+ @webview.terminate
339
+ @is_terminated = true
340
+ end
341
+ end
342
+
343
+ private
344
+
345
+ # TODO: can this be an init()?
346
+ def monkey_patch_console(window)
347
+ # this forwards all console.log/info/error/warn calls also
348
+ # to the terminal that is running the scarpe app
349
+ window.eval <<~JS
350
+ function patchConsole(fn) {
351
+ const original = console[fn];
352
+ console[fn] = function(...args) {
353
+ original(...args);
354
+ puts(...args);
355
+ }
356
+ };
357
+ patchConsole('log');
358
+ patchConsole('info');
359
+ patchConsole('error');
360
+ patchConsole('warn');
361
+ JS
362
+ end
363
+
364
+ def empty
365
+ html = <<~HTML
366
+ <html>
367
+ <head id='head-wvroot'>
368
+ <style id='style-wvroot'>
369
+ /** Style resets **/
370
+ body {
371
+ font-family: arial, Helvetica, sans-serif;
372
+ margin: 0;
373
+ height: 100%;
374
+ overflow: hidden;
375
+ }
376
+ p {
377
+ margin: 0;
378
+ }
379
+ </style>
380
+ </head>
381
+ <body id='body-wvroot'>
382
+ <div id='wrapper-wvroot'></div>
383
+ </body>
384
+ </html>
385
+ HTML
386
+
387
+ CGI.escape(html)
388
+ end
389
+
390
+ public
391
+
392
+ # For now, the WebWrangler gets a bunch of fairly low-level requests
393
+ # to mess with the HTML DOM. This needs to be turned into a nicer API,
394
+ # but first we'll get it all into one place and see what we're doing.
395
+
396
+ # Replace the entire DOM - return a promise for when this has been done.
397
+ # This will often get rid of smaller changes in the queue, which is
398
+ # a good thing since they won't have to be run.
399
+ def replace(html_text)
400
+ @dom_wrangler.request_replace(html_text)
401
+ end
402
+
403
+ # Request a DOM change - return a promise for when this has been done.
404
+ def dom_change(js)
405
+ @dom_wrangler.request_change(js)
406
+ end
407
+
408
+ # Return whether the DOM is, right this moment, confirmed to be fully
409
+ # up to date or not.
410
+ def dom_fully_updated?
411
+ @dom_wrangler.fully_updated?
412
+ end
413
+
414
+ # Return a promise that will be fulfilled when all current DOM changes
415
+ # have committed (but not necessarily any future DOM changes.)
416
+ def dom_promise_redraw
417
+ @dom_wrangler.promise_redraw
418
+ end
419
+
420
+ # Return a promise which will be fulfilled the next time the DOM is
421
+ # fully up to date. Note that a slow trickle of changes can make this
422
+ # take a long time, since it is *not* only changes up to this point.
423
+ # If you want to know that some specific change is done, it's often
424
+ # easiest to use the promise returned by dom_change(), which will
425
+ # be fulfilled when that specific change commits.
426
+ def promise_dom_fully_updated
427
+ @dom_wrangler.promise_fully_updated
428
+ end
429
+
430
+ def on_every_redraw(&block)
431
+ @dom_wrangler.on_every_redraw(&block)
432
+ end
433
+ end
434
+ end
435
+
436
+ # Leaving DOM changes as "meh, async, we'll see when it happens" is terrible for testing.
437
+ # Instead, we need to track whether particular changes have committed yet or not.
438
+ # So we add a single gateway for all DOM changes, and we make sure its work is done
439
+ # before we consider a redraw complete.
440
+ #
441
+ # DOMWrangler batches up changes - it's fine to have a redraw "in flight" and have
442
+ # changes waiting to catch the next bus. But we don't want more than one in flight,
443
+ # since it seems like having too many pending RPC requests can crash Webview. So:
444
+ # one redraw scheduled and one redraw promise waiting around, at maximum.
445
+ class Scarpe
446
+ class WebWrangler
447
+ class DOMWrangler
448
+ include Scarpe::Log
449
+
450
+ attr_reader :waiting_changes
451
+ attr_reader :pending_redraw_promise
452
+ attr_reader :waiting_redraw_promise
453
+
454
+ def initialize(web_wrangler, debug: false)
455
+ log_init("WV::WebWrangler::DOMWrangler")
456
+
457
+ @wrangler = web_wrangler
458
+
459
+ @waiting_changes = []
460
+ @pending_redraw_promise = nil
461
+ @waiting_redraw_promise = nil
462
+
463
+ @fully_up_to_date_promise = nil
464
+
465
+ # Initially we're waiting for a full replacement to happen.
466
+ # It's possible to request updates/changes before we have
467
+ # a DOM in place and before Webview is running. If we do
468
+ # that, we should discard those updates.
469
+ @first_draw_requested = false
470
+
471
+ @redraw_handlers = []
472
+
473
+ # The "fully up to date" logic is complicated and not
474
+ # as well tested as I'd like. This makes it far less
475
+ # likely that the event simply won't fire.
476
+ # With more comprehensive testing, this should be
477
+ # removable.
478
+ web_wrangler.periodic_code("scarpeDOMWranglerHeartbeat") do
479
+ if @fully_up_to_date_promise && fully_updated?
480
+ @log.info("Fulfilling up-to-date promise on heartbeat")
481
+ @fully_up_to_date_promise.fulfilled!
482
+ @fully_up_to_date_promise = nil
483
+ end
484
+ end
485
+ end
486
+
487
+ def request_change(js_code)
488
+ # No updates until there's something to update
489
+ return unless @first_draw_requested
490
+
491
+ @waiting_changes << js_code
492
+
493
+ promise_redraw
494
+ end
495
+
496
+ def self.replacement_code(html_text)
497
+ "document.getElementById('wrapper-wvroot').innerHTML = `#{html_text}`; true"
498
+ end
499
+
500
+ def request_replace(html_text)
501
+ # Replace other pending changes, they're not needed any more
502
+ @waiting_changes = [DOMWrangler.replacement_code(html_text)]
503
+ @first_draw_requested = true
504
+
505
+ @log.debug("Requesting DOM replacement...")
506
+ promise_redraw
507
+ end
508
+
509
+ def on_every_redraw(&block)
510
+ @redraw_handlers << block
511
+ end
512
+
513
+ # What are the states of redraw?
514
+ # "empty" - no waiting promise, no pending-redraw promise, no pending changes
515
+ # "pending only" - no waiting promise, but we have a pending redraw with some changes; it hasn't committed yet
516
+ # "pending and waiting" - we have a waiting promise for our unscheduled changes; we can add more unscheduled
517
+ # changes since we haven't scheduled them yet.
518
+ #
519
+ # This is often called after adding a new waiting change or replacing them, so the state may have just changed.
520
+ # It can also be called when no changes have been made and no updates need to happen.
521
+ def promise_redraw
522
+ if fully_updated?
523
+ # No changes to make, nothing in-process or waiting, so just return a pre-fulfilled promise
524
+ @log.debug("Requesting redraw but there are no pending changes or promises, return pre-fulfilled")
525
+ return Promise.fulfilled
526
+ end
527
+
528
+ # Already have a redraw requested *and* one on deck? Then all current changes will have committed
529
+ # when we (eventually) fulfill the waiting_redraw_promise.
530
+ if @waiting_redraw_promise
531
+ @log.debug("Promising eventual redraw of #{@waiting_changes.size} waiting unscheduled changes.")
532
+ return @waiting_redraw_promise
533
+ end
534
+
535
+ if @waiting_changes.empty?
536
+ # There's no waiting_redraw_promise. There are no waiting changes. But we're not fully updated.
537
+ # So there must be a redraw in flight, and we don't need to schedule a new waiting_redraw_promise.
538
+ @log.debug("Returning in-flight redraw promise")
539
+ return @pending_redraw_promise
540
+ end
541
+
542
+ @log.debug("Requesting redraw with #{@waiting_changes.size} waiting changes and no waiting promise - need to schedule something!")
543
+
544
+ # We have at least one waiting change, possibly newly-added. We have no waiting_redraw_promise.
545
+ # Do we already have a redraw in-flight?
546
+ if @pending_redraw_promise
547
+ # Yes we do. Schedule a new waiting promise. When it turns into the pending_redraw_promise it will
548
+ # grab all waiting changes. In the mean time, it sits here and waits.
549
+ #
550
+ # We *could* do a fancy promise thing and have it update @waiting_changes for itself, etc, when it
551
+ # schedules itself. But we should always be calling promise_redraw or having a redraw fulfilled (see below)
552
+ # when these things change. I'd rather keep the logic in this method. It's easier to reason through
553
+ # all the cases.
554
+ @waiting_redraw_promise = Promise.new
555
+
556
+ @log.debug("Creating a new waiting promise since a pending promise is already in place")
557
+ return @waiting_redraw_promise
558
+ end
559
+
560
+ # We have no redraw in-flight and no pre-existing waiting line. The new change(s) are presumably right
561
+ # after things were fully up-to-date. We can schedule them for immediate redraw.
562
+
563
+ @log.debug("Requesting redraw with #{@waiting_changes.size} waiting changes - scheduling a new redraw for them!")
564
+ promise = schedule_waiting_changes # This clears the waiting changes
565
+ @pending_redraw_promise = promise
566
+
567
+ promise.on_fulfilled do
568
+ @redraw_handlers.each(&:call)
569
+ @pending_redraw_promise = nil
570
+
571
+ if @waiting_redraw_promise
572
+ # While this redraw was in flight, more waiting changes got added and we made a promise
573
+ # about when they'd complete. Now they get scheduled, and we'll fulfill the waiting
574
+ # promise when that redraw finishes. Clear the old waiting promise. We'll add a new one
575
+ # when/if more changes are scheduled during this redraw.
576
+ old_waiting_promise = @waiting_redraw_promise
577
+ @waiting_redraw_promise = nil
578
+
579
+ @log.debug "Fulfilled redraw with #{@waiting_changes.size} waiting changes - scheduling a new redraw for them!"
580
+
581
+ new_promise = promise_redraw
582
+ new_promise.on_fulfilled { old_waiting_promise.fulfilled! }
583
+ else
584
+ # The in-flight redraw completed, and there's still no waiting promise. Good! That means
585
+ # we should be fully up-to-date.
586
+ @log.debug "Fulfilled redraw with no waiting changes - marking us as up to date!"
587
+ if @waiting_changes.empty?
588
+ # We're fully up to date! Fulfill the promise. Now we don't need it again until somebody asks
589
+ # us for another.
590
+ if @fully_up_to_date_promise
591
+ @fully_up_to_date_promise.fulfilled!
592
+ @fully_up_to_date_promise = nil
593
+ end
594
+ else
595
+ @log.error "WHOAH, WHAT? My logic must be wrong, because there's " +
596
+ "no waiting promise, but waiting changes!"
597
+ end
598
+ end
599
+
600
+ @log.debug("Redraw is now fully up-to-date") if fully_updated?
601
+ end.on_rejected do
602
+ @log.error "Could not complete JS redraw! #{promise.reason.full_message}"
603
+ @log.debug("REDRAW FULLY UP TO DATE BUT JS FAILED") if fully_updated?
604
+
605
+ raise "JS Redraw failed! Bailing!"
606
+
607
+ # Later we should figure out how to handle this. Clear the promises and queues and request another redraw?
608
+ end
609
+ end
610
+
611
+ def fully_updated?
612
+ @pending_redraw_promise.nil? && @waiting_redraw_promise.nil? && @waiting_changes.empty?
613
+ end
614
+
615
+ # Return a promise which will be fulfilled when the DOM is fully up-to-date
616
+ def promise_fully_updated
617
+ if fully_updated?
618
+ # No changes to make, nothing in-process or waiting, so just return a pre-fulfilled promise
619
+ return Promise.fulfilled
620
+ end
621
+
622
+ # Do we already have a promise for this? Return it. Everybody can share one.
623
+ if @fully_up_to_date_promise
624
+ return @fully_up_to_date_promise
625
+ end
626
+
627
+ # We're not fully updated, so we need a promise. Create it, return it.
628
+ @fully_up_to_date_promise = Promise.new
629
+ end
630
+
631
+ private
632
+
633
+ # Put together the waiting changes into a new in-flight redraw request.
634
+ # Return it as a promise.
635
+ def schedule_waiting_changes
636
+ return if @waiting_changes.empty?
637
+
638
+ js_code = @waiting_changes.join(";")
639
+ @waiting_changes = [] # They're not waiting any more!
640
+ @wrangler.eval_js_async(js_code)
641
+ end
642
+ end
643
+ end
644
+ end
645
+
646
+ # For now we don't need one of these to add DOM elements, just to manipulate them
647
+ # after initial render.
648
+ class Scarpe
649
+ class WebWrangler
650
+ class ElementWrangler
651
+ attr_reader :html_id
652
+
653
+ def initialize(html_id)
654
+ @webwrangler = WebviewDisplayService.instance.wrangler
655
+ @html_id = html_id
656
+ end
657
+
658
+ def promise_update
659
+ @webwrangler.dom_promise_redraw
660
+ end
661
+
662
+ def value=(new_value)
663
+ @webwrangler.dom_change("document.getElementById('" + html_id + "').value = `" + new_value + "`; true")
664
+ end
665
+
666
+ def inner_text=(new_text)
667
+ @webwrangler.dom_change("document.getElementById('" + html_id + "').innerText = '" + new_text + "'; true")
668
+ end
669
+
670
+ def inner_html=(new_html)
671
+ @webwrangler.dom_change("document.getElementById(\"" + html_id + "\").innerHTML = `" + new_html + "`; true")
672
+ end
673
+
674
+ def set_attribute(attribute, value)
675
+ @webwrangler.dom_change("document.getElementById(\"" + html_id + "\").setAttribute(" + attribute.inspect + "," + value.inspect + "); true")
676
+ end
677
+
678
+ def remove
679
+ @webwrangler.dom_change("document.getElementById('" + html_id + "').remove(); true")
680
+ end
681
+ end
682
+ end
683
+ end