scarpe 0.2.2 → 0.3.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 (223) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -0
  3. data/CHANGELOG.md +16 -2
  4. data/Gemfile.lock +7 -3
  5. data/README.md +24 -8
  6. data/Rakefile +1 -1
  7. data/examples/animate.rb +20 -0
  8. data/examples/arrow.rb +10 -0
  9. data/examples/btn_tooltip.rb +7 -0
  10. data/examples/button_style_changed.rb +7 -0
  11. data/examples/button_styles_default.rb +6 -0
  12. data/examples/gen.rb +8 -8
  13. data/examples/highlander.rb +3 -3
  14. data/examples/legacy/README.md +6 -0
  15. data/examples/legacy/not_checked/shoes-contrib/basic/shoes-notes.rb +1 -1
  16. data/examples/legacy/not_checked/simple/anim-shapes.rb +1 -1
  17. data/examples/legacy/not_checked/speedometer_app.rb +55 -0
  18. data/examples/legacy/working/simple/image-icon.rb +3 -0
  19. data/examples/legacy/{not_checked → working}/simple/image.rb +1 -1
  20. data/examples/list_box_choose.rb +17 -0
  21. data/examples/local_assets/local_file_server.rb +82 -0
  22. data/examples/local_assets/sample.gif +0 -0
  23. data/examples/local_assets/sample.mp4 +0 -0
  24. data/examples/local_fonts.rb +2 -2
  25. data/examples/local_images.rb +2 -3
  26. data/examples/para/para_text.rb +14 -0
  27. data/examples/progress.rb +31 -0
  28. data/examples/radio/radio_groups.rb +2 -2
  29. data/examples/rect.rb +4 -0
  30. data/examples/rotate_shapes.rb +17 -0
  31. data/examples/simpler-menu.rb +21 -0
  32. data/exe/scarpe +2 -1
  33. data/lacci/Gemfile +2 -0
  34. data/lacci/Gemfile.lock +8 -1
  35. data/lacci/lacci.gemspec +1 -1
  36. data/lacci/lib/lacci/scarpe_cli.rb +2 -1
  37. data/lacci/lib/lacci/scarpe_core.rb +2 -1
  38. data/lacci/lib/lacci/version.rb +1 -1
  39. data/lacci/lib/scarpe/niente/app.rb +23 -0
  40. data/lacci/lib/scarpe/niente/display_service.rb +62 -0
  41. data/lacci/lib/scarpe/niente/drawable.rb +57 -0
  42. data/lacci/lib/scarpe/niente/logger.rb +29 -0
  43. data/lacci/lib/scarpe/niente/shoes_spec.rb +87 -0
  44. data/lacci/lib/scarpe/niente.rb +20 -0
  45. data/lacci/lib/shoes/app.rb +88 -43
  46. data/lacci/lib/shoes/background.rb +2 -2
  47. data/lacci/lib/shoes/border.rb +2 -2
  48. data/lacci/lib/shoes/builtins.rb +63 -0
  49. data/lacci/lib/shoes/changelog.rb +52 -0
  50. data/lacci/lib/shoes/colors.rb +3 -1
  51. data/lacci/lib/shoes/constants.rb +19 -1
  52. data/lacci/lib/shoes/display_service.rb +39 -16
  53. data/lacci/lib/shoes/download.rb +2 -2
  54. data/lacci/lib/shoes/drawable.rb +380 -0
  55. data/lacci/lib/shoes/drawables/arc.rb +49 -0
  56. data/lacci/lib/shoes/drawables/arrow.rb +41 -0
  57. data/lacci/lib/shoes/drawables/button.rb +73 -0
  58. data/lacci/lib/shoes/{widgets → drawables}/check.rb +5 -4
  59. data/lacci/lib/shoes/{widgets → drawables}/document_root.rb +3 -3
  60. data/lacci/lib/shoes/{widgets → drawables}/edit_box.rb +6 -6
  61. data/lacci/lib/shoes/{widgets → drawables}/edit_line.rb +6 -6
  62. data/lacci/lib/shoes/{widgets → drawables}/flow.rb +6 -6
  63. data/lacci/lib/shoes/{widgets → drawables}/image.rb +6 -6
  64. data/lacci/lib/shoes/{widgets → drawables}/line.rb +7 -5
  65. data/lacci/lib/shoes/drawables/link.rb +34 -0
  66. data/lacci/lib/shoes/drawables/list_box.rb +56 -0
  67. data/lacci/lib/shoes/drawables/para.rb +118 -0
  68. data/lacci/lib/shoes/drawables/progress.rb +14 -0
  69. data/lacci/lib/shoes/drawables/radio.rb +33 -0
  70. data/lacci/lib/shoes/drawables/rect.rb +17 -0
  71. data/lacci/lib/shoes/{widgets → drawables}/shape.rb +6 -7
  72. data/lacci/lib/shoes/{widgets → drawables}/slot.rb +32 -20
  73. data/lacci/lib/shoes/{widgets → drawables}/span.rb +8 -7
  74. data/lacci/lib/shoes/{widgets → drawables}/stack.rb +6 -4
  75. data/lacci/lib/shoes/drawables/star.rb +50 -0
  76. data/lacci/lib/shoes/drawables/subscription_item.rb +93 -0
  77. data/lacci/lib/shoes/drawables/text_drawable.rb +63 -0
  78. data/lacci/lib/shoes/drawables/video.rb +16 -0
  79. data/lacci/lib/shoes/drawables/widget.rb +69 -0
  80. data/lacci/lib/shoes/drawables.rb +31 -0
  81. data/lacci/lib/shoes/errors.rb +28 -0
  82. data/lacci/lib/shoes/log.rb +2 -2
  83. data/lacci/lib/shoes/ruby_extensions.rb +15 -0
  84. data/lacci/lib/shoes/spacing.rb +2 -2
  85. data/lacci/lib/shoes-spec.rb +93 -0
  86. data/lacci/lib/shoes.rb +27 -7
  87. data/lacci/test/test_helper.rb +54 -0
  88. data/lacci/test/test_lacci.rb +12 -3
  89. data/lacci/test/test_shoes_errors.rb +49 -0
  90. data/lib/scarpe/cats_cradle.rb +81 -59
  91. data/lib/scarpe/errors.rb +77 -0
  92. data/lib/scarpe/evented_assertions.rb +50 -17
  93. data/lib/scarpe/shoes_spec.rb +181 -0
  94. data/lib/scarpe/version.rb +2 -2
  95. data/lib/scarpe/wv/app.rb +20 -20
  96. data/lib/scarpe/wv/arc.rb +4 -47
  97. data/lib/scarpe/wv/arrow.rb +9 -0
  98. data/lib/scarpe/wv/button.rb +7 -35
  99. data/lib/scarpe/wv/check.rb +3 -5
  100. data/lib/scarpe/wv/control_interface.rb +18 -20
  101. data/lib/scarpe/wv/document_root.rb +81 -4
  102. data/lib/scarpe/wv/{widget.rb → drawable.rb} +66 -43
  103. data/lib/scarpe/wv/edit_box.rb +4 -17
  104. data/lib/scarpe/wv/edit_line.rb +4 -18
  105. data/lib/scarpe/wv/flow.rb +2 -18
  106. data/lib/scarpe/wv/image.rb +8 -28
  107. data/lib/scarpe/wv/line.rb +3 -25
  108. data/lib/scarpe/wv/link.rb +3 -16
  109. data/lib/scarpe/wv/list_box.rb +6 -29
  110. data/lib/scarpe/wv/para.rb +11 -30
  111. data/lib/scarpe/wv/progress.rb +19 -0
  112. data/lib/scarpe/wv/radio.rb +9 -10
  113. data/lib/scarpe/wv/rect.rb +13 -0
  114. data/lib/scarpe/wv/shape.rb +3 -8
  115. data/lib/scarpe/wv/slot.rb +8 -25
  116. data/lib/scarpe/wv/span.rb +3 -27
  117. data/lib/scarpe/wv/stack.rb +2 -18
  118. data/lib/scarpe/wv/star.rb +3 -53
  119. data/lib/scarpe/wv/subscription_item.rb +38 -4
  120. data/lib/scarpe/wv/text_drawable.rb +32 -0
  121. data/lib/scarpe/wv/video.rb +15 -15
  122. data/lib/scarpe/wv/web_wrangler.rb +299 -329
  123. data/lib/scarpe/wv/webview_local_display.rb +48 -33
  124. data/lib/scarpe/wv/webview_relay_display.rb +12 -12
  125. data/lib/scarpe/wv/webview_relay_util.rb +7 -10
  126. data/lib/scarpe/wv/wv_display_worker.rb +2 -2
  127. data/lib/scarpe/wv.rb +45 -12
  128. data/lib/scarpe/wv_local.rb +1 -1
  129. data/lib/scarpe/wv_relay.rb +1 -1
  130. data/lib/scarpe.rb +1 -0
  131. data/logger/debug_web_wrangler.json +1 -1
  132. data/logger/scarpe_wv_test.json +1 -1
  133. data/scarpe-components/Gemfile.lock +86 -0
  134. data/scarpe-components/lib/scarpe/components/base64.rb +3 -7
  135. data/scarpe-components/lib/scarpe/components/calzini/alert.rb +49 -0
  136. data/scarpe-components/lib/scarpe/components/calzini/art_widgets.rb +203 -0
  137. data/scarpe-components/lib/scarpe/components/calzini/button.rb +39 -0
  138. data/scarpe-components/lib/scarpe/components/calzini/misc.rb +146 -0
  139. data/scarpe-components/lib/scarpe/components/calzini/para.rb +35 -0
  140. data/scarpe-components/lib/scarpe/components/calzini/slots.rb +155 -0
  141. data/scarpe-components/lib/scarpe/components/calzini/text_widgets.rb +65 -0
  142. data/scarpe-components/lib/scarpe/components/calzini.rb +149 -0
  143. data/scarpe-components/lib/scarpe/components/errors.rb +20 -0
  144. data/scarpe-components/lib/scarpe/components/file_helpers.rb +1 -0
  145. data/scarpe-components/lib/scarpe/components/html.rb +131 -0
  146. data/scarpe-components/lib/scarpe/components/minitest_export_reporter.rb +75 -0
  147. data/scarpe-components/lib/scarpe/components/minitest_import_runnable.rb +98 -0
  148. data/scarpe-components/lib/scarpe/components/minitest_result.rb +86 -0
  149. data/scarpe-components/lib/scarpe/components/modular_logger.rb +5 -5
  150. data/scarpe-components/lib/scarpe/components/print_logger.rb +9 -5
  151. data/scarpe-components/lib/scarpe/components/promises.rb +14 -14
  152. data/scarpe-components/lib/scarpe/components/segmented_file_loader.rb +36 -17
  153. data/scarpe-components/lib/scarpe/components/string_helpers.rb +10 -0
  154. data/scarpe-components/lib/scarpe/components/tiranti.rb +225 -0
  155. data/scarpe-components/lib/scarpe/components/unit_test_helpers.rb +45 -5
  156. data/scarpe-components/lib/scarpe/components/version.rb +2 -2
  157. data/scarpe-components/test/calzini/test_calzini_alert.rb +30 -0
  158. data/scarpe-components/test/calzini/test_calzini_art_drawables.rb +105 -0
  159. data/scarpe-components/test/calzini/test_calzini_button.rb +52 -0
  160. data/scarpe-components/test/calzini/test_calzini_misc.rb +115 -0
  161. data/scarpe-components/test/calzini/test_calzini_para.rb +37 -0
  162. data/scarpe-components/test/calzini/test_calzini_slots.rb +130 -0
  163. data/scarpe-components/test/calzini/test_calzini_text_drawables.rb +41 -0
  164. data/scarpe-components/test/mtr_data/exception.json +1 -0
  165. data/scarpe-components/test/mtr_data/fail_with_message.json +1 -0
  166. data/scarpe-components/test/mtr_data/skipped_no_message.json +1 -0
  167. data/scarpe-components/test/mtr_data/skipped_w_msg.json +1 -0
  168. data/scarpe-components/test/mtr_data/succeed_2_asserts.json +1 -0
  169. data/scarpe-components/test/test_dimensions.rb +26 -0
  170. data/scarpe-components/test/test_helper.rb +20 -0
  171. data/scarpe-components/test/test_html.rb +65 -0
  172. data/scarpe-components/test/test_minitest_result.rb +61 -0
  173. data/scarpe-components/test/test_promises.rb +5 -4
  174. data/scarpe-components/test/test_segmented_app_files.rb +8 -6
  175. data/scarpegen.rb +14 -14
  176. data/sig/scarpe.rbs +1 -1
  177. data/templates/basic_class_template.erb +13 -14
  178. data/templates/class_template_with_event_bind.erb +4 -4
  179. data/templates/class_template_with_shapes.erb +8 -17
  180. data/templates/example_template.erb +1 -1
  181. data/templates/module_template.erb +4 -4
  182. data/templates/webview_template.erb +3 -2
  183. metadata +113 -55
  184. data/examples/legacy/not_checked/shoes-contrib/elements/image-icon.rb +0 -3
  185. data/lacci/lib/shoes/widget.rb +0 -218
  186. data/lacci/lib/shoes/widgets/alert.rb +0 -19
  187. data/lacci/lib/shoes/widgets/arc.rb +0 -51
  188. data/lacci/lib/shoes/widgets/button.rb +0 -35
  189. data/lacci/lib/shoes/widgets/font.rb +0 -14
  190. data/lacci/lib/shoes/widgets/link.rb +0 -25
  191. data/lacci/lib/shoes/widgets/list_box.rb +0 -25
  192. data/lacci/lib/shoes/widgets/para.rb +0 -68
  193. data/lacci/lib/shoes/widgets/radio.rb +0 -35
  194. data/lacci/lib/shoes/widgets/star.rb +0 -44
  195. data/lacci/lib/shoes/widgets/subscription_item.rb +0 -60
  196. data/lacci/lib/shoes/widgets/text_widget.rb +0 -51
  197. data/lacci/lib/shoes/widgets/video.rb +0 -15
  198. data/lacci/lib/shoes/widgets.rb +0 -29
  199. data/lib/scarpe/wv/alert.rb +0 -66
  200. data/lib/scarpe/wv/background.rb +0 -27
  201. data/lib/scarpe/wv/border.rb +0 -24
  202. data/lib/scarpe/wv/control_interface_test.rb +0 -238
  203. data/lib/scarpe/wv/dimensions.rb +0 -22
  204. data/lib/scarpe/wv/font.rb +0 -36
  205. data/lib/scarpe/wv/html.rb +0 -108
  206. data/lib/scarpe/wv/spacing.rb +0 -41
  207. data/lib/scarpe/wv/text_widget.rb +0 -30
  208. /data/examples/legacy/not_checked/{expert → shoes-contrib/basic}/definr.rb +0 -0
  209. /data/examples/legacy/not_checked/{expert → shoes-contrib/basic}/funnies.rb +0 -0
  210. /data/examples/legacy/not_checked/shoes-contrib/{elements → basic}/list_box-select-class.rb +0 -0
  211. /data/examples/legacy/{not_checked/shoes-contrib/basic → working/simple}/basic-edit-box.rb +0 -0
  212. /data/examples/legacy/{not_checked/shoes-contrib/elements → working/simple}/basic-fps.rb +0 -0
  213. /data/examples/legacy/{not_checked/shoes-contrib/elements → working/simple}/border-cat.rb +0 -0
  214. /data/examples/legacy/{not_checked/shoes-contrib/elements → working/simple}/check-mate.rb +0 -0
  215. /data/examples/legacy/{not_checked/shoes-contrib/manipulation → working/simple}/clear-slot.rb +0 -0
  216. /data/examples/legacy/{not_checked/shoes-contrib/basic → working/simple}/clock.rb +0 -0
  217. /data/examples/legacy/{not_checked/shoes-contrib/basic → working/simple}/gradient-shoes.rb +0 -0
  218. /data/examples/legacy/{not_checked/shoes-contrib/basic → working/simple}/list_box-shape-report.rb +0 -0
  219. /data/examples/legacy/{not_checked/shoes-contrib/elements → working/simple}/list_box.rb +0 -0
  220. /data/examples/legacy/{not_checked/shoes-contrib/elements → working/simple}/phat-button.rb +0 -0
  221. /data/examples/legacy/{not_checked/shoes-contrib → working}/simple/simple-calc.rb +0 -0
  222. /data/examples/legacy/{not_checked/shoes-contrib/position → working/simple}/stack-width.rb +0 -0
  223. /data/examples/legacy/{not_checked/shoes-contrib/elements → working/simple}/width-introspec.rb +0 -0
