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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e32d4245e4dc24e8098d8626ca2ad9ba7d4417b6e392d0c0d43c1b003dece24e
4
- data.tar.gz: dea4cd304311e63c498e2c679e8d61ec9b550c56c5a87d33bb4c7b499a08b875
3
+ metadata.gz: 3bf9fa7d8c25eece259eb65e02ac680c3004762fa86951597ae1721010a8af52
4
+ data.tar.gz: b997f33fc1f63480f38144f89c54af16a3f99c7eafaf960b53770ea4894b51e6
5
5
  SHA512:
6
- metadata.gz: 360b04b248cc6011c5a37ca50d284d37d1aebd42f60aa15b7eb3f8b47933faca0d5b6738ae462b3cc67bf9b56b3bd426cb375fa8640111ec00a42a01a5aeaf55
7
- data.tar.gz: 7ef67d55690c0b3ffe845edf747a86a1561dd2aced210d2a870df87f0140eb8aaaab8abbe03212da9557488de655944ecc9d54637679420dc1d8b5594143a5a6
6
+ metadata.gz: 7eb784a865dfd9add9ade9861a62cfa2192e407d1371e49cdd23eb6205c55c36f67d6a31347775ee6b3d43c6e025ea1788e6c51dedf4251a562dcb62f2cda1e5
7
+ data.tar.gz: ea5b4aef895ee41ea78699bac827f8404ebbef97f0d7e8ed93b41382076c3188538e70aa32016b5ff90bd81b05feca8effc977f5452efe30744f25a7d3fe845f
@@ -28,7 +28,6 @@ module Scarpe
28
28
  "Lacci" => [
29
29
  env_or_default("SCARPE_DISPLAY_SERVICE", "(none)"),
30
30
  env_or_default("SCARPE_LOG_CONFIG", "(default)#{Shoes::Log::DEFAULT_LOG_CONFIG.inspect}"),
31
- env_or_default("SCARPE_APP_TEST", "(none)"),
32
31
  ],
