Class: Shoes::App

Inherits:
Drawable show all
Includes:
Log
Defined in:
lacci/lib/shoes/app.rb,
lacci/lib/shoes/app.rb

Overview

These methods will need to be defined on Slots too, but probably need a rework in general.

Constant Summary collapse

CUSTOM_EVENT_LOOP_TYPES =
["displaylib", "return", "wait"]

Constants included from Log

Log::DEFAULT_COMPONENT, Log::DEFAULT_DEBUG_LOG_CONFIG, Log::DEFAULT_LOG_CONFIG

Constants inherited from Drawable

Drawable::DRAW_CONTEXT_STYLES

Class Attribute Summary collapse

Instance Attribute Summary collapse

Attributes inherited from Drawable

#debug_id, #destroyed, #parent

Attributes inherited from Linkable

#linkable_id

Instance Method Summary collapse

Methods included from Log

configure_logger, #log_init, logger

Methods inherited from Drawable

allocate_drawable_id, #app, #banner, #caption, convert_to_float, convert_to_integer, #download, drawable_by_id, drawable_class_by_name, dsl_name, #event, expects_parent?, feature_for_shoes_style, get_shoes_events, #hide, #hover, init_args, #inscription, #inspect, is_widget_class?, #leave, #motion, opt_init_args, optional_init_args, register_drawable_id, registered_shoes_events?, required_init_args, #respond_to_missing?, #set_parent, shoes_events, shoes_style, shoes_style_hashes, shoes_style_name?, shoes_style_names, #shoes_style_values, shoes_styles, #show, #style, #subtitle, #tagline, #title, #toggle, unregister_drawable_id, validate_as

Methods included from MarginHelper

#margin_parse

Methods included from Colors

#gray, #rgb, #to_rgb

Methods inherited from Linkable

#bind_shoes_event, #send_self_event, #send_shoes_event, #unsub_all_shoes_events, #unsub_shoes_event

Constructor Details

#initialize(title: "Shoes!", width: 480, height: 420, resizable: true, features: [], &app_code_body) ⇒ App

Returns a new instance of App.



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lacci/lib/shoes/app.rb', line 28

def initialize(
  title: "Shoes!",
  width: 480,
  height: 420,
  resizable: true,
  features: [],
  &app_code_body
)
  log_init("Shoes::App")

  if Shoes::App.instance
    @log.error("Trying to create a second Shoes::App in the same process! Fail!")
    raise Shoes::Errors::TooManyInstancesError, "Cannot create multiple Shoes::App objects!"
  else
    Shoes::App.instance = self
  end

  # We cd to the app's containing dir when running the app
  @dir = Dir.pwd

  @do_shutdown = false
  @event_loop_type = "displaylib" # the default

  @features = features

  unknown_ext = features - Shoes::FEATURES - Shoes::EXTENSIONS
  unsupported_features = unknown_ext & Shoes::KNOWN_FEATURES
  unless unsupported_features.empty?
    @log.error("Shoes app requires feature(s) not supported by this display service: #{unsupported_features.inspect}!")
    raise Shoes::Errors::UnsupportedFeatureError, "Shoes app needs features: #{unsupported_features.inspect}"
  end
  unless unknown_ext.empty?
    @log.warn("Shoes app requested unknown features #{unknown_ext.inspect}! Known: #{(Shoes::FEATURES + Shoes::EXTENSIONS).inspect}")
  end

  @slots = []

  super

  # This creates the DocumentRoot, including its corresponding display drawable
  @document_root = Shoes::DocumentRoot.new

  # Now create the App display drawable
  create_display_drawable

  # Set up testing *after* Display Service basic objects exist

  if ENV["SHOES_SPEC_TEST"]
    test_code = File.read ENV["SHOES_SPEC_TEST"]
    unless test_code.empty?
      Shoes::Spec.instance.run_shoes_spec_test_code test_code
    end
  end

  @app_code_body = app_code_body

  # Try to de-dup as much as possible and not send repeat or multiple
  # destroy events
  @watch_for_destroy = bind_shoes_event(event_name: "destroy") do
    Shoes::DisplayService.unsub_from_events(@watch_for_destroy) if @watch_for_destroy
    @watch_for_destroy = nil
    self.destroy(send_event: false)
  end

  @watch_for_event_loop = bind_shoes_event(event_name: "custom_event_loop") do |loop_type|
    raise(Shoes::Errors::InvalidAttributeValueError, "Unknown event loop type: #{loop_type.inspect}!") unless CUSTOM_EVENT_LOOP_TYPES.include?(loop_type)

    @event_loop_type = loop_type
  end

  Signal.trap("INT") do
    @log.warn("App interrupted by signal, stopping...")
    puts "\nStopping Shoes app..."
    destroy
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args, **kwargs, &block) ⇒ Object