data/lib/scarpe/wv/app.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Scarpe
4
- # Scarpe::WebviewApp must only be used from the main thread, due to GTK+ limitations.
5
- class WebviewApp < WebviewWidget
3
+ module Scarpe::Webview
4
+ # Scarpe::Webview::App must only be used from the main thread, due to GTK+ limitations.
5
+ class App < Drawable # App inherits from Drawable to set up linkable IDs and event methods
6
6
  attr_reader :control_interface
7
7
 
8
8
  attr_writer :shoes_linkable_id
@@ -10,22 +10,16 @@ class Scarpe
10
10
  def initialize(properties)
11
11
  super
12
12
 
13
- # It's possible to provide a Ruby script by setting
14
- # SCARPE_TEST_CONTROL to its file path. This can
15
- # allow pre-setting test options or otherwise
16
- # performing additional actions not written into
17
- # the Shoes app itself.
18
- #
19
- # The control interface is what lets these files see
20
- # events, specify overrides and so on.
13
+ # Scarpe's ControlInterface sets up event handlers
14
+ # for the display service that aren't sent to
15
+ # Lacci (Shoes). In general it's used for setup
16
+ # and additional control or testing, outside the
17
+ # Shoes app. This is how CatsCradle and Shoes-Spec
18
+ # set up testing, for instance.
21
19
  @control_interface = ControlInterface.new
