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
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)