lacci 0.3.0 → 0.5.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +0 -2
  3. data/Gemfile.lock +4 -32
  4. data/lib/lacci/scarpe_cli.rb +0 -1
  5. data/lib/lacci/version.rb +1 -1
  6. data/lib/scarpe/niente/app.rb +12 -1
  7. data/lib/scarpe/niente/display_service.rb +5 -1
  8. data/lib/scarpe/niente/drawable.rb +2 -0
  9. data/lib/scarpe/niente/shoes_spec.rb +10 -5
  10. data/lib/scarpe/niente.rb +15 -2
  11. data/lib/shoes/app.rb +204 -105
  12. data/lib/shoes/constants.rb +24 -2
  13. data/lib/shoes/display_service.rb +43 -4
  14. data/lib/shoes/drawable.rb +326 -36
  15. data/lib/shoes/drawables/arc.rb +4 -26
  16. data/lib/shoes/drawables/arrow.rb +3 -23
  17. data/lib/shoes/drawables/border.rb +28 -0
  18. data/lib/shoes/drawables/button.rb +5 -21
  19. data/lib/shoes/drawables/check.rb +7 -3
  20. data/lib/shoes/drawables/document_root.rb +4 -4
  21. data/lib/shoes/drawables/edit_box.rb +6 -5
  22. data/lib/shoes/drawables/edit_line.rb +5 -4
  23. data/lib/shoes/drawables/flow.rb +4 -6
  24. data/lib/shoes/drawables/font_helper.rb +62 -0
  25. data/lib/shoes/drawables/image.rb +2 -2
  26. data/lib/shoes/drawables/line.rb +3 -6
  27. data/lib/shoes/drawables/link.rb +16 -9
  28. data/lib/shoes/drawables/list_box.rb +8 -5
  29. data/lib/shoes/drawables/oval.rb +48 -0
  30. data/lib/shoes/drawables/para.rb +106 -18
  31. data/lib/shoes/drawables/progress.rb +2 -1
  32. data/lib/shoes/drawables/radio.rb +5 -3
  33. data/lib/shoes/drawables/rect.rb +7 -6
  34. data/lib/shoes/drawables/shape.rb +4 -3
  35. data/lib/shoes/drawables/slot.rb +102 -9
  36. data/lib/shoes/drawables/stack.rb +7 -12
  37. data/lib/shoes/drawables/star.rb +9 -31
  38. data/lib/shoes/drawables/text_drawable.rb +93 -34
  39. data/lib/shoes/drawables/video.rb +3 -2
  40. data/lib/shoes/drawables/widget.rb +9 -4
  41. data/lib/shoes/drawables.rb +2 -1
  42. data/lib/shoes/errors.rb +13 -3
  43. data/lib/shoes/margin_helper.rb +79 -0
  44. data/lib/shoes.rb +98 -20
  45. metadata +11 -15
  46. data/lib/scarpe/niente/logger.rb +0 -29
  47. data/lib/shoes/drawables/span.rb +0 -27
  48. 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)
@@ -59,7 +60,8 @@ class Shoes
59
60
 
60
61
  return value if h[:validator].nil?
61
62
 
62
- h[:validator].call(value)
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
144
204
 
145
- parent_prop_names | linkable_properties.map { |prop| prop[:name] }
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
212
+
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 ||= @app.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
@@ -155,33 +236,188 @@ class Shoes
155
236
  linkable_properties_hash[name.to_s] ||
156
237
  (self != Shoes::Drawable && superclass.shoes_style_name?(name))
157
238
  end
239
+
240
+ # Current_app is set every time a drawable is created - we don't want to keep a default
241
+ # long because it's possible for apps to alternate who is creating. So make sure it's
242
+ # not kept long, and used up when used once.
243
+
244
+ def with_current_app(app)
245
+ old_cur_app = @current_app
246
+ @current_app = app
247
+ ret = yield
248
+ @current_app = old_cur_app
249
+ ret
250
+ end
251
+
252
+ def use_current_app
253
+ cur_app = @current_app
254
+ @current_app = nil
255
+ cur_app
256
+ end
158
257
  end
159
258
 
259
+ # Every Shoes drawable has positioning properties
260
+ shoes_styles :top, :left, :width, :height
261
+
262
+ # Margins around drawable
263
+ shoes_styles :margin, :margin_top, :margin_bottom, :margin_left, :margin_right
264
+
265
+ # Padding around drawable
266
+ shoes_styles :padding, :padding_top, :padding_bottom, :padding_left, :padding_right
267
+
160
268
  # Shoes uses a "hidden" style property for hide/show
161
269
  shoes_style :hidden
162
270
 
163
271
  attr_reader :debug_id
164
272
 
273
+ # These styles can be set to a current per-slot value and inherited from parent slots.
274
+ # Their value is set at drawable-create time.
275
+ DRAW_CONTEXT_STYLES = [:fill, :stroke, :strokewidth, :rotate, :transform, :translate]
276
+
277
+ include MarginHelper
278
+
165
279
  def initialize(*args, **kwargs)
166
- log_init("Shoes::#{self.class.name}")
280
+ kwargs = margin_parse(kwargs)
281
+ log_init("Shoes::#{self.class.name}") unless @log
167
282
 
168
- default_styles = Shoes::Drawable.drawable_default_styles[self.class]
283
+ # Grab the current app, mark it as used
284
+ @app = self.is_a?(Shoes::App) ? self : Drawable.use_current_app
285
+
286
+ # First, get the list of allowed and disallowed styles for the given features
287
+ # and make sure no disallowed styles were given.
288
+
289
+ app_features = @app.features
290
+ this_app_styles = self.class.shoes_style_names(with_features: @app.features).map(&:to_sym)
291
+ not_this_app_styles = self.class.shoes_style_names(with_features: :all).map(&:to_sym) - this_app_styles
292
+
293
+ bad_styles = kwargs.keys & not_this_app_styles
294
+ unless bad_styles.empty?
295
+ features_needed = bad_styles.map { |s| self.class.feature_for_shoes_style(s) }.uniq
296
+ 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})!"
297
+ end
169
298
 
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)
299
+ # Next, check positional arguments and make sure the correct number and type
300
+ # were passed and match positional args with style names.
301
+
302
+ supplied_args = kwargs.keys
303
+
304
+ req_args = self.class.required_init_args
305
+ opt_args = self.class.optional_init_args
306
+ pos_args = req_args + opt_args
307
+ if req_args != ["any"]
308
+ if args.size > pos_args.size
309
+ raise Shoes::Errors::BadArgumentListError, "Too many arguments given for #{self.class}#initialize! #{args.inspect}"
310
+ end
311
+
312
+ if args.size == 0
313
+ # It's fine to use keyword args instead, but we should make sure they're actually there
314
+ needed_args = req_args.map(&:to_sym) - kwargs.keys
315
+ unless needed_args.empty?
316
+ raise Shoes::Errors::BadArgumentListError, "Keyword arguments for #{self.class}#initialize should also supply #{needed_args.inspect}! #{args.inspect}"
317
+ end
318
+ elsif args.size < req_args.size
319
+ raise Shoes::Errors::BadArgumentListError, "Too few arguments given for #{self.class}#initialize! #{args.inspect}"
178
320
  end
321
+
322
+ # Set each positional argument
323
+ args.each.with_index do |val, idx|
324
+ style_name = pos_args[idx]
325
+ next if style_name.nil? || style_name == "" # It's possible to have non-style positional args
326
+
327
+ val = self.class.validate_as(style_name, args[idx])
328
+ instance_variable_set("@#{style_name}", val)
329
+ supplied_args << style_name.to_sym
330
+ end
331
+ end
332
+
333
+ this_drawable_styles = self.class.shoes_style_names(with_features: @app.features).map(&:to_sym)
334
+ dc = @app.current_draw_context || {}
335
+
336
+ # Styles not passed as arguments can come from the draw context
337
+
338
+ # What styles are in the draw context, are used by this drawable, and weren't
339
+ # given as positional or keyword arguments?
340
+ draw_context_styles = (DRAW_CONTEXT_STYLES & this_drawable_styles) - supplied_args
341
+ unless draw_context_styles.empty?
342
+ draw_context_styles.each do |style|
343
+ dc_val = dc[style.to_s]
344
+ next if dc_val.nil?
345
+
346
+ val = self.class.validate_as(style, dc[style.to_s])
347
+ instance_variable_set("@#{style}", val)
348
+ supplied_args << style
349
+ end
350
+ end
351
+
352
+ # Styles that were *not* passed should be set to defaults
353
+
354
+ default_styles = Shoes::Drawable.drawable_default_styles[self.class]
355
+
356
+ # No arg specified for a property with a default value? Set it to default.
357
+ (default_styles.keys - supplied_args).each do |key|
358
+ val = self.class.validate_as(key, default_styles[key])
359
+ instance_variable_set("@#{key}", val)
360
+ end
361
+
362
+ # If we have a keyword arg for a style, set it as specified.
363
+ (this_drawable_styles & kwargs.keys).each do |key|
364
+ val = self.class.validate_as(key, kwargs[key])
365
+ instance_variable_set("@#{key}", val)
366
+ end
367
+
368
+ # We'd like to avoid unexpected keywords. But we're not disciplined enough to
369
+ # raise an error by default yet. Non-style keywords passed to Drawable#initialize
370
+ # are deprecated at this point, but I need to hunt down the last of them
371
+ # and prevent them.
372
+ unexpected = (kwargs.keys - this_drawable_styles)
373
+ unless unexpected.empty?
374
+ STDERR.puts "Unexpected non-style keyword(s) in #{self.class} initialize: #{unexpected.inspect}"
179
375
  end
