lacci 0.3.0 → 0.4.0

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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/lib/lacci/scarpe_cli.rb +0 -1
  3. data/lib/lacci/version.rb +1 -1
  4. data/lib/scarpe/niente/display_service.rb +5 -1
  5. data/lib/scarpe/niente/drawable.rb +2 -0
  6. data/lib/scarpe/niente/shoes_spec.rb +7 -1
  7. data/lib/scarpe/niente.rb +14 -2
  8. data/lib/shoes/app.rb +44 -50
  9. data/lib/shoes/constants.rb +23 -2
  10. data/lib/shoes/display_service.rb +43 -4
  11. data/lib/shoes/drawable.rb +309 -35
  12. data/lib/shoes/drawables/arc.rb +2 -24
  13. data/lib/shoes/drawables/arrow.rb +2 -22
  14. data/lib/shoes/drawables/border.rb +28 -0
  15. data/lib/shoes/drawables/button.rb +4 -20
  16. data/lib/shoes/drawables/check.rb +7 -3
  17. data/lib/shoes/drawables/document_root.rb +4 -4
  18. data/lib/shoes/drawables/edit_box.rb +6 -5
  19. data/lib/shoes/drawables/edit_line.rb +5 -4
  20. data/lib/shoes/drawables/flow.rb +3 -5
  21. data/lib/shoes/drawables/font_helper.rb +62 -0
  22. data/lib/shoes/drawables/image.rb +2 -2
  23. data/lib/shoes/drawables/line.rb +4 -7
  24. data/lib/shoes/drawables/link.rb +5 -8
  25. data/lib/shoes/drawables/list_box.rb +8 -5
  26. data/lib/shoes/drawables/oval.rb +48 -0
  27. data/lib/shoes/drawables/para.rb +106 -18
  28. data/lib/shoes/drawables/progress.rb +2 -1
  29. data/lib/shoes/drawables/radio.rb +5 -3
  30. data/lib/shoes/drawables/rect.rb +5 -4
  31. data/lib/shoes/drawables/shape.rb +2 -1
  32. data/lib/shoes/drawables/slot.rb +99 -8
  33. data/lib/shoes/drawables/stack.rb +6 -11
  34. data/lib/shoes/drawables/star.rb +8 -30
  35. data/lib/shoes/drawables/text_drawable.rb +93 -34
  36. data/lib/shoes/drawables/video.rb +3 -2
  37. data/lib/shoes/drawables/widget.rb +8 -3
  38. data/lib/shoes/drawables.rb +2 -1
  39. data/lib/shoes/errors.rb +13 -3
  40. data/lib/shoes/margin_helper.rb +79 -0
  41. data/lib/shoes.rb +4 -3
  42. metadata +11 -11
  43. data/lib/scarpe/niente/logger.rb +0 -29
  44. data/lib/shoes/drawables/span.rb +0 -27
  45. data/lib/shoes/spacing.rb +0 -9
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
-
2
+ require_relative 'margin_helper'
3
3
  class Shoes
4
4
  # Shoes::Drawable
5
5
  #
@@ -15,7 +15,7 @@ class Shoes
15
15
  include Shoes::Colors
16
16
 
17
17
  # All Drawables have these so they go in Shoes::Drawable and are inherited
18
- @shoes_events = ["parent", "destroy", "prop_change"]
18
+ @shoes_events = ["parent", "destroy", "prop_change", "hover", "leave", "motion"]
19
19
 
20
20
  class << self
21
21
  attr_accessor :drawable_classes
@@ -43,7 +43,8 @@ class Shoes
43
43
  end
44
44
 
45
45
  def drawable_class_by_name(name)
46
- drawable_classes.detect { |k| k.dsl_name == name.to_s }
46
+ name = name.to_s
47
+ drawable_classes.detect { |k| k.dsl_name == name }
47
48
  end
48
49
 
49
50
  def is_widget_class?(name)
@@ -53,13 +54,14 @@ class Shoes
53
54
  def validate_as(prop_name, value)
54
55
  prop_name = prop_name.to_s
55
56
  hashes = shoes_style_hashes
