ruflet_core 0.0.18 → 0.0.20
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.
- checksums.yaml +4 -4
- data/README.md +33 -2
- data/lib/ruflet/version.rb +1 -1
- data/lib/ruflet_protocol/ruflet/protocol.rb +20 -2
- data/lib/ruflet_ui/ruflet/dsl.rb +1 -0
- data/lib/ruflet_ui/ruflet/event.rb +9 -0
- data/lib/ruflet_ui/ruflet/extensions/qrcode_scanner.rb +72 -0
- data/lib/ruflet_ui/ruflet/extensions.rb +101 -0
- data/lib/ruflet_ui/ruflet/page.rb +126 -2
- data/lib/ruflet_ui/ruflet/ui/control_factory.rb +23 -6
- data/lib/ruflet_ui/ruflet/ui/controls/materials/webview_control.rb +113 -1
- data/lib/ruflet_ui/ruflet/ui/controls/ruflet_controls.rb +3 -3
- data/lib/ruflet_ui/ruflet/ui/controls/shared/{fletapp_control.rb → ruflet_app_control.rb} +4 -2
- data/lib/ruflet_ui/ruflet/ui/controls/shared/ruflet_controls.rb +3 -3
- data/lib/ruflet_ui/ruflet/ui/material_control_methods.rb +6 -1
- data/lib/ruflet_ui/ruflet/ui/material_control_registry.rb +1 -0
- data/lib/ruflet_ui/ruflet/ui/shared_control_forwarders.rb +6 -1
- data/lib/ruflet_ui.rb +17 -2
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7b5d3e1bde28f482994a21faa5d1412808941a9a5f6384df22ec4307d7ce4f35
|
|
4
|
+
data.tar.gz: d5d90041346619db3781eb5f67747ccf91d9657f4b62ba1edafd806f959bd5a0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3b7aa35ac477467d595e0d4e2ce7e246cd5ed309d20bca5a1ef658e4f67735fbc829042750722cf0a513cd300d8621c69977697712e580c5b90d3a482be8377b
|
|
7
|
+
data.tar.gz: 462a296c2022360be5ce1df9ddb97400fb6c6ec87b7a443681f7104f78c4060cb1350c889b41f3b9ad090ba2be27da6e140cfa7c40a5d4093382faf842303109
|
data/README.md
CHANGED
|
@@ -1,3 +1,34 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ruflet_core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`ruflet_core` provides Ruflet's Ruby UI API: controls, control builders, page
|
|
4
|
+
operations, events, services, and application lifecycle behavior.
|
|
5
|
+
|
|
6
|
+
Applications normally receive this package through a generated Ruflet project
|
|
7
|
+
or through `ruflet_rails`; it is not a standalone application server.
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
require "ruflet"
|
|
11
|
+
|
|
12
|
+
Ruflet.run do |page|
|
|
13
|
+
page.title = "Hello"
|
|
14
|
+
page.add(
|
|
15
|
+
container(
|
|
16
|
+
bgcolor: :surface_container_high,
|
|
17
|
+
padding: 24,
|
|
18
|
+
content: text(
|
|
19
|
+
value: "Hello Ruflet",
|
|
20
|
+
style: { color: "DeepOrange500", size: 20, weight: "w600" }
|
|
21
|
+
)
|
|
22
|
+
)
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Color properties accept named colors and hex values. Use Ruby-style symbols
|
|
28
|
+
like `:deep_orange_500`, compact strings like `"DeepOrange500"`, spaced names
|
|
29
|
+
like `"Deep Orange 500"`, semantic theme names like `:primary_container`, or
|
|
30
|
+
hex strings like `"#ff6d00"`. Ruflet normalizes named colors before sending
|
|
31
|
+
them to the client.
|
|
32
|
+
|
|
33
|
+
Use `ruflet_server` to run a standalone server-driven application. Use
|
|
34
|
+
`ruflet_rails` when the UI is hosted by Rails.
|
data/lib/ruflet/version.rb
CHANGED
|
@@ -78,10 +78,28 @@ module Ruflet
|
|
|
78
78
|
}
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
# The client's reply to `invoke_control_method`: the pending call id plus its
|
|
82
|
+
# result/error. Ruflet::ConnectionProtocol (ruflet_rails' Rack-hijack adapter
|
|
83
|
+
# and any other host adapter) normalizes this before handing it to
|
|
84
|
+
# Page#handle_invoke_method_result, which matches `call_id` back to the
|
|
85
|
+
# waiting callback. Without this method that path fell through to the DSL's
|
|
86
|
+
# method_missing and every invoke result was silently discarded.
|
|
87
|
+
def normalize_invoke_method_result_payload(payload)
|
|
88
|
+
payload = {} unless payload.is_a?(Hash)
|
|
89
|
+
{
|
|
90
|
+
"call_id" => payload["call_id"] || payload["callId"],
|
|
91
|
+
"result" => payload.key?("result") ? payload["result"] : payload["value"],
|
|
92
|
+
"error" => payload["error"]
|
|
93
|
+
}
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# page_patch defaults to nil rather than {} because mruby cannot parse a
|
|
97
|
+
# hash literal as a keyword argument default, and this file is compiled
|
|
98
|
+
# into the embedded VM.
|
|
99
|
+
def register_response(session_id:, page_patch: nil)
|
|
82
100
|
{
|
|
83
101
|
"session_id" => session_id,
|
|
84
|
-
"page_patch" => {},
|
|
102
|
+
"page_patch" => page_patch || {},
|
|
85
103
|
"error" => ""
|
|
86
104
|
}
|
|
87
105
|
end
|
data/lib/ruflet_ui/ruflet/dsl.rb
CHANGED
|
@@ -321,6 +321,7 @@ module Ruflet
|
|
|
321
321
|
def video(**props) = _pending_app.video(**props)
|
|
322
322
|
def code_editor(value = nil, **props) = _pending_app.code_editor(value, **props)
|
|
323
323
|
def codeeditor(value = nil, **props) = _pending_app.codeeditor(value, **props)
|
|
324
|
+
def lottie(src = nil, **props) = _pending_app.lottie(src, **props)
|
|
324
325
|
def rive(src = nil, **props) = _pending_app.rive(src, **props)
|
|
325
326
|
def fab(content = nil, **props) = _pending_app.fab(content, **props)
|
|
326
327
|
def cupertino_button(content = nil, **props) = _pending_app.cupertino_button(content, **props)
|
|
@@ -17,6 +17,15 @@ module Ruflet
|
|
|
17
17
|
@control = control
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
+
# A stable convenience accessor shared by built-in and extension events.
|
|
21
|
+
# Extension event names are intentionally not part of the core typed-event
|
|
22
|
+
# registry, so fall back to the same value extraction used by GenericEvent.
|
|
23
|
+
def value
|
|
24
|
+
return typed_data.value if typed_data&.respond_to?(:value)
|
|
25
|
+
|
|
26
|
+
Events::GenericEvent.from_data(data).value
|
|
27
|
+
end
|
|
28
|
+
|
|
20
29
|
private
|
|
21
30
|
|
|
22
31
|
def parse_data(raw)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ruflet
|
|
4
|
+
module Extensions
|
|
5
|
+
class QrCodeScannerControl < Ruflet::Control
|
|
6
|
+
TYPE = "qrcode_scanner".freeze
|
|
7
|
+
WIRE = "qrcode_scanner".freeze
|
|
8
|
+
KEYWORDS = %i[
|
|
9
|
+
align animate_align animate_margin animate_offset animate_opacity
|
|
10
|
+
animate_position animate_rotation animate_scale animate_size aspect_ratio
|
|
11
|
+
auto_start auto_zoom badge bottom camera_facing col data detection_speed
|
|
12
|
+
detection_timeout disabled expand expand_loose fit formats height
|
|
13
|
+
invert_image key left margin offset opacity return_image right rotate rtl
|
|
14
|
+
scale scan_window size_change_interval tap_to_focus tooltip top
|
|
15
|
+
torch_enabled visible width zoom_scale on_animation_end on_detect on_error
|
|
16
|
+
on_size_change
|
|
17
|
+
].freeze
|
|
18
|
+
|
|
19
|
+
def initialize(id: nil, **props)
|
|
20
|
+
super(type: TYPE, id: id, **props)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def start(timeout: 10, on_result: nil)
|
|
24
|
+
invoke_scanner("start", timeout: timeout, on_result: on_result)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def stop(timeout: 10, on_result: nil)
|
|
28
|
+
invoke_scanner("stop", timeout: timeout, on_result: on_result)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def switch_camera(timeout: 10, on_result: nil)
|
|
32
|
+
invoke_scanner("switch_camera", timeout: timeout, on_result: on_result)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def toggle_torch(timeout: 10, on_result: nil)
|
|
36
|
+
invoke_scanner("toggle_torch", timeout: timeout, on_result: on_result)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def set_zoom_scale(value, timeout: 10, on_result: nil)
|
|
40
|
+
invoke_scanner("set_zoom_scale", args: { "value" => value }, timeout: timeout, on_result: on_result)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def reset_zoom_scale(timeout: 10, on_result: nil)
|
|
44
|
+
invoke_scanner("reset_zoom_scale", timeout: timeout, on_result: on_result)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def preprocess_props(props)
|
|
50
|
+
mapped = props.dup
|
|
51
|
+
mapped[:formats] = Array(mapped[:formats]).map(&:to_s) if mapped.key?(:formats)
|
|
52
|
+
mapped["formats"] = Array(mapped["formats"]).map(&:to_s) if mapped.key?("formats")
|
|
53
|
+
mapped
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def invoke_scanner(name, args: nil, timeout: 10, on_result: nil)
|
|
57
|
+
runtime_page&.invoke(self, name, args: args, timeout: timeout, on_result: on_result)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
register_control(
|
|
62
|
+
:qrcode_scanner,
|
|
63
|
+
control_class: QrCodeScannerControl,
|
|
64
|
+
flutter: {
|
|
65
|
+
package: "ruflet_qrcode_scanner",
|
|
66
|
+
import: "package:ruflet_qrcode_scanner/ruflet_qrcode_scanner.dart",
|
|
67
|
+
alias: "ruflet_qrcode_scanner",
|
|
68
|
+
constructor: "Extension"
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ruflet
|
|
4
|
+
# Registry used by Ruflet extension gems to add typed Ruby controls and
|
|
5
|
+
# describe the matching Flet Flutter package. Registering a control makes its
|
|
6
|
+
# helpers available in the bare DSL, Ruflet::DSL, Ruflet, Ruflet::UI, and
|
|
7
|
+
# WidgetBuilder without editing Ruflet's generated control method tables.
|
|
8
|
+
module Extensions
|
|
9
|
+
Registration = Struct.new(
|
|
10
|
+
:name,
|
|
11
|
+
:control_class,
|
|
12
|
+
:helpers,
|
|
13
|
+
:flutter,
|
|
14
|
+
keyword_init: true
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
@registrations = {}
|
|
18
|
+
|
|
19
|
+
class << self
|
|
20
|
+
def register_control(name, control_class:, helpers: nil, flutter: nil)
|
|
21
|
+
key = normalize_name(name)
|
|
22
|
+
helper_names = Array(helpers || key).map { |helper| normalize_name(helper) }.uniq.freeze
|
|
23
|
+
registration = Registration.new(
|
|
24
|
+
name: key,
|
|
25
|
+
control_class: control_class,
|
|
26
|
+
helpers: helper_names,
|
|
27
|
+
flutter: normalize_flutter_metadata(flutter)
|
|
28
|
+
).freeze
|
|
29
|
+
|
|
30
|
+
existing = @registrations[key]
|
|
31
|
+
if existing && existing != registration
|
|
32
|
+
raise ArgumentError, "Ruflet extension `#{key}` is already registered"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
helper_names.each do |helper|
|
|
36
|
+
UI::ControlFactory.register_extension(helper, control_class)
|
|
37
|
+
define_control_helper(helper)
|
|
38
|
+
end
|
|
39
|
+
@registrations[key] = registration
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def [](name)
|
|
43
|
+
@registrations[normalize_name(name)]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def each(&block)
|
|
47
|
+
return enum_for(:each) unless block
|
|
48
|
+
|
|
49
|
+
@registrations.values.each(&block)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def registered?(name)
|
|
53
|
+
@registrations.key?(normalize_name(name))
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def normalize_name(value)
|
|
59
|
+
name = value.to_s.strip.downcase.tr("-", "_")
|
|
60
|
+
raise ArgumentError, "Extension name cannot be empty" if name.empty?
|
|
61
|
+
raise ArgumentError, "Invalid extension name `#{value}`" unless name.match?(/\A[a-z][a-z0-9_]*\z/)
|
|
62
|
+
|
|
63
|
+
name
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def normalize_flutter_metadata(metadata)
|
|
67
|
+
return nil if metadata.nil?
|
|
68
|
+
|
|
69
|
+
values = metadata.transform_keys(&:to_sym)
|
|
70
|
+
package = values.fetch(:package).to_s
|
|
71
|
+
import = values.fetch(:import, "package:#{package}/#{package}.dart").to_s
|
|
72
|
+
alias_name = values.fetch(:alias, package).to_s
|
|
73
|
+
constructor = values.fetch(:constructor, "Extension").to_s
|
|
74
|
+
{
|
|
75
|
+
package: package,
|
|
76
|
+
import: import,
|
|
77
|
+
alias: alias_name,
|
|
78
|
+
constructor: constructor
|
|
79
|
+
}.freeze
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def define_control_helper(helper)
|
|
83
|
+
type = helper
|
|
84
|
+
|
|
85
|
+
UI::ControlMethods.send(:define_method, helper) do |**props, &block|
|
|
86
|
+
control(type, **props, &block)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
UI::SharedControlForwarders.send(:define_method, helper) do |**props, &block|
|
|
90
|
+
control_delegate.public_send(type, **props, &block)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
DSL.define_singleton_method(helper) do |**props, &block|
|
|
94
|
+
_pending_app.public_send(type, **props, &block)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
Kernel.send(:private, helper) if Kernel.method_defined?(helper)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -300,6 +300,90 @@ module Ruflet
|
|
|
300
300
|
self
|
|
301
301
|
end
|
|
302
302
|
|
|
303
|
+
# Complete page state as a property map for the register_client response
|
|
304
|
+
# (Flet's page_patch). The client applies this through Control.update,
|
|
305
|
+
# which merges by control id and keeps existing instances alive — the only
|
|
306
|
+
# patch path that survives a re-register on a client with prior state
|
|
307
|
+
# (reconnect after a backend restart). The op-based view patch replaces
|
|
308
|
+
# control instances and detaches containers on such clients.
|
|
309
|
+
def register_page_patch
|
|
310
|
+
refresh_control_indexes!
|
|
311
|
+
patch = { "views" => build_view_patches }
|
|
312
|
+
@page_props.each { |key, value| patch[key] = serialize_patch_value(value) }
|
|
313
|
+
@overlay_container_mounted = true if @overlay_container.wire_id
|
|
314
|
+
@dialogs_container_mounted = true if @dialogs_container.wire_id
|
|
315
|
+
@services_container_mounted = true if @services_container.wire_id
|
|
316
|
+
patch
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# Page props that must survive a hot reload: the route (kept on purpose),
|
|
320
|
+
# the client-reported window, and the internal overlay/services/dialogs
|
|
321
|
+
# containers (kept mounted so the client keeps their bindings).
|
|
322
|
+
RELOAD_PRESERVED_PAGE_PROPS = %w[route window _overlay _dialogs _services].freeze
|
|
323
|
+
|
|
324
|
+
# Quietly clears session content and page chrome so a reloaded app block
|
|
325
|
+
# can re-render on this same Page without recreating it. The page object
|
|
326
|
+
# must survive a hot reload: a new Page re-sends the internal
|
|
327
|
+
# overlay/service/dialogs containers, which replaces their client-side
|
|
328
|
+
# instances and detaches them (see build_page_patch_ops) — after that,
|
|
329
|
+
# dialog and service patches are silently ignored by the client.
|
|
330
|
+
#
|
|
331
|
+
# Chrome (appbar, drawer, FAB, bgcolor, title, ...) lives in @view_props
|
|
332
|
+
# and @page_props. If the reloaded block no longer sets a piece of chrome,
|
|
333
|
+
# the previous run's value would linger, so both are cleared here. The
|
|
334
|
+
# view is fully rebuilt on the next patch, which drops any @view_props not
|
|
335
|
+
# re-set; page-level props merge on the client, so finalize_reload! sends
|
|
336
|
+
# explicit nils for the ones that disappeared. Sends nothing itself.
|
|
337
|
+
def reset_for_reload!
|
|
338
|
+
@root_controls = []
|
|
339
|
+
@views = []
|
|
340
|
+
@dialogs = []
|
|
341
|
+
@dialog = nil
|
|
342
|
+
@snack_bar = nil
|
|
343
|
+
@bottom_sheet = nil
|
|
344
|
+
@route_change_seen_since_reset = false
|
|
345
|
+
|
|
346
|
+
@reload_prior_page_prop_keys = @page_props.keys - RELOAD_PRESERVED_PAGE_PROPS
|
|
347
|
+
@view_props = {}
|
|
348
|
+
@page_props = @page_props.select { |key, _| RELOAD_PRESERVED_PAGE_PROPS.include?(key) }
|
|
349
|
+
@page_props["route"] ||= (@client_details["route"] || "/")
|
|
350
|
+
@page_props["window"] ||= @window
|
|
351
|
+
@overlay_container.children.clear
|
|
352
|
+
refresh_overlay_container!
|
|
353
|
+
refresh_services_container!
|
|
354
|
+
refresh_dialogs_container!
|
|
355
|
+
self
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
# Called after the reloaded app block ran, before the reload view patch.
|
|
359
|
+
# Page-level props merge on the client (the Page control is not recreated),
|
|
360
|
+
# so chrome the reloaded block dropped must be sent as an explicit nil or
|
|
361
|
+
# it lingers. View props need no such treatment: the View is rebuilt
|
|
362
|
+
# wholesale from @view_props. Also flushes the overlay container, which
|
|
363
|
+
# stays mounted and is skipped by a plain view patch.
|
|
364
|
+
def finalize_reload!
|
|
365
|
+
keys = @reload_prior_page_prop_keys
|
|
366
|
+
if keys
|
|
367
|
+
(keys - @page_props.keys).each { |key| @page_props[key] = nil }
|
|
368
|
+
@reload_prior_page_prop_keys = nil
|
|
369
|
+
end
|
|
370
|
+
push_overlay_update!
|
|
371
|
+
self
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# Called after the reloaded app block ran. The route survives a reload
|
|
375
|
+
# (page props are kept), but the client only sends its route_change event
|
|
376
|
+
# at connect time — apps that render routes exclusively inside
|
|
377
|
+
# on_route_change would be stuck on stale content after a reload. Replay
|
|
378
|
+
# the event for them, unless the block already routed itself (page.go).
|
|
379
|
+
def replay_route_after_reload!
|
|
380
|
+
return self if @route_change_seen_since_reset
|
|
381
|
+
return self unless @page_event_handlers["route_change"].respond_to?(:call)
|
|
382
|
+
|
|
383
|
+
dispatch_page_event(name: "route_change", data: @page_props["route"])
|
|
384
|
+
self
|
|
385
|
+
end
|
|
386
|
+
|
|
303
387
|
def overlay
|
|
304
388
|
@overlay_container.children
|
|
305
389
|
end
|
|
@@ -590,11 +674,13 @@ module Ruflet
|
|
|
590
674
|
def dialog=(value)
|
|
591
675
|
@dialog = value
|
|
592
676
|
refresh_dialogs_container!
|
|
677
|
+
push_dialogs_update! if @dialogs_container_mounted
|
|
593
678
|
end
|
|
594
679
|
|
|
595
680
|
def snack_bar=(value)
|
|
596
681
|
@snack_bar = value
|
|
597
682
|
refresh_dialogs_container!
|
|
683
|
+
push_dialogs_update! if @dialogs_container_mounted
|
|
598
684
|
end
|
|
599
685
|
|
|
600
686
|
def snack_bar
|
|
@@ -608,6 +694,7 @@ module Ruflet
|
|
|
608
694
|
def bottom_sheet=(value)
|
|
609
695
|
@bottom_sheet = value
|
|
610
696
|
refresh_dialogs_container!
|
|
697
|
+
push_dialogs_update! if @dialogs_container_mounted
|
|
611
698
|
end
|
|
612
699
|
|
|
613
700
|
def bottom_sheet
|
|
@@ -640,9 +727,22 @@ module Ruflet
|
|
|
640
727
|
end
|
|
641
728
|
|
|
642
729
|
def show_bottom_sheet(bottom_sheet_control)
|
|
730
|
+
@bottom_sheet = bottom_sheet_control
|
|
643
731
|
show_dialog(bottom_sheet_control)
|
|
644
732
|
end
|
|
645
733
|
|
|
734
|
+
def show_bottomsheet(bottom_sheet_control)
|
|
735
|
+
show_bottom_sheet(bottom_sheet_control)
|
|
736
|
+
end
|
|
737
|
+
|
|
738
|
+
def close_bottom_sheet(bottom_sheet_control = nil)
|
|
739
|
+
close_dialog(bottom_sheet_control || @bottom_sheet)
|
|
740
|
+
end
|
|
741
|
+
|
|
742
|
+
def close_bottomsheet(bottom_sheet_control = nil)
|
|
743
|
+
close_bottom_sheet(bottom_sheet_control)
|
|
744
|
+
end
|
|
745
|
+
|
|
646
746
|
def show_banner(banner_control)
|
|
647
747
|
show_dialog(banner_control)
|
|
648
748
|
end
|
|
@@ -651,13 +751,27 @@ module Ruflet
|
|
|
651
751
|
close_dialog(banner_control)
|
|
652
752
|
end
|
|
653
753
|
|
|
754
|
+
# Flet-compatible generic overlay API. Prefer close_bottom_sheet for sheets
|
|
755
|
+
# so the page's current bottom-sheet slot is handled explicitly.
|
|
756
|
+
def open(dialog_control)
|
|
757
|
+
show_dialog(dialog_control)
|
|
758
|
+
end
|
|
759
|
+
|
|
760
|
+
def close(dialog_control = nil)
|
|
761
|
+
close_dialog(dialog_control)
|
|
762
|
+
end
|
|
763
|
+
|
|
654
764
|
def close_dialog(dialog_control = nil)
|
|
655
765
|
target = dialog_control || latest_open_dialog
|
|
656
766
|
return nil unless target
|
|
657
767
|
|
|
768
|
+
clear_dialog_slots(target)
|
|
769
|
+
# The client pops a dialog's route only when the mounted control sees
|
|
770
|
+
# its `open` prop flip to false (alert_dialog.dart tracks open/_open).
|
|
771
|
+
# Replacing the dialogs container recreates the control client-side and
|
|
772
|
+
# the transition is lost, so patch the open prop on the control itself.
|
|
658
773
|
target.props["open"] = false
|
|
659
|
-
|
|
660
|
-
push_dialogs_update!
|
|
774
|
+
update(target, open: false)
|
|
661
775
|
target
|
|
662
776
|
end
|
|
663
777
|
|
|
@@ -1192,6 +1306,8 @@ module Ruflet
|
|
|
1192
1306
|
patch["content"] = patch.delete("text")
|
|
1193
1307
|
end
|
|
1194
1308
|
|
|
1309
|
+
patch.each { |key, value| control.props[key] = value }
|
|
1310
|
+
|
|
1195
1311
|
# Keep runtime control tree aligned with incremental patches.
|
|
1196
1312
|
if patch.key?("controls")
|
|
1197
1313
|
control.children.clear
|
|
@@ -1577,6 +1693,7 @@ module Ruflet
|
|
|
1577
1693
|
def dispatch_page_event(name:, data:)
|
|
1578
1694
|
event_name = name.to_s
|
|
1579
1695
|
event_name = event_name[3..-1] if event_name.start_with?("on_")
|
|
1696
|
+
@route_change_seen_since_reset = true if event_name == "route_change"
|
|
1580
1697
|
handler = @page_event_handlers[event_name]
|
|
1581
1698
|
return unless handler.respond_to?(:call)
|
|
1582
1699
|
|
|
@@ -1712,10 +1829,17 @@ module Ruflet
|
|
|
1712
1829
|
return false unless @dialogs.include?(control)
|
|
1713
1830
|
|
|
1714
1831
|
@dialogs.delete(control)
|
|
1832
|
+
clear_dialog_slots(control)
|
|
1715
1833
|
refresh_dialogs_container!
|
|
1716
1834
|
true
|
|
1717
1835
|
end
|
|
1718
1836
|
|
|
1837
|
+
def clear_dialog_slots(control)
|
|
1838
|
+
@dialog = nil if @dialog.equal?(control)
|
|
1839
|
+
@snack_bar = nil if @snack_bar.equal?(control)
|
|
1840
|
+
@bottom_sheet = nil if @bottom_sheet.equal?(control)
|
|
1841
|
+
end
|
|
1842
|
+
|
|
1719
1843
|
def assign_split_prop(key, value)
|
|
1720
1844
|
if key == "vertical_alignment" || key == "horizontal_alignment"
|
|
1721
1845
|
@page_props[key] = value
|
|
@@ -13,12 +13,14 @@ module Ruflet
|
|
|
13
13
|
Services::RufletServices::CLASS_MAP
|
|
14
14
|
.merge(Controls::RufletControls::CLASS_MAP)
|
|
15
15
|
.freeze
|
|
16
|
+
EXTENSION_CLASS_MAP = {}
|
|
16
17
|
CONSTRUCTOR_KEYWORDS_CACHE = {}
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
COMMON_ATTRIBUTES = %i[flip ref transform].freeze
|
|
20
|
+
ATTRIBUTE_OVERRIDES = {
|
|
20
21
|
"expansionpanellist" => %i[auto_scroll on_scroll scroll scroll_interval],
|
|
21
|
-
"
|
|
22
|
+
"ruflet_app" => %i[assets_dir on_python_output],
|
|
23
|
+
"rufletapp" => %i[assets_dir on_python_output],
|
|
22
24
|
"image" => %i[
|
|
23
25
|
align animate_align animate_margin animate_offset animate_opacity animate_position
|
|
24
26
|
animate_rotation animate_scale animate_size aspect_ratio badge bottom col disabled
|
|
@@ -40,7 +42,7 @@ module Ruflet
|
|
|
40
42
|
if ENV["RUFLET_DEBUG"] == "1" && normalized_type == "floatingactionbutton"
|
|
41
43
|
Kernel.warn("[factory] type=#{normalized_type} id=#{id.inspect} props=#{props.inspect}")
|
|
42
44
|
end
|
|
43
|
-
klass = CLASS_MAP[normalized_type]
|
|
45
|
+
klass = EXTENSION_CLASS_MAP[normalized_type] || CLASS_MAP[normalized_type]
|
|
44
46
|
if klass
|
|
45
47
|
normalized_props, supplemental_props = normalize_constructor_props(klass, normalized_type, props)
|
|
46
48
|
if ENV["RUFLET_DEBUG"] == "1" && normalized_type == "floatingactionbutton"
|
|
@@ -64,7 +66,7 @@ module Ruflet
|
|
|
64
66
|
|
|
65
67
|
def normalize_constructor_props(klass, type, props)
|
|
66
68
|
keywords = constructor_keywords(klass)
|
|
67
|
-
return [props, {}] if keywords.empty? &&
|
|
69
|
+
return [props, {}] if keywords.empty? && ATTRIBUTE_OVERRIDES[type].nil?
|
|
68
70
|
|
|
69
71
|
allowed = keywords.map(&:to_s)
|
|
70
72
|
mapped = props.each_with_object({}) { |(k, v), out| out[k.to_sym] = v }
|
|
@@ -74,7 +76,7 @@ module Ruflet
|
|
|
74
76
|
if mapped.key?(:value) && !allowed.include?("value") && allowed.include?("text") && !mapped.key?(:text)
|
|
75
77
|
mapped[:text] = mapped.delete(:value)
|
|
76
78
|
end
|
|
77
|
-
supplemental_names =
|
|
79
|
+
supplemental_names = COMMON_ATTRIBUTES + ATTRIBUTE_OVERRIDES.fetch(type, [])
|
|
78
80
|
supplemental = mapped.slice(*supplemental_names)
|
|
79
81
|
[mapped.reject { |key, _| supplemental_names.include?(key) }, supplemental]
|
|
80
82
|
end
|
|
@@ -96,6 +98,21 @@ module Ruflet
|
|
|
96
98
|
[]
|
|
97
99
|
end
|
|
98
100
|
|
|
101
|
+
def register_extension(type, klass)
|
|
102
|
+
key = type.to_s.downcase
|
|
103
|
+
raise ArgumentError, "Extension control type cannot be empty" if key.empty?
|
|
104
|
+
raise ArgumentError, "Extension control must inherit Ruflet::Control" unless klass <= Ruflet::Control
|
|
105
|
+
|
|
106
|
+
existing = EXTENSION_CLASS_MAP[key]
|
|
107
|
+
if existing && existing != klass
|
|
108
|
+
raise ArgumentError, "Extension control `#{key}` is already registered by #{existing}"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
EXTENSION_CLASS_MAP[key] = klass
|
|
112
|
+
CONSTRUCTOR_KEYWORDS_CACHE.delete(klass)
|
|
113
|
+
klass
|
|
114
|
+
end
|
|
115
|
+
|
|
99
116
|
def normalize_common_value(value)
|
|
100
117
|
case value
|
|
101
118
|
when Hash
|
|
@@ -4,11 +4,32 @@ module Ruflet
|
|
|
4
4
|
module UI
|
|
5
5
|
module Controls
|
|
6
6
|
module RufletComponents
|
|
7
|
+
# WebView control — parity with Flet's WebView
|
|
8
|
+
# (https://flet.dev/docs/controls/webview/).
|
|
9
|
+
#
|
|
10
|
+
# Properties: url, bgcolor, prevent_links, plus the usual layout props.
|
|
11
|
+
# Events: on_page_started, on_page_ended, on_web_resource_error,
|
|
12
|
+
# on_progress, on_url_change, on_scroll, on_console_message,
|
|
13
|
+
# on_javascript_alert_dialog.
|
|
14
|
+
# Methods (invoked over the wire on a mounted control): reload, go_back,
|
|
15
|
+
# go_forward, can_go_back, can_go_forward, run_javascript, load_html,
|
|
16
|
+
# load_request, load_file, scroll_to, scroll_by, clear_cache,
|
|
17
|
+
# clear_local_storage, enable_zoom, disable_zoom, set_javascript_mode,
|
|
18
|
+
# get_current_url, get_title, get_user_agent.
|
|
19
|
+
#
|
|
20
|
+
# Platform note: the native webview runs on iOS, Android, macOS, Windows,
|
|
21
|
+
# and Linux. Linux distributions must provide WebKitGTK 4.1. On web it
|
|
22
|
+
# uses an iframe, so browser cross-origin restrictions still apply.
|
|
7
23
|
class WebViewControl < Ruflet::Control
|
|
8
24
|
TYPE = "WebView".freeze
|
|
9
25
|
WIRE = "WebView".freeze
|
|
10
26
|
|
|
11
|
-
def initialize(id: nil, bgcolor: nil, data: nil, enable_javascript: nil, expand: nil,
|
|
27
|
+
def initialize(id: nil, bgcolor: nil, data: nil, enable_javascript: nil, expand: nil,
|
|
28
|
+
height: nil, key: nil, method: nil, opacity: nil, prevent_links: nil,
|
|
29
|
+
rtl: nil, tooltip: nil, url: nil, visible: nil, width: nil,
|
|
30
|
+
on_page_ended: nil, on_page_started: nil, on_web_resource_error: nil,
|
|
31
|
+
on_progress: nil, on_url_change: nil, on_scroll: nil,
|
|
32
|
+
on_console_message: nil, on_javascript_alert_dialog: nil)
|
|
12
33
|
props = {}
|
|
13
34
|
props[:bgcolor] = bgcolor unless bgcolor.nil?
|
|
14
35
|
props[:data] = data unless data.nil?
|
|
@@ -18,6 +39,7 @@ module Ruflet
|
|
|
18
39
|
props[:key] = key unless key.nil?
|
|
19
40
|
props[:method] = method unless method.nil?
|
|
20
41
|
props[:opacity] = opacity unless opacity.nil?
|
|
42
|
+
props[:prevent_links] = prevent_links unless prevent_links.nil?
|
|
21
43
|
props[:rtl] = rtl unless rtl.nil?
|
|
22
44
|
props[:tooltip] = tooltip unless tooltip.nil?
|
|
23
45
|
props[:url] = url unless url.nil?
|
|
@@ -26,8 +48,98 @@ module Ruflet
|
|
|
26
48
|
props[:on_page_ended] = on_page_ended unless on_page_ended.nil?
|
|
27
49
|
props[:on_page_started] = on_page_started unless on_page_started.nil?
|
|
28
50
|
props[:on_web_resource_error] = on_web_resource_error unless on_web_resource_error.nil?
|
|
51
|
+
props[:on_progress] = on_progress unless on_progress.nil?
|
|
52
|
+
props[:on_url_change] = on_url_change unless on_url_change.nil?
|
|
53
|
+
props[:on_scroll] = on_scroll unless on_scroll.nil?
|
|
54
|
+
props[:on_console_message] = on_console_message unless on_console_message.nil?
|
|
55
|
+
props[:on_javascript_alert_dialog] = on_javascript_alert_dialog unless on_javascript_alert_dialog.nil?
|
|
29
56
|
super(type: TYPE, id: id, **props)
|
|
30
57
|
end
|
|
58
|
+
|
|
59
|
+
# --- Navigation --------------------------------------------------
|
|
60
|
+
|
|
61
|
+
def reload = invoke_webview_method("reload")
|
|
62
|
+
def go_back = invoke_webview_method("go_back")
|
|
63
|
+
def go_forward = invoke_webview_method("go_forward")
|
|
64
|
+
|
|
65
|
+
def can_go_back(timeout: 10, &on_result)
|
|
66
|
+
invoke_webview_method("can_go_back", timeout: timeout, on_result: on_result)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def can_go_forward(timeout: 10, &on_result)
|
|
70
|
+
invoke_webview_method("can_go_forward", timeout: timeout, on_result: on_result)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# --- Loading content ---------------------------------------------
|
|
74
|
+
|
|
75
|
+
def load_request(url, method: "get")
|
|
76
|
+
invoke_webview_method("load_request", { "url" => url.to_s, "method" => method.to_s })
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def load_html(value, base_url: nil)
|
|
80
|
+
args = { "value" => value.to_s }
|
|
81
|
+
args["base_url"] = base_url.to_s unless base_url.nil?
|
|
82
|
+
invoke_webview_method("load_html", args)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def load_file(path)
|
|
86
|
+
invoke_webview_method("load_file", { "path" => path.to_s })
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# --- JavaScript injection ---------------------------------------
|
|
90
|
+
|
|
91
|
+
# Run arbitrary JS inside the page — e.g. hide a node so a native
|
|
92
|
+
# control can take its place:
|
|
93
|
+
# webview.run_javascript("document.getElementById('banner').remove()")
|
|
94
|
+
def run_javascript(value)
|
|
95
|
+
invoke_webview_method("run_javascript", { "value" => value.to_s })
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def set_javascript_mode(mode)
|
|
99
|
+
invoke_webview_method("set_javascript_mode", { "mode" => mode.to_s })
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# --- Scrolling ---------------------------------------------------
|
|
103
|
+
|
|
104
|
+
def scroll_to(x, y)
|
|
105
|
+
invoke_webview_method("scroll_to", { "x" => x.to_i, "y" => y.to_i })
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def scroll_by(x, y)
|
|
109
|
+
invoke_webview_method("scroll_by", { "x" => x.to_i, "y" => y.to_i })
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# --- Storage / zoom ----------------------------------------------
|
|
113
|
+
|
|
114
|
+
def clear_cache = invoke_webview_method("clear_cache")
|
|
115
|
+
def clear_local_storage = invoke_webview_method("clear_local_storage")
|
|
116
|
+
def enable_zoom = invoke_webview_method("enable_zoom")
|
|
117
|
+
def disable_zoom = invoke_webview_method("disable_zoom")
|
|
118
|
+
|
|
119
|
+
# --- Introspection (result delivered to the block) ---------------
|
|
120
|
+
|
|
121
|
+
def get_current_url(timeout: 10, &on_result)
|
|
122
|
+
invoke_webview_method("get_current_url", timeout: timeout, on_result: on_result)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def get_title(timeout: 10, &on_result)
|
|
126
|
+
invoke_webview_method("get_title", timeout: timeout, on_result: on_result)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def get_user_agent(timeout: 10, &on_result)
|
|
130
|
+
invoke_webview_method("get_user_agent", timeout: timeout, on_result: on_result)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
private
|
|
134
|
+
|
|
135
|
+
def invoke_webview_method(name, args = nil, timeout: 10, on_result: nil)
|
|
136
|
+
page = runtime_page
|
|
137
|
+
unless page && wire_id
|
|
138
|
+
raise "WebView ##{id} is not mounted yet — add it to the page before calling #{name}."
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
page.invoke(self, name, args: args, timeout: timeout, on_result: on_result)
|
|
142
|
+
end
|
|
31
143
|
end
|
|
32
144
|
end
|
|
33
145
|
end
|
|
@@ -115,7 +115,6 @@ require_relative "shared/dismissible_control"
|
|
|
115
115
|
require_relative "shared/draggable_control"
|
|
116
116
|
require_relative "shared/dragtarget_control"
|
|
117
117
|
require_relative "shared/fill_control"
|
|
118
|
-
require_relative "shared/fletapp_control"
|
|
119
118
|
require_relative "shared/gesturedetector_control"
|
|
120
119
|
require_relative "shared/gridview_control"
|
|
121
120
|
require_relative "shared/hero_control"
|
|
@@ -140,6 +139,7 @@ require_relative "shared/reorderabledraghandle_control"
|
|
|
140
139
|
require_relative "shared/responsiverow_control"
|
|
141
140
|
require_relative "shared/row_control"
|
|
142
141
|
require_relative "shared/rotatedbox_control"
|
|
142
|
+
require_relative "shared/ruflet_app_control"
|
|
143
143
|
require_relative "shared/safearea_control"
|
|
144
144
|
require_relative "shared/screenshot_control"
|
|
145
145
|
require_relative "shared/semantics_control"
|
|
@@ -301,8 +301,6 @@ module Ruflet
|
|
|
301
301
|
"fillediconbutton" => RufletComponents::FilledIconButtonControl,
|
|
302
302
|
"filledtonalbutton" => RufletComponents::FilledTonalButtonControl,
|
|
303
303
|
"filledtonaliconbutton" => RufletComponents::FilledTonalIconButtonControl,
|
|
304
|
-
"flet_app" => RufletComponents::FletAppControl,
|
|
305
|
-
"fletapp" => RufletComponents::FletAppControl,
|
|
306
304
|
"floating_action_button" => RufletComponents::FloatingActionButtonControl,
|
|
307
305
|
"floatingactionbutton" => RufletComponents::FloatingActionButtonControl,
|
|
308
306
|
"gesture_detector" => RufletComponents::GestureDetectorControl,
|
|
@@ -399,6 +397,8 @@ module Ruflet
|
|
|
399
397
|
"row" => RufletComponents::RowControl,
|
|
400
398
|
"rotated_box" => RufletComponents::RotatedBoxControl,
|
|
401
399
|
"rotatedbox" => RufletComponents::RotatedBoxControl,
|
|
400
|
+
"ruflet_app" => RufletComponents::RufletAppControl,
|
|
401
|
+
"rufletapp" => RufletComponents::RufletAppControl,
|
|
402
402
|
"safe_area" => RufletComponents::SafeAreaControl,
|
|
403
403
|
"safearea" => RufletComponents::SafeAreaControl,
|
|
404
404
|
"screenshot" => RufletComponents::ScreenshotControl,
|
|
@@ -4,8 +4,10 @@ module Ruflet
|
|
|
4
4
|
module UI
|
|
5
5
|
module Controls
|
|
6
6
|
module RufletComponents
|
|
7
|
-
class
|
|
8
|
-
TYPE = "
|
|
7
|
+
class RufletAppControl < Ruflet::Control
|
|
8
|
+
TYPE = "ruflet_app".freeze
|
|
9
|
+
# The client widget is still registered under its upstream name, so the
|
|
10
|
+
# wire type stays FletApp while the Ruby API is ruflet_app.
|
|
9
11
|
WIRE = "FletApp".freeze
|
|
10
12
|
|
|
11
13
|
KEYWORDS = [:align, :animate_align, :animate_margin, :animate_offset, :animate_opacity, :animate_position, :animate_rotation, :animate_scale, :animate_size, :app_error_message, :app_startup_screen_message, :args, :aspect_ratio, :badge, :bottom, :col, :data, :disabled, :expand, :expand_loose, :force_pyodide, :height, :key, :left, :margin, :offset, :opacity, :reconnect_interval_ms, :reconnect_timeout_ms, :right, :rotate, :rtl, :scale, :show_app_startup_screen, :size_change_interval, :tooltip, :top, :url, :visible, :width, :on_animation_end, :on_error, :on_size_change].freeze
|
|
@@ -14,7 +14,6 @@ require_relative "dismissible_control"
|
|
|
14
14
|
require_relative "draggable_control"
|
|
15
15
|
require_relative "dragtarget_control"
|
|
16
16
|
require_relative "fill_control"
|
|
17
|
-
require_relative "fletapp_control"
|
|
18
17
|
require_relative "gesturedetector_control"
|
|
19
18
|
require_relative "gridview_control"
|
|
20
19
|
require_relative "hero_control"
|
|
@@ -39,6 +38,7 @@ require_relative "reorderabledraghandle_control"
|
|
|
39
38
|
require_relative "responsiverow_control"
|
|
40
39
|
require_relative "row_control"
|
|
41
40
|
require_relative "rotatedbox_control"
|
|
41
|
+
require_relative "ruflet_app_control"
|
|
42
42
|
require_relative "safearea_control"
|
|
43
43
|
require_relative "screenshot_control"
|
|
44
44
|
require_relative "semantics_control"
|
|
@@ -81,8 +81,6 @@ module Ruflet
|
|
|
81
81
|
"draggable" => RufletComponents::DraggableControl,
|
|
82
82
|
"dragtarget" => RufletComponents::DragTargetControl,
|
|
83
83
|
"fill" => RufletComponents::FillControl,
|
|
84
|
-
"flet_app" => RufletComponents::FletAppControl,
|
|
85
|
-
"fletapp" => RufletComponents::FletAppControl,
|
|
86
84
|
"gesture_detector" => RufletComponents::GestureDetectorControl,
|
|
87
85
|
"gesturedetector" => RufletComponents::GestureDetectorControl,
|
|
88
86
|
"grid_view" => RufletComponents::GridViewControl,
|
|
@@ -117,6 +115,8 @@ module Ruflet
|
|
|
117
115
|
"row" => RufletComponents::RowControl,
|
|
118
116
|
"rotated_box" => RufletComponents::RotatedBoxControl,
|
|
119
117
|
"rotatedbox" => RufletComponents::RotatedBoxControl,
|
|
118
|
+
"ruflet_app" => RufletComponents::RufletAppControl,
|
|
119
|
+
"rufletapp" => RufletComponents::RufletAppControl,
|
|
120
120
|
"safe_area" => RufletComponents::SafeAreaControl,
|
|
121
121
|
"safearea" => RufletComponents::SafeAreaControl,
|
|
122
122
|
"screenshot" => RufletComponents::ScreenshotControl,
|
|
@@ -21,7 +21,7 @@ module Ruflet
|
|
|
21
21
|
|
|
22
22
|
def view(children = nil, **props, &block)
|
|
23
23
|
mapped = props.dup
|
|
24
|
-
mapped[:
|
|
24
|
+
mapped[:controls] = children unless children.nil?
|
|
25
25
|
build_widget(:view, **mapped, &block)
|
|
26
26
|
end
|
|
27
27
|
def column(children = nil, **props, &block)
|
|
@@ -702,6 +702,11 @@ module Ruflet
|
|
|
702
702
|
build_widget(:codeeditor, **mapped)
|
|
703
703
|
end
|
|
704
704
|
def codeeditor(value = nil, **props) = code_editor(value, **props)
|
|
705
|
+
def lottie(src = nil, **props)
|
|
706
|
+
mapped = props.dup
|
|
707
|
+
mapped[:src] = src unless src.nil?
|
|
708
|
+
build_widget(:lottie, **mapped)
|
|
709
|
+
end
|
|
705
710
|
def rive(src = nil, **props)
|
|
706
711
|
mapped = props.dup
|
|
707
712
|
mapped[:src] = src unless src.nil?
|
|
@@ -6,7 +6,11 @@ module Ruflet
|
|
|
6
6
|
def control(type, **props, &block) = control_delegate.control(type, **props, &block)
|
|
7
7
|
def widget(type, **props, &block) = control_delegate.widget(type, **props, &block)
|
|
8
8
|
def service(type, **props, &block) = control_delegate.service(type, **props, &block)
|
|
9
|
-
def view(children = nil, **props, &block)
|
|
9
|
+
def view(children = nil, **props, &block)
|
|
10
|
+
mapped = props.dup
|
|
11
|
+
mapped[:controls] = children unless children.nil?
|
|
12
|
+
control_delegate.control(:view, **mapped, &block)
|
|
13
|
+
end
|
|
10
14
|
def column(children = nil, **props, &block) = control_delegate.column(children, **props, &block)
|
|
11
15
|
def center(**props, &block) = control_delegate.center(**props, &block)
|
|
12
16
|
def row(children = nil, **props, &block) = control_delegate.row(children, **props, &block)
|
|
@@ -278,6 +282,7 @@ module Ruflet
|
|
|
278
282
|
def video(**props) = control_delegate.video(**props)
|
|
279
283
|
def code_editor(value = nil, **props) = control_delegate.code_editor(value, **props)
|
|
280
284
|
def codeeditor(value = nil, **props) = control_delegate.codeeditor(value, **props)
|
|
285
|
+
def lottie(src = nil, **props) = control_delegate.lottie(src, **props)
|
|
281
286
|
def rive(src = nil, **props) = control_delegate.rive(src, **props)
|
|
282
287
|
def cupertino_button(content = nil, **props) = control_delegate.cupertino_button(content, **props)
|
|
283
288
|
def cupertinobutton(content = nil, **props) = control_delegate.cupertinobutton(content, **props)
|
data/lib/ruflet_ui.rb
CHANGED
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
require "ruflet_protocol"
|
|
4
4
|
require_relative "ruflet_ui/ruflet/colors"
|
|
5
5
|
require_relative "ruflet_ui/ruflet/icon_data"
|
|
6
|
-
require_relative "ruflet_ui/ruflet/icons/material/material_icons"
|
|
7
|
-
require_relative "ruflet_ui/ruflet/icons/cupertino/cupertino_icons"
|
|
8
6
|
require_relative "ruflet_ui/ruflet/types/text_style"
|
|
9
7
|
require_relative "ruflet_ui/ruflet/types/geometry"
|
|
10
8
|
require_relative "ruflet_ui/ruflet/control"
|
|
@@ -15,8 +13,25 @@ require_relative "ruflet_ui/ruflet/event"
|
|
|
15
13
|
require_relative "ruflet_ui/ruflet/page"
|
|
16
14
|
require_relative "ruflet_ui/ruflet/app"
|
|
17
15
|
require_relative "ruflet_ui/ruflet/dsl"
|
|
16
|
+
require_relative "ruflet_ui/ruflet/extensions"
|
|
17
|
+
require_relative "ruflet_ui/ruflet/extensions/qrcode_scanner"
|
|
18
18
|
|
|
19
19
|
module Ruflet
|
|
20
|
+
# Icon tables are large (the material set alone parses a ~234KB map and
|
|
21
|
+
# defines thousands of constants) but the common `icon("home")` path passes
|
|
22
|
+
# the name straight through — nothing in the runtime touches these modules
|
|
23
|
+
# unless an app references a constant like `Ruflet::Icons::HOME`. Autoload
|
|
24
|
+
# them so boot (and every full restart) skips that cost until first use.
|
|
25
|
+
#
|
|
26
|
+
# CRuby only: under the embedded mruby VM the framework is concatenated into
|
|
27
|
+
# one blob with no filesystem require, so these modules are already defined
|
|
28
|
+
# there and the guard keeps the autoload calls out of that path entirely.
|
|
29
|
+
if RUBY_ENGINE != "mruby"
|
|
30
|
+
icons_root = File.expand_path("ruflet_ui/ruflet/icons", __dir__)
|
|
31
|
+
autoload :MaterialIcons, File.join(icons_root, "material/material_icons")
|
|
32
|
+
autoload :CupertinoIcons, File.join(icons_root, "cupertino/cupertino_icons")
|
|
33
|
+
end
|
|
34
|
+
|
|
20
35
|
TextStyle = UI::Types::TextStyle
|
|
21
36
|
StrutStyle = UI::Types::StrutStyle
|
|
22
37
|
TextOverflow = UI::Types::TextOverflow
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruflet_core
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.20
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- AdamMusa
|
|
@@ -29,6 +29,8 @@ files:
|
|
|
29
29
|
- lib/ruflet_ui/ruflet/dsl.rb
|
|
30
30
|
- lib/ruflet_ui/ruflet/event.rb
|
|
31
31
|
- lib/ruflet_ui/ruflet/events/gesture_events.rb
|
|
32
|
+
- lib/ruflet_ui/ruflet/extensions.rb
|
|
33
|
+
- lib/ruflet_ui/ruflet/extensions/qrcode_scanner.rb
|
|
32
34
|
- lib/ruflet_ui/ruflet/icon_data.rb
|
|
33
35
|
- lib/ruflet_ui/ruflet/icons/cupertino/cupertino_icons.json
|
|
34
36
|
- lib/ruflet_ui/ruflet/icons/cupertino/cupertino_icons.rb
|
|
@@ -161,7 +163,6 @@ files:
|
|
|
161
163
|
- lib/ruflet_ui/ruflet/ui/controls/shared/draggable_control.rb
|
|
162
164
|
- lib/ruflet_ui/ruflet/ui/controls/shared/dragtarget_control.rb
|
|
163
165
|
- lib/ruflet_ui/ruflet/ui/controls/shared/fill_control.rb
|
|
164
|
-
- lib/ruflet_ui/ruflet/ui/controls/shared/fletapp_control.rb
|
|
165
166
|
- lib/ruflet_ui/ruflet/ui/controls/shared/gesturedetector_control.rb
|
|
166
167
|
- lib/ruflet_ui/ruflet/ui/controls/shared/gridview_control.rb
|
|
167
168
|
- lib/ruflet_ui/ruflet/ui/controls/shared/hero_control.rb
|
|
@@ -186,6 +187,7 @@ files:
|
|
|
186
187
|
- lib/ruflet_ui/ruflet/ui/controls/shared/responsiverow_control.rb
|
|
187
188
|
- lib/ruflet_ui/ruflet/ui/controls/shared/rotatedbox_control.rb
|
|
188
189
|
- lib/ruflet_ui/ruflet/ui/controls/shared/row_control.rb
|
|
190
|
+
- lib/ruflet_ui/ruflet/ui/controls/shared/ruflet_app_control.rb
|
|
189
191
|
- lib/ruflet_ui/ruflet/ui/controls/shared/ruflet_controls.rb
|
|
190
192
|
- lib/ruflet_ui/ruflet/ui/controls/shared/safearea_control.rb
|
|
191
193
|
- lib/ruflet_ui/ruflet/ui/controls/shared/screenshot_control.rb
|