lacci 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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