33
32
  "Ruby and Shell" => [
34
33
  ["RUBY_DESCRIPTION", RUBY_DESCRIPTION],
data/lib/lacci/version.rb CHANGED
@@ -9,5 +9,5 @@
9
9
  # mostly invisible. Instead, look at the {Shoes} module
10
10
  # to see what's in Lacci.
11
11
  module Lacci
12
- VERSION = "0.3.0"
12
+ VERSION = "0.4.0"
13
13
  end
@@ -29,7 +29,7 @@ module Niente
29
29
  # @param properties [Hash] a JSON-serialisable Hash with the drawable's Shoes styles
30
30
  # @param is_widget [Boolean] whether the class is a user-defined Shoes::Widget subclass
31
31
  # @return [Webview::Drawable] the newly-created Webview drawable
32
- def create_display_drawable_for(drawable_class_name, drawable_id, properties, is_widget:)
32
+ def create_display_drawable_for(drawable_class_name, drawable_id, properties, parent_id:, is_widget:)
33
33
  existing = query_display_drawable_for(drawable_id, nil_ok: true)
34
34
  if existing
35
35
  @log.warn("There is already a display drawable for #{drawable_id.inspect}! Returning #{existing.class.name}.")
@@ -47,6 +47,10 @@ module Niente
47
47
  display_drawable.shoes_type = drawable_class_name
48
48
  set_drawable_pairing(drawable_id, display_drawable)
49
49
 
50
+ # Nil parent is okay for DocumentRoot and TextDrawables, so we have to specify it.
51
+ parent = DisplayService.instance.query_display_drawable_for(parent_id, nil_ok: true)
52
+ display_drawable.set_parent(parent)
53
+
50
54
  return display_drawable
51
55
  end
52
56
 
@@ -12,6 +12,7 @@ module Niente
12
12
 
13
13
  super(linkable_id: @shoes_linkable_id)
14
14
 
15
+ # This should only be used for reparenting after a drawable was initially created.
15
16
  bind_shoes_event(event_name: "parent", target: shoes_linkable_id) do |new_parent_id|
16
17
  display_parent = DisplayService.instance.query_display_drawable_for(new_parent_id)
17
18
  if @parent != display_parent
@@ -28,6 +29,7 @@ module Niente
28
29
  end
29
30
 
30
31
  bind_shoes_event(event_name: "destroy", target: shoes_linkable_id) do
32
+ set_parent(nil)
31
33
  end
32
34
  end
33
35
 
@@ -30,7 +30,6 @@ class Niente::Test
30
30
  Object.const_set(Scarpe::Components::StringHelpers.camelize(class_name), test_class)
31
31
  test_name = "test_" + test_name unless test_name.start_with?("test_")
32
32
  test_class.define_method(test_name) do
33
- STDERR.puts "Started running #{class_name.inspect}::#{test_name.inspect}"
34
33
  eval(code)
35
34
  end
36
35
  end
@@ -50,6 +49,13 @@ class Niente::ShoesSpecTest < Minitest::Test
50
49
  Niente::ShoesSpecProxy.new(drawables[0])
51
50
  end
52
51
  end
52
+
53
+ def drawable(*specs)
54
+ drawables = Shoes::App.instance.find_drawables_by(*specs)
55
+ raise Shoes::Errors::MultipleDrawablesFoundError, "Found more than one #{finder_name} matching #{args.inspect}!" if drawables.size > 1
56
+ raise Shoes::Errors::NoDrawablesFoundError, "Found no #{finder_name} matching #{args.inspect}!" if drawables.empty?
57
+ Niente::ShoesSpecProxy.new(drawables[0])
58
+ end
53
59
  end
54
60
 
55
61
  class Niente::ShoesSpecProxy
data/lib/scarpe/niente.rb CHANGED
@@ -6,8 +6,16 @@
6
6
  # how a real display service should act.
7
7
  module Niente; end
8
8
 
9
- require_relative "niente/logger"
10
- Shoes::Log.instance = Niente::LogImpl.new
9
+ require "scarpe/components/print_logger"
10
+ Shoes::Log.instance = Scarpe::Components::PrintLogImpl.new
11
+ if ENV["NIENTE_LOG_LEVEL"]
12
+ pl = Scarpe::Components::PrintLogImpl::PrintLogger
13
+ level = ENV["NIENTE_LOG_LEVEL"].strip.downcase.to_sym
14
+ unless pl::LEVELS.key?(level)
15
+ raise "Unrecognized Niente log level: #{level.inspect}!"
16
+ end
17
+ pl.min_level = pl::LEVELS[level]
18
+ end
11
19
 
12
20
  require_relative "niente/drawable"
13
21
  require_relative "niente/app"
@@ -16,5 +24,9 @@ require_relative "niente/display_service"
16
24
  require_relative "niente/shoes_spec"
17
25
  Shoes::Spec.instance = Niente::Test
18
26
 
27
+ require "scarpe/components/segmented_file_loader"
28
+ loader = Scarpe::Components::SegmentedFileLoader.new
29
+ Shoes.add_file_loader loader
30
+
19
31
  Shoes::DisplayService.set_display_service_class(Niente::DisplayService)
20
32
 
data/lib/shoes/app.rb CHANGED
@@ -8,17 +8,29 @@ class Shoes
8
8
  attr_accessor :instance
9
9
  end
10
10
 
11
+ # The Shoes root of the drawable tree
11
12
  attr_reader :document_root
12
13
 
13
- shoes_styles :title, :width, :height, :resizable
14
+ # The application directory for this app. Often this will be the directory
15
+ # containing the launched application file.
16
+ attr_reader :dir
17
+
18
+ shoes_styles :title, :width, :height, :resizable, :features
19
+
20
+ # This is defined to avoid the linkable-id check in the Shoes-style method_missing def'n
21
+ def features
22
+ @features
23
+ end
14
24
 
15
25
  CUSTOM_EVENT_LOOP_TYPES = ["displaylib", "return", "wait"]
16
26
 
27
+ init_args
17
28
  def initialize(
18
29
  title: "Shoes!",
19
30
  width: 480,
20
31
  height: 420,
21
32
  resizable: true,
33
+ features: [],
22
34
  &app_code_body
23
35
  )
24
36
  log_init("Shoes::App")
@@ -30,36 +42,35 @@ class Shoes
30
42
  Shoes::App.instance = self
31
43
  end
32
44
 
45
+ # We cd to the app's containing dir when running the app
46
+ @dir = Dir.pwd
47
+
33
48
  @do_shutdown = false
34
49
  @event_loop_type = "displaylib" # the default
35
50
 
36
- super
51
+ @features = features
52
+
53
+ unknown_ext = features - Shoes::FEATURES - Shoes::EXTENSIONS
54
+ unsupported_features = unknown_ext & Shoes::KNOWN_FEATURES
55
+ unless unsupported_features.empty?
56
+ @log.error("Shoes app requires feature(s) not supported by this display service: #{unsupported_features.inspect}!")
57
+ raise Shoes::Errors::UnsupportedFeatureError, "Shoes app needs features: #{unsupported_features.inspect}"
58
+ end
59
+ unless unknown_ext.empty?
60
+ @log.warn("Shoes app requested unknown features #{unknown_ext.inspect}! Known: #{(Shoes::FEATURES + Shoes::EXTENSIONS).inspect}")
61
+ end
62
+
63
+ @slots = []
37
64
 
38
- # The draw context tracks current settings like fill and stroke,
39
- # plus potentially other current state that changes from drawable
40
- # to drawable and slot to slot.
41
- @draw_context = {
42
- "fill" => "",
43
- "stroke" => "",
44
- "rotate" => 0,
45
- }
65
+ super
46
66
 
47
67
  # This creates the DocumentRoot, including its corresponding display drawable
48
68
  @document_root = Shoes::DocumentRoot.new
49
69
 
50
- @slots = []
51
-
52
70
  # Now create the App display drawable
53
71
  create_display_drawable
54
72
 
55
- # Set up testing events *after* Display Service basic objects exist
56
- if ENV["SCARPE_APP_TEST"]
57
- test_code = File.read ENV["SCARPE_APP_TEST"]
58
- if test_code != ""
59
- @test_obj = Object.new
60
- @test_obj.instance_eval test_code
61
- end
62
- end
73
+ # Set up testing *after* Display Service basic objects exist
63
74
 
64
75
  if ENV["SHOES_SPEC_TEST"]
65
76
  test_code = File.read ENV["SHOES_SPEC_TEST"]
@@ -133,22 +144,17 @@ class Shoes
133
144
  return super unless klass
134
145
 
135
146
  ::Shoes::App.define_method(name) do |*args, **kwargs, &block|
136
- # Look up the Shoes drawable and create it...
137
- drawable_instance = klass.new(*args, **kwargs, &block)
138
-
139
- unless klass.ancestors.include?(::Shoes::TextDrawable)
140
- # Create this drawable in the current app slot
141
- drawable_instance.set_parent ::Shoes::App.instance.current_slot
142
- end
143
-
144
- drawable_instance
147
+ klass.new(*args, **kwargs, &block)
145
148
  end
146
149
 
147
150
  send(name, *args, **kwargs, &block)
148
151
  end
149
152
 
153
+ # Get the current draw context for the current slot
154
+ #
155
+ # @return [Hash] a hash of Shoes styles for the current draw context
150
156
  def current_draw_context
151
- @draw_context.dup
157
+ current_slot&.current_draw_context
152
158
  end
153
159
 
154
160
  # This usually doesn't return. The display service may take control
@@ -190,7 +196,7 @@ class Shoes
190
196
  def all_drawables
191
197
  out = []
192
198
 
193
- to_add = @document_root.children
199
+ to_add = [@document_root, @document_root.children]
194
200
  until to_add.empty?
195
201
  out.concat(to_add)
196
202
  to_add = to_add.flat_map { |w| w.respond_to?(:children) ? w.children : [] }.compact
@@ -256,30 +262,21 @@ end
256
262
 
257
263
  # These methods will need to be defined on Slots too, but probably need a rework in general.
258
264
  class Shoes::App < Shoes::Drawable
265
+ # This is going to go away. See issue #496
259
266
  def background(...)
260
267
  current_slot.background(...)
261
268
  end
262
269
 
270
+ # This is going to go away. See issue #498
263
271
  def border(...)
264
272
  current_slot.border(...)
265
273
  end
266
274
 
267
- # Draw context methods
268
-
269
- def fill(color)
270
- @draw_context["fill"] = color
271
- end
272
-
273
- def nofill
274
- @draw_context["fill"] = ""
275
- end
276
-
277
- def stroke(color)
278
- @draw_context["stroke"] = color
279
- end
280
-
281
- def nostroke
282
- @draw_context["stroke"] = ""
275
+ # Draw Context methods -- forward to the current slot
276
+ [:fill, :nofill, :stroke, :strokewidth, :nostroke, :rotate].each do |dc_method|
277
+ define_method(dc_method) do |*args|
278
+ current_slot.send(dc_method, *args)
279
+ end
283
280
  end
284
281
 
285
282
  # Shape DSL methods
@@ -300,9 +297,6 @@ class Shoes::App < Shoes::Drawable
300
297
  end
301
298
  end
302
299
 
303
- def rotate(angle)
304
- @draw_context["rotate"] = angle
305
- end
306
300
  # Not implemented yet: curve_to, arc_to
307
301
 
308
302
  alias_method :info, :puts
@@ -13,10 +13,10 @@ class Shoes
13
13
  [ENV["LOCALAPPDATA"], "Shoes"],
14
14
  [ENV["APPDATA"], "Shoes"],
15
15
  [ENV["HOME"], ".shoes"],
16
- [Dir.tmpdir, "shoes"],
17
16
  ]
