ruflet 0.0.4 → 0.0.6

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: 01d8a3779e874187cf24c4373efa47dbb8541c9495e8b54534d1c4028bf0b90c
4
- data.tar.gz: 81d1d6959b1d6e144f24e3c6586f35b6c32fc5f971f039b702a607fe3ef1ea15
3
+ metadata.gz: 71ccb066d48adaf26ea46834bd630001c3a144aca86d5891f1f5c4646755029c
4
+ data.tar.gz: 133976a5d04e617e8faefba38b42d802d308df2d6c920c23d04d906508f0a040
5
5
  SHA512:
6
- metadata.gz: 303761d68b2adda5d36edf77e6270c6668ca166defbc71035df969e012c7539bb6c39b9cd69a20df3ab2c849f1fa20cdb857b663db59eb274bcb36cae206661e
7
- data.tar.gz: b0892bfbb584db2187782958b4d67c86ffbf2ed2899974389ac79b89d770d9898f099eef8ec42477d19b8104a4b5496ed7655b2882f4b7e149cd7d5f0a4b2428
6
+ metadata.gz: e26a8c097c5f85de3fa2e56655503b19a2f54da7b1a285d0b5e7c5094c6d4cc4b6ce3f452460531a958ae915c2408f6a8ccc185d773a4d148e4f3c2b9356e70f
7
+ data.tar.gz: 2fb16bbdd819e73446492e188dc371383387b26a37306512256a431722dabce9695b88d6d66f0f02852e59918e5e7e75e5b4b6a776637718ff0f326eca3d6a4d
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ruflet
4
- VERSION = "0.0.4" unless const_defined?(:VERSION)
4
+ VERSION = "0.0.6" unless const_defined?(:VERSION)
5
5
  end
@@ -5,6 +5,15 @@ require_relative "ui/control_factory"
5
5
 
6
6
  module Ruflet
7
7
  module DSL
8
+ DURATION_FACTORS_MS = {
9
+ days: 86_400_000.0,
10
+ hours: 3_600_000.0,
11
+ minutes: 60_000.0,
12
+ seconds: 1_000.0,
13
+ milliseconds: 1.0,
14
+ microseconds: 0.001
15
+ }.freeze
16
+
8
17
  module_function
9
18
 
10
19
  def _pending_app
@@ -31,6 +40,8 @@ module Ruflet
31
40
  def center(**props, &block) = _pending_app.center(**props, &block)
32
41
  def row(**props, &block) = _pending_app.row(**props, &block)
33
42
  def stack(**props, &block) = _pending_app.stack(**props, &block)
43
+ def grid_view(**props, &block) = _pending_app.grid_view(**props, &block)
44
+ def gridview(**props, &block) = _pending_app.gridview(**props, &block)
34
45
  def container(**props, &block) = _pending_app.container(**props, &block)
35
46
  def gesture_detector(**props, &block) = _pending_app.gesture_detector(**props, &block)
36
47
  def gesturedetector(**props, &block) = _pending_app.gesturedetector(**props, &block)
@@ -49,6 +60,7 @@ module Ruflet
49
60
  def iconbutton(**props) = _pending_app.iconbutton(**props)
50
61
  def app_bar(**props) = _pending_app.app_bar(**props)
51
62
  def appbar(**props) = _pending_app.appbar(**props)
63
+ def clipboard(**props) = _pending_app.clipboard(**props)
52
64
  def text_button(**props) = _pending_app.text_button(**props)
53
65
  def textbutton(**props) = _pending_app.textbutton(**props)
54
66
  def filled_button(**props) = _pending_app.filled_button(**props)
@@ -95,6 +107,7 @@ module Ruflet
95
107
  def cupertinodialogaction(**props) = _pending_app.cupertinodialogaction(**props)
96
108
  def cupertino_navigation_bar(**props) = _pending_app.cupertino_navigation_bar(**props)
97
109
  def cupertinonavigationbar(**props) = _pending_app.cupertinonavigationbar(**props)
110
+ def duration(**parts) = duration_in_milliseconds(parts)
98
111
 
99
112
  class App