56
-
57
+
57
58
  h = hashes.detect { |hash| hash[:name] == prop_name }
58
59
  raise(Shoes::Errors::NoSuchStyleError, "Can't find property #{prop_name.inspect} in #{self} property list: #{hashes.inspect}!") unless h
59
-
60
+
60
61
  return value if h[:validator].nil?
61
-
62
- h[:validator].call(value)
62
+
63
+ # Pass both the property name and value to the validator block
64
+ h[:validator].call(value,prop_name)
63
65
  end
64
66
 
65
67
  # Return a list of Shoes events for this class.
@@ -67,18 +69,65 @@ class Shoes
67
69
  # @return Array[String] the list of event names
68
70
  def get_shoes_events
69
71
  if @shoes_events.nil?
70
- raise UnknownEventsForClass, "Drawable type #{self.class} hasn't defined its list of Shoes events!"
72
+ raise Shoes::Errors::UnknownEventsForClassError, "Drawable type #{self} hasn't defined its list of Shoes events!"
71
73
  end
72
74
 
73
75
  @shoes_events
74
76
  end
75
77
 
78
+ # Return whether Shoes events have already been registered for this class
79
+ #
80
+ # @return [Boolean] true if events have been registered, false if not
81
+ def registered_shoes_events?
82
+ !@shoes_events.nil?
83
+ end
84
+
76
85
  # Set the list of Shoes event names that are allowed for this class.
77
86
  #
78
87
  # @param args [Array] an array of event names, which will be coerced to Strings
79
88
  # @return [void]
80
89
  def shoes_events(*args)
81
- @shoes_events ||= args.map(&:to_s) + self.superclass.get_shoes_events
90
+ if @shoes_events
91
+ raise Shoes::Errors::DoubleRegisteredShoesEventError, "Registering shoes events #{args.inspect} for class #{self} but already registered events as #{@shoes_events.inspect}!"
92
+ end
93
+ @shoes_events = args.map(&:to_s) + self.superclass.get_shoes_events
94
+ end
95
+
96
+ # Require supplying these Shoes style values as positional arguments to
97
+ # initialize. Initialize will get the arg list, then set the specified styles
98
+ # if args are given for them. @see opt_init_args for additional non-required
99
+ # init args.
100
+ #
101
+ # @param args [Array<String,Symbol>] an array of Shoes style names
102
+ # @return [void]
103
+ def init_args(*args)
104
+ raise Shoes::Errors::BadArgumentListError, "Positional init args already set for #{self}!" if @required_init_args
105
+ @required_init_args = args.map(&:to_s)
106
+ end
107
+
108
+ # Allow supplying these Shoes style values as optional positional arguments to
109
+ # initialize after the mandatory args. @see init_args for setting required
110
+ # init args.
111
+ #
112
+ # @param args [Array<String,Symbol>] an array of Shoes style names
113
+ # @return [void]
114
+ def opt_init_args(*args)
115
+ raise Shoes::Errors::BadArgumentListError, "Positional init args already set for #{self}!" if @opt_init_args
116
+ @opt_init_args = args.map(&:to_s)
117
+ end
118
+
119
+ # Return the list of style names for required init args for this class
120
+ #
121
+ # @return [Array<String>] the array of style names as strings
122
+ def required_init_args
123
+ @required_init_args ||= [] # TODO: eventually remove the ||= here
124
+ end
125
+
126
+ # Return the list of style names for optional init args for this class
127
+ #
128
+ # @return [Array<String>] the array of style names as strings
129
+ def optional_init_args
130
+ @opt_init_args ||= []
82
131
  end
83
132
 
84
133
  # Assign a new Shoes Drawable ID number, starting from 1.
@@ -125,24 +174,56 @@ class Shoes
125
174
  # Shoes styles in Shoes Linkables are automatically sync'd with the display side objects.
126
175
  # If a block is passed to shoes_style, that's the validation for the property. It should
127
176
  # convert a given value to a valid value for the property or throw an exception.
