scarpe 0.2.1 → 0.2.2

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 (240) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -0
  3. data/.yardopts +11 -0
  4. data/Gemfile +3 -0
  5. data/Gemfile.lock +112 -0
  6. data/README.md +31 -24
  7. data/Rakefile +13 -1
  8. data/docs/yard/catscradle.md +44 -0
  9. data/docs/yard/template/default/fulldoc/html/setup.rb +13 -0
  10. data/docs/yard/template/default/layout/html/setup.rb +9 -0
  11. data/examples/background_with_image.rb +16 -0
  12. data/examples/bloopsaphone/working/bronx_army_knife.rb +66 -0
  13. data/examples/bloopsaphone/working/morning_serenity.rb +21 -0
  14. data/examples/bloopsaphone/working/simpsons_theme_song_by_why.rb +6 -4
  15. data/examples/button_go_away.rb +1 -1
  16. data/examples/check.rb +18 -0
  17. data/examples/clear_and_append.rb +24 -0
  18. data/examples/download_and_show_image.rb +28 -0
  19. data/examples/edit_box.rb +3 -5
  20. data/examples/fonts.rb +2 -2
  21. data/examples/get_headers.rb +10 -0
  22. data/examples/highlander.rb +2 -0
  23. data/examples/link.rb +2 -2
  24. data/examples/local_fonts.rb +4 -0
  25. data/examples/local_images.rb +4 -0
  26. data/examples/motion_events.rb +20 -0
  27. data/examples/parse_xl_funnies.rb +58 -0
  28. data/examples/radio/radio.rb +16 -0
  29. data/examples/radio/radio_groups.rb +18 -0
  30. data/examples/radio/radio_same_slot.rb +6 -0
  31. data/examples/ruby_racer.rb +13 -15
  32. data/examples/selfitude.rb +18 -0
  33. data/examples/shapes/shapes_fill.rb +4 -3
  34. data/examples/shoes_school.rb +2 -4
  35. data/examples/show_hide.rb +6 -0
  36. data/examples/skip_ci/change_my_audio_source.rb +21 -0
  37. data/examples/skip_ci/guitar_fretboard.rb +137 -0
  38. data/examples/video.rb +10 -0
  39. data/exe/scarpe +42 -66
  40. data/fonts/Pacifico.ttf +0 -0
  41. data/lacci/Gemfile +22 -0
  42. data/lacci/Gemfile.lock +72 -0
  43. data/lacci/Rakefile +12 -0
  44. data/lacci/lacci.gemspec +37 -0
  45. data/lacci/lib/lacci/scarpe_cli.rb +70 -0
  46. data/lacci/lib/lacci/scarpe_core.rb +21 -0
  47. data/lacci/lib/lacci/version.rb +13 -0
  48. data/lacci/lib/shoes/app.rb +264 -0
  49. data/{lib/scarpe → lacci/lib/shoes}/background.rb +1 -1
  50. data/{lib/scarpe → lacci/lib/shoes}/border.rb +1 -1
  51. data/{lib/scarpe → lacci/lib/shoes}/colors.rb +1 -1
  52. data/lacci/lib/shoes/constants.rb +29 -0
  53. data/{lib/scarpe → lacci/lib/shoes}/display_service.rb +40 -45
  54. data/lacci/lib/shoes/download.rb +123 -0
  55. data/lacci/lib/shoes/log.rb +71 -0
  56. data/lacci/lib/shoes/spacing.rb +9 -0
  57. data/{lib/scarpe → lacci/lib/shoes}/widget.rb +63 -43
  58. data/{lib/scarpe → lacci/lib/shoes/widgets}/alert.rb +3 -3
  59. data/{lib/scarpe → lacci/lib/shoes/widgets}/arc.rb +7 -5
  60. data/{lib/scarpe → lacci/lib/shoes/widgets}/button.rb +3 -3
  61. data/lacci/lib/shoes/widgets/check.rb +28 -0
  62. data/lacci/lib/shoes/widgets/document_root.rb +20 -0
  63. data/{lib/scarpe → lacci/lib/shoes/widgets}/edit_box.rb +10 -5
  64. data/{lib/scarpe → lacci/lib/shoes/widgets}/edit_line.rb +2 -2
  65. data/lacci/lib/shoes/widgets/flow.rb +22 -0
  66. data/lacci/lib/shoes/widgets/font.rb +14 -0
  67. data/{lib/scarpe → lacci/lib/shoes/widgets}/image.rb +3 -7
  68. data/lacci/lib/shoes/widgets/line.rb +18 -0
  69. data/{lib/scarpe → lacci/lib/shoes/widgets}/link.rb +2 -2
  70. data/{lib/scarpe → lacci/lib/shoes/widgets}/list_box.rb +2 -2
  71. data/{lib/scarpe → lacci/lib/shoes/widgets}/para.rb +4 -26
  72. data/lacci/lib/shoes/widgets/radio.rb +35 -0
  73. data/lacci/lib/shoes/widgets/shape.rb +37 -0
  74. data/lacci/lib/shoes/widgets/slot.rb +75 -0
  75. data/{lib/scarpe → lacci/lib/shoes/widgets}/span.rb +2 -2
  76. data/lacci/lib/shoes/widgets/stack.rb +24 -0
  77. data/{lib/scarpe → lacci/lib/shoes/widgets}/star.rb +6 -9
  78. data/lacci/lib/shoes/widgets/subscription_item.rb +60 -0
  79. data/lacci/lib/shoes/widgets/text_widget.rb +51 -0
  80. data/lacci/lib/shoes/widgets/video.rb +15 -0
  81. data/lacci/lib/shoes/widgets.rb +29 -0
  82. data/lacci/lib/shoes.rb +127 -0
  83. data/lacci/test/test_colors.rb +39 -0
  84. data/lacci/test/test_helper.rb +9 -0
  85. data/lacci/test/test_lacci.rb +9 -0
  86. data/lib/scarpe/cats_cradle.rb +249 -0
  87. data/lib/scarpe/evented_assertions.rb +88 -0
  88. data/lib/scarpe/version.rb +1 -1
  89. data/lib/scarpe/wv/alert.rb +3 -2
  90. data/lib/scarpe/wv/app.rb +30 -8
  91. data/lib/scarpe/wv/arc.rb +5 -6
  92. data/lib/scarpe/wv/background.rb +10 -1
  93. data/lib/scarpe/wv/border.rb +5 -3
  94. data/lib/scarpe/wv/button.rb +11 -9
  95. data/lib/scarpe/wv/check.rb +29 -0
  96. data/lib/scarpe/wv/control_interface.rb +14 -20
  97. data/lib/scarpe/wv/control_interface_test.rb +13 -28
  98. data/lib/scarpe/wv/document_root.rb +3 -45
  99. data/lib/scarpe/wv/edit_box.rb +5 -7
  100. data/lib/scarpe/wv/edit_line.rb +2 -2
  101. data/lib/scarpe/wv/flow.rb +10 -20
  102. data/lib/scarpe/wv/font.rb +36 -0
  103. data/lib/scarpe/wv/html.rb +3 -2
  104. data/lib/scarpe/wv/image.rb +7 -2
  105. data/lib/scarpe/wv/line.rb +4 -7
  106. data/lib/scarpe/wv/link.rb +1 -0
  107. data/lib/scarpe/wv/list_box.rb +3 -3
  108. data/lib/scarpe/wv/para.rb +16 -14
  109. data/lib/scarpe/wv/radio.rb +34 -0
  110. data/lib/scarpe/wv/shape.rb +44 -8
  111. data/lib/scarpe/wv/slot.rb +81 -0
  112. data/lib/scarpe/wv/spacing.rb +1 -1
  113. data/lib/scarpe/wv/span.rb +10 -8
  114. data/lib/scarpe/wv/stack.rb +10 -30
  115. data/lib/scarpe/wv/star.rb +11 -12
  116. data/lib/scarpe/wv/subscription_item.rb +50 -0
  117. data/lib/scarpe/wv/video.rb +34 -0
  118. data/lib/scarpe/wv/web_wrangler.rb +238 -58
  119. data/lib/scarpe/wv/webview_local_display.rb +27 -5
  120. data/lib/scarpe/wv/webview_relay_display.rb +18 -119
  121. data/lib/scarpe/wv/webview_relay_util.rb +143 -0
  122. data/lib/scarpe/wv/widget.rb +80 -11
  123. data/lib/scarpe/wv/wv_display_worker.rb +17 -4
  124. data/lib/scarpe/wv.rb +33 -4
  125. data/lib/scarpe/wv_local.rb +1 -1
  126. data/lib/scarpe/wv_relay.rb +1 -1
  127. data/lib/scarpe.rb +3 -32
  128. data/scarpe-components/.gitignore +1 -0
  129. data/scarpe-components/Gemfile +22 -0
  130. data/scarpe-components/README.md +35 -0
  131. data/scarpe-components/Rakefile +12 -0
  132. data/scarpe-components/lib/scarpe/components/base64.rb +29 -0
  133. data/scarpe-components/lib/scarpe/components/file_helpers.rb +65 -0
  134. data/scarpe-components/lib/scarpe/components/modular_logger.rb +113 -0
  135. data/scarpe-components/lib/scarpe/components/print_logger.rb +43 -0
  136. data/{lib/scarpe → scarpe-components/lib/scarpe/components}/promises.rb +102 -35
  137. data/scarpe-components/lib/scarpe/components/segmented_file_loader.rb +170 -0
  138. data/scarpe-components/lib/scarpe/components/unit_test_helpers.rb +217 -0
  139. data/scarpe-components/lib/scarpe/components/version.rb +7 -0
  140. data/scarpe-components/scarpe-components.gemspec +38 -0
  141. data/scarpe-components/test/test_components.rb +9 -0
  142. data/scarpe-components/test/test_helper.rb +23 -0
  143. data/scarpe-components/test/test_promises.rb +260 -0
  144. data/scarpe-components/test/test_segmented_app_files.rb +182 -0
  145. data/{lib/scarpe → spikes}/glibui/widget.rb +2 -2
  146. data/{lib/scarpe → spikes}/glibui.rb +1 -1
  147. data/templates/basic_class_template.erb +1 -1
  148. data/templates/class_template_with_event_bind.erb +1 -1
  149. data/templates/class_template_with_shapes.erb +1 -1
  150. data/templates/webview_template.erb +0 -3
  151. metadata +151 -118
  152. data/examples/fill.rb +0 -25
  153. data/examples/legacy/not_checked/shoes-contrib/basic/class-book.yaml +0 -387
  154. data/examples/legacy/not_checked/shoes-contrib/good/good-clock.rb +0 -51
  155. data/examples/legacy/not_checked/shoes-contrib/good/good-follow.rb +0 -26
  156. data/examples/legacy/not_checked/shoes-contrib/good/good-reminder.rb +0 -174
  157. data/examples/legacy/not_checked/shoes-contrib/good/good-vjot.rb +0 -56
  158. data/examples/legacy/not_checked/shoes-contrib/simple/simple-timer.rb +0 -13
  159. data/examples/legacy/not_checked/shoes-dep-samples/good-clock.rb +0 -51
  160. data/examples/legacy/not_checked/shoes-dep-samples/good-follow.rb +0 -26
  161. data/examples/legacy/not_checked/shoes-dep-samples/good-reminder.rb +0 -174
  162. data/examples/legacy/not_checked/shoes-dep-samples/good-vjot.rb +0 -56
  163. data/examples/legacy/not_checked/shoes-dep-samples/simple-accordion.rb +0 -75
  164. data/examples/legacy/not_checked/shoes-dep-samples/simple-anim-shapes.rb +0 -17
  165. data/examples/legacy/not_checked/shoes-dep-samples/simple-anim-text.rb +0 -13
  166. data/examples/legacy/not_checked/shoes-dep-samples/simple-arc.rb +0 -23
  167. data/examples/legacy/not_checked/shoes-dep-samples/simple-bounce.rb +0 -24
  168. data/examples/legacy/not_checked/shoes-dep-samples/simple-calc.rb +0 -70
  169. data/examples/legacy/not_checked/shoes-dep-samples/simple-chipmunk.rb +0 -26
  170. data/examples/legacy/not_checked/shoes-dep-samples/simple-control-sizes.rb +0 -24
  171. data/examples/legacy/not_checked/shoes-dep-samples/simple-curve.rb +0 -26
  172. data/examples/legacy/not_checked/shoes-dep-samples/simple-dialogs.rb +0 -29
  173. data/examples/legacy/not_checked/shoes-dep-samples/simple-draw.rb +0 -13
  174. data/examples/legacy/not_checked/shoes-dep-samples/simple-editor.rb +0 -28
  175. data/examples/legacy/not_checked/shoes-dep-samples/simple-form.rb +0 -28
  176. data/examples/legacy/not_checked/shoes-dep-samples/simple-form.shy +0 -0
  177. data/examples/legacy/not_checked/shoes-dep-samples/simple-mask.rb +0 -21
  178. data/examples/legacy/not_checked/shoes-dep-samples/simple-menu.rb +0 -31
  179. data/examples/legacy/not_checked/shoes-dep-samples/simple-menu1.rb +0 -35
  180. data/examples/legacy/not_checked/shoes-dep-samples/simple-rubygems.rb +0 -29
  181. data/examples/legacy/not_checked/shoes-dep-samples/simple-slide.rb +0 -45
  182. data/examples/legacy/not_checked/shoes-dep-samples/simple-sphere.rb +0 -28
  183. data/examples/legacy/not_checked/shoes-dep-samples/simple-sqlite3.rb +0 -13
  184. data/examples/legacy/not_checked/shoes-dep-samples/simple-timer.rb +0 -13
  185. data/examples/legacy/not_checked/shoes-dep-samples/simple-video.rb +0 -13
  186. data/examples/legacy/not_checked/simple/anim-text.rb +0 -13
  187. data/examples/legacy/not_checked/simple/arc.rb +0 -23
  188. data/examples/legacy/not_checked/simple/bounce.rb +0 -24
  189. data/examples/legacy/not_checked/simple/chipmunk.rb +0 -26
  190. data/examples/legacy/not_checked/simple/curve.rb +0 -26
  191. data/examples/legacy/not_checked/simple/dialogs.rb +0 -29
  192. data/examples/legacy/not_checked/simple/downloader.rb +0 -40
  193. data/examples/legacy/not_checked/simple/draw.rb +0 -13
  194. data/examples/legacy/not_checked/simple/mask.rb +0 -21
  195. data/examples/legacy/not_checked/simple/slide.rb +0 -45
  196. data/examples/legacy/not_checked/simple/sphere.rb +0 -28
  197. data/lib/constants.rb +0 -5
  198. data/lib/scarpe/app.rb +0 -78
  199. data/lib/scarpe/document_root.rb +0 -20
  200. data/lib/scarpe/fill.rb +0 -23
  201. data/lib/scarpe/flow.rb +0 -19
  202. data/lib/scarpe/line.rb +0 -25
  203. data/lib/scarpe/logger.rb +0 -155
  204. data/lib/scarpe/shape.rb +0 -19
  205. data/lib/scarpe/spacing.rb +0 -9
  206. data/lib/scarpe/stack.rb +0 -70
  207. data/lib/scarpe/text_widget.rb +0 -42
  208. data/lib/scarpe/unit_test_helpers.rb +0 -163
  209. data/lib/scarpe/widgets.rb +0 -30
  210. data/lib/scarpe/wv/fill.rb +0 -30
  211. data/lib/scarpe/wv/shape_helper.rb +0 -44
  212. data/scarpe-0.2.0.gem +0 -0
  213. /data/{lib/scarpe → spikes}/glibui/README.md +0 -0
  214. /data/{lib/scarpe → spikes}/glibui/alert.rb +0 -0
  215. /data/{lib/scarpe → spikes}/glibui/app.rb +0 -0
  216. /data/{lib/scarpe → spikes}/glibui/background.rb +0 -0
  217. /data/{lib/scarpe → spikes}/glibui/border.rb +0 -0
  218. /data/{lib/scarpe → spikes}/glibui/button.rb +0 -0
  219. /data/{lib/scarpe → spikes}/glibui/dimensions.rb +0 -0
  220. /data/{lib/scarpe → spikes}/glibui/document_root.rb +0 -0
  221. /data/{lib/scarpe → spikes}/glibui/edit_box.rb +0 -0
  222. /data/{lib/scarpe → spikes}/glibui/edit_line.rb +0 -0
  223. /data/{lib/scarpe → spikes}/glibui/flow.rb +0 -0
  224. /data/{lib/scarpe → spikes}/glibui/html.rb +0 -0
  225. /data/{lib/scarpe → spikes}/glibui/image.rb +0 -0
  226. /data/{lib/scarpe → spikes}/glibui/link.rb +0 -0
  227. /data/{lib/scarpe → spikes}/glibui/local_display.rb +0 -0
  228. /data/{lib/scarpe → spikes}/glibui/para.rb +0 -0
  229. /data/{lib/scarpe → spikes}/glibui/spacing.rb +0 -0
  230. /data/{lib/scarpe → spikes}/glibui/stack.rb +0 -0
  231. /data/{lib/scarpe → spikes}/glibui/text_widget.rb +0 -0
  232. /data/{lib/scarpe → spikes}/libui/alert.rb +0 -0
  233. /data/{lib/scarpe → spikes}/libui/button.rb +0 -0
  234. /data/{lib/scarpe → spikes}/libui/colors.rb +0 -0
  235. /data/{lib/scarpe → spikes}/libui/core.rb +0 -0
  236. /data/{lib/scarpe → spikes}/libui/flow.rb +0 -0
  237. /data/{lib/scarpe → spikes}/libui/libui.rb +0 -0
  238. /data/{lib/scarpe → spikes}/libui/notepad.md +0 -0
  239. /data/{lib/scarpe → spikes}/libui/para.rb +0 -0
  240. /data/{lib/scarpe → spikes}/libui/stack.rb +0 -0