100
113
  include UI::ControlMethods
@@ -127,7 +140,7 @@ module Ruflet
127
140
 
128
141
  def control(type, **props, &block)
129
142
  mapped_props = props.dup
130
- prop_children = mapped_props.delete(:controls) || mapped_props.delete("controls")
143
+ prop_children = extract_children_prop(mapped_props)
131
144
 
132
145
  id = mapped_props.delete(:id)&.to_s || next_id(type)
133
146
  c = Ruflet::UI::ControlFactory.build(type.to_s, id: id, **normalize_props(mapped_props))
@@ -146,6 +159,10 @@ module Ruflet
146
159
  c
147
160
  end
148
161
 
162
+ def duration(**parts)
163
+ DSL.duration(**parts)
164
+ end
165
+
149
166
  def run
150
167
  app_roots = @roots
151
168
  page_props = @page_props.dup
@@ -172,11 +189,39 @@ module Ruflet
172
189
  hash.transform_keys(&:to_s).transform_values { |v| v.is_a?(Symbol) ? v.to_s : v }
173
190
  end
174
191
 
192
+ def extract_children_prop(props)
193
+ props.delete(:children) ||
194
+ props.delete("children") ||
195
+ props.delete(:controls) ||
196
+ props.delete("controls")
197
+ end
198
+
175
199
  def next_id(type)
176
200
  @seq += 1
177
201
  "#{type}_#{@seq}"
178
202
  end
179
203
 
204
+ def duration_in_milliseconds(parts)
205
+ DSL.send(:duration_in_milliseconds, parts)
206
+ end
207
+
208
+ end
209
+
210
+ def duration_in_milliseconds(parts)
211
+ return 0 if parts.nil? || parts.empty?
212
+
213
+ DURATION_FACTORS_MS.reduce(0.0) do |sum, (key, factor)|
214
+ sum + read_duration_part(parts, key) * factor
215
+ end.round
216
+ end
217
+
218
+ def read_duration_part(parts, key)
219
+ raw = parts[key] || parts[key.to_s]
220
+ return 0.0 if raw.nil?
221
+ return raw.to_f if raw.is_a?(Numeric)
222
+ return raw.to_f if raw.is_a?(String) && raw.match?(/\A-?\d+(\.\d+)?\z/)
223
+
224
+ 0.0
180
225
  end
181
226
  end
182
227
  end
@@ -3,49 +3,29 @@
3
3
  require_relative "event"
4
4
  require "ruflet_protocol"
5
5
  require_relative "control"
6
- require_relative "ui/control_methods"
7
6
  require_relative "ui/widget_builder"
8
7
  require_relative "icons/material_icon_lookup"
9
8
  require_relative "icons/cupertino_icon_lookup"
10
- begin
11
- require "set"
12
- rescue LoadError
13
- class Set
14
- def initialize
15
- @index = {}
16
- end
17
-
18
- def include?(value)
19
- @index.key?(value)
20
- end
21
-
22
- def <<(value)
23
- @index[value] = true
24
- self
25
- end
26
- end
27
- end
28
-
29
- begin
30
- require "cgi"
31
- rescue LoadError
32
- module CGI
33
- module_function
34
-
35
- def escape(text)
36
- value = text.to_s
37
- value.gsub(/[^a-zA-Z0-9_.~-]/) { |ch| "%%%02X" % ch.ord }
38
- end
39
- end
40
- end
9
+ require "set"
10
+ require "cgi"
41
11
 
42
12
  module Ruflet
43
13
  class Page
44
- include UI::ControlMethods
45
-
46
14
  PAGE_PROP_KEYS = %w[route title vertical_alignment horizontal_alignment scroll].freeze
47
15
  DIALOG_PROP_KEYS = %w[dialog snack_bar bottom_sheet].freeze
48
16
  BUTTON_TEXT_TYPES = %w[button elevatedbutton textbutton filledbutton].freeze
