ruflet_core 0.0.15 → 0.0.16

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c1823b100c3e97ee941849763509b25179554464310eb640d7954bb48a30d821
4
- data.tar.gz: 80d3f6891f3418bf0b2b3aa2007e08e6578ca336b72bb48b9d84c54cac2397b5
3
+ metadata.gz: 888ec7addc07079a2ef3b649047b8747d031b446489d3015fb96cb5c2101b339
4
+ data.tar.gz: 342aac065292f59865cf01ace0347531803b41692eac2d3453fe5751c773fe0a
5
5
  SHA512:
6
- metadata.gz: e37e04ea218fbc50da5d3a456ecdfe376f12d00fc802c0869da7dff977bafb6d0abab8eecff92e223b42002f94652e40f7d91d2eb7a62f04fbec72bf1c7dd600
7
- data.tar.gz: 42458a85edccff3f66fd88cdc71724a7a94dbf1853fbf080d0751da7b57e783694e0ba65091c908bb4b1f203d6a8e8725e9e594df762f7deb1996e29e1bc88ed
6
+ metadata.gz: 2ef5114b3cc6389dafae6ab528990b7be92c0bc2367c62fc2eb0920feaec0dccc17400df7961df5b162bfbc539c2489818f77ac1dd91a040ebb87e1964016906
7
+ data.tar.gz: 9ad45dc326587bb2a601733b3c1efef6199c9d38424e8a82979d7b9f357fa44ffb579f19de30a3e2c8b2dd78681baf867b7555e9c21883f1f60dd44b7f4f50e0
data/README.md CHANGED
@@ -1,3 +1,19 @@
1
- # ruflet
1
+ # ruflet_core
2
2
 