@@ -1,24 +1,36 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Scarpe
4
+ # This is the simplest type of Webview DisplayService. It creates Webview widgets
5
+ # corresponding to Shoes widgets, manages the Webview and its DOM tree, and
6
+ # generally keeps the Shoes/Webview connection working.
7
+ #
4
8
  # This is an in-process Webview-based display service, with all the limitations that
5
9
  # entails. Slow handlers will crash, ending this display service will end the
6
10
  # process, too many or too large evals can crash the process, etc.
7
- # Normally the intention is to use a RelayDisplayService to a second
11
+ # Frequently it's better to use a RelayDisplayService to a second
8
12
  # process containing one of these.
9
- class WebviewDisplayService < Scarpe::DisplayService
10
- include Scarpe::Log
13
+ class WebviewDisplayService < Shoes::DisplayService
14
+ include Shoes::Log
11
15
 
12
16
  class << self
13
17
  attr_accessor :instance
14
18
  end
15
19
 
16
- # TODO: re-think the list of top-level singleton objects.
20
+ # The ControlInterface is used to handle internal events in Webview Scarpe
17
21
  attr_reader :control_interface
22
+
23
+ # The DocumentRoot is the top widget of the Webview-side widget tree
18
24
  attr_reader :doc_root
25
+
26
+ # app is the Scarpe::WebviewApp
27
+ attr_reader :app
28
+
29
+ # wrangler is the Scarpe::WebWrangler
19
30
  attr_reader :wrangler
20
31
 
21
- # This is called before any of the various WebviewWidgets are created.
32
+ # This is called before any of the various WebviewWidgets are created, to be
33
+ # able to create them and look them up.
22
34
  def initialize
23
35
  if WebviewDisplayService.instance
24
36
  raise "ERROR! This is meant to be a singleton!"
@@ -32,6 +44,13 @@ class Scarpe
32
44
  @display_widget_for = {}
33
45
  end
34
46
 
47
+ # Create a Webview display widget for a specific Shoes widget, and pair it with
48
+ # the linkable ID for this Shoes widget.
49
+ #
50
+ # @param widget_class_name [String] The class name of the Shoes widget, e.g. Shoes::Button
51
+ # @param widget_id [String] the linkable ID for widget events
52
+ # @param properties [Hash] a JSON-serialisable Hash with the widget's display properties
53
+ # @return [WebviewWidget] the newly-created Webview widget
35
54
  def create_display_widget_for(widget_class_name, widget_id, properties)
36
55
  if widget_class_name == "App"
37
56
  unless @doc_root
@@ -64,6 +83,9 @@ class Scarpe
64
83
  display_widget
65
84
  end
66
85
 
86
+ # Destroy the display service and the app. Quit the process (eventually.)
87
+ #
88
+ # @return [void]
67
89
  def destroy