17
+ DEPRECATED_PAGE_WIDGET_METHODS = %i[
18
+ control widget view column center row stack container gesture_detector gesturedetector draggable
19
+ drag_target dragtarget text button elevated_button elevatedbutton text_button textbutton filled_button
20
+ filledbutton icon_button iconbutton text_field textfield checkbox radio radio_group radiogroup
21
+ alert_dialog alertdialog markdown icon image app_bar appbar floating_action_button snack_bar snackbar
22
+ bottom_sheet bottomsheet tabs tab tab_bar tabbar tab_bar_view tabbarview navigation_bar navigationbar
23
+ navigation_bar_destination navigationbardestination fab cupertino_button
24
+ cupertinobutton cupertino_filled_button cupertinofilledbutton cupertino_text_field cupertinotextfield
25
+ cupertino_switch cupertinoswitch cupertino_slider cupertinoslider cupertino_alert_dialog
26
+ cupertinoalertdialog cupertino_action_sheet cupertinoactionsheet cupertino_dialog_action
27
+ cupertinodialogaction cupertino_navigation_bar cupertinonavigationbar
28
+ ].freeze
49
29
 
50
30
  attr_reader :session_id, :client_details, :views
51
31
 
@@ -104,16 +84,6 @@ module Ruflet
104
84
  @page_props["route"] = value
105
85
  end
106
86
 
107
- def scroll
108
- @page_props["scroll"]
109
- end
110
-
111
- def scroll=(value)
112
- v = normalize_value("scroll", value)
113
- @page_props["scroll"] = v
114
- @view_props["scroll"] = v
115
- end
116
-
117
87
  def vertical_alignment
118
88
  @page_props["vertical_alignment"] || @view_props["vertical_alignment"]
119
89
  end
@@ -163,6 +133,11 @@ module Ruflet
163
133
  self
164
134
  end
165
135
 
136
+ def views=(value)
137
+ @views = Array(value).compact
138
+ self
139
+ end
140
+
166
141
  def services
167
142
  @services_container.props["_services"] ||= []
168
143
  end
@@ -181,11 +156,6 @@ module Ruflet
181
156
  self
182
157
  end
183
158
 
184
- def views=(value)
185
- @views = Array(value).compact
186
- self
187
- end
188
-
189
159
  def go(route, **query_params)
190
160
  @page_props["route"] = build_route(route, query_params)
191
161
  dispatch_page_event(name: "route_change", data: @page_props["route"])
@@ -212,22 +182,10 @@ module Ruflet
212
182
  add(*builder.children)
213
183
  end
214
184
 
215
- def appbar(**props, &block)
216
- return @view_props["appbar"] if props.empty? && !block
217
-
218
- WidgetBuilder.new.appbar(**props, &block)
219
- end
220
-
221
185
  def appbar=(value)
222
186
  @view_props["appbar"] = value
223
187
  end
224
188
 
225
- def floating_action_button(**props, &block)
226
- return @view_props["floating_action_button"] if props.empty? && !block
227
-
228
- WidgetBuilder.new.floating_action_button(**props, &block)
229
- end
230
-
231
189
  def floating_action_button=(value)
232
190
  @view_props["floating_action_button"] = value
233
191
  end
@@ -239,40 +197,20 @@ module Ruflet
239
197
  refresh_dialogs_container!
240
198
  end
241
199
 
242
- def snack_bar(**props, &block)
243
- return @snack_bar if props.empty? && !block
244
-
245
- super
246
- end
247
-
248
200
  def snack_bar=(value)
249
201
  @snack_bar = value
250
202
  refresh_dialogs_container!
251
203
  end
252
204
 
253
- def snackbar(**props, &block)
254
- snack_bar(**props, &block)
255
- end
256
-
257
205
  def snackbar=(value)
258
206
  self.snack_bar = value
259
207
  end
260
208
 
261
- def bottom_sheet(**props, &block)
262
- return @bottom_sheet if props.empty? && !block
263
-
264
- super
265
- end
266
-
267
209
  def bottom_sheet=(value)
268
210
  @bottom_sheet = value
269
211
  refresh_dialogs_container!
270
212
  end
271
213
 
272
- def bottomsheet(**props, &block)
273
- bottom_sheet(**props, &block)
274
- end
275
-
276
214
  def bottomsheet=(value)