18
17
 
19
18
  top, file = homes.detect { |home_top, _| home_top && File.exist?(home_top) }
19
+ return nil if top.nil?
20
20
  File.join(top, file)
21
21
  end
22
22
 
@@ -32,8 +32,29 @@ class Shoes
32
32
  HALF_PI = 1.57079632679489661923
33
33
  PI = 3.14159265358979323846
34
34
 
35
- # This should be set up by the Display Service when it loads
35
+ # These should be set up by the Display Service when it loads. They are intentionally
36
+ # *not* frozen so that the Display Service can add to them (and then optionally
37
+ # freeze them.)
38
+
39
+ # Fonts currently loaded and available
36
40
  FONTS = []
41
+
42
+ # Standard features available in this display service - see KNOWN_FEATURES.
43
+ # These may or may not require the Shoes.app requesting them per-app.
44
+ FEATURES = []
45
+
46
+ # Nonstandard extensions, e.g. Scarpe extensions, supported by this display lib.
47
+ # An application may have to request the extensions for them to be available so
48
+ # that a casual reader can see Shoes.app(features: :scarpe) and realize why
49
+ # there are nonstandard styles or drawables.
50
+ EXTENSIONS = []
51
+
52
+ # These are all known features supported by this version of Lacci.
53
+ # Features on this list are allowed to be in FEATURES. Anything else
54
+ # goes in EXTENSIONS and is nonstandard.
55
+ KNOWN_FEATURES = [
56
+ :html, # Supports .to_html on display objects, HTML classes on drawables, etc.
57
+ ].freeze
37
58
  end