68
90
  @app.destroy
69
91
  WebviewDisplayService.instance = nil
@@ -3,129 +3,24 @@
3
3
  require "socket"
4
4
  require "rbconfig"
5
5
 
6
- class Scarpe
7
- class AppShutdownError < Scarpe::Error; end
8
-
9
- module WVRelayUtil # Make sure the including class also includes Scarpe::Log
10
- def ready_to_read?(timeout = 0.0)
11
- r, _, e = IO.select [@from], [], [@from, @to].uniq, timeout
12
-
13
- # On timeout, select returns nil instead of arrays.
14
- return if r.nil?
15
-
16
- unless e.empty?
17
- raise "#{@i_am}: Got error on connection(s) from IO.select! Dying!"
18
- end
19
-
20
- !r.empty?
21
- end
22
-
23
- def send_datagram(contents)
24
- str_data = JSON.dump contents
25
- dgram_str = (str_data.length.to_s + "a" + str_data).encode(Encoding::BINARY)
26
- to_write = dgram_str.bytesize
27
- written = 0
28
-
29
- until written == to_write
30
- count = @to.write(dgram_str.byteslice(written..-1))
31
- if count.nil? || count == 0
32
- raise "Something was wrong in send_datagram! Write returned #{count.inspect}!"
33
- end
34
-
35
- written += count
36
- end
37
-
38
- nil
39
- end
40
-
41
- def receive_datagram
42
- @readbuf ||= String.new.encode(Encoding::BINARY)
43
- to_read = nil
44
-
45
- loop do
46
- # Have we read a packet length already, sitting in @readbuf?
47
- a_idx = @readbuf.index("a")
48
- if a_idx
49
- to_read = @readbuf[0..a_idx].to_i
50
- @readbuf = @readbuf[(a_idx + 1)..-1]
51
- break
52
- end
53
-
54
- # If not, read more bytes
55
- new_bytes = @from.read(10)
56
- if new_bytes.nil?
57
- # This is perfectly normal, if the connection closed
58
- raise AppShutdownError, "Got an unexpected EOF reading datagram! " +
59
- "Did the #{@i_am == :child ? "parent" : "child"} process die?"
60
- end
61
- @readbuf << new_bytes
62
- end
63
-
64
- loop do
65
- if @readbuf.bytesize >= to_read
66
- out = @readbuf.byteslice(0, to_read)
67
- @readbuf = @readbuf.byteslice(to_read, -1)
68
- return out
69
- end
70
-
71
- new_bytes = @from.read(to_read - @readbuf.bytesize)
72
- @readbuf << new_bytes
73
- end
74
- rescue
75
- raise AppShutdownError, "Got exception #{$!.class} when receiving datagram... #{$!.inspect}"
76
- end
77
-
78
- def respond_to_datagram
79
- message = receive_datagram
80
- m_data = JSON.parse(message)
81
-
82
- if m_data["type"] == "event"
83
- kwargs_hash = {}
84
- m_data["kwargs"].each { |k, v| kwargs_hash[k.to_sym] = v }
85
- send_shoes_event(
86
- *m_data["args"],
87
- event_name: m_data["kwargs"]["event_name"],
88
- target: m_data["kwargs"]["event_target"],
89
- **kwargs_hash,
90
- )
91
- elsif m_data["type"] == "create"
92
- raise "Parent process should never receive :create datagram!" if @i_am == :parent
6
+ require_relative "webview_relay_util"
93
7
 
94
- @wv_display.create_display_widget_for(m_data["class_name"], m_data["id"], m_data["properties"])
95
- elsif m_data["type"] == "destroy"
96
- if @i_am == :parent
97
- @shutdown = true
98
- else
99
- @log.info("Shutting down...")
100
- exit 0
101
- end
102
- else
103
- @log.error("Unrecognized datagram type:event: #{m_data.inspect}!")
104
- end
105
- end
106
-
107
- def event_loop_for(t = 1.5)
108
- t_start = Time.now
109
- delay_time = t
110
-
111
- while Time.now - t_start < delay_time
112
- if ready_to_read?(0.1)
113
- respond_to_datagram
114
- else
115
- sleep 0.1
116
- end
117
- end
118
- end
119
- end
120
-
121
- # This "display service" actually creates a child process and sends events
122
- # back and forth, but creates no widgets of its own.
123
- class WVRelayDisplayService < Scarpe::DisplayService
124
- include Scarpe::Log
125
- include WVRelayUtil # Needs Scarpe::Log
8
+ class Scarpe
9
+ # This display service creates a child process and sends events
10
+ # back and forth, but creates no widgets of its own. The child
11
+ # process will spawn a worker with its own WebviewDisplayService
12
+ # where the real Webview exists. By splitting the Webview
13
+ # process from the Shoes widgets, it can be easier to return
14
+ # control to Webview's event handler promptly. Also, the Ruby
15
+ # process could run background threads if it wanted, and
16
+ # otherwise behave like a process ***not*** containing Webview.
17
+ class WVRelayDisplayService < Shoes::DisplayService
18
+ include Shoes::Log
19
+ include WVRelayUtil # Needs Shoes::Log
126
20
 
127
21
  attr_accessor :shutdown
128
22
 
23
+ # Create a Webview Relay Display Service
129
24
  def initialize
130
25
  super()
131
26
  log_init("WV::RelayDisplayService")
@@ -158,6 +53,7 @@ class Scarpe
158
53
  end
159
54
  end
160
55
 
56
+ # Run, sending and responding to datagrams continuously.
161
57
  def run_event_loop
162
58
  until @shutdown
163
59
  respond_to_datagram while ready_to_read?
@@ -169,11 +65,14 @@ class Scarpe
169
65
  self.destroy
170
66
  end
171
67
 
68
+ # This method sends a message to the worker process to create a widget. No actual
69
+ # widget is created or registered with the display service.
172
70
  def create_display_widget_for(widget_class_name, widget_id, properties)
173
71
  send_datagram({ type: :create, class_name: widget_class_name, id: widget_id, properties: })
174
72
  # Don't need to return anything. It wouldn't be used anyway.
175
73
  end
176
74
 
75
+ # Tell the worker process to quit, and set a flag telling the event loop to shut down.
177
76
  def destroy
178
77
  unless @shutdown
179
78
  send_datagram({ type: :destroy })
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "socket"
4
+
5
+ class Scarpe
6
+ # An error occurred which would normally be handled by shutting down the app
7
+ class AppShutdownError < Scarpe::Error; end
8
+
9
+ # WVRelayUtil defines the datagram format for the sockets that connect a parent
10
+ # Shoes application with a child display server.
11
+ #
12
+ # The class including this module should also include Shoes::Log so that it can
13
+ # be used.
14
+ module WVRelayUtil
15
+ # Checks whether the internal socket is ready to be read from.
16
+ # If timeout is greater than 0, this will block for up to that long.
17
+ #
18
+ # @param timeout [Float] the longest to wait for more input to read
19
+ # @return [Boolean] whether the socket has data ready for reading
20
+ def ready_to_read?(timeout = 0.0)
21
+ r, _, e = IO.select [@from], [], [@from, @to].uniq, timeout
22
+
23
+ # On timeout, select returns nil instead of arrays.
24
+ return if r.nil?
25
+
26
+ unless e.empty?
27
+ raise "#{@i_am}: Got error on connection(s) from IO.select! Dying!"
28
+ end
29
+
30
+ !r.empty?
31
+ end
32
+
33
+ # Send bytes on the internal socket to the opposite side.
34
+ #
35
+ # @param contents [String] data to send
36
+ # @return [void]
37
+ def send_datagram(contents)
38
+ str_data = JSON.dump contents
39
+ dgram_str = (str_data.length.to_s + "a" + str_data).encode(Encoding::BINARY)
40
+ to_write = dgram_str.bytesize
41
+ written = 0
42
+
43
+ until written == to_write
44
+ count = @to.write(dgram_str.byteslice(written..-1))
45
+ if count.nil? || count == 0
46
+ raise "Something was wrong in send_datagram! Write returned #{count.inspect}!"
47
+ end
48
+
49
+ written += count
50
+ end
51
+
52
+ nil
53
+ end
54
+
55
+ # Read data from the internal socket. Read until a whole datagram
56
+ # has been received and then return it.
57
+ #
58
+ # @return [String] the received datagram
59
+ def receive_datagram
60
+ @readbuf ||= String.new.encode(Encoding::BINARY)
61
+ to_read = nil
62
+
63
+ loop do
64
+ # Have we read a packet length already, sitting in @readbuf?
65
+ a_idx = @readbuf.index("a")
66
+ if a_idx
67
+ to_read = @readbuf[0..a_idx].to_i
68
+ @readbuf = @readbuf[(a_idx + 1)..-1]
69
+ break
70
+ end
71
+
72
+ # If not, read more bytes
73
+ new_bytes = @from.read(10)
74
+ if new_bytes.nil?
75
+ # This is perfectly normal, if the connection closed
76
+ raise AppShutdownError, "Got an unexpected EOF reading datagram! " +
77
+ "Did the #{@i_am == :child ? "parent" : "child"} process die?"
78
+ end
79
+ @readbuf << new_bytes
80
+ end
81
+
82
+ loop do
83
+ if @readbuf.bytesize >= to_read
84
+ out = @readbuf.byteslice(0, to_read)
85
+ @readbuf = @readbuf.byteslice(to_read, -1)
86
+ return out
87
+ end
88
+
89
+ new_bytes = @from.read(to_read - @readbuf.bytesize)
90
+ @readbuf << new_bytes
91
+ end
92
+ rescue
93
+ raise AppShutdownError, "Got exception #{$!.class} when receiving datagram... #{$!.inspect}"
94
+ end
95
+
96
+ # Read a datagram from the internal buffer and then dispatch it to the
97
+ # appropriate handler.
98
+ def respond_to_datagram
99
+ message = receive_datagram
100
+ m_data = JSON.parse(message)
101
+
102
+ if m_data["type"] == "event"
103
+ kwargs_hash = {}
104
+ m_data["kwargs"].each { |k, v| kwargs_hash[k.to_sym] = v }
105
+ send_shoes_event(
106
+ *m_data["args"],
107
+ event_name: m_data["kwargs"]["event_name"],
108
+ target: m_data["kwargs"]["event_target"],
109
+ **kwargs_hash,
110
+ )
111
+ elsif m_data["type"] == "create"
112
+ raise "Parent process should never receive :create datagram!" if @i_am == :parent
113
+
114
+ @wv_display.create_display_widget_for(m_data["class_name"], m_data["id"], m_data["properties"])
115
+ elsif m_data["type"] == "destroy"
116
+ if @i_am == :parent
117
+ @shutdown = true
118
+ else
119
+ @log.info("Shutting down...")
120
+ exit 0
121
+ end
122
+ else
123
+ @log.error("Unrecognized datagram type:event: #{m_data.inspect}!")
124
+ end
125
+ end
126
+
127
+ # Loop for up to `t` seconds, reading data and waiting.
128
+ #
129
+ # @param t [Float] the number of seconds to loop for
130
+ def event_loop_for(t = 1.5)
131
+ t_start = Time.now
132
+ delay_time = t
133
+
134
+ while Time.now - t_start < delay_time
135
+ if ready_to_read?(0.1)
136
+ respond_to_datagram
137
+ else
138
+ sleep 0.1
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
@@ -1,14 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Scarpe
4
- class WebviewWidget < DisplayService::Linkable
5
- include Scarpe::Log
4
+ # The WebviewWidget parent class helps connect a Webview widget with
5
+ # its Shoes equivalent, render itself to the Webview DOM, handle
6
+ # Javascript events and generally keep things working in Webview.
7
+ class WebviewWidget < Shoes::Linkable
8
+ include Shoes::Log
6
9
 
7
10
  class << self
11
+ # Return the corresponding Webview class for a particular Shoes class name
8
12
  def display_class_for(scarpe_class_name)
9
- scarpe_class = Scarpe.const_get(scarpe_class_name)
10
- unless scarpe_class.ancestors.include?(Scarpe::DisplayService::Linkable)
11
- raise "Scarpe Webview can only get display classes for Scarpe " +
13
+ scarpe_class = Shoes.const_get(scarpe_class_name)
14
+ unless scarpe_class.ancestors.include?(Shoes::Linkable)
15
+ raise "Scarpe Webview can only get display classes for Shoes " +
12
16
  "linkable widgets, not #{scarpe_class_name.inspect}!"
13
17
  end
14
18
 
@@ -21,10 +25,17 @@ class Scarpe
21
25
  end
22
26
  end
23
27
 
28
+ # The Shoes ID corresponding to the Shoes widget for this Webview widget
24
29
  attr_reader :shoes_linkable_id
30
+
31
+ # The WebviewWidget parent of this widget
25
32
  attr_reader :parent
33
+
34
+ # An array of WebviewWidget children (possibly empty) of this widget
26
35
  attr_reader :children
27
36
 
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.
28
39
  def initialize(properties)
29
40
  log_init("WV::Widget")
30
41
 
@@ -64,17 +75,42 @@ class Scarpe
64
75
  super(linkable_id: @shoes_linkable_id)
65
76
  end
66
77
 
67
- # This exists to be overridden by children watching for changes
78
+ # Properties_changed will be called automatically when properties change.
79
+ # The widget should delete any changes from the Hash that it knows how
80
+ # to incrementally handle, and pass the rest to super. If any changes
81
+ # go entirely un-handled, a full redraw will be scheduled.
82
+ # This exists to be overridden by children watching for changes.
83
+ #
84
+ # @param changes [Hash] a Hash of new values for properties that have changed
68
85
  def properties_changed(changes)
86
+ # If a widget does something really nonstandard with its html_id or element, it will
87
+ # need to override to prevent this from happening. That's easy enough, though.
88
+ if changes.key?("hidden")
89
+ hidden = changes.delete("hidden")
90
+ if hidden
91
+ html_element.set_style("display", "none")
92
+ else
93
+ new_style = style # Get current display CSS property, which may vary by subclass
94
+ disp = new_style[:display]
95
+ html_element.set_style("display", disp || "block")
96
+ end
97
+ end
98
+
69
99
  needs_update! unless changes.empty?
70
100
  end
71
101
 
102
+ # Give this widget a new parent, including managing the appropriate child lists for parent widgets.
72
103
  def set_parent(new_parent)
73
104
  @parent&.remove_child(self)
74
105
  new_parent&.add_child(self)
75
106
  @parent = new_parent
76
107
  end
77
108
 
109
+ # A shorter inspect text for prettier irb output
110
+ def inspect
111
+ "#<#{self.class}:#{self.object_id} @shoes_linkable_id=#{@shoes_linkable_id} @parent=#{@parent.inspect} @children=#{@children.inspect}>"
112
+ end
113
+
78
114
  protected
79
115
 
80
116
  # Do not call directly, use set_parent
@@ -122,24 +158,43 @@ class Scarpe
122
158
  "#%0.2X%0.2X%0.2X" % [r_int, g_int, b_int]
123
159
  end
124
160
 
161
+ # CSS styles
162
+ def style
163
+ styles = {}
164
+ if @hidden
165
+ styles[:display] = "none"
166
+ end
167
+ styles
168
+ end
169
+
125
170
  public
126
171
 
127
- # This gets a mini-webview for just this element and its children, if any
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.
174
+ #
175
+ # @return [Scarpe::WebWrangler::ElementWrangler] a DOM object manager
128
176
  def html_element
129
- @elt_wrangler ||= WebviewDisplayService.instance.doc_root.get_element_wrangler(html_id)
177
+ @elt_wrangler ||= Scarpe::WebWrangler::ElementWrangler.new(html_id)
130
178
  end
131
179
 
132
180
  # Return a promise that guarantees all currently-requested changes have completed
181
+ #
182
+ # @return [Scarpe::Promise] a promise that will be fulfilled when all pending changes have finished
133
183
  def promise_update
134
184
  html_element.promise_update
135
185
  end
136
186
 
187
+ # Get the object's HTML ID
188
+ #
189
+ # @return [String] the HTML ID
137
190
  def html_id
138
191
  object_id.to_s
139
192
  end
140
193
 
141
194
  # to_html is intended to get the HTML DOM rendering of this object and its children.
142
195
  # Calling it should be side-effect-free and NOT update the webview.
196
+ #
197
+ # @return [String] the rendered HTML
143
198
  def to_html
144
199
  @children ||= []
145
200
  child_markup = @children.map(&:to_html).join
@@ -150,27 +205,41 @@ class Scarpe
150
205
  end
151
206
  end
152
207
 
153
- # This binds a Scarpe JS callback, handled via a single dispatch point in the document root
208
+ # This binds a Scarpe JS callback, handled via a single dispatch point in the app
209
+ #
210
+ # @param event [String] the Scarpe widget event name
211
+ # @yield the block to call when the event occurs
154
212
  def bind(event, &block)
155
213
  raise("Widget has no linkable_id! #{inspect}") unless linkable_id
156
214
 
157
- WebviewDisplayService.instance.doc_root.bind("#{linkable_id}-#{event}", &block)
215
+ WebviewDisplayService.instance.app.bind("#{linkable_id}-#{event}", &block)
158
216
  end
159
217
 
160
218
  # Removes the element from both the Ruby Widget tree and the HTML DOM.
161
219
  # Return a promise for when that HTML change will be visible.
220
+ #
221
+ # @return [Scarpe::Promise] a promise that is fulfilled when the HTML change is complete
162
222
  def destroy_self
163
223
  @parent&.remove_child(self)
164
224
  html_element.remove
165
225
  end
166
226
 
227
+ # Request a full redraw of all widgets.
228
+ #
167
229
  # It's really hard to do dirty-tracking here because the redraws are fully asynchronous.
168
230
  # And so we can't easily cancel one "in flight," and we can't easily pick up the latest
169
231
  # changes... And we probably don't want to, because we may be halfway through a batch.
232
+ #
233
+ # @return [void]
170
234
  def needs_update!
171
- WebviewDisplayService.instance.doc_root.request_redraw!
235
+ WebviewDisplayService.instance.app.request_redraw!
172
236
  end
173
237
 
238
+ # Generate JS code to trigger a specific event name on this widget with the supplies arguments.
239
+ #
240
+ # @param handler_function_name [String] the event name - @see #bind
241
+ # @param args [Array] additional arguments that will be passed to the event in the generated JS
242
+ # @return [String] the generated JS code
174
243
  def handler_js_code(handler_function_name, *args)
175
244
  raise("Widget has no linkable_id! #{inspect}") unless linkable_id
176
245
 
@@ -7,9 +7,12 @@ require "socket"
7
7
  SCARPE_DIR = File.join(__dir__, "../..")