277
215
  self.bottom_sheet = value
278
216
  end
@@ -290,12 +228,78 @@ module Ruflet
290
228
  self
291
229
  end
292
230
 
231
+ def invoke(control_or_id, method_name, args: nil, timeout: 10)
232
+ control = resolve_control(control_or_id)
233
+ return nil unless control
234
+
235
+ call_id = "call_#{Ruflet::Control.generate_id}"
236
+ send_message(Protocol::ACTIONS[:invoke_control_method], {
237
+ "control_id" => control.wire_id,
238
+ "call_id" => call_id,
239
+ "name" => method_name.to_s,
240
+ "args" => args,
241
+ "timeout" => timeout
242
+ })
243
+
244
+ call_id
245
+ end
246
+
247
+ def launch_url(url, mode: "external_application", web_view_configuration: nil, browser_configuration: nil, web_only_window_name: nil, timeout: 10)
248
+ launcher = ensure_url_launcher_service
249
+ invoke(
250
+ launcher,
251
+ "launch_url",
252
+ args: {
253
+ "url" => url,
254
+ "mode" => mode,
255
+ "web_view_configuration" => web_view_configuration,
256
+ "browser_configuration" => browser_configuration,
257
+ "web_only_window_name" => web_only_window_name
258
+ }.compact,
259
+ timeout: timeout
260
+ )
261
+ end
262
+
263
+ def can_launch_url(url, timeout: 10)
264
+ launcher = ensure_url_launcher_service
265
+ invoke(launcher, "can_launch_url", args: { "url" => url }, timeout: timeout)
266
+ end
267
+
268
+ def set_clipboard(value, timeout: 10)
269
+ clipboard = ensure_clipboard_service
270
+ invoke(clipboard, "set", args: { "data" => value.to_s }, timeout: timeout)
271
+ end
272
+
273
+ def get_clipboard(timeout: 10)
274
+ clipboard = ensure_clipboard_service
275
+ invoke(clipboard, "get", timeout: timeout)
276
+ end
277
+
278
+ def set_clipboard_files(files, timeout: 10)
279
+ clipboard = ensure_clipboard_service
280
+ invoke(clipboard, "set_files", args: { "files" => Array(files).map(&:to_s) }, timeout: timeout)
281
+ end
282
+
283
+ def get_clipboard_files(timeout: 10)
284
+ clipboard = ensure_clipboard_service
285
+ invoke(clipboard, "get_files", timeout: timeout)
286
+ end
287
+
288
+ def set_clipboard_image(value, timeout: 10)
289
+ clipboard = ensure_clipboard_service
290
+ invoke(clipboard, "set_image", args: { "data" => value }, timeout: timeout)
291
+ end
292
+
293
+ def get_clipboard_image(timeout: 10)
294
+ clipboard = ensure_clipboard_service
295
+ invoke(clipboard, "get_image", timeout: timeout)
296
+ end
297
+
293
298
  def pop_dialog
294
299
  dialog_control = latest_open_dialog
295
300
  return nil unless dialog_control
296
301
 
297
302
  dialog_control.props["open"] = false
298
- @dialogs.delete(dialog_control)
299
303
  refresh_dialogs_container!
300
304
  push_dialogs_update!
301
305
  dialog_control
@@ -316,26 +320,15 @@ module Ruflet
316
320
  control = resolve_control(control_or_id)
317
321
  return self unless control
318
322
 
319
- visited = Set.new
320
- props.each_value { |value| register_embedded_value(value, visited) }
321
-
322
- raw_props = props.dup
323
- if BUTTON_TEXT_TYPES.include?(control.type)
324
- if raw_props.key?(:text) || raw_props.key?("text")
325
- text_value = raw_props.key?(:text) ? raw_props.delete(:text) : raw_props.delete("text")
326
- raw_props[:content] = text_value unless raw_props.key?(:content) || raw_props.key?("content")
327
- end
328
- end
329
-
330
- normalized_control_props = control.send(:normalize_props, raw_props)
331
- normalized_control_props.each { |k, v| control.props[k] = v }
332
-
333
- patch = normalize_props(raw_props)
323
+ patch = normalize_props(props)
334
324
  if BUTTON_TEXT_TYPES.include?(control.type) && patch.key?("text")