38
59
 
39
60
  # Access and assign the release constants
@@ -32,7 +32,14 @@ class Shoes
32
32
  # This is in the eigenclass/metaclass, *not* instances of DisplayService
33
33
  include Shoes::Log
34
34
 
35
+ # Send a Shoes event to all subscribers.
35
36
  # An event_target may be nil, to indicate there is no target.
37
+ #
38
+ # @param event_name [String] the name of the event
39
+ # @param event_target [String] the specific target, if any
40
+ # @param args [Array] arguments to pass to the subscribing block
41
+ # @param args [Array] keyword arguments to pass to the subscribing block
42
+ # @return [void]
36
43
  def dispatch_event(event_name, event_target, *args, **kwargs)
37
44
  @@display_event_handlers ||= {}
38
45
 
@@ -69,10 +76,20 @@ class Shoes
69
76
  kwargs[:event_name] = event_name
70
77
  kwargs[:event_target] = event_target if event_target
71
78
  handlers.each { |h| h[:handler].call(*args, **kwargs) }
79
+ nil
72
80
  end
73
81
 
74
- # It's permitted to subscribe to event_name :any for all event names, and event_target :any for all targets.
75
- # An event_target of nil means "no target", and only matches events dispatched with a nil target.
82
+ # Subscribe to the given event name and target.
83
+ # It's permitted to subscribe to event_name :any for all event names,
84
+ # and event_target :any for all targets. An event_target of nil means
85
+ # "no target", and only matches events dispatched with a nil target.
86
+ # The subscription will return an unsubscribe ID, which can be used
87
+ # later to unsubscribe from the notification.
88
+ #
89
+ # @param event_name [String,Symbol] the event name to subscribe to, or :any for all event names
90
+ # @param event_target [String,Symbol,NilClass] the event target to subscribe to, or :any for all targets - nil is a valid target
91
+ # @block the block to call when the event occurs - it will receive arguments from the event-dispatch call
92
+ # @return [Integer] an unsubscription ID which can be used later to cancel the subscription
76
93
  def subscribe_to_event(event_name, event_target, &handler)