We use method_missing for drawable-creating methods like "button". The parent's method_missing will auto-create Shoes style getters and setters. This is similar to the method_missing in Shoes::Slot, but different in where the new drawable appears.



142
143
144
145
146
147
148
149
150
151
# File 'lacci/lib/shoes/app.rb', line 142

def method_missing(name, *args, **kwargs, &block)
  klass = ::Shoes::Drawable.drawable_class_by_name(name)
  return super unless klass

  ::Shoes::App.define_method(name) do |*args, **kwargs, &block|
    klass.new(*args, **kwargs, &block)
  end

  send(name, *args, **kwargs, &block)
end

Class Attribute Details

.instanceObject

Returns the value of attribute instance.



8
9
10
# File 'lacci/lib/shoes/app.rb', line 8

def instance
  @instance
end

Instance Attribute Details

#dirObject (readonly)

The application directory for this app. Often this will be the directory containing the launched application file.



16
17
18
# File 'lacci/lib/shoes/app.rb', line 16

def dir
  @dir
end

#document_rootObject (readonly)

The Shoes root of the drawable tree



12
13
14
# File 'lacci/lib/shoes/app.rb', line 12

def document_root
  @document_root
end

Instance Method Details

#all_drawablesObject



196
197
198
199
200
201
202
203
204
205
206
# File 'lacci/lib/shoes/app.rb', line 196

def all_drawables
  out = []

  to_add = [@document_root, @document_root.children]
  until to_add.empty?
    out.concat(to_add)
    to_add = to_add.flat_map { |w| w.respond_to?(:children) ? w.children : [] }.compact
  end

  out
end

#backgroundObject

This is going to go away. See issue #496



266
267
268
# File 'lacci/lib/shoes/app.rb', line 266

def background(...)
  current_slot.background(...)
end

#borderObject

This is going to go away. See issue #498



271
272
273
# File 'lacci/lib/shoes/app.rb', line 271

def border(...)
  current_slot.border(...)
end

#current_draw_contextHash

Get the current draw context for the current slot

Returns:

  • (Hash)

    a hash of Shoes styles for the current draw context



156
157
158
# File 'lacci/lib/shoes/app.rb', line 156

def current_draw_context
  current_slot&.current_draw_context
end

#current_slotObject



125
126
127
# File 'lacci/lib/shoes/app.rb', line 125

def current_slot
  @slots[-1]
end

#destroy(send_event: true) ⇒ Object



191
192
193
194
# File 'lacci/lib/shoes/app.rb', line 191

def destroy(send_event: true)
  @do_shutdown = true
  send_shoes_event(event_name: "destroy") if send_event
end

#featuresObject

This is defined to avoid the linkable-id check in the Shoes-style method_missing def'n



21
22
23
# File 'lacci/lib/shoes/app.rb', line 21

def features
  @features
end

#find_drawables_by(*specs) ⇒ Object

We can add various ways to find drawables here. These are sort of like Shoes selectors, used for testing.



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lacci/lib/shoes/app.rb', line 210