22
- if ENV["SCARPE_TEST_CONTROL"]
23
- require "scarpe/components/unit_test_helpers"
24
- @control_interface.instance_eval File.read(ENV["SCARPE_TEST_CONTROL"])
25
- end
26
20
 
27
21
  # TODO: rename @view
28
- @view = Scarpe::WebWrangler.new title: @title,
22
+ @view = Scarpe::Webview::WebWrangler.new title: @title,
29
23
  width: @width,
30
24
  height: @height,
31
25
  resizable: @resizable
@@ -63,6 +57,8 @@ class Scarpe
63
57
  def run
64
58
  @control_interface.dispatch_event(:init)
65
59
 
60
+ @view.empty_page = empty_page_element
61
+
66
62
  # This takes control of the main thread and never returns. And it *must* be run from
67
63
  # the main thread. And it stops any Ruby background threads.
68
64
  # That's totally cool and normal, right?
@@ -80,14 +76,18 @@ class Scarpe
80
76
  end
81
77
  end
82
78
 
83
- # All JS callbacks to Scarpe widgets are dispatched
79
+ # All JS callbacks to Scarpe drawables are dispatched
84
80
  # via this handler
85
81
  def handle_callback(name, *args)
86
- @callbacks[name].call(*args)
82
+ if @callbacks.key?(name)
83
+ @callbacks[name].call(*args)
84
+ else
85
+ raise Scarpe::UnknownEventTypeError, "No such Webview callback: #{name.inspect}!"
86
+ end
87
87
  end
88
88
 
89
89
  # Bind a Scarpe callback name; see handle_callback above.
90
- # See Scarpe::Widget for how the naming is set up
90
+ # See Scarpe::Drawable for how the naming is set up
91
91
  def bind(name, &block)
92
92
  @callbacks[name] = block
93
93
  end
@@ -97,7 +97,7 @@ class Scarpe
97
97
  #
98
98
  # @return [void]
99
99
  def request_redraw!
100
- wrangler = WebviewDisplayService.instance.wrangler
100
+ wrangler = DisplayService.instance.wrangler
101
101
  if wrangler.is_running
102
102
  wrangler.replace(@document_root.to_html)
103
103
  end
data/lib/scarpe/wv/arc.rb CHANGED
@@ -1,56 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Scarpe
4
- class WebviewArc < Scarpe::WebviewWidget
3
+ module Scarpe::Webview
4
+ class Arc < Drawable
5
5
  def initialize(properties)
6
6
  super(properties)
7
7
  end
8
8
 
9
- def element(&block)
10
- HTML.render do |h|
11
- h.div(id: html_id, style: style) do
12
- h.svg(width: @width, height: @height) do
13
- h.path(d: arc_path)
14
- end
15
- block.call(h) if block_given?
16
- end
17
- end
18
- end
19
-
20
- protected
21
-
22
- def style
23
- super.merge({
24
- left: "#{@left}px",
25
- top: "#{@top}px",
26
- width: "#{@width}px",
27
- height: "#{@height}px",
28
- })
29
- end
30
-
31
- private
32
-
33
- def arc_path
34
- center_x = @width / 2
35
- center_y = @height / 2
36
- radius_x = @width / 2
37
- radius_y = @height / 2
38
- start_angle_degrees = radians_to_degrees(@angle1) % 360
39
- end_angle_degrees = radians_to_degrees(@angle2) % 360
40
- large_arc_flag = (end_angle_degrees - start_angle_degrees) % 360 > 180 ? 1 : 0
41
-
42
- "M#{center_x} #{center_y} L#{@width} #{center_y} " \
43
- "A#{radius_x} #{radius_y} 0 #{large_arc_flag} 0 " \
44
- "#{center_x + radius_x * Math.cos(degrees_to_radians(end_angle_degrees))} " \
45
- "#{center_y + radius_y * Math.sin(degrees_to_radians(end_angle_degrees))} Z"
46
- end
47
-
48
- def degrees_to_radians(degrees)
49
- degrees * Math::PI / 180
50
- end
51
-
52
- def radians_to_degrees(radians)
53
- radians * (180.0 / Math::PI)
9
+ def element
10
+ render("arc")
54
11
  end
55
12
  end
56
13
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Scarpe::Webview
4
+ class Arrow < Drawable
5
+ def element
6
+ render("arrow")
7
+ end
8
+ end
9
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Scarpe
4
- class WebviewButton < WebviewWidget
3
+ module Scarpe::Webview
4
+ class Button < Drawable
5
5
  def initialize(properties)
6
6
  super
7
7
 
@@ -10,43 +10,15 @@ class Scarpe
10
10
  # This will be sent to the bind_self_event in Button
11
11
  send_self_event(event_name: "click")
12
12
  end
13
- end
14
13
 
15
- def element
16
- HTML.render do |h|
17
- h.button(id: html_id, onclick: handler_js_code("click"), style: style) do
18
- @text
19
- end
14
+ bind("hover") do
15
+ # This will be sent to the bind_self_event in Button
16
+ send_self_event(event_name: "hover")
20
17
  end
21
18
  end
22
19
 
23
- protected
24
-
25
- def style
26
- styles = super
27
-
28
- styles[:"background-color"] = @color
29
- styles[:"padding-top"] = @padding_top
30
- styles[:"padding-bottom"] = @padding_bottom
31
- styles[:color] = @text_color
32
- styles[:width] = Dimensions.length(@width) if @width
33
- styles[:height] = Dimensions.length(@height) if @height
34
- styles[:"font-size"] = @font_size
35
-
36
- styles[:top] = Dimensions.length(@top) if @top
37
- styles[:left] = Dimensions.length(@left) if @left
38
- styles[:position] = "absolute" if @top || @left
39
- styles[:"font-size"] = Dimensions.length(font_size) if @size
40
- styles[:"font-family"] = @font if @font
41
- styles[:color] = rgb_to_hex(@stroke) if @stroke
42
-
43
- styles
44
- end
45
-
46
- def font_size
47
- font_size = @size.is_a?(Symbol) ? SIZES[@size] : @size
48
-
49
- Dimensions.length(font_size)
20
+ def element
21
+ render("button")
50
22
  end
51
23
  end
52
24
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Scarpe
4
- class WebviewCheck < Scarpe::WebviewWidget
3
+ module Scarpe::Webview
4
+ class Check < Drawable
5
5
  attr_reader :text
6
6
 
7
7
  def initialize(properties)
@@ -21,9 +21,7 @@ class Scarpe
21
21
  end
22
22
 
23
23
  def element
24
- HTML.render do |h|
25
- h.input(type: :checkbox, id: html_id, onclick: handler_js_code("click"), value: "hmm #{text}", checked: @checked, style:)
26
- end
24
+ render("check")
27
25
  end
28
26
  end
29
27
  end
@@ -10,19 +10,22 @@
10
10
  # And if you depend on this from the framework, I'll add a check-mode that
11
11
  # never dispatches any events to any handlers. Do NOT test me on this.
12
12
 
13
- class Scarpe
13
+ module Scarpe::Webview
14
14
  class ControlInterface
15
15
  include Shoes::Log
16
16
 
17
17
  SUBSCRIBE_EVENTS = [:init, :shutdown, :next_redraw, :every_redraw, :next_heartbeat, :every_heartbeat]
18
18
  DISPATCH_EVENTS = [:init, :shutdown, :redraw, :heartbeat]
19
+ INVALID_SYSTEM_COMPONENTS_MESSAGE = "Must pass non-nil app and wrangler to ControlInterface#set_system_components!"
20
+ CONTROL_INTERFACE_INIT_MESSAGE = "ControlInterface code needs to be wrapped in handlers like on_event(:init) " +
21
+ "to make sure they have access to app, doc_root, wrangler, etc!"
19
22
 
20
23
  attr_writer :doc_root
21
24
  attr_reader :do_shutdown
22
25
 
23
26
  # The control interface needs to see major system components to hook into their events
24
27
  def initialize
25
- log_init("WV::ControlInterface")
28
+ log_init("Webview::ControlInterface")
26
29
 
27
30
  @do_shutdown = false
28
31
  @event_handlers = {}
@@ -35,11 +38,15 @@ class Scarpe
35
38
 
36
39
  # This should get called once, from Shoes::App
37
40
  def set_system_components(app:, doc_root:, wrangler:)
38
- unless app && wrangler
39
- @log.error("False app passed to set_system_components!") unless app
40
- @log.error("False wrangler passed to set_system_components!") unless wrangler
41
- raise "Must pass non-nil app and wrangler to ControlInterface#set_system_components!"
41
+ unless app
42
+ @log.error("False app passed to set_system_components!")
43
+ raise Scarpe::MissingAppError, INVALID_SYSTEM_COMPONENTS_MESSAGE
42
44
  end
45
+ unless wrangler
46
+ @log.error("False wrangler passed to set_system_components!")
47
+ raise Scarpe::MissingWranglerError, INVALID_SYSTEM_COMPONENTS_MESSAGE
48
+ end
49
+
43
50
  @app = app
44
51
  @doc_root = doc_root # May be nil at this point
45
52
  @wrangler = wrangler
@@ -50,28 +57,19 @@ class Scarpe
50
57
  end
51
58
 
52
59
  def app
53
- unless @app
54
- raise "ControlInterface code needs to be wrapped in handlers like on_event(:init) " +
55
- "to make sure they have access to app, doc_root, wrangler, etc!"
56
- end
60
+ raise Scarpe::MissingAppError, CONTROL_INTERFACE_INIT_MESSAGE unless @app
57
61
 
58
62
  @app
59
63
  end
60
64
 
61
65
  def doc_root
62
- unless @doc_root
63
- raise "ControlInterface code needs to be wrapped in handlers like on_event(:init) " +
64
- "to make sure they have access to app, doc_root, wrangler, etc!"
65
- end
66
+ raise Scarpe::MissingDocRootError, CONTROL_INTERFACE_INIT_MESSAGE unless @doc_root
66
67
 