3
- Part of Ruflet monorepo.
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(text("Hello Ruflet"))
15
+ end
16
+ ```
17
+
18
+ Use `ruflet_server` to run a standalone server-driven application. Use
19
+ `ruflet_rails` when the UI is hosted by Rails.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ruflet
4
- VERSION = "0.0.15" unless const_defined?(:VERSION)
4
+ VERSION = "0.0.16" unless const_defined?(:VERSION)
5
5
  end
data/lib/ruflet_core.rb CHANGED
@@ -10,7 +10,8 @@ module Ruflet
10
10
 
11
11
  module_function
12
12
 
13
- def run(entrypoint = nil, host: "0.0.0.0", port: 8550, &block)
13
+ def run(entrypoint = nil, host: "0.0.0.0", port: nil, &block)
14
+ port = normalize_run_port(port || ENV["RUFLET_PORT"] || 8550)
14
15
  callback = entrypoint || block
15
16
  raise ArgumentError, "Ruflet.run requires a callable entrypoint or block" unless callback.respond_to?(:call)
16
17
 
@@ -37,4 +38,10 @@ module Ruflet
37
38
  ensure
38
39
  @run_interceptors_mutex.synchronize { @run_interceptors.delete(interceptor) }
39
40
  end
41
+
42
+ def normalize_run_port(value)
43
+ Integer(value)
44
+ rescue ArgumentError, TypeError
45
+ 8550
46
+ end
40
47
  end
@@ -151,11 +151,25 @@ module Ruflet
151
151
  end
152
152
 
153
153
  def self.normalize_color(color)
154
- return color.to_s if color.is_a?(Symbol)
155
- return color if color.is_a?(String)
156
- return color.to_s unless color.respond_to?(:to_s)
154
+ return canonicalize(color.to_s) if color.is_a?(Symbol)
155
+ return canonicalize(color) if color.is_a?(String)
156
+ return canonicalize(color.to_s) unless color.respond_to?(:to_s)
157
157
 
158
- color.to_s
158
+ canonicalize(color.to_s)
159
+ end
160
+
161
+ # Canonicalizes a named color into flet's wire format. Flet color names are
162
+ # lowercase with no separators ("bluegrey", "deeporange", "red500"), so we
163
+ # strip underscores/whitespace and downcase. Hex values (#... / 0x...) and
164
+ # the optional ",opacity" suffix are preserved untouched.
165
+ def self.canonicalize(value)
166
+ return value unless value.is_a?(String)
167
+
168
+ color, separator, opacity = value.partition(",")
169
+ color = color.strip.downcase
170
+ color = color.delete("_ \t\n") unless color.start_with?("#") || color.start_with?("0x")
171
+
172
+ "#{color}#{separator}#{opacity}"
159
173
  end
160
174
 
161
175
  BASE_PREFIX = {
@@ -5,6 +5,7 @@ require "securerandom"
5
5
  rescue LoadError
6
6
  nil
7
7
  end
8
+ require_relative "colors"
8
9
  require_relative "icon_data"
9
10
  require_relative "icons/material_icon_lookup"
10
11
  require_relative "icons/cupertino_icon_lookup"
@@ -198,7 +199,7 @@ module Ruflet
198
199
 
199
200
  def normalize_color_prop(key, value)
200
201
  return value unless value.is_a?(String)
201
- return value.downcase if color_prop_key?(key)
202
+ return Ruflet::Colors.canonicalize(value) if color_prop_key?(key)
202
203
 
203
204
  value
204
205
  end
@@ -68,6 +68,15 @@ module Ruflet
68
68
  def autocompletesuggestion(key = nil, **props) = _pending_app.autocompletesuggestion(key, **props)
69
69
  def context_menu(content = nil, **props) = _pending_app.context_menu(content, **props)
70
70
  def contextmenu(content = nil, **props) = _pending_app.contextmenu(content, **props)
71
+ def autofill_group(content = nil, **props) = _pending_app.autofill_group(content, **props)
72
+ def autofillgroup(content = nil, **props) = _pending_app.autofillgroup(content, **props)
73
+ def hero(content = nil, **props) = _pending_app.hero(content, **props)
74
+ def overlay(children = nil, **props) = _pending_app.overlay(children, **props)
75
+ def shader_mask(content = nil, **props) = _pending_app.shader_mask(content, **props)
76
+ def shadermask(content = nil, **props) = _pending_app.shadermask(content, **props)
77
+ def shimmer(content = nil, **props) = _pending_app.shimmer(content, **props)
78
+ def text_span(text = nil, **props) = _pending_app.text_span(text, **props)
79
+ def textspan(text = nil, **props) = _pending_app.textspan(text, **props)
71
80
  def keyboard_listener(content = nil, **props) = _pending_app.keyboard_listener(content, **props)
72
81
  def keyboardlistener(content = nil, **props) = _pending_app.keyboardlistener(content, **props)
73
82
  def gesture_detector(**props, &block) = _pending_app.gesture_detector(**props, &block)
@@ -302,6 +311,10 @@ module Ruflet
302
311
  def web_view(**props) = _pending_app.web_view(**props)
303
312
  def webview(**props) = _pending_app.webview(**props)
304
313
  def video(**props) = _pending_app.video(**props)
314
+ def spinkit(**variant) = _pending_app.spinkit(**variant)
315
+ def code_editor(value = nil, **props) = _pending_app.code_editor(value, **props)
316
+ def codeeditor(value = nil, **props) = _pending_app.codeeditor(value, **props)
317
+ def rive(src = nil, **props) = _pending_app.rive(src, **props)
305
318
  def fab(content = nil, **props) = _pending_app.fab(content, **props)
306
319
  def cupertino_button(content = nil, **props) = _pending_app.cupertino_button(content, **props)
307
320
  def cupertinobutton(content = nil, **props) = _pending_app.cupertinobutton(content, **props)
@@ -244,6 +244,42 @@ module Ruflet
244
244
  @view_props["bgcolor"] = normalize_value("bgcolor", value)
245
245
  end
246
246
 
247
+ # Client-reported page properties. The Flutter client sends these in its
248
+ # register payload (see Protocol.normalize_register_payload), where they are
249
+ # stored in @client_details; expose them as readers so apps can do
250
+ # `page.width`, `page.platform`, etc. without reaching into client_details.
251
+ def width
252
+ client_reported_prop("width")
253
+ end
254
+
255
+ def height
256
+ client_reported_prop("height")
257
+ end
258
+
259
+ def platform
260
+ client_reported_prop("platform")
261
+ end
262
+
263
+ def platform_brightness
264
+ client_reported_prop("platform_brightness")
265
+ end
266
+
267
+ def web
268
+ client_reported_prop("web")
269
+ end
270
+
271
+ def pwa
272
+ client_reported_prop("pwa")
273
+ end
274
+
275
+ def wasm
276
+ client_reported_prop("wasm")
277
+ end
278
+
279
+ def media
280
+ client_reported_prop("media")
281
+ end
282
+
247
283
  def add(*controls, appbar: nil, bottom_appbar: nil, floating_action_button: nil, navigation_bar: nil, dialog: nil, snack_bar: nil, bottom_sheet: nil)
248
284
  controls = controls.flatten
249
285
  visited = Set.new
@@ -412,6 +448,10 @@ module Ruflet
412
448
  @page_event_handlers["view_pop"] = handler
413
449
  end
414
450
 
451
+ def on_resize=(handler)
452
+ @page_event_handlers["resize"] = handler
453
+ end
454
+
415
455
  def on(event_name, &block)
416
456
  @page_event_handlers[event_name.to_s.sub(/\Aon_/, "")] = block
417
457
  self
@@ -1239,6 +1279,12 @@ module Ruflet
1239
1279
  # view — the picker that "reappears after going home".
1240
1280
  dismiss_tracked_dialogs! if route_from_event && route_from_event != @page_props["route"]
1241
1281
  @page_props["route"] = route_from_event if route_from_event
1282
+ elsif name.to_s == "resize"
1283
+ # The client reports the live page size via the "resize" event. Store
1284
+ # it so `page.width`/`page.height` reflect the real viewport — without
1285
+ # this, responsive layouts collapse on clients (e.g. embedded/iOS)
1286
+ # that don't know their size at the initial handshake.
1287
+ store_reported_page_size(data)
1242
1288
  end
1243
1289
  dispatch_page_event(name: name, data: data)
1244
1290
  return
@@ -1301,6 +1347,12 @@ module Ruflet
1301
1347
 
1302
1348
  private
1303
1349
 
1350
+ def client_reported_prop(name)
1351
+ return @page_props[name] if @page_props.key?(name)
1352
+
1353
+ @client_details[name]
1354
+ end
1355
+
1304
1356
  def embedded_async_timeout_available?
1305
1357
  !Object.const_defined?(:RUFLET_EMBEDDED_FAKE_THREAD)
1306
1358
  end
@@ -1556,6 +1608,15 @@ module Ruflet
1556
1608
  end
1557
1609
  end
1558
1610
 
1611
+ def store_reported_page_size(data)
1612
+ return unless data.is_a?(Hash)
1613
+
1614
+ width = data["width"] || data[:width]
1615
+ height = data["height"] || data[:height]
1616
+ @page_props["width"] = width unless width.nil?
1617
+ @page_props["height"] = height unless height.nil?
1618
+ end
1619
+
1559
1620
  def dispatch_page_event(name:, data:)
1560
1621
  handler = @page_event_handlers[name.to_s.sub(/\Aon_/, "")]
1561
1622
  return unless handler.respond_to?(:call)
@@ -1780,6 +1841,7 @@ module Ruflet
1780
1841
  existing = service_by_type(type)
1781
1842
  return [existing, false] if existing
1782
1843
 
1844
+ # `service` already syncs via add_service -> push_services_update!.
1783
1845
  [service(type), true]
1784
1846
  end
1785
1847
 
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruflet
4
+ module UI
5
+ module Controls
6
+ module RufletComponents
7
+ # CodeEditor control — parity with Flet's CodeEditor extension
8
+ # (https://flet.dev/docs/controls/codeeditor/).
9
+ #
10
+ # Properties: value, language, code_theme, text_style, padding, selection,
11
+ # gutter_style, autocomplete, autocomplete_words, issues, read_only,
12
+ # autofocus, plus the usual layout props.
13
+ # Events: on_change, on_selection_change, on_focus, on_blur.
14
+ # Methods (invoked over the wire on a mounted control): focus, fold_at,
15
+ # fold_comment_at_line_zero, fold_imports.
16
+ #
17
+ # `language` accepts a highlight.js identifier (e.g. "python", "ruby",
18
+ # "javascript"); `code_theme` accepts a highlight.js theme name shared
19
+ # with Markdown (e.g. "atom-one-light", "atom-one-dark", "monokai-sublime").
20
+ class CodeEditorControl < Ruflet::Control
21
+ TYPE = "CodeEditor".freeze
22
+ WIRE = "CodeEditor".freeze
23
+
24
+ def initialize(id: nil, adaptive: nil, autocomplete: nil, autocomplete_words: nil,
25
+ autofocus: nil, badge: nil, code_theme: nil, col: nil, data: nil,
26
+ disabled: nil, expand: nil, expand_loose: nil, gutter_style: nil,
27
+ height: nil, issues: nil, key: nil, language: nil, opacity: nil,
28
+ padding: nil, read_only: nil, rtl: nil, selection: nil,
29
+ text_style: nil, tooltip: nil, value: nil, visible: nil, width: nil,
30
+ on_blur: nil, on_change: nil, on_focus: nil, on_selection_change: nil)
31
+ props = {}
32
+ props[:adaptive] = adaptive unless adaptive.nil?
33
+ props[:autocomplete] = autocomplete unless autocomplete.nil?
34
+ props[:autocomplete_words] = autocomplete_words unless autocomplete_words.nil?
35
+ props[:autofocus] = autofocus unless autofocus.nil?
36
+ props[:badge] = badge unless badge.nil?
37
+ props[:code_theme] = code_theme unless code_theme.nil?
38
+ props[:col] = col unless col.nil?
39
+ props[:data] = data unless data.nil?
40
+ props[:disabled] = disabled unless disabled.nil?
41
+ props[:expand] = expand unless expand.nil?
42
+ props[:expand_loose] = expand_loose unless expand_loose.nil?
43
+ props[:gutter_style] = gutter_style unless gutter_style.nil?
44
+ props[:height] = height unless height.nil?
45
+ props[:issues] = issues unless issues.nil?
46
+ props[:key] = key unless key.nil?
47
+ props[:language] = language unless language.nil?
48
+ props[:opacity] = opacity unless opacity.nil?
49
+ props[:padding] = padding unless padding.nil?
50
+ props[:read_only] = read_only unless read_only.nil?
51
+ props[:rtl] = rtl unless rtl.nil?
52
+ props[:selection] = selection unless selection.nil?
53
+ props[:text_style] = text_style unless text_style.nil?
54
+ props[:tooltip] = tooltip unless tooltip.nil?
55
+ props[:value] = value unless value.nil?
56
+ props[:visible] = visible unless visible.nil?
57
+ props[:width] = width unless width.nil?
58
+ props[:on_blur] = on_blur unless on_blur.nil?
59
+ props[:on_change] = on_change unless on_change.nil?
60
+ props[:on_focus] = on_focus unless on_focus.nil?
61
+ props[:on_selection_change] = on_selection_change unless on_selection_change.nil?
62
+ super(type: TYPE, id: id, **props)
63
+ end
64
+
65
+ # Request focus for the editor.
66
+ def focus = invoke_editor_method("focus")
67
+
68
+ # Fold the code block that starts at the given line number.
69
+ def fold_at(line_number)
70
+ invoke_editor_method("fold_at", { "line_number" => line_number.to_i })
71
+ end
72
+
73
+ # Fold the comment block at line 0 (e.g. a license header).
74
+ def fold_comment_at_line_zero = invoke_editor_method("fold_comment_at_line_zero")
75
+
76
+ # Fold all import sections.
77
+ def fold_imports = invoke_editor_method("fold_imports")
78
+
79
+ private
80
+
81
+ def invoke_editor_method(name, args = nil, timeout: 10, on_result: nil)
82
+ page = runtime_page
83
+ unless page && wire_id
84
+ raise "CodeEditor ##{id} is not mounted yet — add it to the page before calling #{name}."
85
+ end
86
+
87
+ page.invoke(self, name, args: args, timeout: timeout, on_result: on_result)
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruflet
4
+ module UI
5
+ module Controls
6
+ module RufletComponents
7
+ # Rive control — parity with Flet's Rive extension
8
+ # (https://flet.dev/docs/controls/rive/). Renders a Rive
9
+ # (https://rive.app) animation from a `.riv` file.
10
+ #
11
+ # Properties: src, placeholder, artboard, alignment, enable_antialiasing,
12
+ # use_artboard_size, fit, speed_multiplier, animations, state_machines,
13
+ # headers, clip_rect, plus the usual layout props.
14
+ # No events or methods — playback is driven by `animations` /
15
+ # `state_machines` and `speed_multiplier`.
16
+ #
17
+ # `src` is required and may be a network URL (e.g.
18
+ # "https://cdn.rive.app/animations/vehicles.riv") or a bundled asset path.
19
+ #
20
+ # Note: the Flet API names the artboard property `artboard`, but the
21
+ # underlying renderer reads `art_board` / `use_art_board_size` on the wire.
22
+ # Both spellings are accepted here and normalized to the wire keys.
23
+ class RiveControl < Ruflet::Control
24
+ TYPE = "Rive".freeze
25
+ WIRE = "Rive".freeze
26
+
27
+ def initialize(id: nil, adaptive: nil, alignment: nil, animate_offset: nil,
28
+ animate_opacity: nil, animate_position: nil, animate_rotation: nil,
29
+ animate_scale: nil, animations: nil, art_board: nil, artboard: nil,
30
+ aspect_ratio: nil, badge: nil, bottom: nil, clip_rect: nil, col: nil,
31
+ data: nil, disabled: nil, enable_antialiasing: nil, expand: nil,
32
+ expand_loose: nil, fit: nil, headers: nil, height: nil, key: nil,
33
+ left: nil, offset: nil, opacity: nil, placeholder: nil, right: nil,
34
+ rotate: nil, rtl: nil, scale: nil, speed_multiplier: nil, src: nil,
35
+ state_machines: nil, tooltip: nil, top: nil, use_art_board_size: nil,
36
+ use_artboard_size: nil, visible: nil, width: nil)
37
+ # Accept both the Flet-style names and the wire keys.
38
+ art_board = artboard if art_board.nil?
39
+ use_art_board_size = use_artboard_size if use_art_board_size.nil?
40
+
41
+ props = {}
42
+ props[:adaptive] = adaptive unless adaptive.nil?
43
+ props[:alignment] = alignment unless alignment.nil?
44
+ props[:animate_offset] = animate_offset unless animate_offset.nil?
45
+ props[:animate_opacity] = animate_opacity unless animate_opacity.nil?
46
+ props[:animate_position] = animate_position unless animate_position.nil?
47
+ props[:animate_rotation] = animate_rotation unless animate_rotation.nil?
48
+ props[:animate_scale] = animate_scale unless animate_scale.nil?
49
+ props[:animations] = animations unless animations.nil?
50
+ props[:art_board] = art_board unless art_board.nil?
51
+ props[:aspect_ratio] = aspect_ratio unless aspect_ratio.nil?
52
+ props[:badge] = badge unless badge.nil?
53
+ props[:bottom] = bottom unless bottom.nil?
54
+ props[:clip_rect] = clip_rect unless clip_rect.nil?
55
+ props[:col] = col unless col.nil?
56
+ props[:data] = data unless data.nil?
57
+ props[:disabled] = disabled unless disabled.nil?
58
+ props[:enable_antialiasing] = enable_antialiasing unless enable_antialiasing.nil?
59
+ props[:expand] = expand unless expand.nil?
60
+ props[:expand_loose] = expand_loose unless expand_loose.nil?
61
+ props[:fit] = fit unless fit.nil?
62
+ props[:headers] = headers unless headers.nil?
63
+ props[:height] = height unless height.nil?
64
+ props[:key] = key unless key.nil?
65
+ props[:left] = left unless left.nil?
66
+ props[:offset] = offset unless offset.nil?
67
+ props[:opacity] = opacity unless opacity.nil?
68
+ props[:placeholder] = placeholder unless placeholder.nil?
69
+ props[:right] = right unless right.nil?
70
+ props[:rotate] = rotate unless rotate.nil?
71
+ props[:rtl] = rtl unless rtl.nil?
72
+ props[:scale] = scale unless scale.nil?
73
+ props[:speed_multiplier] = speed_multiplier unless speed_multiplier.nil?
74
+ props[:src] = src unless src.nil?
75
+ props[:state_machines] = state_machines unless state_machines.nil?
76
+ props[:tooltip] = tooltip unless tooltip.nil?
77
+ props[:top] = top unless top.nil?
78
+ props[:use_art_board_size] = use_art_board_size unless use_art_board_size.nil?
79
+ props[:visible] = visible unless visible.nil?
80
+ props[:width] = width unless width.nil?
81
+ super(type: TYPE, id: id, **props)
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -15,6 +15,7 @@ require_relative "card_control"
15
15
  require_relative "checkbox_control"
16
16
  require_relative "chip_control"
17
17
  require_relative "circleavatar_control"
18
+ require_relative "codeeditor_control"
18
19
  require_relative "container_control"
19
20
  require_relative "contextmenu_control"
20
21
  require_relative "datacell_control"
@@ -57,6 +58,7 @@ require_relative "radio_control"
57
58
  require_relative "radiogroup_control"
58
59
  require_relative "rangeslider_control"
59
60
  require_relative "reorderablelistview_control"
61
+ require_relative "rive_control"
60
62
  require_relative "searchbar_control"
61
63
  require_relative "segment_control"
62
64
  require_relative "segmentedbutton_control"
@@ -120,6 +122,8 @@ module Ruflet
120
122
  "chip" => RufletComponents::ChipControl,
121
123
  "circle_avatar" => RufletComponents::CircleAvatarControl,
122
124
  "circleavatar" => RufletComponents::CircleAvatarControl,
125
+ "code_editor" => RufletComponents::CodeEditorControl,
126
+ "codeeditor" => RufletComponents::CodeEditorControl,
123
127
  "container" => RufletComponents::ContainerControl,
124
128
  "context_menu" => RufletComponents::ContextMenuControl,
125
129
  "contextmenu" => RufletComponents::ContextMenuControl,
@@ -235,6 +239,7 @@ module Ruflet
235
239
  "rangeslider" => RufletComponents::RangeSliderControl,
236
240
  "reorderable_list_view" => RufletComponents::ReorderableListViewControl,
237
241
  "reorderablelistview" => RufletComponents::ReorderableListViewControl,
242
+ "rive" => RufletComponents::RiveControl,
238
243
  "search_bar" => RufletComponents::SearchBarControl,
239
244
  "searchbar" => RufletComponents::SearchBarControl,
240
245
  "segment" => RufletComponents::SegmentControl,
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruflet
4
+ module UI
5
+ module Controls
6
+ module RufletComponents
7
+ # Base for every flet_spinkit variant (https://flet.dev/docs/controls/spinkit/).
8
+ # Each concrete spinner is a LayoutControl with its own wire name
9
+ # ("SpinKitRotatingCircle", "SpinKitWave", ...). The Dart widget reads
10
+ # color/size/duration for all of them plus the optional line_width /
11
+ # border_width / item_count / wave_type used by a few variants, so those
12
+ # are accepted on every spinner (the client ignores the ones a variant
13
+ # doesn't use, matching the upstream implementation).
14
+ class SpinKitControl < Ruflet::Control
15
+ def initialize(id: nil, align: nil, animate_align: nil, animate_offset: nil, animate_opacity: nil, animate_position: nil, animate_rotation: nil, animate_scale: nil, animate_size: nil, aspect_ratio: nil, badge: nil, border_width: nil, bottom: nil, col: nil, color: nil, data: nil, disabled: nil, duration: nil, expand: nil, expand_loose: nil, height: nil, item_count: nil, key: nil, left: nil, line_width: nil, offset: nil, opacity: nil, right: nil, rotate: nil, rtl: nil, scale: nil, size: nil, tooltip: nil, top: nil, visible: nil, wave_type: nil, width: nil, on_animation_end: nil)
16
+ raise ArgumentError, "spinkit size must be greater than or equal to 0" unless size.nil? || size >= 0
17
+
18
+ props = {}
19
+ props[:align] = align unless align.nil?
20
+ props[:animate_align] = animate_align unless animate_align.nil?
21
+ props[:animate_offset] = animate_offset unless animate_offset.nil?
22
+ props[:animate_opacity] = animate_opacity unless animate_opacity.nil?
23
+ props[:animate_position] = animate_position unless animate_position.nil?
24
+ props[:animate_rotation] = animate_rotation unless animate_rotation.nil?
25
+ props[:animate_scale] = animate_scale unless animate_scale.nil?
26
+ props[:animate_size] = animate_size unless animate_size.nil?
27
+ props[:aspect_ratio] = aspect_ratio unless aspect_ratio.nil?
28
+ props[:badge] = badge unless badge.nil?
29
+ props[:border_width] = border_width unless border_width.nil?
30
+ props[:bottom] = bottom unless bottom.nil?
31
+ props[:col] = col unless col.nil?
32
+ props[:color] = color unless color.nil?
33
+ props[:data] = data unless data.nil?
34
+ props[:disabled] = disabled unless disabled.nil?
35
+ props[:duration] = duration unless duration.nil?
36
+ props[:expand] = expand unless expand.nil?
37
+ props[:expand_loose] = expand_loose unless expand_loose.nil?
38
+ props[:height] = height unless height.nil?
39
+ props[:item_count] = item_count unless item_count.nil?
40
+ props[:key] = key unless key.nil?
41
+ props[:left] = left unless left.nil?
42
+ props[:line_width] = line_width unless line_width.nil?
43
+ props[:offset] = offset unless offset.nil?
44
+ props[:opacity] = opacity unless opacity.nil?
45
+ props[:right] = right unless right.nil?
46
+ props[:rotate] = rotate unless rotate.nil?
47
+ props[:rtl] = rtl unless rtl.nil?
48
+ props[:scale] = scale unless scale.nil?
49
+ props[:size] = size unless size.nil?
50
+ props[:tooltip] = tooltip unless tooltip.nil?
51
+ props[:top] = top unless top.nil?
52
+ props[:visible] = visible unless visible.nil?
53
+ props[:wave_type] = wave_type unless wave_type.nil?
54
+ props[:width] = width unless width.nil?
55
+ props[:on_animation_end] = on_animation_end unless on_animation_end.nil?
56
+ super(type: self.class::TYPE, id: id, **props)
57
+ end
58
+ end
59
+
60
+ # wire name ("_c") => ruflet type key. Mirrors flet_spinkit's 30 controls.
61
+ SPINKIT_WIRE_TO_TYPE = {
62
+ "SpinKitRotatingCircle" => "spinkit_rotating_circle",
63
+ "SpinKitRotatingPlain" => "spinkit_rotating_plain",
64
+ "SpinKitDoubleBounce" => "spinkit_double_bounce",
65
+ "SpinKitWave" => "spinkit_wave",
66
+ "SpinKitWanderingCubes" => "spinkit_wandering_cubes",
67
+ "SpinKitFadingFour" => "spinkit_fading_four",
68
+ "SpinKitFadingCube" => "spinkit_fading_cube",
69
+ "SpinKitPulse" => "spinkit_pulse",
70
+ "SpinKitChasingDots" => "spinkit_chasing_dots",
71
+ "SpinKitThreeBounce" => "spinkit_three_bounce",
72
+ "SpinKitCircle" => "spinkit_circle",
73
+ "SpinKitCubeGrid" => "spinkit_cube_grid",
74
+ "SpinKitFadingCircle" => "spinkit_fading_circle",
75
+ "SpinKitFoldingCube" => "spinkit_folding_cube",
76
+ "SpinKitPumpingHeart" => "spinkit_pumping_heart",
77
+ "SpinKitHourGlass" => "spinkit_hour_glass",
78
+ "SpinKitPouringHourGlass" => "spinkit_pouring_hour_glass",
79
+ "SpinKitPouringHourGlassRefined" => "spinkit_pouring_hour_glass_refined",
80
+ "SpinKitFadingGrid" => "spinkit_fading_grid",
81
+ "SpinKitRing" => "spinkit_ring",
82
+ "SpinKitRipple" => "spinkit_ripple",
83
+ "SpinKitDualRing" => "spinkit_dual_ring",
84
+ "SpinKitSpinningCircle" => "spinkit_spinning_circle",
85
+ "SpinKitSpinningLines" => "spinkit_spinning_lines",
86
+ "SpinKitSquareCircle" => "spinkit_square_circle",
87
+ "SpinKitThreeInOut" => "spinkit_three_in_out",
88
+ "SpinKitDancingSquare" => "spinkit_dancing_square",
89
+ "SpinKitPianoWave" => "spinkit_piano_wave",
90
+ "SpinKitPulsingGrid" => "spinkit_pulsing_grid",
91
+ "SpinKitWaveSpinner" => "spinkit_wave_spinner"
92
+ }.freeze
93
+
94
+ # type key => control class, e.g. "spinkit_wave" => SpinKitWaveControl.
95
+ SPINKIT_CONTROLS = {}
96
+
97
+ SPINKIT_WIRE_TO_TYPE.each do |wire, type_key|
98
+ klass = Class.new(SpinKitControl)
99
+ klass.const_set(:TYPE, type_key)
100
+ klass.const_set(:WIRE, wire)
101
+ const_set("#{wire}Control", klass)
102
+ SPINKIT_CONTROLS[type_key] = klass
103
+ end
104
+
105
+ SPINKIT_CONTROLS.freeze
106
+ end
107
+ end
108
+ end
109
+ end
@@ -39,6 +39,7 @@ require_relative "materials/card_control"
39
39
  require_relative "materials/checkbox_control"
40
40
  require_relative "materials/chip_control"
41
41
  require_relative "materials/circleavatar_control"
42
+ require_relative "materials/codeeditor_control"
42
43
  require_relative "materials/container_control"
43
44
  require_relative "materials/contextmenu_control"
44
45
  require_relative "materials/datacell_control"
@@ -58,6 +59,7 @@ require_relative "materials/filledbutton_control"
58
59
  require_relative "materials/fillediconbutton_control"
59
60
  require_relative "materials/filledtonalbutton_control"
60
61
  require_relative "materials/filledtonaliconbutton_control"
62
+ require_relative "materials/spinkit_controls"
61
63
  require_relative "materials/floatingactionbutton_control"
62
64
  require_relative "materials/iconbutton_control"
63
65
  require_relative "materials/listtile_control"
@@ -81,6 +83,7 @@ require_relative "materials/radio_control"
81
83
  require_relative "materials/radiogroup_control"
82
84
  require_relative "materials/rangeslider_control"
83
85
  require_relative "materials/reorderablelistview_control"
86
+ require_relative "materials/rive_control"
84
87
  require_relative "materials/searchbar_control"
85
88
  require_relative "materials/segment_control"
86
89
  require_relative "materials/segmentedbutton_control"
@@ -206,6 +209,8 @@ module Ruflet
206
209
  "circle" => RufletComponents::CircleControl,
207
210
  "circle_avatar" => RufletComponents::CircleAvatarControl,
208
211
  "circleavatar" => RufletComponents::CircleAvatarControl,
212
+ "code_editor" => RufletComponents::CodeEditorControl,
213
+ "codeeditor" => RufletComponents::CodeEditorControl,
209
214
  "color" => RufletComponents::ColorControl,
210
215
  "column" => RufletComponents::ColumnControl,
211
216
  "container" => RufletComponents::ContainerControl,
@@ -408,6 +413,7 @@ module Ruflet
408
413
  "reorderable_list_view" => RufletComponents::ReorderableListViewControl,
409
414
  "reorderabledraghandle" => RufletComponents::ReorderableDragHandleControl,
410
415
  "reorderablelistview" => RufletComponents::ReorderableListViewControl,
416
+ "rive" => RufletComponents::RiveControl,
411
417
  "responsive_row" => RufletComponents::ResponsiveRowControl,
412
418
  "responsiverow" => RufletComponents::ResponsiveRowControl,
413
419
  "row" => RufletComponents::RowControl,
@@ -464,7 +470,7 @@ module Ruflet
464
470
  "window" => RufletComponents::WindowControl,
465
471
  "window_drag_area" => RufletComponents::WindowDragAreaControl,
466
472
  "windowdragarea" => RufletComponents::WindowDragAreaControl,
467
- }.freeze
473
+ }.merge(RufletComponents::SPINKIT_CONTROLS).freeze
468
474
  end
469
475
  end
470
476
  end
@@ -81,6 +81,39 @@ module Ruflet
81
81
  build_widget(:contextmenu, **mapped)
82
82
  end
83
83
  def contextmenu(content = nil, **props) = context_menu(content, **props)
84
+ def autofill_group(content = nil, **props)
85
+ mapped = props.dup
86
+ mapped[:content] = content unless content.nil?
87
+ build_widget(:autofillgroup, **mapped)
88
+ end
89
+ def autofillgroup(content = nil, **props) = autofill_group(content, **props)
90
+ def hero(content = nil, **props)
91
+ mapped = props.dup
92
+ mapped[:content] = content unless content.nil?
93
+ build_widget(:hero, **mapped)
94
+ end
95
+ def overlay(children = nil, **props)
96
+ mapped = props.dup
97
+ mapped[:controls] = children unless children.nil?
98
+ build_widget(:overlay, **mapped)
99
+ end
100
+ def shader_mask(content = nil, **props)
101
+ mapped = props.dup
102
+ mapped[:content] = content unless content.nil?
103
+ build_widget(:shadermask, **mapped)
104
+ end
105
+ def shadermask(content = nil, **props) = shader_mask(content, **props)
106
+ def shimmer(content = nil, **props)
107
+ mapped = props.dup
108
+ mapped[:content] = content unless content.nil?
109
+ build_widget(:shimmer, **mapped)
110
+ end
111
+ def text_span(text = nil, **props)
112
+ mapped = props.dup
113
+ mapped[:text] = text unless text.nil?
114
+ build_widget(:textspan, **mapped)
115
+ end
116
+ def textspan(text = nil, **props) = text_span(text, **props)
84
117
  def keyboard_listener(content = nil, **props)
85
118
  mapped = props.dup
86
119
  mapped[:content] = content unless content.nil?
@@ -606,6 +639,31 @@ module Ruflet
606
639
  def webview(**props) = web_view(**props)
607
640
  def video(**props) = build_widget(:video, **props)
608
641
 
642
+ # spinkit(wave: { color: "red", size: 50 }) — one variant keyword whose
643
+ # value is the props hash. See https://flet.dev/docs/controls/spinkit/
644
+ def spinkit(**variant)
645
+ raise ArgumentError, "spinkit expects exactly one variant, e.g. spinkit(wave: { color: ... })" unless variant.size == 1
646
+
647
+ name, props = variant.first
648
+ props ||= {}
649
+ raise ArgumentError, "spinkit #{name} options must be a Hash" unless props.is_a?(Hash)
650
+
651
+ build_widget(:"spinkit_#{name}", **props)
652
+ end
653
+
654
+ def code_editor(value = nil, **props)
655
+ mapped = props.dup
656
+ mapped[:value] = value unless value.nil?
657
+ build_widget(:codeeditor, **mapped)
658
+ end
659
+ def codeeditor(value = nil, **props) = code_editor(value, **props)
660
+
661
+ def rive(src = nil, **props)
662
+ mapped = props.dup
663
+ mapped[:src] = src unless src.nil?
664
+ build_widget(:rive, **mapped)
665
+ end
666
+
609
667
  def fab(content = nil, **props)
610
668
  mapped = normalize_fab_props(props.dup, content)
611
669
  build_widget(:floatingactionbutton, **mapped)
@@ -26,6 +26,15 @@ module Ruflet
26
26
  def autocompletesuggestion(key = nil, **props) = control_delegate.autocompletesuggestion(key, **props)
27
27
  def context_menu(content = nil, **props) = control_delegate.context_menu(content, **props)
28
28
  def contextmenu(content = nil, **props) = control_delegate.contextmenu(content, **props)
29
+ def autofill_group(content = nil, **props) = control_delegate.autofill_group(content, **props)
30
+ def autofillgroup(content = nil, **props) = control_delegate.autofillgroup(content, **props)
31
+ def hero(content = nil, **props) = control_delegate.hero(content, **props)
32
+ def overlay(children = nil, **props) = control_delegate.overlay(children, **props)
33
+ def shader_mask(content = nil, **props) = control_delegate.shader_mask(content, **props)
34
+ def shadermask(content = nil, **props) = control_delegate.shadermask(content, **props)
35
+ def shimmer(content = nil, **props) = control_delegate.shimmer(content, **props)
36
+ def text_span(text = nil, **props) = control_delegate.text_span(text, **props)
37
+ def textspan(text = nil, **props) = control_delegate.textspan(text, **props)
29
38
  def keyboard_listener(content = nil, **props) = control_delegate.keyboard_listener(content, **props)
30
39
  def keyboardlistener(content = nil, **props) = control_delegate.keyboardlistener(content, **props)
31
40
  def gesture_detector(**props, &block) = control_delegate.gesture_detector(**props, &block)
@@ -261,6 +270,10 @@ module Ruflet
261
270
  def web_view(**props) = control_delegate.web_view(**props)
262
271
  def webview(**props) = control_delegate.webview(**props)
263
272
  def video(**props) = control_delegate.video(**props)
273
+ def spinkit(**variant) = control_delegate.spinkit(**variant)
274
+ def code_editor(value = nil, **props) = control_delegate.code_editor(value, **props)
275
+ def codeeditor(value = nil, **props) = control_delegate.codeeditor(value, **props)
276
+ def rive(src = nil, **props) = control_delegate.rive(src, **props)
264
277
  def cupertino_button(content = nil, **props) = control_delegate.cupertino_button(content, **props)
265
278
  def cupertinobutton(content = nil, **props) = control_delegate.cupertinobutton(content, **props)
266
279
  def cupertino_filled_button(content = nil, **props) = control_delegate.cupertino_filled_button(content, **props)
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.15
4
+ version: 0.0.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - AdamMusa
@@ -83,6 +83,7 @@ files:
83
83
  - lib/ruflet_ui/ruflet/ui/controls/materials/checkbox_control.rb
84
84
  - lib/ruflet_ui/ruflet/ui/controls/materials/chip_control.rb
85
85
  - lib/ruflet_ui/ruflet/ui/controls/materials/circleavatar_control.rb
86
+ - lib/ruflet_ui/ruflet/ui/controls/materials/codeeditor_control.rb
86
87
  - lib/ruflet_ui/ruflet/ui/controls/materials/container_control.rb
87
88
  - lib/ruflet_ui/ruflet/ui/controls/materials/contextmenu_control.rb
88
89
  - lib/ruflet_ui/ruflet/ui/controls/materials/datacell_control.rb
@@ -125,6 +126,7 @@ files:
125
126
  - lib/ruflet_ui/ruflet/ui/controls/materials/radiogroup_control.rb
126
127
  - lib/ruflet_ui/ruflet/ui/controls/materials/rangeslider_control.rb
127
128
  - lib/ruflet_ui/ruflet/ui/controls/materials/reorderablelistview_control.rb
129
+ - lib/ruflet_ui/ruflet/ui/controls/materials/rive_control.rb
128
130
  - lib/ruflet_ui/ruflet/ui/controls/materials/ruflet_controls.rb
129
131
  - lib/ruflet_ui/ruflet/ui/controls/materials/searchbar_control.rb
130
132
  - lib/ruflet_ui/ruflet/ui/controls/materials/segment_control.rb
@@ -132,6 +134,7 @@ files:
132
134
  - lib/ruflet_ui/ruflet/ui/controls/materials/selectionarea_control.rb
133
135
  - lib/ruflet_ui/ruflet/ui/controls/materials/slider_control.rb
134
136
  - lib/ruflet_ui/ruflet/ui/controls/materials/snackbar_control.rb
137
+ - lib/ruflet_ui/ruflet/ui/controls/materials/spinkit_controls.rb
135
138
  - lib/ruflet_ui/ruflet/ui/controls/materials/submenubutton_control.rb
136
139
  - lib/ruflet_ui/ruflet/ui/controls/materials/switch_control.rb
137
140
  - lib/ruflet_ui/ruflet/ui/controls/materials/tab_control.rb
@@ -233,7 +236,8 @@ files:
233
236
  - lib/ruflet_ui/ruflet/ui/shared_control_forwarders.rb
234
237
  - lib/ruflet_ui/ruflet/ui/widget_builder.rb
235
238
  homepage: https://github.com/AdamMusa/Ruflet
236
- licenses: []
239
+ licenses:
240
+ - MIT
237
241
  metadata: {}
238
242
  rdoc_options: []
239
243
  require_paths: