scarpe 0.2.0 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +4 -0
- data/.yardopts +11 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +112 -0
- data/README.md +31 -24
- data/Rakefile +13 -1
- data/docs/yard/catscradle.md +44 -0
- data/docs/yard/template/default/fulldoc/html/setup.rb +13 -0
- data/docs/yard/template/default/layout/html/setup.rb +9 -0
- data/examples/background_with_image.rb +16 -0
- data/examples/bloopsaphone/working/bronx_army_knife.rb +66 -0
- data/examples/bloopsaphone/working/morning_serenity.rb +21 -0
- data/examples/bloopsaphone/working/simpsons_theme_song_by_why.rb +6 -4
- data/examples/button_go_away.rb +1 -1
- data/examples/check.rb +18 -0
- data/examples/clear_and_append.rb +24 -0
- data/examples/download_and_show_image.rb +28 -0
- data/examples/edit_box.rb +3 -5
- data/examples/fonts.rb +2 -2
- data/examples/get_headers.rb +10 -0
- data/examples/highlander.rb +2 -0
- data/examples/link.rb +2 -2
- data/examples/local_fonts.rb +4 -0
- data/examples/local_images.rb +4 -0
- data/examples/motion_events.rb +20 -0
- data/examples/parse_xl_funnies.rb +58 -0
- data/examples/radio/radio.rb +16 -0
- data/examples/radio/radio_groups.rb +18 -0
- data/examples/radio/radio_same_slot.rb +6 -0
- data/examples/ruby_racer.rb +13 -15
- data/examples/selfitude.rb +18 -0
- data/examples/shapes/shapes_fill.rb +4 -3
- data/examples/shoes_school.rb +2 -4
- data/examples/show_hide.rb +6 -0
- data/examples/skip_ci/change_my_audio_source.rb +21 -0
- data/examples/skip_ci/guitar_fretboard.rb +137 -0
- data/examples/video.rb +10 -0
- data/exe/scarpe +42 -66
- data/fonts/Pacifico.ttf +0 -0
- data/lacci/Gemfile +22 -0
- data/lacci/Gemfile.lock +72 -0
- data/lacci/Rakefile +12 -0
- data/lacci/lacci.gemspec +37 -0
- data/lacci/lib/lacci/scarpe_cli.rb +70 -0
- data/lacci/lib/lacci/scarpe_core.rb +21 -0
- data/lacci/lib/lacci/version.rb +13 -0
- data/lacci/lib/shoes/app.rb +264 -0
- data/{lib/scarpe → lacci/lib/shoes}/background.rb +1 -1
- data/{lib/scarpe → lacci/lib/shoes}/border.rb +1 -1
- data/{lib/scarpe → lacci/lib/shoes}/colors.rb +1 -1
- data/lacci/lib/shoes/constants.rb +29 -0
- data/{lib/scarpe → lacci/lib/shoes}/display_service.rb +40 -45
- data/lacci/lib/shoes/download.rb +123 -0
- data/lacci/lib/shoes/log.rb +71 -0
- data/lacci/lib/shoes/spacing.rb +9 -0
- data/{lib/scarpe → lacci/lib/shoes}/widget.rb +63 -43
- data/{lib/scarpe → lacci/lib/shoes/widgets}/alert.rb +3 -3
- data/{lib/scarpe → lacci/lib/shoes/widgets}/arc.rb +7 -5
- data/{lib/scarpe → lacci/lib/shoes/widgets}/button.rb +3 -3
- data/lacci/lib/shoes/widgets/check.rb +28 -0
- data/lacci/lib/shoes/widgets/document_root.rb +20 -0
- data/{lib/scarpe → lacci/lib/shoes/widgets}/edit_box.rb +10 -5
- data/{lib/scarpe → lacci/lib/shoes/widgets}/edit_line.rb +2 -2
- data/lacci/lib/shoes/widgets/flow.rb +22 -0
- data/lacci/lib/shoes/widgets/font.rb +14 -0
- data/{lib/scarpe → lacci/lib/shoes/widgets}/image.rb +3 -7
- data/lacci/lib/shoes/widgets/line.rb +18 -0
- data/{lib/scarpe → lacci/lib/shoes/widgets}/link.rb +2 -2
- data/{lib/scarpe → lacci/lib/shoes/widgets}/list_box.rb +2 -2
- data/{lib/scarpe → lacci/lib/shoes/widgets}/para.rb +4 -26
- data/lacci/lib/shoes/widgets/radio.rb +35 -0
- data/lacci/lib/shoes/widgets/shape.rb +37 -0
- data/lacci/lib/shoes/widgets/slot.rb +75 -0
- data/{lib/scarpe → lacci/lib/shoes/widgets}/span.rb +2 -2
- data/lacci/lib/shoes/widgets/stack.rb +24 -0
- data/{lib/scarpe → lacci/lib/shoes/widgets}/star.rb +6 -9
- data/lacci/lib/shoes/widgets/subscription_item.rb +60 -0
- data/lacci/lib/shoes/widgets/text_widget.rb +51 -0
- data/lacci/lib/shoes/widgets/video.rb +15 -0
- data/lacci/lib/shoes/widgets.rb +29 -0
- data/lacci/lib/shoes.rb +127 -0
- data/lacci/test/test_colors.rb +39 -0
- data/lacci/test/test_helper.rb +9 -0
- data/lacci/test/test_lacci.rb +9 -0
- data/lib/scarpe/cats_cradle.rb +249 -0
- data/lib/scarpe/evented_assertions.rb +88 -0
- data/lib/scarpe/version.rb +1 -1
- data/lib/scarpe/wv/alert.rb +3 -2
- data/lib/scarpe/wv/app.rb +30 -8
- data/lib/scarpe/wv/arc.rb +5 -6
- data/lib/scarpe/wv/background.rb +10 -1
- data/lib/scarpe/wv/border.rb +5 -3
- data/lib/scarpe/wv/button.rb +11 -9
- data/lib/scarpe/wv/check.rb +29 -0
- data/lib/scarpe/wv/control_interface.rb +14 -20
- data/lib/scarpe/wv/control_interface_test.rb +13 -28
- data/lib/scarpe/wv/document_root.rb +3 -45
- data/lib/scarpe/wv/edit_box.rb +5 -7
- data/lib/scarpe/wv/edit_line.rb +2 -2
- data/lib/scarpe/wv/flow.rb +10 -20
- data/lib/scarpe/wv/font.rb +36 -0
- data/lib/scarpe/wv/html.rb +3 -2
- data/lib/scarpe/wv/image.rb +7 -2
- data/lib/scarpe/wv/line.rb +4 -7
- data/lib/scarpe/wv/link.rb +1 -0
- data/lib/scarpe/wv/list_box.rb +3 -3
- data/lib/scarpe/wv/para.rb +16 -14
- data/lib/scarpe/wv/radio.rb +34 -0
- data/lib/scarpe/wv/shape.rb +44 -8
- data/lib/scarpe/wv/slot.rb +81 -0
- data/lib/scarpe/wv/spacing.rb +1 -1
- data/lib/scarpe/wv/span.rb +10 -8
- data/lib/scarpe/wv/stack.rb +10 -30
- data/lib/scarpe/wv/star.rb +11 -12
- data/lib/scarpe/wv/subscription_item.rb +50 -0
- data/lib/scarpe/wv/video.rb +34 -0
- data/lib/scarpe/wv/web_wrangler.rb +238 -58
- data/lib/scarpe/wv/webview_local_display.rb +27 -5
- data/lib/scarpe/wv/webview_relay_display.rb +18 -119
- data/lib/scarpe/wv/webview_relay_util.rb +143 -0
- data/lib/scarpe/wv/widget.rb +80 -11
- data/lib/scarpe/wv/wv_display_worker.rb +17 -4
- data/lib/scarpe/wv.rb +33 -4
- data/lib/scarpe/wv_local.rb +1 -1
- data/lib/scarpe/wv_relay.rb +1 -1
- data/lib/scarpe.rb +3 -32
- data/scarpe-components/.gitignore +1 -0
- data/scarpe-components/Gemfile +22 -0
- data/scarpe-components/README.md +35 -0
- data/scarpe-components/Rakefile +12 -0
- data/scarpe-components/lib/scarpe/components/base64.rb +29 -0
- data/scarpe-components/lib/scarpe/components/file_helpers.rb +65 -0
- data/scarpe-components/lib/scarpe/components/modular_logger.rb +113 -0
- data/scarpe-components/lib/scarpe/components/print_logger.rb +43 -0
- data/{lib/scarpe → scarpe-components/lib/scarpe/components}/promises.rb +102 -35
- data/scarpe-components/lib/scarpe/components/segmented_file_loader.rb +170 -0
- data/scarpe-components/lib/scarpe/components/unit_test_helpers.rb +217 -0
- data/scarpe-components/lib/scarpe/components/version.rb +7 -0
- data/scarpe-components/scarpe-components.gemspec +38 -0
- data/scarpe-components/test/test_components.rb +9 -0
- data/scarpe-components/test/test_helper.rb +23 -0
- data/scarpe-components/test/test_promises.rb +260 -0
- data/scarpe-components/test/test_segmented_app_files.rb +182 -0
- data/{lib/scarpe → spikes}/glibui/widget.rb +2 -2
- data/{lib/scarpe → spikes}/glibui.rb +1 -1
- data/templates/basic_class_template.erb +1 -1
- data/templates/class_template_with_event_bind.erb +1 -1
- data/templates/class_template_with_shapes.erb +1 -1
- data/templates/webview_template.erb +0 -3
- metadata +165 -117
- data/examples/fill.rb +0 -25
- data/examples/legacy/not_checked/shoes-contrib/basic/class-book.yaml +0 -387
- data/examples/legacy/not_checked/shoes-contrib/good/good-clock.rb +0 -51
- data/examples/legacy/not_checked/shoes-contrib/good/good-follow.rb +0 -26
- data/examples/legacy/not_checked/shoes-contrib/good/good-reminder.rb +0 -174
- data/examples/legacy/not_checked/shoes-contrib/good/good-vjot.rb +0 -56
- data/examples/legacy/not_checked/shoes-contrib/simple/simple-timer.rb +0 -13
- data/examples/legacy/not_checked/shoes-dep-samples/good-clock.rb +0 -51
- data/examples/legacy/not_checked/shoes-dep-samples/good-follow.rb +0 -26
- data/examples/legacy/not_checked/shoes-dep-samples/good-reminder.rb +0 -174
- data/examples/legacy/not_checked/shoes-dep-samples/good-vjot.rb +0 -56
- data/examples/legacy/not_checked/shoes-dep-samples/simple-accordion.rb +0 -75
- data/examples/legacy/not_checked/shoes-dep-samples/simple-anim-shapes.rb +0 -17
- data/examples/legacy/not_checked/shoes-dep-samples/simple-anim-text.rb +0 -13
- data/examples/legacy/not_checked/shoes-dep-samples/simple-arc.rb +0 -23
- data/examples/legacy/not_checked/shoes-dep-samples/simple-bounce.rb +0 -24
- data/examples/legacy/not_checked/shoes-dep-samples/simple-calc.rb +0 -70
- data/examples/legacy/not_checked/shoes-dep-samples/simple-chipmunk.rb +0 -26
- data/examples/legacy/not_checked/shoes-dep-samples/simple-control-sizes.rb +0 -24
- data/examples/legacy/not_checked/shoes-dep-samples/simple-curve.rb +0 -26
- data/examples/legacy/not_checked/shoes-dep-samples/simple-dialogs.rb +0 -29
- data/examples/legacy/not_checked/shoes-dep-samples/simple-draw.rb +0 -13
- data/examples/legacy/not_checked/shoes-dep-samples/simple-editor.rb +0 -28
- data/examples/legacy/not_checked/shoes-dep-samples/simple-form.rb +0 -28
- data/examples/legacy/not_checked/shoes-dep-samples/simple-form.shy +0 -0
- data/examples/legacy/not_checked/shoes-dep-samples/simple-mask.rb +0 -21
- data/examples/legacy/not_checked/shoes-dep-samples/simple-menu.rb +0 -31
- data/examples/legacy/not_checked/shoes-dep-samples/simple-menu1.rb +0 -35
- data/examples/legacy/not_checked/shoes-dep-samples/simple-rubygems.rb +0 -29
- data/examples/legacy/not_checked/shoes-dep-samples/simple-slide.rb +0 -45
- data/examples/legacy/not_checked/shoes-dep-samples/simple-sphere.rb +0 -28
- data/examples/legacy/not_checked/shoes-dep-samples/simple-sqlite3.rb +0 -13
- data/examples/legacy/not_checked/shoes-dep-samples/simple-timer.rb +0 -13
- data/examples/legacy/not_checked/shoes-dep-samples/simple-video.rb +0 -13
- data/examples/legacy/not_checked/simple/anim-text.rb +0 -13
- data/examples/legacy/not_checked/simple/arc.rb +0 -23
- data/examples/legacy/not_checked/simple/bounce.rb +0 -24
- data/examples/legacy/not_checked/simple/chipmunk.rb +0 -26
- data/examples/legacy/not_checked/simple/curve.rb +0 -26
- data/examples/legacy/not_checked/simple/dialogs.rb +0 -29
- data/examples/legacy/not_checked/simple/downloader.rb +0 -40
- data/examples/legacy/not_checked/simple/draw.rb +0 -13
- data/examples/legacy/not_checked/simple/mask.rb +0 -21
- data/examples/legacy/not_checked/simple/slide.rb +0 -45
- data/examples/legacy/not_checked/simple/sphere.rb +0 -28
- data/lib/constants.rb +0 -5
- data/lib/scarpe/app.rb +0 -78
- data/lib/scarpe/document_root.rb +0 -20
- data/lib/scarpe/fill.rb +0 -23
- data/lib/scarpe/flow.rb +0 -19
- data/lib/scarpe/line.rb +0 -25
- data/lib/scarpe/logger.rb +0 -155
- data/lib/scarpe/shape.rb +0 -19
- data/lib/scarpe/spacing.rb +0 -9
- data/lib/scarpe/stack.rb +0 -70
- data/lib/scarpe/text_widget.rb +0 -42
- data/lib/scarpe/unit_test_helpers.rb +0 -163
- data/lib/scarpe/widgets.rb +0 -30
- data/lib/scarpe/wv/fill.rb +0 -30
- data/lib/scarpe/wv/shape_helper.rb +0 -44
- /data/{lib/scarpe → spikes}/glibui/README.md +0 -0
- /data/{lib/scarpe → spikes}/glibui/alert.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/app.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/background.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/border.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/button.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/dimensions.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/document_root.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/edit_box.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/edit_line.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/flow.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/html.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/image.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/link.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/local_display.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/para.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/spacing.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/stack.rb +0 -0
- /data/{lib/scarpe → spikes}/glibui/text_widget.rb +0 -0
- /data/{lib/scarpe → spikes}/libui/alert.rb +0 -0
- /data/{lib/scarpe → spikes}/libui/button.rb +0 -0
- /data/{lib/scarpe → spikes}/libui/colors.rb +0 -0
- /data/{lib/scarpe → spikes}/libui/core.rb +0 -0
- /data/{lib/scarpe → spikes}/libui/flow.rb +0 -0
- /data/{lib/scarpe → spikes}/libui/libui.rb +0 -0
- /data/{lib/scarpe → spikes}/libui/notepad.md +0 -0
- /data/{lib/scarpe → spikes}/libui/para.rb +0 -0
- /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
|
-
#
|
11
|
+
# Frequently it's better to use a RelayDisplayService to a second
|
8
12
|
# process containing one of these.
|
9
|
-
class WebviewDisplayService <
|
10
|
-
include
|
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
|
-
#
|
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
|
-
|
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
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
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
|
data/lib/scarpe/wv/widget.rb
CHANGED
@@ -1,14 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class Scarpe
|
4
|
-
|
5
|
-
|
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 =
|
10
|
-
unless scarpe_class.ancestors.include?(
|
11
|
-
raise "Scarpe Webview can only get display classes for
|
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
|
-
#
|
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 ||=
|
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
|
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.
|
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.
|
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
|
-
|
21
|
-
|
22
|
-
|
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 =
|
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"
|
data/lib/scarpe/wv_local.rb
CHANGED
data/lib/scarpe/wv_relay.rb
CHANGED