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.
["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.
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
-
#features ⇒ Object
This is defined to avoid the linkable-id check in the Shoes-style method_missing def'n.
-
#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.
- #visit(name) ⇒ 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.
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 |
# File 'lacci/lib/shoes/app.rb', line 39 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 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 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.
158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lacci/lib/shoes/app.rb', line 158 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.
35 36 37 |
# File 'lacci/lib/shoes/app.rb', line 35 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 |
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.
229 230 231 232 233 |
# File 'lacci/lib/shoes/app.rb', line 229 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
214 215 216 217 218 219 220 221 222 223 224 |
# File 'lacci/lib/shoes/app.rb', line 214 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
314 315 316 |
# File 'lacci/lib/shoes/app.rb', line 314 def background(...) current_slot.background(...) end |
#border ⇒ Object
This is going to go away. See issue #498
319 320 321 |
# File 'lacci/lib/shoes/app.rb', line 319 def border(...) current_slot.border(...) end |
#current_draw_context ⇒ Hash
Get the current draw context for the current slot
174 175 176 |
# File 'lacci/lib/shoes/app.rb', line 174 def current_draw_context current_slot&.current_draw_context end |
#current_slot ⇒ Object
141 142 143 |
# File 'lacci/lib/shoes/app.rb', line 141 def current_slot @slots[-1] end |
#destroy(send_event: true) ⇒ Object
209 210 211 212 |
# File 'lacci/lib/shoes/app.rb', line 209 def destroy(send_event: true) @do_shutdown = true send_shoes_event(event_name: "destroy") if send_event end |
#features ⇒ Object
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 |
#find_drawables_by(*specs) ⇒ Object
We can add various ways to find drawables here. These are sort of like Shoes selectors, used for testing.
237 238 239 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 |
# File 'lacci/lib/shoes/app.rb', line 237 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 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 |
#init ⇒ Object
121 122 123 124 125 126 |
# File 'lacci/lib/shoes/app.rb', line 121 def init send_shoes_event(event_name: "init") return if @do_shutdown with_slot(@document_root, &@app_code_body) end |
#line_to(x, y) ⇒ Object
340 341 342 343 344 345 346 |
# File 'lacci/lib/shoes/app.rb', line 340 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
332 333 334 335 336 337 338 |
# File 'lacci/lib/shoes/app.rb', line 332 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 |
#page(name, &block) ⇒ Object
279 280 281 282 283 284 285 286 |
# File 'lacci/lib/shoes/app.rb', line 279 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
135 136 137 138 139 |
# File 'lacci/lib/shoes/app.rb', line 135 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.
131 132 133 |
# File 'lacci/lib/shoes/app.rb', line 131 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.
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
# File 'lacci/lib/shoes/app.rb', line 182 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 |
#visit(name) ⇒ Object
288 289 290 291 292 293 294 295 296 |
# File 'lacci/lib/shoes/app.rb', line 288 def visit(name) if @pages && @pages[name] @document_root.clear do instance_eval(&@pages[name]) end else puts "Error: URL '#{name}' not found" end end |
#with_slot(slot_item, &block) ⇒ Object
145 146 147 148 149 150 151 152 |
# File 'lacci/lib/shoes/app.rb', line 145 def with_slot(slot_item, &block) return unless block_given? push_slot(slot_item) instance_eval(&block) ensure pop_slot end |