Class: Shoes::App
- 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 =
These are the allowed values for custom_event_loop events.
- displaylib means the display library is not going to return from running the app
- return means the display library will return and the loop will be handled outside Lacci's control
- wait means Lacci should busy-wait and send eternal heartbeats from the "run" event
If the display service grabs control and keeps it, Webview-style, that means "displaylib" should be the value. A Scarpe-Wasm-style "return" is appropriate if the code can finish without Ruby ending the process at the end of the source file. A "wait" can prevent Ruby from finishing early, but also prevents multiple applications. Only "return" will normally allow multiple Shoes applications.
%w[displaylib return wait]
Constants included from Log
Log::DEFAULT_COMPONENT, Log::DEFAULT_DEBUG_LOG_CONFIG, Log::DEFAULT_LOG_CONFIG
Constants inherited from Drawable
Class Attribute Summary collapse
-
.set_test_code ⇒ Object
Returns the value of attribute set_test_code.
Instance Attribute Summary collapse
-
#dir ⇒ Object
readonly
The application directory for this app.
-
#document_root ⇒ Object
readonly
The Shoes root of the drawable tree.
-
#features ⇒ Object
readonly
This is defined to avoid the linkable-id check in the Shoes-style method_missing def'n.
Attributes inherited from Drawable
#debug_id, #destroyed, #parent
Attributes inherited from Linkable
Class Method Summary collapse
-
.find_drawables_by(*specs) ⇒ Object
We can add various ways to find drawables here.
Instance Method Summary collapse
- #all_drawables ⇒ Object
-
#background ⇒ Object
This is going to go away.
-
#border ⇒ Object
This is going to go away.
-
#current_draw_context ⇒ Hash
Get the current draw context for the current slot.
- #current_slot ⇒ Object
- #destroy(send_event: true) ⇒ Object
-
#find_drawables_by(*specs) ⇒ Object
We can add various ways to find drawables here.
- #init ⇒ Object
-
#initialize(title: 'Shoes!', width: 480, height: 420, resizable: true, features: [], &app_code_body) ⇒ App
constructor
A new instance of App.
- #line_to(x, y) ⇒ Object
-
#method_missing(name, *args, **kwargs, &block) ⇒ Object
We use method_missing for drawable-creating methods like "button".
-
#move_to(x, y) ⇒ Object
Shape DSL methods.
- #page(name, &block) ⇒ Object
- #pop_slot ⇒ Object
-
#push_slot(slot) ⇒ Object
"Container" drawables like flows, stacks, masks and the document root are considered "slots" in Shoes parlance.
-
#run ⇒ Object
This usually doesn't return.
- #url(path, method_name) ⇒ Object
- #visit(name_or_path) ⇒ Object
- #with_slot(slot_item, &block) ⇒ Object
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, use_current_app, validate_as, with_current_app
Methods included from MarginHelper
Methods included from Colors
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.
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 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'lacci/lib/shoes/app.rb', line 37 def initialize( title: 'Shoes!', width: 480, height: 420, resizable: true, features: [], &app_code_body ) log_init('Shoes::App') if Shoes::FEATURES.include?(:multi_app) || Shoes.APPS.empty? Shoes.APPS.push self else @log.error('Trying to create a second Shoes::App in the same process! Fail!') raise Shoes::Errors::TooManyInstancesError, 'Cannot create multiple Shoes::App objects!' 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 = [] @content_container = nil @routes = {} super # This creates the DocumentRoot, including its corresponding display drawable Drawable.with_current_app(self) do @document_root = Shoes::DocumentRoot.new end # Now create the App display drawable create_display_drawable # Set up testing *after* Display Service basic objects exist if ENV['SHOES_SPEC_TEST'] && !Shoes::App.set_test_code test_code = File.read ENV['SHOES_SPEC_TEST'] unless test_code.empty? Shoes::App.set_test_code = true 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 destroy(send_event: false) end @watch_for_event_loop = bind_shoes_event(event_name: 'custom_event_loop') do |loop_type| unless CUSTOM_EVENT_LOOP_TYPES.include?(loop_type) raise(Shoes::Errors::InvalidAttributeValueError, "Unknown event loop type: #{loop_type.inspect}!") end @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.
162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lacci/lib/shoes/app.rb', line 162 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| Drawable.with_current_app(self) do klass.new(*args, **kwargs, &block) end end send(name, *args, **kwargs, &block) end |
Class Attribute Details
.set_test_code ⇒ Object
Returns the value of attribute set_test_code.
33 34 35 |
# File 'lacci/lib/shoes/app.rb', line 33 def set_test_code @set_test_code end |
Instance Attribute Details
#dir ⇒ Object (readonly)
The application directory for this app. Often this will be the directory containing the launched application file.
12 13 14 |
# File 'lacci/lib/shoes/app.rb', line 12 def dir @dir end |
#document_root ⇒ Object (readonly)
The Shoes root of the drawable tree
8 9 10 |
# File 'lacci/lib/shoes/app.rb', line 8 def document_root @document_root end |
#features ⇒ Object (readonly)
This is defined to avoid the linkable-id check in the Shoes-style method_missing def'n
17 18 19 |
# File 'lacci/lib/shoes/app.rb', line 17 def features @features end |
Class Method Details
.find_drawables_by(*specs) ⇒ Object
We can add various ways to find drawables here. These are sort of like Shoes selectors, used for testing. This method finds a drawable across all active Shoes apps.
232 233 234 235 236 |
# File 'lacci/lib/shoes/app.rb', line 232 def self.find_drawables_by(*specs) Shoes.APPS.flat_map do |app| app.find_drawables_by(*specs) end end |
Instance Method Details
#all_drawables ⇒ Object
217 218 219 220 221 222 223 224 225 226 227 |
# File 'lacci/lib/shoes/app.rb', line 217 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 |
#background ⇒ Object
This is going to go away. See issue #496
355 356 357 |
# File 'lacci/lib/shoes/app.rb', line 355 def background(...) current_slot.background(...) end |
#border ⇒ Object
This is going to go away. See issue #498
360 361 362 |
# File 'lacci/lib/shoes/app.rb', line 360 def border(...) current_slot.border(...) end |
#current_draw_context ⇒ Hash
Get the current draw context for the current slot
178 179 180 |
# File 'lacci/lib/shoes/app.rb', line 178 def current_draw_context current_slot&.current_draw_context end |
#current_slot ⇒ Object
145 146 147 |
# File 'lacci/lib/shoes/app.rb', line 145 def current_slot @slots[-1] end |
#destroy(send_event: true) ⇒ Object
212 213 214 215 |
# File 'lacci/lib/shoes/app.rb', line 212 def destroy(send_event: true) @do_shutdown = true send_shoes_event(event_name: 'destroy') if send_event 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.
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 |
# File 'lacci/lib/shoes/app.rb', line 240 def find_drawables_by(*specs) drawables = all_drawables specs.each do |spec| if spec == Shoes::App drawables = [@app] 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}" drawables = [] end when '@' if @app.instance_variables.include?(spec.to_sym) drawables &= [@app.instance_variable_get(spec)] else # raise Shoes::Errors::InvalidAttributeValueError, "Can't find top-level instance variable: #{spec.inspect}!" drawables = [] end else unless s.start_with?('id:') raise Shoes::Errors::InvalidAttributeValueError, "Don't know how to find drawables by #{spec.inspect}!" end find_id = Integer(s[3..-1]) drawable = Shoes::Drawable.drawable_by_id(find_id) drawables &= [drawable] end else raise(Shoes::Errors::InvalidAttributeValueError, "Don't know how to find drawables by #{spec.inspect}!") end end drawables end |
#init ⇒ Object
124 125 126 127 128 129 130 |
# File 'lacci/lib/shoes/app.rb', line 124 def init send_shoes_event(event_name: 'init') return if @do_shutdown with_slot(@document_root, &@app_code_body) render_index_if_defined_on_first_boot end |
#line_to(x, y) ⇒ Object
384 385 386 387 388 389 390 391 392 393 |
# File 'lacci/lib/shoes/app.rb', line 384 def line_to(x, y) unless x.is_a?(Numeric) && y.is_a?(Numeric) raise(Shoes::Errors::InvalidAttributeValueError, 'Pass only Numeric arguments to line_to!') end return unless current_slot.is_a?(::Shoes::Shape) current_slot.add_shape_command(['line_to', x, y]) end |
#move_to(x, y) ⇒ Object
Shape DSL methods
373 374 375 376 377 378 379 380 381 382 |
# File 'lacci/lib/shoes/app.rb', line 373 def move_to(x, y) unless x.is_a?(Numeric) && y.is_a?(Numeric) raise(Shoes::Errors::InvalidAttributeValueError, 'Pass only Numeric arguments to move_to!') end return unless current_slot.is_a?(::Shoes::Shape) current_slot.add_shape_command(['move_to', x, y]) end |
#page(name, &block) ⇒ Object
283 284 285 286 287 288 289 290 |
# File 'lacci/lib/shoes/app.rb', line 283 def page(name, &block) @pages ||= {} @pages[name] = proc do stack(width: 1.0, height: 1.0) do instance_eval(&block) end end end |
#pop_slot ⇒ Object
139 140 141 142 143 |
# File 'lacci/lib/shoes/app.rb', line 139 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.
135 136 137 |
# File 'lacci/lib/shoes/app.rb', line 135 def push_slot(slot) @slots.push(slot) end |
#run ⇒ Object
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.
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lacci/lib/shoes/app.rb', line 186 def run if @do_shutdown warn '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. Shoes::DisplayService.dispatch_event('heartbeat', nil) until @do_shutdown 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 |
#url(path, method_name) ⇒ Object
329 330 331 332 333 334 335 336 337 |
# File 'lacci/lib/shoes/app.rb', line 329 def url(path, method_name) if path.is_a?(String) && path.include?('(') # Convert string patterns to regex regex = Regexp.new("^#{path.gsub(/\(.*?\)/, '(.*?)')}$") @routes[regex] = method_name else @routes[path] = method_name end end |
#visit(name_or_path) ⇒ Object
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 |
# File 'lacci/lib/shoes/app.rb', line 292 def visit(name_or_path) # First, check for exact page match (symbol) if @pages && @pages[name_or_path] @document_root.clear do instance_eval(&@pages[name_or_path]) end return end # Second, check URL routes route, method_name = @routes.find { |r, _| r === name_or_path } if route @document_root.clear do if route.is_a?(Regexp) match_data = route.match(name_or_path) send(method_name, *match_data.captures) else send(method_name) end end return end # Third, if it's a string path like "/page2", try matching page :page2 if name_or_path.is_a?(String) && name_or_path.start_with?("/") page_name = name_or_path[1..-1].to_sym # "/page2" -> :page2 if @pages && @pages[page_name] @document_root.clear do instance_eval(&@pages[page_name]) end return end end puts "Error: URL '#{name_or_path}' not found" end |
#with_slot(slot_item, &block) ⇒ Object
149 150 151 152 153 154 155 156 |
# File 'lacci/lib/shoes/app.rb', line 149 def with_slot(slot_item, &block) return unless block_given? push_slot(slot_item) instance_eval(&block) ensure pop_slot end |