77
94
  @@display_event_handlers ||= {}
78
95
  @@display_event_unsub_id ||= 0
@@ -96,6 +113,10 @@ class Shoes
96
113
  id
97
114
  end
98
115
 
116
+ # Unsubscribe from any event subscriptions matching the unsub ID.
117
+ #
118
+ # @param unsub_id [Integer] the unsub ID returned when subscribing
119
+ # @return [void]
99
120
  def unsub_from_events(unsub_id)
100
121
  raise "Must provide an unsubscribe ID!" if unsub_id.nil?
101
122
 
@@ -106,17 +127,32 @@ class Shoes
106
127
  end
107
128
  end
108
129
 
130
+ # Reset the display service, for instance between unit tests.
131
+ # This destroys all existing subscriptions.
132
+ #
133
+ # @return [void]
109
134
  def full_reset!
110
135
  @@display_event_handlers = {}
111
136
  @json_debug_serialize = nil
112
137
  end
113
138
 
139
+ # Set the Display Service class which will handle display service functions
140
+ # for this process. This can only be set once. The display service can be
141
+ # a subclass of Shoes::DisplayService, but isn't required to be.
142
+ #
143
+ # Shoes will create an instance of this class with no arguments passed to
144
+ # initialize, and use it as the display service for the lifetime of the
145
+ # process.
146
+ #
147
+ # @param klass [Class] the class for the display service
114
148
  def set_display_service_class(klass)
115
149
  raise "Can only set a single display service class!" if @display_service_klass
116
150
 
117
151
  @display_service_klass = klass
118
152
  end
119
153
 
154
+ # Get the current display service instance. This requires a display service
155
+ # class having been set first. @see set_display_service_class
120
156
  def display_service
121
157
  return @service if @service
122
158
 
@@ -126,9 +162,13 @@ class Shoes
126
162
  end
127
163
  end
128
164
 
165
+ def initialize
166
+ @display_drawable_for = {}
167
+ end
168
+
129
169
  # These methods are an interface to DisplayService objects.
130
170
 
131
- def create_display_drawable_for(drawable_class_name, drawable_id, properties, is_widget:)
171
+ def create_display_drawable_for(drawable_class_name, drawable_id, properties, parent_id:, is_widget:)
132
172
  raise "Override in DisplayService implementation!"
133
173
  end
134
174
 
@@ -174,7 +214,6 @@ class Shoes
174
214
  def initialize(linkable_id: object_id)
175
215
  @linkable_id = linkable_id
176
216
  @subscriptions = {}
177
- @display_drawable_for ||= {}
178
217
  end
179
218
 
180
219
  def send_self_event(*args, event_name:, **kwargs)