67
68
  @doc_root
68
69
  end
69
70
 
70
71
  def wrangler
71
- unless @wrangler
72
- raise "ControlInterface code needs to be wrapped in handlers like on_event(:init) " +
73
- "to make sure they have access to app, doc_root, wrangler, etc!"
74
- end
72
+ raise Scarpe::MissingWranglerError, CONTROL_INTERFACE_INIT_MESSAGE unless @wrangler
75
73
 
76
74
  @wrangler
77
75
  end
@@ -82,7 +80,7 @@ class Scarpe
82
80
  # On recognised events, this sets a handler for that event
83
81
  def on_event(event, &block)
84
82
  unless SUBSCRIBE_EVENTS.include?(event)
85
- raise "Illegal subscribe to event #{event.inspect}! Valid values are: #{SUBSCRIBE_EVENTS.inspect}"
83
+ raise Scarpe::IllegalSubscribeEventError, "Illegal subscribe to event #{event.inspect}! Valid values are: #{SUBSCRIBE_EVENTS.inspect}"
86
84
  end
87
85
 
88
86
  @unsub_id ||= 0
@@ -97,7 +95,7 @@ class Scarpe
97
95
  @log.debug("CTL event #{event.inspect} #{args.inspect} #{keywords.inspect}")
98
96
 
99
97
  unless DISPATCH_EVENTS.include?(event)
100
- raise "Illegal dispatch of event #{event.inspect}! Valid values are: #{DISPATCH_EVENTS.inspect}"
98
+ raise Scarpe::IllegalDispatchEventError, "Illegal dispatch of event #{event.inspect}! Valid values are: #{DISPATCH_EVENTS.inspect}"
101
99
  end
102
100
 
103
101
  if @do_shutdown
@@ -1,8 +1,85 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Scarpe
4
- # A WebviewDocumentRoot is a WebviewFlow, with all the same properties
5
- # and basic behavior.
6
- class WebviewDocumentRoot < Scarpe::WebviewFlow
3
+ module Scarpe::Webview
4
+ # A DocumentRoot is a {Scarpe::Webview::Flow}, with all the same properties
5
+ # and basic behavior. It also reserves space for Builtins like fonts, alerts,
6
+ # etc. which don't have individual {Shoes::Drawable} objects.
7
+ class DocumentRoot < Flow
8
+ def initialize(properties)
9
+ super
10
+
11
+ @fonts = []
12
+ @alerts = []
13
+
14
+ bind_shoes_event(event_name: "builtin") do |cmd_name, args|
15
+ case cmd_name
16
+ when "font"
17
+ @fonts << args[0]
18
+ # Can't just create font_updater and alert_updater on initialize - not everything is set up
19
+ @font_updater ||= Scarpe::Webview::WebWrangler::ElementWrangler.new("root-fonts")
20
+ @font_updater.inner_html = font_contents
21
+ when "alert"
22
+ bind_ok_event
23
+ @alerts << args[0]
24
+ @alert_updater ||= Scarpe::Webview::WebWrangler::ElementWrangler.new("root-alerts")
25
+ @alert_updater.inner_html = alert_contents
26
+ else
27
+ raise Scarpe::UnknownBuiltinCommandError, "Unexpected builtin command: #{cmd_name.inspect}!"
28
+ end
29
+ end
30
+ end
31
+
32
+ def element(&block)
33
+ contents = block ? block.call : ""
34
+ super do
35
+ contents + HTML.render do |h|
36
+ h.div(id: "root-fonts") do
37
+ font_contents
38
+ end
39
+ h.div(id: "root-alerts") do
40
+ alert_contents
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ # This needs to occur after initialize() because the app isn't yet allocated initially,
49
+ # so we can't call bind() for app events yet.
50
+ def bind_ok_event
51
+ return if @ok_event_setup_done
52
+
53
+ @ok_event_setup_done = true
54
+
55
+ # Done with the alert(s), delete them
56
+ bind("OK") do
57
+ @alerts = []
58
+ needs_update!
59
+ end
60
+ end
61
+
62
+ def font_contents
63
+ HTML.render do |h|
64
+ @fonts.each do |font|
65
+ h.link(href: font, rel: "stylesheet")
66
+ h.style do
67
+ font_name = File.basename(font, ".*")
68
+ <<~CSS
69
+ @font-face {
70
+ font-family: #{font_name};
71
+ src: url("data:font/truetype;base64,#{encode_file_to_base64(font)}") format('truetype');
72
+ }
73
+ CSS
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ def alert_contents
80
+ @alerts.map do |alert_text|
81
+ render("alert", { "text" => alert_text, "event_name" => "OK" })
82
+ end.join + " "
83
+ end
7
84
  end
8
85
  end
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Scarpe
4
- # The WebviewWidget parent class helps connect a Webview widget with
3
+ module Scarpe::Webview
4
+ # The Webview::Drawable parent class helps connect a Webview drawable with
5
5
  # its Shoes equivalent, render itself to the Webview DOM, handle
6
6
  # Javascript events and generally keep things working in Webview.
7
- class WebviewWidget < Shoes::Linkable
7
+ class Drawable < Shoes::Linkable
8
+ # This will pick up the log implementation from wv.rb
8
9
  include Shoes::Log
9
10
 
10
11
  class << self
@@ -12,55 +13,59 @@ class Scarpe
12
13
  def display_class_for(scarpe_class_name)
13
14
  scarpe_class = Shoes.const_get(scarpe_class_name)
14
15
  unless scarpe_class.ancestors.include?(Shoes::Linkable)