def find_drawables_by(*specs)
  drawables = all_drawables
  specs.each do |spec|
    if spec == Shoes::App
      drawables = [Shoes::App.instance]
    elsif spec.is_a?(Class)
      drawables.select! { |w| spec === w }
    elsif spec.is_a?(Symbol) || spec.is_a?(String)
      s = spec.to_s
      case s[0]
      when "$"
        begin
          # I'm not finding a global_variable_get or similar...
          global_value = eval s
          drawables &= [global_value]
        rescue
          raise Shoes::Errors::InvalidAttributeValueError, "Error getting global variable: #{spec.inspect}"
        end
      when "@"
        if Shoes::App.instance.instance_variables.include?(spec.to_sym)
          drawables &= [self.instance_variable_get(spec)]
        else
          raise Shoes::Errors::InvalidAttributeValueError, "Can't find top-level instance variable: #{spec.inspect}!"
        end
      else
        if s.start_with?("id:")
          find_id = Integer(s[3..-1])
          drawable = Shoes::Drawable.drawable_by_id(find_id)
          drawables &= [drawable]
        else
          raise Shoes::Errors::InvalidAttributeValueError, "Don't know how to find drawables by #{spec.inspect}!"
        end
      end
    else
      raise(Shoes::Errors::InvalidAttributeValueError, "Don't know how to find drawables by #{spec.inspect}!")
    end
  end
  drawables
end

#initObject



105
106
107
108
109
110
# File 'lacci/lib/shoes/app.rb', line 105

def init
  send_shoes_event(event_name: "init")
  return if @do_shutdown

  ::Shoes::App.instance.with_slot(@document_root, &@app_code_body)
end

#line_to(x, y) ⇒ Object



292
293
294
295
296
297
298
# File 'lacci/lib/shoes/app.rb', line 292

def line_to(x, y)
  raise(Shoes::Errors::InvalidAttributeValueError, "Pass only Numeric arguments to line_to!") unless x.is_a?(Numeric) && y.is_a?(Numeric)

  if current_slot.is_a?(::Shoes::Shape)
    current_slot.add_shape_command(["line_to", x, y])
  end
end

#move_to(x, y) ⇒ Object

Shape DSL methods



284
285
286
287
288
289
290
# File 'lacci/lib/shoes/app.rb', line 284

def move_to(x, y)
  raise(Shoes::Errors::InvalidAttributeValueError, "Pass only Numeric arguments to move_to!") unless x.is_a?(Numeric) && y.is_a?(Numeric)

  if current_slot.is_a?(::Shoes::Shape)
    current_slot.add_shape_command(["move_to", x, y])
  end
end

#pop_slotObject



119
120
121
122
123
# File 'lacci/lib/shoes/app.rb', line 119

def pop_slot
  return if @slots.size <= 1

  @slots.pop
end

#push_slot(slot) ⇒ Object

"Container" drawables like flows, stacks, masks and the document root are considered "slots" in Shoes parlance. When a new slot is created, we push it here in order to track what drawables are found in that slot.



115
116
117
# File 'lacci/lib/shoes/app.rb', line 115

def push_slot(slot)
  @slots.push(slot)
end

#runObject

This usually doesn't return. The display service may take control of the main thread. Local Webview even stops any background threads. However, some display libraries don't want to shut down and don't want to (and/or can't) take control of the event loop.



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lacci/lib/shoes/app.rb', line 164

def run
  if @do_shutdown
    $stderr.puts "Destroy has already been signaled, but we just called Shoes::App.run!"
    return
  end

  # The display lib can send us an event to customise the event loop handling.
  # But it must do so before the "run" event returns.
  send_shoes_event(event_name: "run")

  case @event_loop_type
  when "wait"
    # Display lib wants us to busy-wait instead of it.
    until @do_shutdown
      Shoes::DisplayService.dispatch_event("heartbeat", nil)
    end
  when "displaylib"
    # If run event returned, that means we're done.
    destroy
  when "return"
    # We can just return to the main event loop. But we shouldn't call destroy.
    # Presumably some event loop *outside* our event loop is handling things.
  else
    raise Shoes::Errors::InvalidAttributeValueError, "Internal error! Incorrect event loop type: #{@event_loop_type.inspect}!"
  end
end

#with_slot(slot_item, &block) ⇒ Object



129
130
131
132
133
134
135
136
# File 'lacci/lib/shoes/app.rb', line 129

def with_slot(slot_item, &block)
  return unless block_given?

  push_slot(slot_item)
  Shoes::App.instance.instance_eval(&block)
ensure
  pop_slot
end