scarpe 0.3.0 → 0.5.0

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 (297) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/rules/commit-style-preferences.mdc +72 -0
  3. data/.cursor/rules/component_context.mdc +82 -0
  4. data/.cursor/rules/debug-failed-tests.mdc +100 -0
  5. data/.cursor/rules/display_service_context.mdc +80 -0
  6. data/.cursor/rules/event_handling_context.mdc +100 -0
  7. data/.cursor/rules/git-pager-handling.mdc +64 -0
  8. data/.cursor/rules/lacci-context.mdc +52 -0
  9. data/.cursor/rules/scarpe_design_context.mdc +46 -0
  10. data/.cursor/rules/shoes_compatibility_context.mdc +75 -0
  11. data/.cursor/rules/timeout_context.mdc +78 -0
  12. data/.cursor/rules/update_lacci_and_wv.mdc +8 -0
  13. data/.cursor/rules/what_is_scarpe.mdc +22 -0
  14. data/.cursor/rules/writing-new-rules.mdc +73 -0
  15. data/CHANGELOG.md +46 -5
  16. data/CLAUDE.md +223 -0
  17. data/Gemfile +2 -2
  18. data/Gemfile.lock +85 -55
  19. data/LICENSE.txt +7 -1
  20. data/README.md +80 -20
  21. data/Rakefile +73 -14
  22. data/docs/SCARPE_FEATURES.md +38 -0
  23. data/docs/_config.yml +13 -0
  24. data/docs/calzini_components_and_updates.md +78 -0
  25. data/docs/display_service_separation.md +39 -0
  26. data/docs/documentation.md +43 -0
  27. data/docs/event_loops.md +66 -0
  28. data/docs/image.png +0 -0
  29. data/docs/index.md +118 -0
  30. data/docs/lacci.md +121 -0
  31. data/docs/scarpe_shoes_incompatibilities.md +71 -0
  32. data/docs/shoes_and_display_events.md +55 -0
  33. data/docs/shoes_implementations.md +79 -0
  34. data/docs/static/manual.md +5 -0
  35. data/docs/static/scarpe-logo.png +0 -0
  36. data/docs/timeouts_and_handlers.md +66 -0
  37. data/docs/web_archaeology.md +76 -0
  38. data/examples/Edit_box_Styles.rb +8 -0
  39. data/examples/Kerning.rb +7 -0
  40. data/examples/background_with_image.rb +14 -5
  41. data/examples/bloopsaphone/working/feepogram.rb +1 -1
  42. data/examples/bloopsaphone/working/le_dance_des_rubis.rb +135 -0
  43. data/examples/bloopsaphone/working/pixel_dreams_in_ruby.rb +131 -0
  44. data/examples/bloopsaphone/working/type_rebellion.rb +157 -0
  45. data/examples/border.rb +11 -0
  46. data/examples/check.rb +2 -0
  47. data/examples/download_and_show_image.rb +3 -0
  48. data/examples/flags/finland.rb +15 -0
  49. data/examples/flags/italy.rb +11 -0
  50. data/examples/flags/mauritius.rb +14 -0
  51. data/examples/font_family.rb +17 -0
  52. data/examples/font_shorthand.rb +9 -0
  53. data/examples/gen.rb +4 -0
  54. data/examples/internal_link_navigation.rb +19 -0
  55. data/examples/legacy/not_checked/shoes-manual/append.rb +10 -0
  56. data/examples/legacy/not_checked/shoes-manual/background_change.rb +12 -0
  57. data/examples/legacy/not_checked/shoes-manual/background_pattern.rb +5 -0
  58. data/examples/legacy/not_checked/shoes-manual/basic_app.rb +8 -0
  59. data/examples/legacy/not_checked/shoes-manual/border.rb +9 -0
  60. data/examples/legacy/not_checked/shoes-manual/builtins/FONTS.rb +5 -0
  61. data/examples/legacy/not_checked/shoes-manual/builtins/ask.rb +2 -0
  62. data/examples/legacy/not_checked/shoes-manual/builtins/ask_color.rb +5 -0
  63. data/examples/legacy/not_checked/shoes-manual/builtins/ask_open_file.rb +5 -0
  64. data/examples/legacy/not_checked/shoes-manual/builtins/ask_save_folder.rb +2 -0
  65. data/examples/legacy/not_checked/shoes-manual/builtins/confirm.rb +4 -0
  66. data/examples/legacy/not_checked/shoes-manual/builtins/debug.rb +2 -0
  67. data/examples/legacy/not_checked/shoes-manual/builtins/info.rb +3 -0
  68. data/examples/legacy/not_checked/shoes-manual/button.rb +9 -0
  69. data/examples/legacy/not_checked/shoes-manual/clear.rb +7 -0
  70. data/examples/legacy/not_checked/shoes-manual/custom_header.rb +13 -0
  71. data/examples/legacy/not_checked/shoes-manual/displace.rb +14 -0
  72. data/examples/legacy/not_checked/shoes-manual/edit_box.rb +8 -0
  73. data/examples/legacy/not_checked/shoes-manual/fill_pattern.rb +5 -0
  74. data/examples/legacy/not_checked/shoes-manual/fonts.rb +7 -0
  75. data/examples/legacy/not_checked/shoes-manual/gutter.rb +6 -0
  76. data/examples/legacy/not_checked/shoes-manual/image_web.rb +4 -0
  77. data/examples/legacy/not_checked/shoes-manual/keypress.rb +7 -0
  78. data/examples/legacy/not_checked/shoes-manual/list_box.rb +10 -0
  79. data/examples/legacy/not_checked/shoes-manual/motion.rb +10 -0
  80. data/examples/legacy/not_checked/shoes-manual/mouse.rb +8 -0
  81. data/examples/legacy/not_checked/shoes-manual/move.rb +14 -0
  82. data/examples/legacy/not_checked/shoes-manual/nested_ovals.rb +8 -0
  83. data/examples/legacy/not_checked/shoes-manual/oval.rb +7 -0
  84. data/examples/legacy/not_checked/shoes-manual/ovals.rb +6 -0
  85. data/examples/legacy/not_checked/shoes-manual/ovals_image.rb +8 -0
  86. data/examples/legacy/not_checked/shoes-manual/prepend.rb +7 -0
  87. data/examples/legacy/not_checked/shoes-manual/progress_bar.rb +10 -0
  88. data/examples/legacy/not_checked/shoes-manual/radio.rb +18 -0
  89. data/examples/legacy/not_checked/shoes-manual/radio_alternative_1.rb +7 -0
  90. data/examples/legacy/not_checked/shoes-manual/radio_alternative_2.rb +9 -0
  91. data/examples/legacy/not_checked/shoes-manual/rotate_rectangle.rb +6 -0
  92. data/examples/legacy/not_checked/shoes-manual/shape.rb +11 -0
  93. data/examples/legacy/not_checked/shoes-manual/static/avatar.png +0 -0
  94. data/examples/legacy/not_checked/shoes-manual/stroke.rb +5 -0
  95. data/examples/legacy/not_checked/shoes-manual/style.rb +3 -0
  96. data/examples/legacy/not_checked/shoes-manual/style_alternative_1.rb +4 -0
  97. data/examples/legacy/not_checked/shoes-manual/style_alternative_2.rb +5 -0
  98. data/examples/legacy/not_checked/shoes-manual/style_length.rb +5 -0
  99. data/examples/legacy/not_checked/shoes-manual/timer.rb +6 -0
  100. data/examples/legacy/not_checked/shoes-manual/trigger_window.rb +8 -0
  101. data/examples/legacy/not_checked/shoes-manual/window_owner.rb +8 -0
  102. data/examples/legacy/working/shoes_manual/alert_button.rb +2 -0
  103. data/examples/legacy/working/shoes_manual/animate.rb +7 -0
  104. data/examples/legacy/working/shoes_manual/background_para.rb +4 -0
  105. data/examples/legacy/working/shoes_manual/button_alternative.rb +7 -0
  106. data/examples/legacy/working/shoes_manual/checkbox.rb +17 -0
  107. data/examples/legacy/working/shoes_manual/download.rb +12 -0
  108. data/examples/legacy/working/shoes_manual/edit_box.rb +6 -0
  109. data/examples/legacy/working/shoes_manual/editline.rb +7 -0
  110. data/examples/legacy/working/shoes_manual/fixed_height.rb +8 -0
  111. data/examples/legacy/working/shoes_manual/fixed_width.rb +12 -0
  112. data/examples/legacy/working/shoes_manual/image.rb +5 -0
  113. data/examples/legacy/working/shoes_manual/instance_variable_check.rb +10 -0
  114. data/examples/legacy/working/shoes_manual/message.rb +18 -0
  115. data/examples/legacy/working/shoes_manual/rectangle.rb +6 -0
  116. data/examples/legacy/working/shoes_manual/save_download.rb +12 -0
  117. data/examples/legacy/working/shoes_manual/self_check.rb +10 -0
  118. data/examples/legacy/working/shoes_manual/stack.rb +7 -0
  119. data/examples/legacy/working/shoes_manual/style_info.rb +8 -0
  120. data/examples/legacy/working/shoes_manual/utf8_support.rb +8 -0
  121. data/examples/legacy/working/shoes_manual/width.rb +4 -0
  122. data/examples/local_assets/multi_image.rb +5 -0
  123. data/examples/local_assets/small.png +0 -0
  124. data/examples/local_fonts.rb +3 -0
  125. data/examples/margin.rb +13 -0
  126. data/examples/margin_check.rb +27 -0
  127. data/examples/oval-with-kwargs.rb +3 -0
  128. data/examples/oval.rb +26 -0
  129. data/examples/page_navigation_single_app.rb +42 -0
  130. data/examples/para_font_styles.rb +17 -0
  131. data/examples/para_font_variant.rb +6 -0
  132. data/examples/para_fontweight.rb +13 -0
  133. data/examples/parse_xl_funnies.rb +3 -0
  134. data/examples/rect.rb +1 -1
  135. data/examples/scarpe_ext.rb +3 -0
  136. data/examples/shapes/star.rb +1 -3
  137. data/examples/shoes_subclass_app.rb +25 -0
  138. data/examples/spacing.rb +1 -1
  139. data/examples/span.rb +4 -2
  140. data/examples/url_routing_example.rb +67 -0
  141. data/lacci/Gemfile +0 -2
  142. data/lacci/Gemfile.lock +4 -32
  143. data/lacci/lacci.gemspec +3 -3
  144. data/lacci/lib/lacci/scarpe_cli.rb +0 -1
  145. data/lacci/lib/lacci/version.rb +1 -1
  146. data/lacci/lib/scarpe/niente/app.rb +12 -1
  147. data/lacci/lib/scarpe/niente/display_service.rb +5 -1
  148. data/lacci/lib/scarpe/niente/drawable.rb +2 -0
  149. data/lacci/lib/scarpe/niente/shoes_spec.rb +10 -5
  150. data/lacci/lib/scarpe/niente.rb +15 -2
  151. data/lacci/lib/shoes/app.rb +204 -105
  152. data/lacci/lib/shoes/constants.rb +24 -2
  153. data/lacci/lib/shoes/display_service.rb +43 -4
  154. data/lacci/lib/shoes/drawable.rb +326 -36
  155. data/lacci/lib/shoes/drawables/arc.rb +4 -26
  156. data/lacci/lib/shoes/drawables/arrow.rb +3 -23
  157. data/lacci/lib/shoes/drawables/border.rb +28 -0
  158. data/lacci/lib/shoes/drawables/button.rb +5 -21
  159. data/lacci/lib/shoes/drawables/check.rb +7 -3
  160. data/lacci/lib/shoes/drawables/document_root.rb +4 -4
  161. data/lacci/lib/shoes/drawables/edit_box.rb +6 -5
  162. data/lacci/lib/shoes/drawables/edit_line.rb +5 -4
  163. data/lacci/lib/shoes/drawables/flow.rb +4 -6
  164. data/lacci/lib/shoes/drawables/font_helper.rb +62 -0
  165. data/lacci/lib/shoes/drawables/image.rb +2 -2
  166. data/lacci/lib/shoes/drawables/line.rb +3 -6
  167. data/lacci/lib/shoes/drawables/link.rb +16 -9
  168. data/lacci/lib/shoes/drawables/list_box.rb +8 -5
  169. data/lacci/lib/shoes/drawables/oval.rb +48 -0
  170. data/lacci/lib/shoes/drawables/para.rb +106 -18
  171. data/lacci/lib/shoes/drawables/progress.rb +2 -1
  172. data/lacci/lib/shoes/drawables/radio.rb +5 -3
  173. data/lacci/lib/shoes/drawables/rect.rb +7 -6
  174. data/lacci/lib/shoes/drawables/shape.rb +4 -3
  175. data/lacci/lib/shoes/drawables/slot.rb +102 -9
  176. data/lacci/lib/shoes/drawables/stack.rb +7 -12
  177. data/lacci/lib/shoes/drawables/star.rb +9 -31
  178. data/lacci/lib/shoes/drawables/text_drawable.rb +93 -34
  179. data/lacci/lib/shoes/drawables/video.rb +3 -2
  180. data/lacci/lib/shoes/drawables/widget.rb +9 -4
  181. data/lacci/lib/shoes/drawables.rb +2 -1
  182. data/lacci/lib/shoes/errors.rb +13 -3
  183. data/lacci/lib/shoes/margin_helper.rb +79 -0
  184. data/lacci/lib/shoes.rb +98 -20
  185. data/lacci/test/.gitignore +1 -0
  186. data/lacci/test/test_draw_context.rb +167 -0
  187. data/lacci/test/test_font_helper.rb +57 -0
  188. data/lacci/test/test_helper.rb +31 -4
  189. data/lacci/test/test_lacci.rb +93 -6
  190. data/lacci/test/test_margin_helper.rb +82 -0
  191. data/lacci/test/test_niente_test_infra.rb +40 -0
  192. data/lacci/test/test_oval.rb +82 -0
  193. data/lacci/test/test_parenting.rb +140 -0
  194. data/lacci/test/test_shoes_errors.rb +15 -13
  195. data/lacci/test/test_text_drawables.rb +23 -0
  196. data/lib/scarpe/assets.rb +19 -0
  197. data/lib/scarpe/cats_cradle.rb +57 -98
  198. data/lib/scarpe/shoes_spec.rb +24 -44
  199. data/lib/scarpe/version.rb +1 -1
  200. data/lib/scarpe/wv/app.rb +1 -0
  201. data/lib/scarpe/wv/arc.rb +0 -4
  202. data/lib/scarpe/wv/border.rb +15 -0
  203. data/lib/scarpe/wv/control_interface.rb +2 -10
  204. data/lib/scarpe/wv/document_root.rb +2 -2
  205. data/lib/scarpe/wv/drawable.rb +6 -40
  206. data/lib/scarpe/wv/edit_box.rb +4 -1
  207. data/lib/scarpe/wv/edit_line.rb +4 -1
  208. data/lib/scarpe/wv/image.rb +2 -5
  209. data/lib/scarpe/wv/link.rb +4 -2
  210. data/lib/scarpe/wv/oval.rb +13 -0
  211. data/lib/scarpe/wv/para.rb +1 -0
  212. data/lib/scarpe/wv/scarpe_extensions.rb +8 -0
  213. data/lib/scarpe/wv/shape.rb +10 -5
  214. data/lib/scarpe/wv/text_drawable.rb +72 -14
  215. data/lib/scarpe/wv/web_wrangler.rb +33 -11
  216. data/lib/scarpe/wv/webview_local_display.rb +6 -2
  217. data/lib/scarpe/wv.rb +16 -2
  218. data/scarpe-components/Gemfile +4 -3
  219. data/scarpe-components/Gemfile.lock +6 -37
  220. data/scarpe-components/README.md +2 -2
  221. data/scarpe-components/assets/bootstrap-themes/bootstrap-cerulean.css +12229 -0
  222. data/scarpe-components/assets/bootstrap-themes/bootstrap-cosmo.css +11810 -0
  223. data/scarpe-components/assets/bootstrap-themes/bootstrap-cyborg.css +12210 -0
  224. data/scarpe-components/assets/bootstrap-themes/bootstrap-darkly.css +12153 -0
  225. data/scarpe-components/assets/bootstrap-themes/bootstrap-flatly.css +12126 -0
  226. data/scarpe-components/assets/bootstrap-themes/bootstrap-icons.min.css +5 -0
  227. data/scarpe-components/assets/bootstrap-themes/bootstrap-journal.css +12099 -0
  228. data/scarpe-components/assets/bootstrap-themes/bootstrap-litera.css +12211 -0
  229. data/scarpe-components/assets/bootstrap-themes/bootstrap-lumen.css +12369 -0
  230. data/scarpe-components/assets/bootstrap-themes/bootstrap-lux.css +11928 -0
  231. data/scarpe-components/assets/bootstrap-themes/bootstrap-materia.css +13184 -0
  232. data/scarpe-components/assets/bootstrap-themes/bootstrap-minty.css +12177 -0
  233. data/scarpe-components/assets/bootstrap-themes/bootstrap-morph.css +12750 -0
  234. data/scarpe-components/assets/bootstrap-themes/bootstrap-pulse.css +11890 -0
  235. data/scarpe-components/assets/bootstrap-themes/bootstrap-quartz.css +12622 -0
  236. data/scarpe-components/assets/bootstrap-themes/bootstrap-sandstone.css +12201 -0
  237. data/scarpe-components/assets/bootstrap-themes/bootstrap-simplex.css +12186 -0
  238. data/scarpe-components/assets/bootstrap-themes/bootstrap-sketchy.css +12451 -0
  239. data/scarpe-components/assets/bootstrap-themes/bootstrap-slate.css +12492 -0
  240. data/scarpe-components/assets/bootstrap-themes/bootstrap-solar.css +12149 -0
  241. data/scarpe-components/assets/bootstrap-themes/bootstrap-spacelab.css +12266 -0
  242. data/scarpe-components/assets/bootstrap-themes/bootstrap-superhero.css +12216 -0
  243. data/scarpe-components/assets/bootstrap-themes/bootstrap-united.css +12077 -0
  244. data/scarpe-components/assets/bootstrap-themes/bootstrap-vapor.css +12549 -0
  245. data/scarpe-components/assets/bootstrap-themes/bootstrap-yeti.css +12325 -0
  246. data/scarpe-components/assets/bootstrap-themes/bootstrap-zephyr.css +12283 -0
  247. data/scarpe-components/assets/bootstrap-themes/bootstrap.bundle.min.js +7 -0
  248. data/scarpe-components/lib/scarpe/components/asset_server.rb +219 -0
  249. data/scarpe-components/lib/scarpe/components/base64.rb +22 -0
  250. data/scarpe-components/lib/scarpe/components/calzini/{art_widgets.rb → art_drawables.rb} +42 -18
  251. data/scarpe-components/lib/scarpe/components/calzini/border.rb +38 -0
  252. data/scarpe-components/lib/scarpe/components/calzini/button.rb +6 -8
  253. data/scarpe-components/lib/scarpe/components/calzini/misc.rb +14 -16
  254. data/scarpe-components/lib/scarpe/components/calzini/para.rb +218 -11
  255. data/scarpe-components/lib/scarpe/components/calzini/slots.rb +16 -60
  256. data/scarpe-components/lib/scarpe/components/calzini.rb +88 -1
  257. data/scarpe-components/lib/scarpe/components/errors.rb +4 -0
  258. data/scarpe-components/lib/scarpe/components/html.rb +4 -1
  259. data/scarpe-components/lib/scarpe/components/minitest_export_reporter.rb +11 -3
  260. data/scarpe-components/lib/scarpe/components/minitest_result.rb +41 -0
  261. data/scarpe-components/lib/scarpe/components/port_helpers.rb +30 -0
  262. data/scarpe-components/lib/scarpe/components/print_logger.rb +17 -2
  263. data/scarpe-components/lib/scarpe/components/process_helpers.rb +37 -0
  264. data/scarpe-components/lib/scarpe/components/segmented_file_loader.rb +1 -1
  265. data/scarpe-components/lib/scarpe/components/tiranti.rb +42 -100
  266. data/scarpe-components/lib/scarpe/components/unit_test_helpers.rb +3 -1
  267. data/scarpe-components/lib/scarpe/components/version.rb +1 -1
  268. data/scarpe-components/scarpe-components.gemspec +1 -1
  269. data/scarpe-components/test/assets/big-image.png +0 -0
  270. data/scarpe-components/test/assets/big-stylesheet.css +497 -0
  271. data/scarpe-components/test/assets/little-image.png +0 -0
  272. data/scarpe-components/test/assets/little-stylesheet.css +1 -0
  273. data/scarpe-components/test/calzini/test_calzini_art_drawables.rb +7 -7
  274. data/scarpe-components/test/calzini/test_calzini_button.rb +7 -5
  275. data/scarpe-components/test/calzini/test_calzini_misc.rb +9 -9
  276. data/scarpe-components/test/calzini/test_calzini_para.rb +6 -9
  277. data/scarpe-components/test/calzini/test_calzini_slots.rb +12 -57
  278. data/scarpe-components/test/calzini/test_calzini_text_drawables.rb +83 -18
  279. data/scarpe-components/test/calzini/test_various.rb +133 -0
  280. data/scarpe-components/test/test_asset_server.rb +72 -0
  281. data/scarpe-components/test/test_components.rb +31 -2
  282. data/scarpe-components/test/test_helper.rb +0 -1
  283. data/scarpe-components/test/test_minitest_result.rb +7 -0
  284. data/scarpe-components/test/test_port_helpers.rb +12 -0
  285. data/scarpe-components/test/test_segmented_app_files.rb +2 -0
  286. data/tasks/check_html_fixtures.rb +140 -0
  287. data/tasks/regenerate_html_fixtures.rb +104 -0
  288. data/templates/class_template_with_shapes.erb +0 -11
  289. metadata +225 -39
  290. data/.rubocop.yml +0 -88
  291. data/lacci/lib/scarpe/niente/logger.rb +0 -29
  292. data/lacci/lib/shoes/drawables/span.rb +0 -27
  293. data/lacci/lib/shoes/spacing.rb +0 -9
  294. data/lib/scarpe/evented_assertions.rb +0 -121
  295. data/lib/scarpe/wv/span.rb +0 -44
  296. data/scarpe-components/lib/scarpe/components/calzini/text_widgets.rb +0 -65
  297. /data/examples/legacy/{not_checked → working}/shoes3-tests/editline/editline.rb +0 -0