15
- raise "Scarpe Webview can only get display classes for Shoes " +
16
- "linkable widgets, not #{scarpe_class_name.inspect}!"
16
+ raise Scarpe::InvalidClassError, "Scarpe Webview can only get display classes for Shoes " +
17
+ "linkable drawables, not #{scarpe_class_name.inspect}!"
17
18
  end
18
19
 
19
- klass = Scarpe.const_get("Webview" + scarpe_class_name.split("::")[-1])
20
+ klass = Scarpe::Webview.const_get(scarpe_class_name.split("::")[-1])
20
21
  if klass.nil?
21
- raise "Couldn't find corresponding Scarpe Webview class for #{scarpe_class_name.inspect}!"
22
+ raise Scarpe::MissingClassError, "Couldn't find corresponding Scarpe Webview class for #{scarpe_class_name.inspect}!"
22
23
  end
23
24
 
24
25
  klass
25
26
  end
26
27
  end
27
28
 
28
- # The Shoes ID corresponding to the Shoes widget for this Webview widget
29
+ # The Shoes ID corresponding to the Shoes drawable for this Webview drawable
29
30
  attr_reader :shoes_linkable_id
30
31
 
31
- # The WebviewWidget parent of this widget
32
+ # The Webview::Drawable parent of this drawable
32
33
  attr_reader :parent
33
34
 
34
- # An array of WebviewWidget children (possibly empty) of this widget
35
+ # An array of Webview::Drawable children (possibly empty) of this drawable
35
36
  attr_reader :children
36
37
 
37
- # Set instance variables for the display properties of this widget. Bind Shoes
38
- # events for changes of parent widget and changes of property values.
38
+ # Set instance variables for the Shoes styles of this drawable. Bind Shoes
39
+ # events for changes of parent drawable and changes of property values.
39
40
  def initialize(properties)
40
- log_init("WV::Widget")
41
+ log_init("Webview::Drawable")
42
+
43
+ @shoes_style_names = properties.keys.map(&:to_s) - ["shoes_linkable_id"]
41
44
 
42
45
  # Call method, which looks up the parent
43
46
  @shoes_linkable_id = properties["shoes_linkable_id"] || properties[:shoes_linkable_id]
44
47
  unless @shoes_linkable_id
45
- raise "Could not find property shoes_linkable_id in #{properties.inspect}!"
48
+ raise Scarpe::MissingAttributeError, "Could not find property shoes_linkable_id in #{properties.inspect}!"
46
49
  end
47
50
 
48
- # Set the display properties
51
+ # Set the Shoes styles as instance variables
49
52
  properties.each do |k, v|
50
53
  next if k == "shoes_linkable_id"
51
54
 
52
55
  instance_variable_set("@" + k.to_s, v)
53
56
  end
54
57
 
55
- # The parent field is *almost* simple enough that a typed display property would handle it.
58
+ # Must call this before bind
59
+ super(linkable_id: @shoes_linkable_id)
60
+
56
61
  bind_shoes_event(event_name: "parent", target: shoes_linkable_id) do |new_parent_id|
57
- display_parent = WebviewDisplayService.instance.query_display_widget_for(new_parent_id)
62
+ display_parent = DisplayService.instance.query_display_drawable_for(new_parent_id)
58
63
  if @parent != display_parent
59
64
  set_parent(display_parent)
60
65
  end
61
66
  end
62
67
 
63
- # When Shoes widgets change properties, we get a change notification here
68
+ # When Shoes drawables change properties, we get a change notification here
64
69
  bind_shoes_event(event_name: "prop_change", target: shoes_linkable_id) do |prop_changes|
65
70
  prop_changes.each do |k, v|
66
71
  instance_variable_set("@" + k, v)
@@ -71,19 +76,25 @@ class Scarpe
71
76
  bind_shoes_event(event_name: "destroy", target: shoes_linkable_id) do
72
77
  destroy_self
73
78
  end
79
+ end
74
80
 
75
- super(linkable_id: @shoes_linkable_id)
81
+ def shoes_styles
82
+ p = {}
83
+ @shoes_style_names.each do |prop_name|
84
+ p[prop_name] = instance_variable_get("@#{prop_name}")
85
+ end
86
+ p
76
87
  end
77
88
 
78
89
  # Properties_changed will be called automatically when properties change.
79
- # The widget should delete any changes from the Hash that it knows how
90
+ # The drawable should delete any changes from the Hash that it knows how
80
91
  # to incrementally handle, and pass the rest to super. If any changes
81
92
  # go entirely un-handled, a full redraw will be scheduled.
82
93
  # This exists to be overridden by children watching for changes.
83
94
  #
84
95
  # @param changes [Hash] a Hash of new values for properties that have changed
85
96
  def properties_changed(changes)
86
- # If a widget does something really nonstandard with its html_id or element, it will
97
+ # If a drawable does something really nonstandard with its html_id or element, it will
87
98
  # need to override to prevent this from happening. That's easy enough, though.
88
99
  if changes.key?("hidden")
89
100
  hidden = changes.delete("hidden")
@@ -99,7 +110,7 @@ class Scarpe
99
110
  needs_update! unless changes.empty?
100
111
  end
101
112
 
102
- # Give this widget a new parent, including managing the appropriate child lists for parent widgets.
113
+ # Give this drawable a new parent, including managing the appropriate child lists for parent drawables.
103
114
  def set_parent(new_parent)
104
115
  @parent&.remove_child(self)
105
116
  new_parent&.add_child(self)
@@ -108,7 +119,7 @@ class Scarpe
108
119
 
109
120
  # A shorter inspect text for prettier irb output
110
121
  def inspect