335
325
  patch["content"] = patch.delete("text")
336
326
  end
337
327
 
338
- patch_ops = patch.map { |k, v| [0, 0, k, v] }
328
+ visited = Set.new
329
+ patch.each_value { |value| register_embedded_value(value, visited) }
330
+
331
+ patch_ops = patch.map { |k, v| [0, 0, k, serialize_patch_value(v)] }
339
332
 
340
333
  send_message(Protocol::ACTIONS[:patch_control], {
341
334
  "id" => control.wire_id,
@@ -345,22 +338,6 @@ module Ruflet
345
338
  self
346
339
  end
347
340
 
348
- def invoke(control_or_id, method_name, args: nil, timeout: 10)
349
- control = resolve_control(control_or_id)
350
- return nil unless control
351
-
352
- call_id = "call_#{Ruflet::Control.generate_id}"
353
- send_message(Protocol::ACTIONS[:invoke_control_method], {
354
- "control_id" => control.wire_id,
355
- "call_id" => call_id,
356
- "name" => method_name.to_s,
357
- "args" => args,
358
- "timeout" => timeout
359
- })
360
-
361
- call_id
362
- end
363
-
364
341
  def patch_page(control_id, **props)
365
342
  update(control_id, **props)
366
343
  end
@@ -398,6 +375,45 @@ module Ruflet
398
375
  end
399
376
  end
400
377
 
378
+ def method_missing(name, *args, &block)
379
+ method_name = name.to_s
380
+ prop_name = method_name.delete_suffix("=")
381
+
382
+ if method_name.end_with?("=")
383
+ if DEPRECATED_PAGE_WIDGET_METHODS.include?(prop_name.to_sym)
384
+ Kernel.warn("[DEPRECATION] `page.#{prop_name}(...)` is no longer supported.")
385
+ raise NoMethodError, "Use `#{prop_name}(...)` as a free widget helper, then attach with `page.add(...)`."
386
+ end
387
+ assign_split_prop(prop_name, normalize_value(prop_name, args.first))
388
+ return args.first
389
+ end
390
+
391
+ if args.empty? && !block
392
+ return @page_props[method_name] if @page_props.key?(method_name)
393
+ return @view_props[method_name] if @view_props.key?(method_name)
394
+ return instance_variable_get("@#{method_name}") if DIALOG_PROP_KEYS.include?(method_name)
395
+ end
396
+
397
+ if DEPRECATED_PAGE_WIDGET_METHODS.include?(name.to_sym)
398
+ Kernel.warn("[DEPRECATION] `page.#{name}(...)` is no longer supported.")
399
+ raise NoMethodError, "Use `#{name}(...)` as a free widget helper, then attach with `page.add(...)`."
400
+ end
401
+
402
+ super
403
+ end
404
+
405
+ def respond_to_missing?(name, include_private = false)
406
+ method_name = name.to_s
407
+ prop_name = method_name.delete_suffix("=")
408
+ DEPRECATED_PAGE_WIDGET_METHODS.include?(name.to_sym) ||
409
+ DEPRECATED_PAGE_WIDGET_METHODS.include?(prop_name.to_sym) ||
410
+ method_name.end_with?("=") ||
411
+ @page_props.key?(method_name) ||
412
+ @view_props.key?(method_name) ||
413
+ DIALOG_PROP_KEYS.include?(method_name) ||
414
+ super
415
+ end
416
+
401
417
  private
402
418
 
403
419
  def build_widget(type, **props, &block) = WidgetBuilder.new.control(type, **props, &block)
@@ -489,7 +505,7 @@ module Ruflet
489
505
  def resolve_control(control_or_id)
490
506
  if control_or_id.respond_to?(:wire_id)
491
507
  control_or_id
492
- elsif numeric_string?(control_or_id.to_s)
508
+ elsif control_or_id.to_s.match?(/^\d+$/)
493
509
  @wire_index[control_or_id.to_i]
494
510
  else
495
511
  @control_index[control_or_id.to_s]
@@ -509,32 +525,10 @@ module Ruflet
509
525
  return codepoint unless codepoint.nil?
510
526
  end
511
527
 
512
- if value.is_a?(Ruflet::Control)
513
- register_control_tree(value, Set.new)
514
- return value.to_patch
515
- end
516
- return serialize_value(value) if value.is_a?(Array) || value.is_a?(Hash)
517
-
518
528
  return value.value if value.is_a?(Ruflet::IconData)
519
529
  value.is_a?(Symbol) ? value.to_s : value
520
530
  end
521
531
 
522
- def serialize_value(value)
523
- case value
524
- when Ruflet::Control
525
- register_control_tree(value, Set.new)
526
- value.to_patch
527
- when Ruflet::IconData
528
- value.value
529
- when Array
530
- value.map { |v| serialize_value(v) }
531
- when Hash
532
- value.transform_values { |v| serialize_value(v) }
533
- else
534
- value
535
- end
536
- end
537
-
538
532
  def build_route(route, query_params = {})
539
533
  base = route.to_s
540
534
  return base if query_params.nil? || query_params.empty?
@@ -586,12 +580,6 @@ module Ruflet
586
580
  key == "icon" || key.end_with?("_icon")
587
581
  end
588
582
 
589
- def numeric_string?(value)
590
- return false if value.empty?
591
- value.each_byte { |b| return false unless b >= 0x30 && b <= 0x39 }
592
- true
593
- end
594
-
595
583
  def refresh_dialogs_container!
596
584
  dialog_controls = (@dialogs + dialog_slots).uniq
597
585
  @dialogs_container.props["controls"] = dialog_controls
@@ -679,25 +667,29 @@ module Ruflet
679
667
  end
680
668
 
681
669
  def resolve_icon_codepoint(value)
682
- return nil unless value.is_a?(Integer) || value.is_a?(Symbol) || value.is_a?(String)
683
-
684
- codepoint = nil
685
- begin
686
- codepoint = Ruflet::MaterialIconLookup.codepoint_for(value)
687
- rescue NameError
688
- codepoint = nil
670
+ codepoint = Ruflet::MaterialIconLookup.codepoint_for(value)
671
+ if codepoint.nil? || codepoint == value
672
+ codepoint = Ruflet::CupertinoIconLookup.codepoint_for(value)
689
673
  end
674
+ codepoint
675
+ end
690
676
 
691
- if codepoint.nil? || (value.is_a?(Integer) && codepoint == value)
692
- begin
693
- cupertino = Ruflet::CupertinoIconLookup.codepoint_for(value)
694
- codepoint = cupertino unless cupertino.nil?
695
- rescue NameError
696
- codepoint = nil
697
- end
698
- end
677
+ def ensure_url_launcher_service
678
+ launcher = services.find { |service| service.is_a?(Control) && service.type == "url_launcher" }
679
+ return launcher if launcher
699
680
 
700
- codepoint
681
+ launcher = build_widget(:url_launcher)
682
+ add_service(launcher)
683
+ launcher
684
+ end
685
+
686
+ def ensure_clipboard_service
687
+ clipboard = services.find { |service| service.is_a?(Control) && service.type == "clipboard" }
688
+ return clipboard if clipboard
689
+
690
+ clipboard = build_widget(:clipboard)
691
+ add_service(clipboard)
692
+ clipboard
701
693
  end
702
694
  end
703
695
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruflet
4
+ module UI
5
+ module Controls
6
+ class GridViewControl < Ruflet::Control
7
+ def initialize(id: nil, **props)
8
+ super(type: "gridview", id: id, **props)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -7,6 +7,61 @@ module Ruflet
7
7
  def initialize(id: nil, **props)
8
8
  super(type: "snackbar", id: id, **props)
9
9
  end
10
+
11
+ private
12
+
13
+ def preprocess_props(props)
14
+ mapped = props.dup
15
+ key = if mapped.key?(:duration)
16
+ :duration
17
+ elsif mapped.key?("duration")
18
+ "duration"
19
+ end
20
+ return mapped unless key
21
+
22
+ mapped[key] = normalize_duration_ms(mapped[key])
23
+ mapped
24
+ end
25
+
26
+ def normalize_duration_ms(value)
27
+ return value.to_i if value.is_a?(Numeric)
28
+ return value.to_i if value.is_a?(String) && value.match?(/\A\d+(\.\d+)?\z/)
29
+
30
+ parts =
31
+ if value.is_a?(Hash)
32
+ value
33
+ elsif value.respond_to?(:to_h)
34
+ value.to_h
35
+ else
36
+ nil
37
+ end
38
+ return value unless parts.is_a?(Hash)
39
+
40
+ days = read_number(parts, "days")
41
+ hours = read_number(parts, "hours")
42
+ minutes = read_number(parts, "minutes")
43
+ seconds = read_number(parts, "seconds")
44
+ milliseconds = read_number(parts, "milliseconds")
45
+ microseconds = read_number(parts, "microseconds")
46
+
47
+ total_ms = 0.0
48
+ total_ms += days * 86_400_000.0
49
+ total_ms += hours * 3_600_000.0
50
+ total_ms += minutes * 60_000.0
51
+ total_ms += seconds * 1_000.0
52
+ total_ms += milliseconds
53
+ total_ms += microseconds / 1000.0
54
+ total_ms.round
55
+ end
56
+
57
+ def read_number(parts, key)
58
+ raw = parts[key] || parts[key.to_sym]
59
+ return 0.0 if raw.nil?
60
+ return raw.to_f if raw.is_a?(Numeric)
61
+ return raw.to_f if raw.is_a?(String) && raw.match?(/\A-?\d+(\.\d+)?\z/)
62
+
63
+ 0.0
64
+ end
10
65
  end
11
66
  end
12
67
  end
@@ -6,6 +6,7 @@ require_relative "controls/material/container_control"
6
6
  require_relative "controls/material/column_control"
7
7
  require_relative "controls/material/row_control"
8
8
  require_relative "controls/material/stack_control"
9
+ require_relative "controls/material/grid_view_control"
9
10
  require_relative "controls/material/gesture_detector_control"
10
11
  require_relative "controls/material/draggable_control"
11
12
  require_relative "controls/material/drag_target_control"
@@ -44,6 +45,8 @@ module Ruflet
44
45
  "column" => Controls::ColumnControl,
45
46
  "row" => Controls::RowControl,
46
47
  "stack" => Controls::StackControl,
48
+ "gridview" => Controls::GridViewControl,
49
+ "grid_view" => Controls::GridViewControl,
47
50
  "container" => Controls::ContainerControl,
48
51
  "gesturedetector" => Controls::GestureDetectorControl,
49
52
  "gesture_detector" => Controls::GestureDetectorControl,
@@ -27,6 +27,8 @@ module Ruflet
27
27
 
28
28
  def row(**props, &block) = build_widget(:row, **props, &block)
29
29
  def stack(**props, &block) = build_widget(:stack, **props, &block)
30
+ def grid_view(**props, &block) = build_widget(:gridview, **props, &block)
31
+ def gridview(**props, &block) = grid_view(**props, &block)
30
32
  def container(**props, &block) = build_widget(:container, **normalize_container_props(props), &block)
31
33
  def gesture_detector(**props, &block) = build_widget(:gesturedetector, **props, &block)
32
34
  def gesturedetector(**props, &block) = gesture_detector(**props, &block)
@@ -82,6 +84,7 @@ module Ruflet
82
84
  def app_bar(**props) = build_widget(:appbar, **props)
83
85
  def appbar(**props) = app_bar(**props)
84
86
  def url_launcher(**props) = build_widget(:url_launcher, **props)
87
+ def clipboard(**props) = build_widget(:clipboard, **props)
85
88
  def floating_action_button(**props) = build_widget(:floatingactionbutton, **props)
86
89
  def floatingactionbutton(**props) = floating_action_button(**props)
87
90
  def tabs(**props, &block) = build_widget(:tabs, **props, &block)
@@ -8,6 +8,8 @@ module Ruflet
8
8
  "column" => "Column",
9
9
  "row" => "Row",
10
10
  "stack" => "Stack",
11
+ "gridview" => "GridView",
12
+ "grid_view" => "GridView",
11
13
  "view" => "View",
12
14
  "container" => "Container",
13
15
  "checkbox" => "Checkbox",
@@ -79,6 +81,7 @@ module Ruflet
79
81
  "line" => "Line",
80
82
  "service_registry" => "ServiceRegistry",
81
83
  "url_launcher" => "UrlLauncher",
84
+ "clipboard" => "Clipboard",
82
85
  "audio" => "Audio",
83
86
  "video" => "Video",
84
87
  "flashlight" => "Flashlight",
@@ -10,6 +10,8 @@ module Ruflet
10
10
  def center(**props, &block) = control_delegate.center(**props, &block)
11
11
  def row(**props, &block) = control_delegate.row(**props, &block)
12
12
  def stack(**props, &block) = control_delegate.stack(**props, &block)
13
+ def grid_view(**props, &block) = control_delegate.grid_view(**props, &block)
14
+ def gridview(**props, &block) = control_delegate.gridview(**props, &block)
13
15
  def container(**props, &block) = control_delegate.container(**props, &block)
14
16
  def gesture_detector(**props, &block) = control_delegate.gesture_detector(**props, &block)
15
17
  def gesturedetector(**props, &block) = control_delegate.gesturedetector(**props, &block)
@@ -44,6 +46,7 @@ module Ruflet
44
46
  def fab(content = nil, **props) = control_delegate.fab(content, **props)
45
47
  def app_bar(**props) = control_delegate.app_bar(**props)
46
48
  def appbar(**props) = control_delegate.appbar(**props)
49
+ def clipboard(**props) = control_delegate.clipboard(**props)
47
50
  def floating_action_button(**props) = control_delegate.floating_action_button(**props)
48
51
  def floatingactionbutton(**props) = control_delegate.floatingactionbutton(**props)
49
52
  def tabs(**props, &block) = control_delegate.tabs(**props, &block)
@@ -74,6 +77,7 @@ module Ruflet
74
77
  def cupertinodialogaction(**props) = control_delegate.cupertinodialogaction(**props)
75
78
  def cupertino_navigation_bar(**props) = control_delegate.cupertino_navigation_bar(**props)
76
79
  def cupertinonavigationbar(**props) = control_delegate.cupertinonavigationbar(**props)
80
+ def duration(**parts) = control_delegate.duration(**parts)
77
81
 
78
82
  private
79
83
 
@@ -19,7 +19,7 @@ module Ruflet
19
19
 
20
20
  def control(type, **props, &block)
21
21
  mapped_props = props.dup
22
- prop_children = mapped_props.delete(:controls) || mapped_props.delete("controls")
22
+ prop_children = extract_children_prop(mapped_props)
23
23
 
24
24
  node = UI::ControlFactory.build(type, **mapped_props)
25
25
  if block
@@ -44,5 +44,12 @@ module Ruflet
44
44
  def build_widget(type, **props, &block) = control(type, **props, &block)
45
45
 
46
46
  private
47
+
48
+ def extract_children_prop(props)
49
+ props.delete(:children) ||
50
+ props.delete("children") ||
51
+ props.delete(:controls) ||
52
+ props.delete("controls")
53
+ end
47
54
  end
48
55
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruflet
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - AdamMusa
@@ -60,6 +60,7 @@ files:
60
60
  - lib/ruflet_ui/ruflet/ui/controls/material/filled_button_control.rb
61
61
  - lib/ruflet_ui/ruflet/ui/controls/material/floating_action_button_control.rb
62
62
  - lib/ruflet_ui/ruflet/ui/controls/material/gesture_detector_control.rb
63
+ - lib/ruflet_ui/ruflet/ui/controls/material/grid_view_control.rb
63
64
  - lib/ruflet_ui/ruflet/ui/controls/material/icon_button_control.rb
64
65
  - lib/ruflet_ui/ruflet/ui/controls/material/icon_control.rb
65
66
  - lib/ruflet_ui/ruflet/ui/controls/material/image_control.rb