128
- def shoes_style(name, &validator)
177
+ #
178
+ # If feature is non-nil, it's the feature that an app must request in order to see this
179
+ # property.
180
+ #
181
+ # @param name [String,Symbol] the style name
182
+ # @param feature [Symbol,NilClass] the feature that must be defined for an app to request this style, or nil
183
+ # @block if block is given, call it to map the given style value to a valid value, or raise an exception
184
+ def shoes_style(name, feature: nil, &validator)
129
185
  name = name.to_s
130
186
 
131
187
  return if linkable_properties_hash[name]
132
188
 
133
- linkable_properties << { name: name, validator: }
189
+ linkable_properties << { name: name, validator:, feature: }
134
190
  linkable_properties_hash[name] = true
135
191
  end
136
192
 
137
- # Add these names as Shoes styles
138
- def shoes_styles(*names)
139
- names.each { |n| shoes_style(n) }
193
+ # Add these names as Shoes styles with the given validator and feature, if any
194
+ def shoes_styles(*names, feature: nil, &validator)
195
+ names.each { |n| shoes_style(n, feature:, &validator) }
140
196
  end
141
197
 
142
- def shoes_style_names
143
- parent_prop_names = self != Shoes::Drawable ? self.superclass.shoes_style_names : []
198
+ # Query what feature, if any, is required to use a specific shoes_style.
199
+ # If no specific feature is needed, nil will be returned.
200
+ def feature_for_shoes_style(style_name)
201
+ style_name = style_name.to_s
202
+ lp = linkable_properties.detect { |prop| prop[:name] == style_name }
203
+ return lp[:feature] if lp
204
+
205
+ # If we get to the top of the superclass tree and we didn't find it, it's not here
206
+ if self.class == ::Shoes::Drawable
207
+ raise Shoes::Errors::NoSuchStyleError, "Can't find information for style #{style_name.inspect}!"
208
+ end
209
+
210
+ super
211
+ end
144
212
 
145
- parent_prop_names | linkable_properties.map { |prop| prop[:name] }
213
+ # Return a list of shoes_style names with the given features. If with_features is nil,
214
+ # return them with a list of features for the current Shoes::App. For the list of
215
+ # styles available with no features requested, pass nil to with_features.
216
+ def shoes_style_names(with_features: nil)
217
+ # No with_features given? Use the ones requested by this Shoes::App
218
+ with_features ||= Shoes::App.instance.features
219
+ parent_prop_names = self != Shoes::Drawable ? self.superclass.shoes_style_names(with_features:) : []
220
+
221
+ if with_features == :all
222
+ subclass_props = linkable_properties
223
+ else
224
+ subclass_props = linkable_properties.select { |prop| !prop[:feature] || with_features.include?(prop[:feature]) }
225
+ end
226
+ parent_prop_names | subclass_props.map { |prop| prop[:name] }
146
227
  end
147
228
 
148
229
  def shoes_style_hashes
@@ -157,31 +238,170 @@ class Shoes
157
238
  end
158
239
  end
159
240
 
241
+ # Every Shoes drawable has positioning properties
242
+ shoes_styles :top, :left, :width, :height
243
+
244
+ # Margins around drawable
245
+ shoes_styles :margin, :margin_top, :margin_bottom, :margin_left, :margin_right
246
+
247
+ # Padding around drawable
248
+ shoes_styles :padding, :padding_top, :padding_bottom, :padding_left, :padding_right
249
+
160
250
  # Shoes uses a "hidden" style property for hide/show
161
251
  shoes_style :hidden
162
252
 
163
253
  attr_reader :debug_id
164
254
 
255
+ # These styles can be set to a current per-slot value and inherited from parent slots.
256
+ # Their value is set at drawable-create time.
257
+ DRAW_CONTEXT_STYLES = [:fill, :stroke, :strokewidth, :rotate, :transform, :translate]
258
+
259
+ include MarginHelper
260
+
165
261
  def initialize(*args, **kwargs)
166
- log_init("Shoes::#{self.class.name}")
262
+ kwargs = margin_parse(kwargs)
263
+ log_init("Shoes::#{self.class.name}") unless @log
167
264
 
168
- default_styles = Shoes::Drawable.drawable_default_styles[self.class]
265
+ # First, get the list of allowed and disallowed styles for the given features
266
+ # and make sure no disallowed styles were given.
267
+
268
+ app_features = Shoes::App.instance.features
269
+ this_app_styles = self.class.shoes_style_names.map(&:to_sym)
270
+ not_this_app_styles = self.class.shoes_style_names(with_features: :all).map(&:to_sym) - this_app_styles
271
+
272
+ bad_styles = kwargs.keys & not_this_app_styles
273
+ unless bad_styles.empty?
274
+ features_needed = bad_styles.map { |s| self.class.feature_for_shoes_style(s) }.uniq
275
+ raise Shoes::Errors::UnsupportedFeatureError, "The style(s) #{bad_styles.inspect} are only defined for applications that request specific features: #{features_needed.inspect} (you requested #{app_features.inspect})!"
276
+ end
277
+
278
+ # Next, check positional arguments and make sure the correct number and type
279
+ # were passed and match positional args with style names.
280
+
281
+ supplied_args = kwargs.keys
282
+
283
+ req_args = self.class.required_init_args
284
+ opt_args = self.class.optional_init_args
285
+ pos_args = req_args + opt_args
286
+ if req_args != ["any"]
287
+ if args.size > pos_args.size
288
+ raise Shoes::Errors::BadArgumentListError, "Too many arguments given for #{self.class}#initialize! #{args.inspect}"
289
+ end
290
+
291
+ if args.size == 0
292
+ # It's fine to use keyword args instead, but we should make sure they're actually there
293
+ needed_args = req_args.map(&:to_sym) - kwargs.keys
294
+ unless needed_args.empty?
295
+ raise Shoes::Errors::BadArgumentListError, "Keyword arguments for #{self.class}#initialize should also supply #{needed_args.inspect}! #{args.inspect}"
296
+ end
297
+ elsif args.size < req_args.size
298
+ raise Shoes::Errors::BadArgumentListError, "Too few arguments given for #{self.class}#initialize! #{args.inspect}"
299
+ end
300
+
301
+ # Set each positional argument
302
+ args.each.with_index do |val, idx|
303
+ style_name = pos_args[idx]
304
+ next if style_name.nil? || style_name == "" # It's possible to have non-style positional args
169
305
 
170
- self.class.shoes_style_names.each do |prop|
171
- prop_sym = prop.to_sym
172
- if kwargs.key?(prop_sym)
173
- val = self.class.validate_as(prop, kwargs[prop_sym])
174
- instance_variable_set("@" + prop, val)
175
- elsif default_styles.key?(prop_sym)
176
- val = self.class.validate_as(prop, default_styles[prop_sym])
177
- instance_variable_set("@" + prop, val)
306
+ val = self.class.validate_as(style_name, args[idx])
307
+ instance_variable_set("@#{style_name}", val)
308
+ supplied_args << style_name.to_sym
178
309
  end
179
310
  end
180
311
 
312
+ this_drawable_styles = self.class.shoes_style_names.map(&:to_sym)
313
+ dc = Shoes::App.instance.current_draw_context || {}
314
+
315
+ # Styles not passed as arguments can come from the draw context
316
+
317
+ # What styles are in the draw context, are used by this drawable, and weren't
318
+ # given as positional or keyword arguments?
319
+ draw_context_styles = (DRAW_CONTEXT_STYLES & this_drawable_styles) - supplied_args
320
+ unless draw_context_styles.empty?
321
+ # When we first call this, there is no parent. We don't want to set the parent
322
+ # yet because that will send a notification, and *that* should wait until after
323
+ # we've told the display service that this drawable was created. So instead
324
+ # we'll query the parent object's draw context directly.
325
+
326
+ draw_context_styles.each do |style|
327
+ dc_val = dc[style.to_s]
328
+ next if dc_val.nil?
329
+
330
+ val = self.class.validate_as(style, dc[style.to_s])
331
+ instance_variable_set("@#{style}", val)
332
+ supplied_args << style
333
+ end
334
+ end
335
+
336
+ # Styles that were *not* passed should be set to defaults
337
+
338
+ default_styles = Shoes::Drawable.drawable_default_styles[self.class]
339
+
340
+ # No arg specified for a property with a default value? Set it to default.
341
+ (default_styles.keys - supplied_args).each do |key|
342
+ val = self.class.validate_as(key, default_styles[key])
343
+ instance_variable_set("@#{key}", val)
344
+ end
345
+
346
+ # If we have a keyword arg for a style, set it as specified.
347
+ (this_drawable_styles & kwargs.keys).each do |key|
348
+ val = self.class.validate_as(key, kwargs[key])
349
+ instance_variable_set("@#{key}", val)
350
+ end
351
+
352
+ # We'd like to avoid unexpected keywords. But we're not disciplined enough to
353
+ # raise an error by default yet. Non-style keywords passed to Drawable#initialize
354
+ # are deprecated at this point, but I need to hunt down the last of them
355
+ # and prevent them.
356
+ unexpected = (kwargs.keys - this_drawable_styles)
357
+ unless unexpected.empty?
358
+ STDERR.puts "Unexpected non-style keyword(s) in #{self.class} initialize: #{unexpected.inspect}"
359
+ end
360
+
181
361
  super(linkable_id: Shoes::Drawable.allocate_drawable_id)
182
362
  Shoes::Drawable.register_drawable_id(self.linkable_id, self)
183
363
 
184
364
  generate_debug_id
365
+
366
+ parent = ::Shoes::App.instance.current_slot
367
+ if self.class.expects_parent?
368
+ set_parent(parent, notify: false)
369
+ end
370
+
371
+ unless self.class.registered_shoes_events?
372
+ # No Shoes events declared and we're creating an instance?
373
+ # Default to no class-specific events.
374
+ self.class.shoes_events
375
+ end
376
+
377
+ # Binding the motion events here isn't perfect.
378
+ # What about drawables like SubscriptionItem that
379
+ # have no motion events? With the current Lacci
380
+ # implementation, the answer is that those events
381
+ # will never be sent. Calling .hover on one will
382
+ # be useless, harmless, and allowed. If you want
383
+ # to make it disallowed, you can do something like
384
+ # define a SubscriptionItem#hover that raises an
385
+ # exception instead.
386
+
387
+ bind_self_event("hover") do
388
+ @hover&.call
389
+ end
390
+
391
+ bind_self_event("leave") do
392
+ @leave&.call
393
+ end
394
+
395
+ bind_self_event("motion") do |x, y|
396
+ @motion&.call(x, y)
397
+ end
398
+ end
399
+
400
+ def self.expects_parent?
401
+ return false if [::Shoes::App, ::Shoes::DocumentRoot].include?(self)
402
+ return false if self < ::Shoes::TextDrawable
403
+
404
+ true
185
405
  end
186
406
 
187
407
  # Calling stack.app or drawable.app will execute the block
@@ -223,13 +443,14 @@ class Shoes
223
443
  private
224
444
 
225
445
  def validate_event_name(event_name)
226
- unless self.class.get_shoes_events.include?(event_name.to_s)
227
- raise Shoes::UnregisteredShoesEvent, "Drawable #{self.inspect} tried to bind Shoes event #{event_name}, which is not in #{evetns.inspect}!"
446
+ events = self.class.get_shoes_events
447
+ unless events.include?(event_name.to_s)
448
+ raise Shoes::Errors::UnregisteredShoesEventError, "Drawable #{self.inspect} tried to bind Shoes event #{event_name}, which is not in #{events.inspect}!"
228
449
  end
229
450
  end
230
451
 
231
452
  def bind_self_event(event_name, &block)
232
- raise(Shoes::Errors::NoLinkableIdError, "Drawable has no linkable_id! #{inspect}") unless linkable_id
453
+ raise(Shoes::Errors::NoSuchLinkableIdError, "Drawable has no linkable_id! #{inspect}") unless linkable_id
233
454
 
234
455
  validate_event_name(event_name)
235
456
 
@@ -292,10 +513,11 @@ class Shoes
292
513
  klass_name = self.class.name.delete_prefix("Scarpe::").delete_prefix("Shoes::")
293
514
 
294
515
  is_widget = Shoes::Drawable.is_widget_class?(klass_name)
516
+ parent_id = @parent&.linkable_id
295
517
 
296
518
  # Should we send an event so this can be discovered from someplace other than
297
519
  # the DisplayService?
298
- ::Shoes::DisplayService.display_service.create_display_drawable_for(klass_name, self.linkable_id, shoes_style_values, is_widget:)
520
+ ::Shoes::DisplayService.display_service.create_display_drawable_for(klass_name, self.linkable_id, shoes_style_values, parent_id:, is_widget:)
299
521
  end
300
522
 
301
523
  public
@@ -303,11 +525,17 @@ class Shoes
303
525
  attr_reader :parent
304
526
  attr_reader :destroyed
305
527
 
306
- def set_parent(new_parent)
528
+ # Set the Drawable's parent drawable. Notify the display service
529
+ # that the parent has changed unless instructed not to.
530
+ # We don't notify when first creating the Drawable. The create
531
+ # event that is first sent will include the parent.
532
+ def set_parent(new_parent, notify: true)
307
533
  @parent&.remove_child(self)
308
534
  new_parent&.add_child(self)
309
535
  @parent = new_parent
310
- send_shoes_event(new_parent.linkable_id, event_name: "parent", target: linkable_id)
536
+ return unless notify
537
+
538
+ send_shoes_event(new_parent&.linkable_id, event_name: "parent", target: linkable_id)
311
539
  end
312
540
 
313
541
  # Removes the element from the Shoes::Drawable tree and removes all event subscriptions
@@ -336,6 +564,28 @@ class Shoes
336
564
  self.hidden = !self.hidden
337
565
  end
338
566
 
567
+ # Set the hover handler. Not every drawable may do something useful with this.
568
+ #
569
+ # @yield A block to be called when the cursor moves to be over the drawable.
570
+ def hover(&block)
571
+ @hover = block
572
+ end
573
+
574
+ # Set the leave handler. Not every drawable may do something useful with this.
575
+ #
576
+ # @yield A block to be called when the cursor moves to be off of the drawable.
577
+ def leave(&block)
578
+ @leave = block
579
+ end
580
+
581
+ # Set the motion handler, called with x and y coordinates as params.
582
+ # Not every drawable may do something useful with this.
583
+ #
584
+ # @yield A block to be called when the cursor moves around over the drawable.
585
+ def motion(&block)
586
+ @motion = block
587
+ end
588
+
339
589
  # We use method_missing to auto-create Shoes style getters and setters.
340
590
  def method_missing(name, *args, **kwargs, &block)
341
591
  name_s = name.to_s
@@ -344,7 +594,7 @@ class Shoes
344
594
  prop_name = name_s[0..-2]
345
595
  if self.class.shoes_style_name?(prop_name)
346
596
  self.class.define_method(name) do |new_value|
347
- raise(Shoes::Errors::NoLinkableIdError, "Trying to set Shoes styles in an object with no linkable ID! #{inspect}") unless linkable_id
597
+ raise(Shoes::Errors::NoSuchLinkableIdError, "Trying to set Shoes styles in a #{self.class} with no linkable ID!") unless linkable_id
348
598
 
349
599
  new_value = self.class.validate_as(prop_name, new_value)
350
600
  instance_variable_set("@" + prop_name, new_value)
@@ -357,7 +607,7 @@ class Shoes
357
607
 
358
608
  if self.class.shoes_style_name?(name_s)
359
609
  self.class.define_method(name) do
360
- raise(Shoes::Errors::NoLinkableIdError, "Trying to get Shoes styles in an object with no linkable ID! #{inspect}") unless linkable_id
610
+ raise(Shoes::Errors::NoSuchLinkableIdError, "Trying to get Shoes styles in an object with no linkable ID! #{inspect}") unless linkable_id
361
611
 
362
612
  instance_variable_get("@" + name_s)
363
613
  end
@@ -376,5 +626,29 @@ class Shoes
376
626
 
377
627
  super
378
628
  end