8
8
 
9
9
  $LOAD_PATH.prepend(SCARPE_DIR)
10
+ ENV["SCARPE_DISPLAY_SERVICE"] = "wv_local"
10
11
  require "scarpe"
11
12
  require "scarpe/wv_local"
12
13
 
14
+ require_relative "webview_relay_util"
15
+
13
16
  # This script exists to create a WebviewDisplayService that can be operated remotely over a socket.
14
17
 
15
18
  if ARGV.length != 1
@@ -17,12 +20,22 @@ if ARGV.length != 1
17
20
  exit(-1)
18
21
  end
19
22
 
20
- class WebviewContainedService < Scarpe::DisplayService::Linkable
21
- include Scarpe::Log
22
- include Scarpe::WVRelayUtil # Needs Scarpe::Log
23
+ # This is the implementation of a freestanding Scarpe Webview display server,
24
+ # which connects via sockets and sends events and properties back and forth
25
+ # with a display-less Shoes app. The interface is designed to allow fork-based
26
+ # usage, where a parent process could create a paired sockets and start the
27
+ # child server. It can also be used via TCP sockets or similar, where a single
28
+ # socket is both input and output.
29
+ class Scarpe::Webview::ContainedService < Shoes::Linkable
30
+ include Shoes::Log
31
+ include Scarpe::WVRelayUtil # Needs Shoes::Log
23
32
 
24
33
  attr_reader :log
25
34
 
35
+ # Create a new DisplayService.
36
+ #
37
+ # @param from [Socket] a readable socket to get input from the Shoes process
38
+ # @param to [Socket] a writable socket on which to send output to the Shoes process
26
39
  def initialize(from, to)
27
40
  super()
28
41
  log_init("WV::DisplayWorker")
@@ -60,6 +73,6 @@ end
60
73
 
61
74
  s = TCPSocket.new("localhost", ARGV[0].to_i)
62
75
 
63
- SERVICE = WebviewContainedService.new(s, s)
76
+ SERVICE = Scarpe::Webview::ContainedService.new(s, s)
64
77
 
65
78
  SERVICE.log.info("Finished event loop. Exiting!")
data/lib/scarpe/wv.rb CHANGED
@@ -2,29 +2,56 @@
2
2
 
3
3
  # Scarpe Webview Display Services
4
4
 
5
+ # This file is for everything that should be included by *both* wv_local and wv_relay
6
+
7
+ require "securerandom"
8
+ require "json"
9
+
10
+ require "bloops"
11
+ require "scarpe/components/modular_logger"
12
+ require "scarpe/components/promises"
13
+
14
+ # Module to contain the various Scarpe Webview classes
15
+ module Scarpe::Webview; end
16
+
17
+ # Set up hierarchical logging using the SCARPE_LOG_CONFIG var for configuration
18
+ log_config = if ENV["SCARPE_LOG_CONFIG"]
19
+ JSON.load_file(ENV["SCARPE_LOG_CONFIG"])
20
+ else
21
+ ENV["SCARPE_DEBUG"] ? Shoes::Log::DEFAULT_DEBUG_LOG_CONFIG : Shoes::Log::DEFAULT_LOG_CONFIG
22
+ end
23
+
24
+ Shoes::Log.instance = Scarpe::Components::ModularLogImpl.new
25
+ Shoes::Log.configure_logger(log_config)
26
+
27
+ require "scarpe/components/segmented_file_loader"
28
+ loader = Scarpe::Components::SegmentedFileLoader.new
29
+ Shoes.add_file_loader loader
30
+
5
31
  require_relative "wv/web_wrangler"
6
32
  require_relative "wv/control_interface"
7
33
 
8
34
  require_relative "wv/widget"
9
- require_relative "wv/webview_local_display"
10
- require_relative "wv/webview_relay_display"
11
35
 
12
36
  require_relative "wv/dimensions"
13
37
  require_relative "wv/html"
14
38
 
15
39
  require_relative "wv/spacing"
16
40
  require_relative "wv/star"
41
+ require_relative "wv/radio"
17
42
  require_relative "wv/background"
18
43
  require_relative "wv/border"
19
44
 
20
- require_relative "wv/fill"
21
45
  require_relative "wv/arc"
46
+ require_relative "wv/font"
22
47
 
23
48
  require_relative "wv/app"
24
- require_relative "wv/document_root"
25
49
  require_relative "wv/para"
50
+ require_relative "wv/slot"
26
51
  require_relative "wv/stack"
27
52
  require_relative "wv/flow"
53
+ require_relative "wv/document_root"
54
+ require_relative "wv/subscription_item"
28
55
  require_relative "wv/button"
29
56
  require_relative "wv/image"
30
57
  require_relative "wv/edit_box"
@@ -37,3 +64,5 @@ require_relative "wv/shape"
37
64
  require_relative "wv/text_widget"
38
65
  require_relative "wv/link"
39
66
  require_relative "wv/line"
67
+ require_relative "wv/video"
68
+ require_relative "wv/check"
@@ -3,4 +3,4 @@
3
3
  require_relative "wv"
4
4
  require_relative "wv/webview_local_display"
5
5
 
6
- Scarpe::DisplayService.set_display_service_class(Scarpe::WebviewDisplayService)
6
+ Shoes::DisplayService.set_display_service_class(Scarpe::WebviewDisplayService)
@@ -3,4 +3,4 @@
3
3
  require_relative "wv"
4
4
  require_relative "wv/webview_relay_display"
5
5
 
6
- Scarpe::DisplayService.set_display_service_class(Scarpe::WVRelayDisplayService)
6
+ Shoes::DisplayService.set_display_service_class(Scarpe::WVRelayDisplayService)