@@ -16,7 +16,7 @@ module Scarpe::Components::Calzini
16
16
  oninput = handler_js_code("change", "this.value")
17
17
 
18
18
  HTML.render do |h|
19
- h.textarea(id: html_id, oninput: oninput, style: edit_box_style(props)) { props["text"] }
19
+ h.textarea(id: html_id, oninput: oninput,onmouseover: handler_js_code("hover"), style: edit_box_style(props),title: props["tooltip"]) { props["text"] }
20
20
  end
21
21
  end
22
22
 
@@ -24,12 +24,20 @@ module Scarpe::Components::Calzini
24
24
  oninput = handler_js_code("change", "this.value")
25
25
 
26
26
  HTML.render do |h|
27
- h.input(id: html_id, oninput: oninput, value: props["text"], style: edit_line_style(props))
27
+ h.input(
28
+ id: html_id,
29
+ type: props["secret"] ? :password : :text,
30
+ oninput: oninput,
31
+ onmouseover: handler_js_code("hover"),
32
+ value: props["text"],
33
+ style: edit_line_style(props),
34
+ title: props["tooltip"]
35
+ )
28
36
  end
29
37
  end
30
38
 
31
39
  def image_element(props)
32
- style = image_style(props)
40
+ style = drawable_style(props)
33
41
 
34
42
  if props["click"]
35
43
  HTML.render do |h|
@@ -111,26 +119,16 @@ module Scarpe::Components::Calzini
111
119
  drawable_style(props).merge({
112
120
  height: dimensions_length(props["height"]),
113
121
  width: dimensions_length(props["width"]),
122
+ font: props["font"]? parse_font(props) : nil
114
123
  }.compact)
115
124
  end
116
125
 
117
126
  def edit_line_style(props)
118
127
  styles = drawable_style(props)
119
128
 
129
+ styles[:font] = props["font"]? parse_font(props) : nil
120
130
  styles[:width] = dimensions_length(props["width"]) if props["width"]
121
-
122
- styles
123
- end
124
-
125
- def image_style(props)
126
- styles = drawable_style(props)
127
-
128
- styles[:width] = dimensions_length(props["width"]) if props["width"]
129
- styles[:height] = dimensions_length(props["height"]) if props["height"]
130
-
131
- styles[:top] = dimensions_length(props["top"]) if props["top"]
132
- styles[:left] = dimensions_length(props["left"]) if props["left"]
133
- styles[:position] = "absolute" if props["top"] || props["left"]
131
+ styles[:color] = rgb_to_hex(props["stroke"])
134
132
 
135
133
  styles
136
134
  end
@@ -1,27 +1,166 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Scarpe::Components::Calzini
4
- # para_element is a bit of a hard one, since it does not-entirely-trivial
5
- # mapping between display objects and IDs. But we don't want Calzini
6
- # messing with the display service or display objects.
7
4
  def para_element(props, &block)
5
+ # Align requires an extra wrapping div.
6
+
7
+ # Stacking strikethrough with underline requires multiple elements.
8
+ # We handle this by making strikethrough part of the main element,
9
+ # but using an extra wrapping element for underline.
10
+
11
+ tag = props["tag"] || "p"
12
+
13
+ para_styles, extra_styles = para_style(props)
14
+
8
15
  HTML.render do |h|
9
- h.p(**para_options(props), &block)
16
+ if extra_styles.empty?
17
+ h.send(tag, id: html_id, style: para_styles, &block)
18
+ else
19
+ h.div(id: html_id, style: extra_styles.merge(width: "100%")) do
20
+ h.send(tag, style: para_styles, &block)
21
+ end
22
+ end
10
23
  end
11
24
  end
12
25
 
13
26
  private
14
27
 
15
- def para_options(props)
16
- (props["html_attributes"] || {}).merge(id: html_id, style: para_style(props))
28
+ def para_style(props)
29
+
30
+ ds = drawable_style(props)
31
+ s1, s2 = text_specific_styles(props)
32
+ [ds.merge(s1), s2]
17
33
  end
18
34
 
19
- def para_style(props)
20
- drawable_style(props).merge({
21
- color: rgb_to_hex(props["stroke"]),
35
+ def text_specific_styles(props)
36
+ # Shoes3 allows align: right on TextDrawables like em(), but it does
37
+ # nothing. We can ignore it or (maybe in future?) warn if we see it.
38
+
39
+ strikethrough = props["strikethrough"]
40
+ strikethrough = nil if strikethrough == "" || strikethrough == "none"
41
+ s1 = {
42
+ "font-variant": props["font_variant"],
43
+ "color": rgb_to_hex(props["stroke"]),
44
+ "background-color": rgb_to_hex(props["fill"]),
22
45
  "font-size": para_font_size(props),
23
- "font-family": props["font"],
24
- }.compact)
46
+ "font-family": props["family"],
47
+ "text-decoration-line": strikethrough ? "line-through" : nil,
48
+ "text-decoration-color": props["strikecolor"] ? rgb_to_hex(props["strikecolor"]) : nil,
49
+ "font-weight": props["font_weight"]? props["font_weight"] : nil,
50
+ :'font-style' => case props["emphasis"]
51
+ when "normal"
52
+ "normal"
53
+ when "oblique"
54
+ "oblique"
55
+ when "italic"
56
+ "italic"
57
+ else
58
+ nil
59
+ end,
60
+ :'letter-spacing' => props["kerning"] ? "#{props["kerning"]}px" : nil,
61
+ :'vertical-align' => props["rise"] ? "#{props["rise"]}px" : nil
62
+ }.compact
63
+
64
+ s2 = {}
65
+ if props["align"] && props["align"] != ""
66
+ s2[:"text-align"] = props["align"]
67
+ end
68
+
69
+ unless [nil, "none"].include?(props["underline"])
70
+ undercolor = rgb_to_hex props["undercolor"]
71
+ s2["text-decoration-color"] = undercolor if undercolor
72
+ end
73
+
74
+ # [nil, "none", "single", "double", "low", "error"]
75
+ case props["underline"]
76
+ when nil, "none"
77
+ # Do nothing
78
+ when "single"
79
+ s2["text-decoration-line"] = "underline"
80
+ when "double"
81
+ s2["text-decoration-line"] = "underline"
82
+ s2["text-decoration-style"] = "double"
83
+ when "error"
84
+ s2["text-decoration-line"] = "underline"
85
+ s2["text-decoration-style"] = "wavy"
86
+ when "low"
87
+ s2["text-decoration-line"] = "underline"
88
+ s2["text-underline-offset"] = "0.3rem"
89
+ else
90
+ # This should normally be unreachable
91
+ raise Shoes::Errors::InvalidAttributeValueError, "Unexpected underline type #{props["underline"].inspect}!"
92
+ end
93
+
94
+ [s1, s2]
95
+ end
96
+
97
+
98
+ def parse_font(props)
99
+
100
+ def contains_number?(str)
101
+ !!(str =~ /\d/)
102
+ end
103
+ def contains_only_numbers?(string)
104
+ /^\d+\z/ =~ string
105
+ end
106
+
107
+
108
+ input = props["font"]
109
+ regex = /\s+(?=(?:[^']*'[^']*')*[^']*$)(?![^']*,[^']*')/
110
+ result = input.split(regex)
111
+
112
+ fs = "normal"
113
+ fv = "normal"
114
+ fw = "normal"
115
+ fss = "medium"
116
+ ff = "Arial"
117
+
118
+ fos = ["italic", "oblique"]
119
+ fov = ["small-caps", "initial", "inherit"]
120
+ fow = ["bold", "bolder", "lighter", "100", "200", "300", "400", "500", "600", "700", "800", "900"]
121
+ foss = ["xx-small", "x-small", "small","large", "x-large", "xx-large", "smaller", "larger"]
122
+
123
+ result.each do |i|
124
+ if fos.include?(i)
125
+ fs = i
126
+ next
127
+ elsif fov.include?(i)
128
+ fv = i
129
+ next
130
+ elsif fow.include?(i)
131
+ fw = i
132
+ next
133
+ elsif foss.include?(i)
134
+ fss = i
135
+ next
136
+ else
137
+ if contains_number?(i)
138
+
139
+ if contains_only_numbers?(i)
140
+ fss = i + "px"
141
+ else
142
+ fss = i
143
+ end
144
+
145
+ elsif i != "normal" && i != "medium" && i.strip != ""
146
+
147
+ if ff == "Arial"
148
+
149
+ ff = i
150
+
151
+ else
152
+
153
+ ff = ff+ i
154
+
155
+ end
156
+ end
157
+ end
158
+
159
+ end
160
+
161
+
162
+ "#{fs} #{fv} #{fw} #{fss} #{ff}"
163
+
25
164
  end
26
165
 
27
166
  def para_font_size(props)
@@ -32,4 +171,72 @@ module Scarpe::Components::Calzini
32
171
 
33
172
  dimensions_length(font_size)
34
173
  end
174
+
175
+ public
176
+
177
+ # The text element is used to render the equivalent of Shoes cText,
178
+ # which includes em, strong, span, link and so on. We use a
179
+ # "content" tag for it which alternates plaintext with a hash of
180
+ # properties.
181
+ def text_drawable_element(prop_array)
182
+ out = String.new # Need unfrozen string
183
+
184
+ # Each item should be a String or a property Hash
185
+ # :items, :html_id, :tag, :props
186
+ prop_array.each do |item|
187
+ if item.is_a?(String)
188
+ out << item.gsub("\n", "<br/>")
189
+ else
190
+ s, extra = text_drawable_style(item[:props])
191
+ out << HTML.render do |h|
192
+ if extra.empty?
193
+ h.send(
194
+ item[:tag] || "span",
195
+ class: "id_#{item[:html_id]}",
196
+ style: s,
197
+ **text_drawable_attrs(item[:props])
198
+ ) do
199
+ text_drawable_element(item[:items])
200
+ end
201
+ else
202
+ h.span(class: "id_#{item[:html_id]}", style: extra) do
203
+ h.send(
204
+ item[:tag] || "span",
205
+ class: "id_#{item[:html_id]}",
206
+ style: s,
207
+ **text_drawable_attrs(item[:props])
208
+ ) do
209
+ text_drawable_element(item[:items])
210
+ end
211
+ end
212
+ end
213
+ end
214
+ end
215
+ end
216
+
217
+ out
218
+ end
219
+
220
+ private
221
+
222
+ def text_drawable_attrs(props)
223
+ click_value = props["click"]
224
+ # Internal routes (starting with /) should use onclick, not href
225
+ # External URLs use href for normal browser navigation
226
+ is_internal_route = click_value.is_a?(String) && click_value.start_with?("/")
227
+
228
+ {
229
+ # These properties will normally only be set by link()
230
+ href: is_internal_route ? nil : click_value,
231
+ onclick: props["has_block"] ? handler_js_code("click") : nil,
232
+ }.compact
233
+ end
234
+
235
+ def text_drawable_style(props)
236
+ s, extra_s = text_specific_styles(props)
237
+
238
+ # Add hover styles, perhaps with CSS pseudo-class
239
+
240
+ [drawable_style(props).merge(s), extra_s]
241
+ end
35
242
  end
@@ -3,27 +3,30 @@
3
3
  module Scarpe::Components::Calzini
4
4
  def slot_element(props, &block)
5
5
  HTML.render do |h|
6
- h.div((props["html_attributes"] || {}).merge(id: html_id, style: slot_style(props)), &block)
6
+ h.div((props["html_attributes"] || {}).merge(id: html_id, style: slot_style(props))) do
7
+ h.div(style: { height: "100%", width: "100%" }, &block)
8
+ end
7
9
  end
8
10
  end
9
11
 
10
12
  def flow_element(props, &block)
11
13
  HTML.render do |h|
12
- h.div((props["html_attributes"] || {}).merge(id: html_id, style: flow_style(props)), &block)
14
+ h.div((props["html_attributes"] || {}).merge(id: html_id, style: flow_style(props))) do
15
+ h.div(style: { height: "100%", width: "100%", position: "relative" }, &block)
16
+ end
13
17
  end
14
18
  end
15
19
 
16
20
  def stack_element(props, &block)
17
21
  HTML.render do |h|
18
- h.div((props["html_attributes"] || {}).merge(id: html_id, style: stack_style(props)), &block)
22
+ h.div((props["html_attributes"] || {}).merge(id: html_id, style: stack_style(props))) do
23
+ h.div(style: { height: "100%", width: "100%", position: "relative" }, &block)
24
+ end
19
25
  end
20
26
  end
21
27
 
22
28
  def documentroot_element(props, &block)
23
- HTML.render do |h|
24
- # DocumentRoot rendering intentionally uses flow styles.
25
- h.div((props["html_attributes"] || {}).merge(id: html_id, style: flow_style(props)), &block)
26
- end
29
+ flow_element(props, &block)
27
30
  end
28
31
 
29
32
  private
@@ -32,12 +35,14 @@ module Scarpe::Components::Calzini
32
35
  styles = drawable_style(props)
33
36
  styles = background_style(props, styles)
34
37
  styles = border_style(props, styles)
35
- styles = spacing_styles_for_attr("margin", props, styles)
36
- styles = spacing_styles_for_attr("padding", props, styles)
37
38
 
38
39
  styles[:width] = dimensions_length(props["width"]) if props["width"]
39
40
  styles[:height] = dimensions_length(props["height"]) if props["height"]
40
41
 
42
+ ## A new slot defines a new coordinate system, so absolutely-positioned children
43
+ ## are relative to it. But that's going to need a lot of testing and Shoes3 comparison.
44
+ #styles[:position] = "relative"
45
+
41
46
  styles
42
47
  end
43
48
 
@@ -95,61 +100,12 @@ module Scarpe::Components::Calzini
95
100
  "linear-gradient(45deg, #{bc.first}, #{bc.last})"
96
101
  when ->(value) { File.exist?(value) }
97
102
  "url(data:image/png;base64,#{encode_file_to_base64(bc)})"
103
+ when ->(value) { valid_url?(value) }
104
+ "url(#{bc})"
98
105
  else
99
106
  bc
100
107
  end
101
108
 
102
109
  styles.merge(background: color)
103
110
  end
104
-
105
- SPACING_DIRECTIONS = [:left, :right, :top, :bottom]
106
-
107
- # We extract the appropriate margin and padding from the margin and
108
- # padding properties. If there are no margin or padding properties,
109
- # we fall back to props["options"] margin or padding, if it exists.
110
- #
111
- # Margin or padding (in either props or props["options"]) can be
112
- # a Hash with directions as keys, or an Array of left/right/top/bottom,
113
- # or a constant, which means all four are that constant. You can
114
- # also specify a "margin" plus "margin-top" which is constant but
115
- # margin-top is overridden, or similar.
116
- #
117
- # If any margin or padding property exists in props then we don't
118
- # check props["options"].
119
- def spacing_styles_for_attr(attr, props, styles, with_options: true)
120
- spacing_styles = {}
121
-
122
- case props[attr]
123
- when Hash
124
- props[attr].each do |dir, value|
125
- spacing_styles[:"#{attr}-#{dir}"] = dimensions_length value
126
- end
127
- when Array
128
- SPACING_DIRECTIONS.zip(props[attr]).to_h.compact.each do |dir, value|
129
- spacing_styles[:"#{attr}-#{dir}"] = dimensions_length(value)
130
- end
131
- when String, Numeric
132
- spacing_styles[attr.to_sym] = dimensions_length(props[attr])
133
- end
134
-
135
- SPACING_DIRECTIONS.each do |dir|
136
- if props["#{attr}_#{dir}"]
137
- spacing_styles[:"#{attr}-#{dir}"] = dimensions_length props["#{attr}_#{dir}"]
138
- end
139
- end
140
-
141
- unless spacing_styles.empty?
142
- return styles.merge(spacing_styles)
143
- end
144
-
145
- # We should see if there are spacing properties in props["options"],
146
- # unless we're currently doing that.
147
- if with_options && props["options"]
148
- spacing_styles = spacing_styles_for_attr(attr, props["options"], {}, with_options: false)
149
- styles.merge spacing_styles
150
- else
151
- # No "options" or we already checked it? Return the styles we were given.
152
- styles
153
- end
154
- end
155
111
  end
@@ -68,6 +68,10 @@ module Scarpe::Components::Calzini
68
68
  p {
69
69
  margin: 0;
70
70
  }
71
+ #wrapper-wvroot {
72
+ height: 100%;
73
+ width: 100%;
74
+ }
71
75
  </style>
72
76
  </head>
73
77
  <body id='body-wvroot'>
@@ -110,13 +114,96 @@ module Scarpe::Components::Calzini
110
114
  if props["hidden"]
111
115
  styles[:display] = "none"
112
116
  end
117
+
118
+ # Do we need to set CSS positioning here, especially if displace is set? Position: relative maybe?
119
+ # We need some Shoes3 screenshots and HTML-based tests here...
120
+
121
+ if props["top"] || props["left"]
122
+ styles[:position] = "absolute"
123
+ end
124
+
125
+ styles[:top] = dimensions_length(props["top"]) if props["top"]
126
+ styles[:left] = dimensions_length(props["left"]) if props["left"]
127
+ styles[:width] = dimensions_length(props["width"]) if props["width"]
128
+ styles[:height] = dimensions_length(props["height"]) if props["height"]
129
+ styles[:"margin-left"] = dimensions_length(props["margin_left"]) if props["margin_left"]
130
+ styles[:"margin-right"] = dimensions_length(props["margin_right"]) if props["margin_right"]
131
+ styles[:"margin-top"] = dimensions_length(props["margin_top"]) if props["margin_top"]
132
+ styles[:"margin-bottom"] = dimensions_length(props["margin_bottom"]) if props["margin_bottom"]
133
+
134
+
135
+
136
+ styles = spacing_styles_for_attr("padding", props, styles)
137
+
113
138
  styles
114
139
  end
115
140
 
141
+ SPACING_DIRECTIONS = [:left, :right, :top, :bottom]
142
+
143
+ # We extract the appropriate margin and padding from the margin and
144
+ # padding properties. If there are no margin or padding properties,
145
+ # we fall back to props["options"] margin or padding, if it exists.
146
+ #
147
+ # Margin or padding (in either props or props["options"]) can be
148
+ # a Hash with directions as keys, or an Array of left/right/top/bottom,
149
+ # or a constant, which means all four are that constant. You can
150
+ # also specify a "margin" plus "margin-top" which is constant but
151
+ # margin-top is overridden, or similar.
152
+ #
153
+ # If any margin or padding property exists in props then we don't
154
+ # check props["options"].
155
+ def spacing_styles_for_attr(attr, props, styles, with_options: true)
156
+ spacing_styles = {}
157
+
158
+ case props[attr]
159
+ when Hash
160
+ props[attr].each do |dir, value|
161
+ spacing_styles[:"#{attr}-#{dir}"] = dimensions_length value
162
+ end
163
+ when Array
164
+ SPACING_DIRECTIONS.zip(props[attr]).to_h.compact.each do |dir, value|
165
+ spacing_styles[:"#{attr}-#{dir}"] = dimensions_length(value)
166
+ end
167
+ when String, Numeric
168
+ spacing_styles[attr.to_sym] = dimensions_length(props[attr])
169
+ end
170
+
171
+ SPACING_DIRECTIONS.each do |dir|
172
+ if props["#{attr}_#{dir}"]
173
+ spacing_styles[:"#{attr}-#{dir}"] = dimensions_length props["#{attr}_#{dir}"]
174
+ end
175
+ end
176
+
177
+ unless spacing_styles.empty?
178
+ return styles.merge(spacing_styles)
179
+ end
180
+
181
+ # We should see if there are spacing properties in props["options"],
182
+ # unless we're currently doing that.
183
+ if with_options && props["options"]
184
+ spacing_styles = spacing_styles_for_attr(attr, props["options"], {}, with_options: false)
185
+ styles.merge spacing_styles
186
+ else
187
+ # No "options" or we already checked it? Return the styles we were given.
188
+ styles
189
+ end
190
+ end
191
+
192
+ def first_color_of(*colors)
193
+ colors.compact!
194
+ colors.select! { |c| c != "" }
195
+ rgb_to_hex(colors[0])
196
+ end
197
+
116
198
  # Convert an [r, g, b, a] array to an HTML hex color code
117
199
  # Arrays support alpha. HTML hex does not. So premultiply.
118
200
  def rgb_to_hex(color)
119
- return color if color.nil?
201
+ return nil if color.nil?
202
+ return "#000000" if color == ""
203
+
204
+ # TODO: need to figure out if it's a color name like "aquamarine"
205
+ # or a hex code or an image file to use as a pattern or what.
206
+ return color if color.is_a?(String)
120
207
 
121
208
  r, g, b, a = *color
122
209
  if r.is_a?(Float)
@@ -3,9 +3,13 @@
3
3
  # Also defined in scarpe_core
4
4
  class Scarpe::Error < StandardError; end
5
5
 
6
+ # TODO: this should be under Scarpe::Errors, and also probably merged into the normal
7
+ # Scarpe errors file.
6
8
  module Scarpe
7
9
  class InternalError < Scarpe::Error; end
8
10
 
11
+ class OperationNotAllowedError < Scarpe::Error; end
12
+
9
13
  class FileContentError < Scarpe::Error; end
10
14
 
11
15
  class NoOperationError < Scarpe::Error; end
@@ -20,6 +20,9 @@ class Scarpe::Components::HTML
20
20
  :u,
21
21
  :line,
22
22
  :span,
23
+ :sub,
24
+ :sup,
25
+ :del,
23
26
  :svg,
24
27
  :h1,
25
28
  :h2,
@@ -27,7 +30,7 @@ class Scarpe::Components::HTML
27
30
  :h4,
28
31
  :h5,
29
32
  ].freeze
30
- VOID_TAGS = [:input, :img, :polygon, :source, :link, :path, :rect].freeze
33
+ VOID_TAGS = [:input, :img, :polygon, :source, :link, :path, :rect, :ellipse].freeze
31
34
 
32
35
  TAGS = (CONTENT_TAGS + VOID_TAGS).freeze
33
36
 
@@ -24,11 +24,18 @@ module Minitest
24
24
  # the reporter will be automatically overridden and print to console instead.
25
25
  #
26
26
  # Based on https://gist.github.com/davidwessman/09a13840a8a80080e3842ac3051714c7
27
- class ShoesExportReporter < DefaultReporter
27
+ class ShoesExportReporter < BaseReporter
28
+ class << self
29
+ attr_accessor :metadata
30
+ attr_accessor :export_file
31
+ end
32
+
28
33
  def self.activate!
29
34
  unless ENV["SHOES_MINITEST_EXPORT_FILE"]
30
35
  raise "ShoesExportReporter is available, but no export file was specified! Set SHOES_MINITEST_EXPORT_FILE!"
31
36
  end
37
+ ShoesExportReporter.export_file = File.expand_path(ENV["SHOES_MINITEST_EXPORT_FILE"])
38
+ ShoesExportReporter.metadata ||= {}
32
39
 
33
40
  Minitest::Reporters.use!
34
41
  end
@@ -58,6 +65,7 @@ module Minitest
58
65
  failures: failures,
59
66
  time: result.time,
60
67
  metadata: result.respond_to?(:metadata) ? result.metadata : {},
68
+ exporter_metadata: ShoesExportReporter.metadata,
61
69
  source_location: begin
62
70
  result.source_location
63
71
  rescue
@@ -66,8 +74,8 @@ module Minitest
66
74
  }