629
+
630
+ def self.convert_to_integer(value, attribute_name)
631
+ begin
632
+ value = Integer(value)
633
+ raise Shoes::Errors::InvalidAttributeValueError, "Negative number '#{value}' not allowed for attribute '#{attribute_name}'" if value < 0
634
+
635
+ value
636
+ rescue ArgumentError
637
+ error_message = "Invalid value '#{value}' provided for attribute '#{attribute_name}'. The value should be a number."
638
+ raise Shoes::Errors::InvalidAttributeValueError, error_message
639
+ end
640
+ end
641
+
642
+ def self.convert_to_float(value, attribute_name)
643
+ begin
644
+ value = Float(value)
645
+ raise Shoes::Errors::InvalidAttributeValueError, "Negative number '#{value}' not allowed for attribute '#{attribute_name}'" if value < 0
646
+
647
+ value
648
+ rescue ArgumentError
649
+ error_message = "Invalid value '#{value}' provided for attribute '#{attribute_name}'. The value should be a number."
650
+ raise Shoes::Errors::InvalidAttributeValueError, error_message
651
+ end
652
+ end
379
653
  end
380
654
  end
@@ -13,37 +13,15 @@ class Shoes
13
13
  shoes_style(prop) { |val| convert_to_float(val, prop) }
14
14
  end
15
15
 
16
- def initialize(*args)
16
+ init_args :left, :top, :width, :height, :angle1, :angle2
17
+ def initialize(*args, **kwargs)
17
18
  @draw_context = Shoes::App.instance.current_draw_context
18
19
 
19
20
  super
20
- self.left, self.top, self.width, self.height, self.angle1, self.angle2 = args
21
21
 
22
22
  create_display_drawable
23
23
  end
24
24
 
25
- def self.convert_to_integer(value, attribute_name)
26
- begin
27
- value = Integer(value)
28
- raise Shoes::Errors::InvalidAttributeValueError, "Negative number '#{value}' not allowed for attribute '#{attribute_name}'" if value < 0
29
25
 
30
- value
31
- rescue ArgumentError
32
- error_message = "Invalid value '#{value}' provided for attribute '#{attribute_name}'. The value should be a number."
33
- raise Shoes::Errors::InvalidAttributeValueError, error_message
34
- end
35
- end
36
-
37
- def self.convert_to_float(value, attribute_name)
38
- begin
39
- value = Float(value)
40
- raise Shoes::Errors::InvalidAttributeValueError, "Negative number '#{value}' not allowed for attribute '#{attribute_name}'" if value < 0
41
-
42
- value
43
- rescue ArgumentError
44
- error_message = "Invalid value '#{value}' provided for attribute '#{attribute_name}'. The value should be a number."
45
- raise Shoes::Errors::InvalidAttributeValueError, error_message
46
- end
47
- end
48
26
  end
49
27
  end
@@ -9,33 +9,13 @@ class Shoes
9
9
  shoes_style(prop) { |val| val.is_a?(Hash) ? val : convert_to_integer(val, prop) }
10
10
  end
11
11
 
12
- def initialize(*args)
12
+ init_args :left, :top, :width
13
+ def initialize(*args, **kwargs)
13
14
  @draw_context = Shoes::App.instance.current_draw_context
14
15
 
15
16
  super
16
17
 
17
- if args.length == 1 && args[0].is_a?(Hash)
18
- options = args[0]
19
- self.left = options[:left]
20
- self.top = options[:top]
21
- self.width = options[:width]
22
- else
23
- self.left, self.top, self.width = args
24
- end
25
-
26
18
  create_display_drawable
27
19
  end
28
-
29
- def self.convert_to_integer(value, attribute_name)
30
- begin
31
- value = Integer(value)
32
- raise Shoes::Errors::InvalidAttributeValueError, "Negative number '#{value}' not allowed for attribute '#{attribute_name}'" if value < 0
33
-
34
- value
35
- rescue ArgumentError
36
- error_message = "Invalid value '#{value}' provided for attribute '#{attribute_name}'. The value should be a number."
37
- raise Shoes::Errors::InvalidAttributeValueError, error_message
38
- end
39
- end
40
20
  end
