scarpe-wasm 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +21 -0
- data/README.md +39 -0
- data/Rakefile +12 -0
- data/lib/scarpe/wasm/alert.rb +65 -0
- data/lib/scarpe/wasm/app.rb +107 -0
- data/lib/scarpe/wasm/arc.rb +54 -0
- data/lib/scarpe/wasm/background.rb +27 -0
- data/lib/scarpe/wasm/border.rb +24 -0
- data/lib/scarpe/wasm/button.rb +50 -0
- data/lib/scarpe/wasm/check.rb +29 -0
- data/lib/scarpe/wasm/control_interface.rb +147 -0
- data/lib/scarpe/wasm/control_interface_test.rb +234 -0
- data/lib/scarpe/wasm/dimensions.rb +22 -0
- data/lib/scarpe/wasm/document_root.rb +8 -0
- data/lib/scarpe/wasm/edit_box.rb +44 -0
- data/lib/scarpe/wasm/edit_line.rb +43 -0
- data/lib/scarpe/wasm/flow.rb +24 -0
- data/lib/scarpe/wasm/font.rb +36 -0
- data/lib/scarpe/wasm/html.rb +108 -0
- data/lib/scarpe/wasm/image.rb +41 -0
- data/lib/scarpe/wasm/line.rb +35 -0
- data/lib/scarpe/wasm/link.rb +29 -0
- data/lib/scarpe/wasm/list_box.rb +50 -0
- data/lib/scarpe/wasm/para.rb +90 -0
- data/lib/scarpe/wasm/radio.rb +34 -0
- data/lib/scarpe/wasm/shape.rb +68 -0
- data/lib/scarpe/wasm/slot.rb +81 -0
- data/lib/scarpe/wasm/spacing.rb +41 -0
- data/lib/scarpe/wasm/span.rb +66 -0
- data/lib/scarpe/wasm/stack.rb +24 -0
- data/lib/scarpe/wasm/star.rb +61 -0
- data/lib/scarpe/wasm/subscription_item.rb +50 -0
- data/lib/scarpe/wasm/text_widget.rb +30 -0
- data/lib/scarpe/wasm/version.rb +7 -0
- data/lib/scarpe/wasm/video.rb +42 -0
- data/lib/scarpe/wasm/wasm_calls.rb +118 -0
- data/lib/scarpe/wasm/wasm_local_display.rb +94 -0
- data/lib/scarpe/wasm/web_wrangler.rb +679 -0
- data/lib/scarpe/wasm/webview_relay_display.rb +220 -0
- data/lib/scarpe/wasm/widget.rb +228 -0
- data/lib/scarpe/wasm/wv_display_worker.rb +75 -0
- data/lib/scarpe/wasm.rb +46 -0
- data/scarpe-wasm.gemspec +39 -0
- data/sig/scarpe/wasm.rbs +6 -0
- metadata +92 -0
@@ -0,0 +1,220 @@
|
|
1
|
+
# # frozen_string_literal: true
|
2
|
+
|
3
|
+
# require "socket"
|
4
|
+
# require "rbconfig"
|
5
|
+
|
6
|
+
# class Scarpe
|
7
|
+
# # An error occurred which would normally be handled by shutting down the app
|
8
|
+
# class AppShutdownError < Scarpe::Error; end
|
9
|
+
|
10
|
+
# # WVRelayUtil defines the datagram format for the sockets that connect a parent
|
11
|
+
# # Shoes application with a child display server.
|
12
|
+
# #
|
13
|
+
# # The class including this module should also include Shoes::Log so that it can
|
14
|
+
# # be used.
|
15
|
+
# module WVRelayUtil
|
16
|
+
# # Checks whether the internal socket is ready to be read from.
|
17
|
+
# # If timeout is greater than 0, this will block for up to that long.
|
18
|
+
# #
|
19
|
+
# # @param timeout [Float] the longest to wait for more input to read
|
20
|
+
# # @return [Boolean] whether the socket has data ready for reading
|
21
|
+
# def ready_to_read?(timeout = 0.0)
|
22
|
+
# r, _, e = IO.select [@from], [], [@from, @to].uniq, timeout
|
23
|
+
|
24
|
+
# # On timeout, select returns nil instead of arrays.
|
25
|
+
# return if r.nil?
|
26
|
+
|
27
|
+
# unless e.empty?
|
28
|
+
# raise "#{@i_am}: Got error on connection(s) from IO.select! Dying!"
|
29
|
+
# end
|
30
|
+
|
31
|
+
# !r.empty?
|
32
|
+
# end
|
33
|
+
|
34
|
+
# # Send bytes on the internal socket to the opposite side.
|
35
|
+
# #
|
36
|
+
# # @param contents [String] data to send
|
37
|
+
# # @return [void]
|
38
|
+
# def send_datagram(contents)
|
39
|
+
# str_data = JSON.dump contents
|
40
|
+
# dgram_str = (str_data.length.to_s + "a" + str_data).encode(Encoding::BINARY)
|
41
|
+
# to_write = dgram_str.bytesize
|
42
|
+
# written = 0
|
43
|
+
|
44
|
+
# until written == to_write
|
45
|
+
# count = @to.write(dgram_str.byteslice(written..-1))
|
46
|
+
# if count.nil? || count == 0
|
47
|
+
# raise "Something was wrong in send_datagram! Write returned #{count.inspect}!"
|
48
|
+
# end
|
49
|
+
|
50
|
+
# written += count
|
51
|
+
# end
|
52
|
+
|
53
|
+
# nil
|
54
|
+
# end
|
55
|
+
|
56
|
+
# # Read data from the internal socket. Read until a whole datagram
|
57
|
+
# # has been received and then return it.
|
58
|
+
# #
|
59
|
+
# # @return [String] the received datagram
|
60
|
+
# def receive_datagram
|
61
|
+
# @readbuf ||= String.new.encode(Encoding::BINARY)
|
62
|
+
# to_read = nil
|
63
|
+
|
64
|
+
# loop do
|
65
|
+
# # Have we read a packet length already, sitting in @readbuf?
|
66
|
+
# a_idx = @readbuf.index("a")
|
67
|
+
# if a_idx
|
68
|
+
# to_read = @readbuf[0..a_idx].to_i
|
69
|
+
# @readbuf = @readbuf[(a_idx + 1)..-1]
|
70
|
+
# break
|
71
|
+
# end
|
72
|
+
|
73
|
+
# # If not, read more bytes
|
74
|
+
# new_bytes = @from.read(10)
|
75
|
+
# if new_bytes.nil?
|
76
|
+
# # This is perfectly normal, if the connection closed
|
77
|
+
# raise AppShutdownError, "Got an unexpected EOF reading datagram! " +
|
78
|
+
# "Did the #{@i_am == :child ? "parent" : "child"} process die?"
|
79
|
+
# end
|
80
|
+
# @readbuf << new_bytes
|
81
|
+
# end
|
82
|
+
|
83
|
+
# loop do
|
84
|
+
# if @readbuf.bytesize >= to_read
|
85
|
+
# out = @readbuf.byteslice(0, to_read)
|
86
|
+
# @readbuf = @readbuf.byteslice(to_read, -1)
|
87
|
+
# return out
|
88
|
+
# end
|
89
|
+
|
90
|
+
# new_bytes = @from.read(to_read - @readbuf.bytesize)
|
91
|
+
# @readbuf << new_bytes
|
92
|
+
# end
|
93
|
+
# rescue
|
94
|
+
# raise AppShutdownError, "Got exception #{$!.class} when receiving datagram... #{$!.inspect}"
|
95
|
+
# end
|
96
|
+
|
97
|
+
# # Read a datagram from the internal buffer and then dispatch it to the
|
98
|
+
# # appropriate handler.
|
99
|
+
# def respond_to_datagram
|
100
|
+
# message = receive_datagram
|
101
|
+
# m_data = JSON.parse(message)
|
102
|
+
|
103
|
+
# if m_data["type"] == "event"
|
104
|
+
# kwargs_hash = {}
|
105
|
+
# m_data["kwargs"].each { |k, v| kwargs_hash[k.to_sym] = v }
|
106
|
+
# send_shoes_event(
|
107
|
+
# *m_data["args"],
|
108
|
+
# event_name: m_data["kwargs"]["event_name"],
|
109
|
+
# target: m_data["kwargs"]["event_target"],
|
110
|
+
# **kwargs_hash,
|
111
|
+
# )
|
112
|
+
# elsif m_data["type"] == "create"
|
113
|
+
# raise "Parent process should never receive :create datagram!" if @i_am == :parent
|
114
|
+
|
115
|
+
# @wv_display.create_display_widget_for(m_data["class_name"], m_data["id"], m_data["properties"])
|
116
|
+
# elsif m_data["type"] == "destroy"
|
117
|
+
# if @i_am == :parent
|
118
|
+
# @shutdown = true
|
119
|
+
# else
|
120
|
+
# @log.info("Shutting down...")
|
121
|
+
# exit 0
|
122
|
+
# end
|
123
|
+
# else
|
124
|
+
# @log.error("Unrecognized datagram type:event: #{m_data.inspect}!")
|
125
|
+
# end
|
126
|
+
# end
|
127
|
+
|
128
|
+
# # Loop for up to `t` seconds, reading data and waiting.
|
129
|
+
# #
|
130
|
+
# # @param t [Float] the number of seconds to loop for
|
131
|
+
# def event_loop_for(t = 1.5)
|
132
|
+
# t_start = Time.now
|
133
|
+
# delay_time = t
|
134
|
+
|
135
|
+
# while Time.now - t_start < delay_time
|
136
|
+
# if ready_to_read?(0.1)
|
137
|
+
# respond_to_datagram
|
138
|
+
# else
|
139
|
+
# sleep 0.1
|
140
|
+
# end
|
141
|
+
# end
|
142
|
+
# end
|
143
|
+
# end
|
144
|
+
|
145
|
+
# # This display service creates a child process and sends events
|
146
|
+
# # back and forth, but creates no widgets of its own. The child
|
147
|
+
# # process will spawn a worker with its own WebviewDisplayService
|
148
|
+
# # where the real Webview exists. By splitting the Webview
|
149
|
+
# # process from the Shoes widgets, it can be easier to return
|
150
|
+
# # control to Webview's event handler promptly. Also, the Ruby
|
151
|
+
# # process could run background threads if it wanted, and
|
152
|
+
# # otherwise behave like a process ***not*** containing Webview.
|
153
|
+
# class WVRelayDisplayService < Shoes::DisplayService
|
154
|
+
# include Shoes::Log
|
155
|
+
# include WVRelayUtil # Needs Shoes::Log
|
156
|
+
|
157
|
+
# attr_accessor :shutdown
|
158
|
+
|
159
|
+
# # Create a Webview Relay Display Service
|
160
|
+
# def initialize
|
161
|
+
# super()
|
162
|
+
# log_init("WV::RelayDisplayService")
|
163
|
+
|
164
|
+
# @event_subs = []
|
165
|
+
# @shutdown = false
|
166
|
+
# @i_am = :parent
|
167
|
+
|
168
|
+
# server = TCPServer.new("127.0.0.1", 0)
|
169
|
+
# port = server.addr[1]
|
170
|
+
|
171
|
+
# @pid = spawn(RbConfig.ruby, File.join(__dir__, "wv_display_worker.rb"), port.to_s)
|
172
|
+
# @from = @to = server.accept
|
173
|
+
|
174
|
+
# # Subscribe to all event notifications and relay them to the worker
|
175
|
+
# @event_subs << bind_shoes_event(event_name: :any, target: :any) do |*args, **kwargs|
|
176
|
+
# unless kwargs[:relayed]
|
177
|
+
# kwargs[:relayed] = true
|
178
|
+
# send_datagram({ type: :event, args:, kwargs: })
|
179
|
+
# end
|
180
|
+
|
181
|
+
# # Forward the run event to the child process before doing this
|
182
|
+
# if event_name == "run"
|
183
|
+
# run_event_loop
|
184
|
+
# end
|
185
|
+
# rescue AppShutdownError
|
186
|
+
# @shutdown = true
|
187
|
+
# @log.info("Attempting to shut down...")
|
188
|
+
# self.destroy
|
189
|
+
# end
|
190
|
+
# end
|
191
|
+
|
192
|
+
# # Run, sending and responding to datagrams continuously.
|
193
|
+
# def run_event_loop
|
194
|
+
# until @shutdown
|
195
|
+
# respond_to_datagram while ready_to_read?
|
196
|
+
# sleep 0.1
|
197
|
+
# end
|
198
|
+
# rescue AppShutdownError
|
199
|
+
# @shutdown = true
|
200
|
+
# @log.info("Attempting to shut down...")
|
201
|
+
# self.destroy
|
202
|
+
# end
|
203
|
+
|
204
|
+
# # This method sends a message to the worker process to create a widget. No actual
|
205
|
+
# # widget is created or registered with the display service.
|
206
|
+
# def create_display_widget_for(widget_class_name, widget_id, properties)
|
207
|
+
# send_datagram({ type: :create, class_name: widget_class_name, id: widget_id, properties: })
|
208
|
+
# # Don't need to return anything. It wouldn't be used anyway.
|
209
|
+
# end
|
210
|
+
|
211
|
+
# # Tell the worker process to quit, and set a flag telling the event loop to shut down.
|
212
|
+
# def destroy
|
213
|
+
# unless @shutdown
|
214
|
+
# send_datagram({ type: :destroy })
|
215
|
+
# end
|
216
|
+
# @shutdown = true
|
217
|
+
# (@events_subs || []).each { |unsub_id| DisplayService.unsub_from_events(unsub_id) }
|
218
|
+
# end
|
219
|
+
# end
|
220
|
+
# end
|
@@ -0,0 +1,228 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Scarpe
|
4
|
+
# The WASMWidget parent class helps connect a WASM widget with
|
5
|
+
# its Shoes equivalent, render itself to the WASM DOM, handle
|
6
|
+
# Javascript events and generally keep things working in WASM.
|
7
|
+
class WASMWidget < Shoes::Linkable
|
8
|
+
include Shoes::Log
|
9
|
+
|
10
|
+
class << self
|
11
|
+
# Return the corresponding WASM class for a particular Shoes class name
|
12
|
+
def display_class_for(scarpe_class_name)
|
13
|
+
scarpe_class = Shoes.const_get(scarpe_class_name)
|
14
|
+
unless scarpe_class.ancestors.include?(Shoes::Linkable)
|
15
|
+
raise "Scarpe WASM can only get display classes for Shoes " +
|
16
|
+
"linkable widgets, not #{scarpe_class_name.inspect}!"
|
17
|
+
end
|
18
|
+
|
19
|
+
klass = Scarpe.const_get("WASM" + scarpe_class_name.split("::")[-1])
|
20
|
+
if klass.nil?
|
21
|
+
raise "Couldn't find corresponding Scarpe WASM class for #{scarpe_class_name.inspect}!"
|
22
|
+
end
|
23
|
+
|
24
|
+
klass
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# The Shoes ID corresponding to the Shoes widget for this WASM widget
|
29
|
+
attr_reader :shoes_linkable_id
|
30
|
+
|
31
|
+
# The WASMWidget parent of this widget
|
32
|
+
attr_reader :parent
|
33
|
+
|
34
|
+
# An array of WASMWidget children (possibly empty) of this widget
|
35
|
+
attr_reader :children
|
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.
|
39
|
+
def initialize(properties)
|
40
|
+
log_init("WASM::Widget")
|
41
|
+
|
42
|
+
# Call method, which looks up the parent
|
43
|
+
@shoes_linkable_id = properties["shoes_linkable_id"] || properties[:shoes_linkable_id]
|
44
|
+
unless @shoes_linkable_id
|
45
|
+
raise "Could not find property shoes_linkable_id in #{properties.inspect}!"
|
46
|
+
end
|
47
|
+
|
48
|
+
# Set the display properties
|
49
|
+
properties.each do |k, v|
|
50
|
+
next if k == "shoes_linkable_id"
|
51
|
+
|
52
|
+
instance_variable_set("@" + k.to_s, v)
|
53
|
+
end
|
54
|
+
|
55
|
+
# The parent field is *almost* simple enough that a typed display property would handle it.
|
56
|
+
bind_shoes_event(event_name: "parent", target: shoes_linkable_id) do |new_parent_id|
|
57
|
+
display_parent = WASMDisplayService.instance.query_display_widget_for(new_parent_id)
|
58
|
+
if @parent != display_parent
|
59
|
+
set_parent(display_parent)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# When Shoes widgets change properties, we get a change notification here
|
64
|
+
bind_shoes_event(event_name: "prop_change", target: shoes_linkable_id) do |prop_changes|
|
65
|
+
prop_changes.each do |k, v|
|
66
|
+
instance_variable_set("@" + k, v)
|
67
|
+
end
|
68
|
+
properties_changed(prop_changes)
|
69
|
+
end
|
70
|
+
|
71
|
+
bind_shoes_event(event_name: "destroy", target: shoes_linkable_id) do
|
72
|
+
destroy_self
|
73
|
+
end
|
74
|
+
|
75
|
+
super(linkable_id: @shoes_linkable_id)
|
76
|
+
end
|
77
|
+
|
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
|
85
|
+
def properties_changed(changes)
|
86
|
+
needs_update! unless changes.empty?
|
87
|
+
end
|
88
|
+
|
89
|
+
# Give this widget a new parent, including managing the appropriate child lists for parent widgets.
|
90
|
+
def set_parent(new_parent)
|
91
|
+
@parent&.remove_child(self)
|
92
|
+
new_parent&.add_child(self)
|
93
|
+
@parent = new_parent
|
94
|
+
end
|
95
|
+
|
96
|
+
# A shorter inspect text for prettier irb output
|
97
|
+
def inspect
|
98
|
+
"#<#{self.class}:#{self.object_id} @shoes_linkable_id=#{@shoes_linkable_id} @parent=#{@parent.inspect} @children=#{@children.inspect}>"
|
99
|
+
end
|
100
|
+
|
101
|
+
protected
|
102
|
+
|
103
|
+
# Do not call directly, use set_parent
|
104
|
+
def remove_child(child)
|
105
|
+
@children ||= []
|
106
|
+
unless @children.include?(child)
|
107
|
+
@log.error("remove_child: no such child(#{child.inspect}) for"\
|
108
|
+
" parent(#{parent.inspect})!")
|
109
|
+
end
|
110
|
+
@children.delete(child)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Do not call directly, use set_parent
|
114
|
+
def add_child(child)
|
115
|
+
@children ||= []
|
116
|
+
@children << child
|
117
|
+
|
118
|
+
# If we add a child, we should redraw ourselves
|
119
|
+
needs_update!
|
120
|
+
end
|
121
|
+
|
122
|
+
# Convert an [r, g, b, a] array to an HTML hex color code
|
123
|
+
# Arrays support alpha. HTML hex does not. So premultiply.
|
124
|
+
def rgb_to_hex(color)
|
125
|
+
return color if color.nil?
|
126
|
+
|
127
|
+
r, g, b, a = *color
|
128
|
+
if r.is_a?(Float)
|
129
|
+
a ||= 1.0
|
130
|
+
r_float = r * a
|
131
|
+
g_float = g * a
|
132
|
+
b_float = b * a
|
133
|
+
else
|
134
|
+
a ||= 255
|
135
|
+
a_float = (a / 255.0)
|
136
|
+
r_float = (r.to_f / 255.0) * a_float
|
137
|
+
g_float = (g.to_f / 255.0) * a_float
|
138
|
+
b_float = (b.to_f / 255.0) * a_float
|
139
|
+
end
|
140
|
+
|
141
|
+
r_int = (r_float * 255.0).to_i.clamp(0, 255)
|
142
|
+
g_int = (g_float * 255.0).to_i.clamp(0, 255)
|
143
|
+
b_int = (b_float * 255.0).to_i.clamp(0, 255)
|
144
|
+
|
145
|
+
"#%0.2X%0.2X%0.2X" % [r_int, g_int, b_int]
|
146
|
+
end
|
147
|
+
|
148
|
+
public
|
149
|
+
|
150
|
+
# This gets a mini-WASM for just this element and its children, if any.
|
151
|
+
# It is normally called by the widget itself to do its DOM management.
|
152
|
+
#
|
153
|
+
# @return [Scarpe::WebWrangler::ElementWrangler] a DOM object manager
|
154
|
+
def html_element
|
155
|
+
@elt_wrangler ||= Scarpe::WebWrangler::ElementWrangler.new(html_id)
|
156
|
+
end
|
157
|
+
|
158
|
+
# Return a promise that guarantees all currently-requested changes have completed
|
159
|
+
#
|
160
|
+
# @return [Scarpe::Promise] a promise that will be fulfilled when all pending changes have finished
|
161
|
+
def promise_update
|
162
|
+
html_element.promise_update
|
163
|
+
end
|
164
|
+
|
165
|
+
# Get the object's HTML ID
|
166
|
+
#
|
167
|
+
# @return [String] the HTML ID
|
168
|
+
def html_id
|
169
|
+
object_id.to_s
|
170
|
+
end
|
171
|
+
|
172
|
+
# to_html is intended to get the HTML DOM rendering of this object and its children.
|
173
|
+
# Calling it should be side-effect-free and NOT update the WASM.
|
174
|
+
#
|
175
|
+
# @return [String] the rendered HTML
|
176
|
+
def to_html
|
177
|
+
@children ||= []
|
178
|
+
child_markup = @children.map(&:to_html).join
|
179
|
+
if respond_to?(:element)
|
180
|
+
element { child_markup }
|
181
|
+
else
|
182
|
+
child_markup
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# This binds a Scarpe JS callback, handled via a single dispatch point in the app
|
187
|
+
#
|
188
|
+
# @param event [String] the Scarpe widget event name
|
189
|
+
# @yield the block to call when the event occurs
|
190
|
+
def bind(event, &block)
|
191
|
+
raise("Widget has no linkable_id! #{inspect}") unless linkable_id
|
192
|
+
|
193
|
+
WASMDisplayService.instance.app.bind("#{linkable_id}-#{event}", &block)
|
194
|
+
end
|
195
|
+
|
196
|
+
# Removes the element from both the Ruby Widget tree and the HTML DOM.
|
197
|
+
# Return a promise for when that HTML change will be visible.
|
198
|
+
#
|
199
|
+
# @return [Scarpe::Promise] a promise that is fulfilled when the HTML change is complete
|
200
|
+
def destroy_self
|
201
|
+
@parent&.remove_child(self)
|
202
|
+
html_element.remove
|
203
|
+
end
|
204
|
+
|
205
|
+
# Request a full redraw of all widgets.
|
206
|
+
#
|
207
|
+
# It's really hard to do dirty-tracking here because the redraws are fully asynchronous.
|
208
|
+
# And so we can't easily cancel one "in flight," and we can't easily pick up the latest
|
209
|
+
# changes... And we probably don't want to, because we may be halfway through a batch.
|
210
|
+
#
|
211
|
+
# @return [void]
|
212
|
+
def needs_update!
|
213
|
+
WASMDisplayService.instance.app.request_redraw!
|
214
|
+
end
|
215
|
+
|
216
|
+
# Generate JS code to trigger a specific event name on this widget with the supplies arguments.
|
217
|
+
#
|
218
|
+
# @param handler_function_name [String] the event name - @see #bind
|
219
|
+
# @param args [Array] additional arguments that will be passed to the event in the generated JS
|
220
|
+
# @return [String] the generated JS code
|
221
|
+
def handler_js_code(handler_function_name, *args)
|
222
|
+
raise("Widget has no linkable_id! #{inspect}") unless linkable_id
|
223
|
+
|
224
|
+
js_args = ["'#{linkable_id}-#{handler_function_name}'", *args].join(", ")
|
225
|
+
"scarpeHandler(#{js_args})"
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# #!/usr/bin/env ruby
|
2
|
+
# # frozen_string_literal: true
|
3
|
+
|
4
|
+
# require "socket"
|
5
|
+
|
6
|
+
# # Wherever this file is installed, locate Scarpe relative to it. This could be an installed gem or a dev repo.
|
7
|
+
# SCARPE_DIR = File.join(__dir__, "../..")
|
8
|
+
|
9
|
+
# $LOAD_PATH.prepend(SCARPE_DIR)
|
10
|
+
# require "scarpe"
|
11
|
+
# require "scarpe/wv_local"
|
12
|
+
|
13
|
+
# # This script exists to create a WebviewDisplayService that can be operated remotely over a socket.
|
14
|
+
|
15
|
+
# if ARGV.length != 1
|
16
|
+
# $stderr.puts("Usage: wv_display_worker.rb tcp_port_num")
|
17
|
+
# exit(-1)
|
18
|
+
# end
|
19
|
+
|
20
|
+
# # This is the implementation of a freestanding Scarpe Webview display server,
|
21
|
+
# # which connects via sockets and sends events and properties back and forth
|
22
|
+
# # with a display-less Shoes app. The interface is designed to allow fork-based
|
23
|
+
# # usage, where a parent process could create a paired sockets and start the
|
24
|
+
# # child server. It can also be used via TCP sockets or similar, where a single
|
25
|
+
# # socket is both input and output.
|
26
|
+
# class WebviewContainedService < Shoes::Linkable
|
27
|
+
# include Shoes::Log
|
28
|
+
# include Scarpe::WVRelayUtil # Needs Shoes::Log
|
29
|
+
|
30
|
+
# attr_reader :log
|
31
|
+
|
32
|
+
# # Create a new DisplayService.
|
33
|
+
# #
|
34
|
+
# # @param from [Socket] a readable socket to get input from the Shoes process
|
35
|
+
# # @param to [Socket] a writable socket on which to send output to the Shoes process
|
36
|
+
# def initialize(from, to)
|
37
|
+
# super()
|
38
|
+
# log_init("WV::DisplayWorker")
|
39
|
+
|
40
|
+
# @i_am = :child
|
41
|
+
# @event_subs = []
|
42
|
+
# @wv_display = Scarpe::WebviewDisplayService.new
|
43
|
+
|
44
|
+
# @from = from
|
45
|
+
# @to = to
|
46
|
+
|
47
|
+
# @init_done = false
|
48
|
+
|
49
|
+
# # Wait to register our periodic_code until the wrangler exists
|
50
|
+
# @event_subs << bind_shoes_event(event_name: "init") do
|
51
|
+
# @wv_display.wrangler.periodic_code("datagramProcessor", 0.1) do
|
52
|
+
# respond_to_datagram while ready_to_read?(0.0)
|
53
|
+
# end
|
54
|
+
# @init_done = true
|
55
|
+
# end
|
56
|
+
|
57
|
+
# # Subscribe to all event notifications and relay them to the opposite side
|
58
|
+
# @event_subs << bind_shoes_event(event_name: :any, target: :any) do |*args, **kwargs|
|
59
|
+
# unless kwargs[:relayed] || kwargs["relayed"]
|
60
|
+
# kwargs[:relayed] = true
|
61
|
+
# send_datagram({ type: :event, args:, kwargs: })
|
62
|
+
# end
|
63
|
+
# end
|
64
|
+
|
65
|
+
# # Run for 2.5 seconds to let the app be created and "run" to get called.
|
66
|
+
# # Once that happens, Webview will take over the event loop.
|
67
|
+
# event_loop_for(2.5)
|
68
|
+
# end
|
69
|
+
# end
|
70
|
+
|
71
|
+
# s = TCPSocket.new("localhost", ARGV[0].to_i)
|
72
|
+
|
73
|
+
# SERVICE = WebviewContainedService.new(s, s)
|
74
|
+
|
75
|
+
# SERVICE.log.info("Finished event loop. Exiting!")
|
data/lib/scarpe/wasm.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Scarpe WASM Display Services
|
4
|
+
|
5
|
+
require_relative "wasm/wasm_version"
|
6
|
+
|
7
|
+
require_relative "wasm/wasm_calls"
|
8
|
+
require_relative "wasm/web_wrangler"
|
9
|
+
require_relative "wasm/control_interface"
|
10
|
+
|
11
|
+
require_relative "wasm/widget"
|
12
|
+
require_relative "wasm/wasm_local_display"
|
13
|
+
|
14
|
+
require_relative "wasm/dimensions"
|
15
|
+
require_relative "wasm/html"
|
16
|
+
|
17
|
+
require_relative "wasm/spacing"
|
18
|
+
require_relative "wasm/star"
|
19
|
+
require_relative "wasm/radio"
|
20
|
+
require_relative "wasm/background"
|
21
|
+
require_relative "wasm/border"
|
22
|
+
|
23
|
+
require_relative "wasm/arc"
|
24
|
+
require_relative "wasm/font"
|
25
|
+
|
26
|
+
require_relative "wasm/app"
|
27
|
+
require_relative "wasm/para"
|
28
|
+
require_relative "wasm/slot"
|
29
|
+
require_relative "wasm/stack"
|
30
|
+
require_relative "wasm/flow"
|
31
|
+
require_relative "wasm/document_root"
|
32
|
+
require_relative "wasm/subscription_item"
|
33
|
+
require_relative "wasm/button"
|
34
|
+
require_relative "wasm/image"
|
35
|
+
require_relative "wasm/edit_box"
|
36
|
+
require_relative "wasm/edit_line"
|
37
|
+
require_relative "wasm/list_box"
|
38
|
+
require_relative "wasm/alert"
|
39
|
+
require_relative "wasm/span"
|
40
|
+
require_relative "wasm/shape"
|
41
|
+
|
42
|
+
require_relative "wasm/text_widget"
|
43
|
+
require_relative "wasm/link"
|
44
|
+
require_relative "wasm/line"
|
45
|
+
require_relative "wasm/video"
|
46
|
+
require_relative "wasm/check"
|
data/scarpe-wasm.gemspec
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/scarpe/wasm/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "scarpe-wasm"
|
7
|
+
spec.version = Scarpe::Wasm::VERSION
|
8
|
+
spec.authors = ["Giovanni Borgh", "Noah Gibbs", "Scarpe Team"]
|
9
|
+
spec.email = ["the.codefolio.guy@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = "A WASM display library for Scarpe."
|
12
|
+
#spec.description = "TODO: Write a longer description or delete this line."
|
13
|
+
spec.homepage = "https://github.com/scarpe-team/scarpe-wasm"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = ">= 3.2.0"
|
16
|
+
|
17
|
+
#spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
|
18
|
+
|
19
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
20
|
+
#spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
|
21
|
+
#spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
22
|
+
|
23
|
+
# Specify which files should be added to the gem when it is released.
|
24
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
25
|
+
spec.files = Dir.chdir(__dir__) do
|
26
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
27
|
+
(File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
spec.bindir = "exe"
|
31
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
32
|
+
spec.require_paths = ["lib"]
|
33
|
+
|
34
|
+
# Uncomment to register a new dependency of your gem
|
35
|
+
# spec.add_dependency "example-gem", "~> 1.0"
|
36
|
+
|
37
|
+
# For more information and examples about making a new gem, check out our
|
38
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
39
|
+
end
|