111
- "#<#{self.class}:#{self.object_id} @shoes_linkable_id=#{@shoes_linkable_id} @parent=#{@parent.inspect} @children=#{@children.inspect}>"
122
+ "#<#{self.class}:#{self.object_id} @shoes_linkable_id=#{@shoes_linkable_id} @children=#{@children.inspect}>"
112
123
  end
113
124
 
114
125
  protected
@@ -169,18 +180,22 @@ class Scarpe
169
180
 
170
181
  public
171
182
 
172
- # This gets a mini-webview for just this element and its children, if any.
173
- # It is normally called by the widget itself to do its DOM management.
183
+ # This gets an accessor for just this element's HTML ID.
184
+ # It is normally called by the drawable itself to do its DOM management.
185
+ # Drawables are required to use their html_id for their outermost element,
186
+ # to make sure that remove(), hidden() etc. affect every part of the drawable.
174
187
  #
175
188
  # @return [Scarpe::WebWrangler::ElementWrangler] a DOM object manager
176
189
  def html_element
177
- @elt_wrangler ||= Scarpe::WebWrangler::ElementWrangler.new(html_id)
190
+ @elt_wrangler ||= Scarpe::Webview::WebWrangler::ElementWrangler.new(html_id)
178
191
  end
179
192
 
180
193
  # Return a promise that guarantees all currently-requested changes have completed
181
194
  #
182
195
  # @return [Scarpe::Promise] a promise that will be fulfilled when all pending changes have finished
183
196
  def promise_update
197
+ # Doesn't matter what ElementWrangler we use -- they all return an update promise
198
+ # that includes all pending updates, no matter who they're for.
184
199
  html_element.promise_update
185
200
  end
186
201
 
@@ -188,7 +203,7 @@ class Scarpe
188
203
  #
189
204
  # @return [String] the HTML ID
190
205
  def html_id
191
- object_id.to_s
206
+ @linkable_id.to_s
192
207
  end
193
208
 
194
209
  # to_html is intended to get the HTML DOM rendering of this object and its children.
@@ -198,50 +213,58 @@ class Scarpe
198
213
  def to_html
199
214
  @children ||= []
200
215
  child_markup = @children.map(&:to_html).join
201
- if respond_to?(:element)
202
- element { child_markup }
203
- else
204
- child_markup
205
- end
216
+ element { child_markup }
206
217
  end
207
218
 
208
219
  # This binds a Scarpe JS callback, handled via a single dispatch point in the app
209
220
  #
210
- # @param event [String] the Scarpe widget event name
221
+ # @param event [String] the Scarpe drawable event name
211
222
  # @yield the block to call when the event occurs
212
223
  def bind(event, &block)
213
- raise("Widget has no linkable_id! #{inspect}") unless linkable_id
224
+ raise(Scarpe::MissingAttributeError, "Drawable has no linkable_id! #{inspect}") unless linkable_id
214
225
 
215
- WebviewDisplayService.instance.app.bind("#{linkable_id}-#{event}", &block)
226
+ DisplayService.instance.app.bind("#{linkable_id}-#{event}", &block)
216
227
  end
217
228
 
218
- # Removes the element from both the Ruby Widget tree and the HTML DOM.
229
+ # Removes the element from both the Ruby Drawable tree and the HTML DOM.
230
+ # Unsubscribe from all Shoes events.
219
231
  # Return a promise for when that HTML change will be visible.
220
232
  #
221
233
  # @return [Scarpe::Promise] a promise that is fulfilled when the HTML change is complete
222
234
  def destroy_self
223
235
  @parent&.remove_child(self)
236
+ unsub_all_shoes_events
224
237
  html_element.remove
225
238
  end
226
239
 
227
- # Request a full redraw of all widgets.
240
+ # Request a full redraw of the entire window, including the entire tree of
241
+ # drawables and the outer "empty page" frame.
242
+ #
243
+ # @return [void]
244
+ def full_window_redraw!
245
+ DisplayService.instance.app.request_redraw!
246
+ end
247
+
248
+ # Request a full redraw of this drawable, including all its children.
249
+ # Can be overridden in drawable subclasses if needed. An override would normally
250
+ # only be needed if re-rendering the element with the given html_id
251
+ # wasn't enough (and then remove would also need to be overridden.)
228
252
  #
229
- # It's really hard to do dirty-tracking here because the redraws are fully asynchronous.
230
- # And so we can't easily cancel one "in flight," and we can't easily pick up the latest
231
- # changes... And we probably don't want to, because we may be halfway through a batch.
253
+ # This occurs by default if a property is changed and the drawable
254
+ # doesn't remove its change in property_changed.
232
255
  #
233
256
  # @return [void]
234
257
  def needs_update!
235
- WebviewDisplayService.instance.app.request_redraw!
258
+ html_element.outer_html = to_html
236
259
  end
237
260
 
238
- # Generate JS code to trigger a specific event name on this widget with the supplies arguments.
261
+ # Generate JS code to trigger a specific event name on this drawable with the supplies arguments.
239
262
  #
240
263
  # @param handler_function_name [String] the event name - @see #bind
241
264
  # @param args [Array] additional arguments that will be passed to the event in the generated JS
242
265
  # @return [String] the generated JS code
243
266
  def handler_js_code(handler_function_name, *args)
244
- raise("Widget has no linkable_id! #{inspect}") unless linkable_id
267
+ raise(Scarpe::MissingAttributeError, "Drawable has no linkable_id! #{inspect}") unless linkable_id
245
268
 
246
269
  js_args = ["'#{linkable_id}-#{handler_function_name}'", *args].join(", ")
247
270
  "scarpeHandler(#{js_args})"