41
21
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Shoes
4
+ class Border < Shoes::Drawable
5
+ # Shoes style with verification or value mapping:
6
+ # shoes_style(:left) { |val| convert_to_integer(val, "left") }
7
+
8
+ shoes_styles :stroke, :strokewidth # Write your shoes styles here
9
+
10
+ shoes_style(:strokewidth) { |val| convert_to_integer(val, "strokewidth") }
11
+ shoes_style(:curve) { |val| convert_to_integer(val, "curve") }
12
+
13
+ Shoes::Drawable.drawable_default_styles[Shoes::Border][:stroke] = :black
14
+ Shoes::Drawable.drawable_default_styles[Shoes::Border][:strokewidth] = 1
15
+ Shoes::Drawable.drawable_default_styles[Shoes::Border][:curve] = 0
16
+
17
+ opt_init_args :stroke, :strokewidth, :curve
18
+ def initialize(*args, **kwargs)
19
+ super
20
+ @draw_context = Shoes::App.instance.current_draw_context
21
+
22
+ create_display_drawable
23
+ end
24
+
25
+ private
26
+
27
+ end
28
+ end
@@ -2,10 +2,10 @@
2
2
 
3
3
  class Shoes
4
4
  class Button < Shoes::Drawable
5
- include Shoes::Log
6
5
  shoes_styles :text, :width, :height, :top, :left, :color, :padding_top, :padding_bottom, :text_color, :size, :font_size, :tooltip
7
- shoes_events :click, :hover
6
+ shoes_events :click
8
7
 
8
+ init_args :text
9
9
  # Creates a new Button object.
10
10
  #
11
11
  # @param text [String] The text displayed on the button.
@@ -32,27 +32,18 @@ class Shoes
32
32
  # )
33
33
  # }
34
34
  # end
35
- def initialize(text, width: nil, height: nil, top: nil, left: nil, color: nil, padding_top: nil, padding_bottom: nil, size: 12, text_color: nil,
36
- font_size: nil, tooltip: nil, &block)
37
-
38
- log_init("Button")
39
-
35
+ def initialize(*args, **kwargs, &block)
40
36
  # Properties passed as positional args, not keywords, don't get auto-set
41
- @text = text
42
37
  @block = block
43
38
 
44
39
  super
45
40
 
46
- # Bind to a handler named "click"
41
+ # Bind block to a handler named "click"
47
42
  bind_self_event("click") do
48
43
  @log.debug("Button clicked, calling handler") if @block
49
44
  @block&.call
50
45
  end
51
46
 
52
- bind_self_event("hover") do
53
- @hover&.call
54
- end
55
-
56
47
  create_display_drawable
57
48
  end
58
49
 
@@ -62,12 +53,5 @@ class Shoes
62
53
  def click(&block)
63
54
  @block = block
64
55
  end
65
-
66
- # Set the hover handler
67
- #
68
- # @yield A block to be called when the cursor moves to be over the button.
69
- def hover(&block)
70
- @hover = block
71
- end
72
56
  end
73
57
  end
@@ -5,17 +5,21 @@ class Shoes
5
5
  shoes_styles :checked
6
6
  shoes_events :click
7
7
 
8
- def initialize(checked = nil, &block)
8
+ init_args
9
+ opt_init_args :checked
10
+ def initialize(*args, **kwargs, &block)
9
11
  @block = block
10
12
  super
11
13
 
12
- bind_self_event("click") { click }
14
+ bind_self_event("click") do
15
+ self.checked = !checked?
16
+ @block.call(self) if @block
17
+ end
13
18
  create_display_drawable
14
19
  end
15
20
 
16
21
  def click(&block)
17
22
  @block = block
18
- self.checked = !checked?
19
23
  end
20
24
 
21
25
  def checked?
@@ -4,11 +4,11 @@ class Shoes
4
4
  class DocumentRoot < Shoes::Flow
5
5
  shoes_events # No DocumentRoot-specific events yet
6
6
 
7
- def initialize
8
- @height = "100%"
9
- @width = @margin = @padding = nil
10
- @options = {}
7
+ Shoes::Drawable.drawable_default_styles[Shoes::DocumentRoot][:height] = "100%"
8
+ Shoes::Drawable.drawable_default_styles[Shoes::DocumentRoot][:width] = "100%"
11
9
 
10
+ init_args
11
+ def initialize(**kwargs, &block)
12
12
  super
13
13
  end
14
14