180
376
 
181
377
  super(linkable_id: Shoes::Drawable.allocate_drawable_id)
182
378
  Shoes::Drawable.register_drawable_id(self.linkable_id, self)
183
379
 
184
380
  generate_debug_id
381
+
382
+ parent = @app.current_slot
383
+ if self.class.expects_parent?
384
+ set_parent(parent, notify: false)
385
+ end
386
+
387
+ unless self.class.registered_shoes_events?
388
+ # No Shoes events declared and we're creating an instance?
389
+ # Default to no class-specific events.
390
+ self.class.shoes_events
391
+ end
392
+
393
+ # Binding the motion events here isn't perfect.
394
+ # What about drawables like SubscriptionItem that
395
+ # have no motion events? With the current Lacci
396
+ # implementation, the answer is that those events
397
+ # will never be sent. Calling .hover on one will
398
+ # be useless, harmless, and allowed. If you want
399
+ # to make it disallowed, you can do something like
400
+ # define a SubscriptionItem#hover that raises an
401
+ # exception instead.
402
+
403
+ bind_self_event("hover") do
404
+ @hover&.call
405
+ end
406
+
407
+ bind_self_event("leave") do
408
+ @leave&.call
409
+ end
410
+
411
+ bind_self_event("motion") do |x, y|
412
+ @motion&.call(x, y)
413
+ end
414
+ end
415
+
416
+ def self.expects_parent?
417
+ return false if [::Shoes::App, ::Shoes::DocumentRoot].include?(self)
418
+ return false if self < ::Shoes::TextDrawable
419
+
420
+ true
185
421
  end
186
422
 
187
423
  # Calling stack.app or drawable.app will execute the block
@@ -196,8 +432,8 @@ class Shoes
196
432
  # @return [Shoes::App] the Shoes app
197
433
  # @yield the block to call with the Shoes App as self
198
434
  def app(&block)
199
- Shoes::App.instance.with_slot(self, &block) if block_given?
200
- Shoes::App.instance
435
+ @app.with_slot(self, &block) if block_given?
436
+ @app
201
437
  end
202
438
 
203
439
  private
@@ -223,13 +459,14 @@ class Shoes
223
459
  private
224
460
 
225
461
  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}!"
462
+ events = self.class.get_shoes_events
463
+ unless events.include?(event_name.to_s)
464
+ raise Shoes::Errors::UnregisteredShoesEventError, "Drawable #{self.inspect} tried to bind Shoes event #{event_name}, which is not in #{events.inspect}!"
228
465
  end
229
466
  end
230
467
 
231
468
  def bind_self_event(event_name, &block)
232
- raise(Shoes::Errors::NoLinkableIdError, "Drawable has no linkable_id! #{inspect}") unless linkable_id
469
+ raise(Shoes::Errors::NoSuchLinkableIdError, "Drawable has no linkable_id! #{inspect}") unless linkable_id
233
470
 
234
471
  validate_event_name(event_name)
235
472
 
@@ -250,8 +487,8 @@ class Shoes
250
487
  send_shoes_event(*args, **kwargs, event_name:, target: linkable_id)
251
488
  end
252
489
 
253
- def shoes_style_values
254
- all_property_names = self.class.shoes_style_names
490
+ def shoes_style_values(with_features: @app.features)
491
+ all_property_names = self.class.shoes_style_names(with_features:)
255
492
 
256
493
  properties = {}
257
494
  all_property_names.each do |prop|
@@ -292,10 +529,11 @@ class Shoes
292
529
  klass_name = self.class.name.delete_prefix("Scarpe::").delete_prefix("Shoes::")
293
530
 
294
531
  is_widget = Shoes::Drawable.is_widget_class?(klass_name)
532
+ parent_id = @parent&.linkable_id
295
533
 
296
534
  # Should we send an event so this can be discovered from someplace other than
297
535
  # the DisplayService?
298
- ::Shoes::DisplayService.display_service.create_display_drawable_for(klass_name, self.linkable_id, shoes_style_values, is_widget:)
536
+ ::Shoes::DisplayService.display_service.create_display_drawable_for(klass_name, self.linkable_id, shoes_style_values, parent_id:, is_widget:)
299
537
  end
300
538
 
301
539
  public
@@ -303,11 +541,17 @@ class Shoes
303
541
  attr_reader :parent
304
542
  attr_reader :destroyed
305
543
 
306
- def set_parent(new_parent)
544
+ # Set the Drawable's parent drawable. Notify the display service
545
+ # that the parent has changed unless instructed not to.
546
+ # We don't notify when first creating the Drawable. The create
547
+ # event that is first sent will include the parent.
548
+ def set_parent(new_parent, notify: true)
307
549
  @parent&.remove_child(self)
308
550
  new_parent&.add_child(self)
309
551
  @parent = new_parent
310
- send_shoes_event(new_parent.linkable_id, event_name: "parent", target: linkable_id)
552
+ return unless notify
553
+
554
+ send_shoes_event(new_parent&.linkable_id, event_name: "parent", target: linkable_id)
311
555
  end
312
556
 
313
557
  # Removes the element from the Shoes::Drawable tree and removes all event subscriptions
@@ -336,6 +580,28 @@ class Shoes
336
580
  self.hidden = !self.hidden
337
581
  end
338
582
 
583
+ # Set the hover handler. Not every drawable may do something useful with this.
584
+ #
585
+ # @yield A block to be called when the cursor moves to be over the drawable.
586
+ def hover(&block)
587
+ @hover = block
588
+ end
589
+
590
+ # Set the leave handler. Not every drawable may do something useful with this.
591
+ #
592
+ # @yield A block to be called when the cursor moves to be off of the drawable.
593
+ def leave(&block)
594
+ @leave = block
595
+ end
596
+
597
+ # Set the motion handler, called with x and y coordinates as params.
598
+ # Not every drawable may do something useful with this.
599
+ #
600
+ # @yield A block to be called when the cursor moves around over the drawable.
601
+ def motion(&block)
602
+ @motion = block
603
+ end
604
+
339
605
  # We use method_missing to auto-create Shoes style getters and setters.
340
606
  def method_missing(name, *args, **kwargs, &block)
341
607
  name_s = name.to_s
@@ -344,7 +610,7 @@ class Shoes
344
610
  prop_name = name_s[0..-2]
345
611
  if self.class.shoes_style_name?(prop_name)
346
612
  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
613
+ raise(Shoes::Errors::NoSuchLinkableIdError, "Trying to set Shoes styles in a #{self.class} with no linkable ID!") unless linkable_id
348
614
 
349
615
  new_value = self.class.validate_as(prop_name, new_value)
350
616
  instance_variable_set("@" + prop_name, new_value)
@@ -357,7 +623,7 @@ class Shoes
357
623
 
358
624
  if self.class.shoes_style_name?(name_s)
359
625
  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
626
+ raise(Shoes::Errors::NoSuchLinkableIdError, "Trying to get Shoes styles in an object with no linkable ID! #{inspect}") unless linkable_id
361
627
 
362
628
  instance_variable_get("@" + name_s)
363
629
  end
@@ -376,5 +642,29 @@ class Shoes
376
642
 
377
643
  super
378
644
  end
645
+
646
+ def self.convert_to_integer(value, attribute_name)
647
+ begin
648
+ value = Integer(value)
649
+ raise Shoes::Errors::InvalidAttributeValueError, "Negative number '#{value}' not allowed for attribute '#{attribute_name}'" if value < 0
650
+
651
+ value
652
+ rescue ArgumentError
653
+ error_message = "Invalid value '#{value}' provided for attribute '#{attribute_name}'. The value should be a number."
654
+ raise Shoes::Errors::InvalidAttributeValueError, error_message
655
+ end
656
+ end
657
+
658
+ def self.convert_to_float(value, attribute_name)
659
+ begin
660
+ value = Float(value)
661
+ raise Shoes::Errors::InvalidAttributeValueError, "Negative number '#{value}' not allowed for attribute '#{attribute_name}'" if value < 0
662
+
663
+ value
664
+ rescue ArgumentError
665
+ error_message = "Invalid value '#{value}' provided for attribute '#{attribute_name}'. The value should be a number."
666
+ raise Shoes::Errors::InvalidAttributeValueError, error_message
667
+ end
668
+ end
379
669
  end
380
670
  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)
17
- @draw_context = Shoes::App.instance.current_draw_context
18
-
16
+ init_args :left, :top, :width, :height, :angle1, :angle2
17
+ def initialize(*args, **kwargs)
19
18
  super
20
- self.left, self.top, self.width, self.height, self.angle1, self.angle2 = args
21
-
22
- create_display_drawable
23
- end
24
19
 
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
20
+ @draw_context = @app.current_draw_context
29
21
 
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
22
+ create_display_drawable
35
23
  end
36
24
 
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
25
 
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)
13
- @draw_context = Shoes::App.instance.current_draw_context
14
-
12
+ init_args :left, :top, :width
13
+ def initialize(*args, **kwargs)
15
14
  super
16
15
 
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
16
+ @draw_context = @app.current_draw_context
25
17
 
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 = @app.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,25 +32,16 @@ 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
- @block&.call
50
- end
51
-
52
- bind_self_event("hover") do
53
- @hover&.call
44
+ @block&.call if @block
54
45
  end
55
46
 
56
47
  create_display_drawable
@@ -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