67
75
  end
68
76
 
69
- out_file = File.expand_path ENV["SHOES_MINITEST_EXPORT_FILE"]
70
- puts "Writing Minitest results to #{out_file.inspect}."
77
+ out_file = ShoesExportReporter.export_file
78
+ #puts "Writing Minitest results to #{out_file.inspect}."
71
79
  File.write(out_file, JSON.dump(results))
72
80
  end
73
81
  end
@@ -56,6 +56,47 @@ class Scarpe::Components::MinitestResult
56
56
  end
57
57
  end
58
58
 
59
+ def one_word_result
60
+ return "error" if self.error?
61
+ return "fail" if self.fail?
62
+ return "skip" if self.skip?
63
+ "success"
64
+ end
65
+
66
+ def result_and_message
67
+ return ["error", error_message] if self.error?
68
+ return ["fail", fail_message] if self.fail?
69
+ return ["skip", skip_message] if self.skip?
70
+ ["success", "OK"]
71
+ end
72
+
73
+ def console_summary
74
+ return "Error(s): #{@exceptions.inspect}" if self.error?
75
+ return "Failure: #{@failures.inspect}" if self.fail?
76
+ return "Skip: #{skip_message.inspect}" if self.skip?
77
+ "Success!"
78
+ end
79
+
80
+ def check(expect_result: :success, min_asserts: nil, max_asserts: nil)
81
+ unless [:error, :fail, :skip, :success].include?(expect_result)
82
+ raise Scarpe::InternalError, "Expected test result should be one of [:success, :fail, :error, :skip]!"
83
+ end
84
+
85
+ res, msg = result_and_message
86
+ if expect_result.to_s != res
87
+ return [false, "Expected #{expect_result} but got #{res}: #{msg}!"]
88
+ end
89
+
90
+ if min_asserts && @assertions < min_asserts
91
+ return [false, "Expected success with at least #{min_asserts} assertions but found only #{@assertions}!"]
92
+ end
93
+ if max_asserts && @assertions > max_asserts
94
+ return [false, "Expected success with no more than #{max_asserts} assertions but found only #{@assertions}!"]
95
+ end
96
+
97
+ [true, ""]
98
+ end
99
+
59
100
  def error?
60
101
  !@exceptions.empty?
61
102
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "socket"
4
+
5
+ module Scarpe::Components
6
+ module PortHelpers
7
+ MAX_SERVER_STARTUP_WAIT = 5.0
8
+
9
+ def port_working?(ip, port_num)
10
+ begin
11
+ TCPSocket.new(ip, port_num)
12
+ rescue Errno::ECONNREFUSED
13
+ return false
14
+ end
15
+ return true
16
+ end
17
+
18
+ def wait_until_port_working(ip, port_num, max_wait: MAX_SERVER_STARTUP_WAIT)
19
+ t_start = Time.now
20
+ loop do
21
+ if Time.now - t_start > max_wait
22
+ raise "Server on port #{port_num} didn't start up in time!"
23
+ end
24
+
25
+ sleep 0.1
26
+ return if port_working?(ip, port_num)
27
+ end
28
+ end
29
+ end
30
+ end