Class: Shoes

Inherits:
Object
  • Object
show all
Defined in:
lacci/lib/shoes.rb,
lacci/lib/shoes.rb,
lacci/lib/shoes/app.rb,
lacci/lib/shoes/log.rb,
lacci/lib/shoes-spec.rb,
lacci/lib/shoes/border.rb,
lacci/lib/shoes/colors.rb,
lacci/lib/shoes/errors.rb,
lacci/lib/shoes/download.rb,
lacci/lib/shoes/drawable.rb,
lacci/lib/shoes/changelog.rb,
lacci/lib/shoes/constants.rb,
lacci/lib/shoes/background.rb,
lacci/lib/shoes/drawables/arc.rb,
lacci/lib/shoes/drawables/flow.rb,
lacci/lib/shoes/drawables/line.rb,
lacci/lib/shoes/drawables/link.rb,
lacci/lib/shoes/drawables/oval.rb,
lacci/lib/shoes/drawables/para.rb,
lacci/lib/shoes/drawables/para.rb,
lacci/lib/shoes/drawables/rect.rb,
lacci/lib/shoes/drawables/star.rb,
lacci/lib/shoes/display_service.rb,
lacci/lib/shoes/drawables/arrow.rb,
lacci/lib/shoes/drawables/check.rb,
lacci/lib/shoes/drawables/image.rb,
lacci/lib/shoes/drawables/radio.rb,
lacci/lib/shoes/drawables/shape.rb,
lacci/lib/shoes/drawables/stack.rb,
lacci/lib/shoes/drawables/video.rb,
lacci/lib/shoes/drawables/border.rb,
lacci/lib/shoes/drawables/button.rb,
lacci/lib/shoes/drawables/edit_box.rb,
lacci/lib/shoes/drawables/list_box.rb,
lacci/lib/shoes/drawables/progress.rb,
lacci/lib/shoes/drawables/edit_line.rb,
lacci/lib/shoes/drawables/document_root.rb,
lacci/lib/shoes/drawables/text_drawable.rb

Overview

Lacci Shoes apps operate in multiple layers. A Shoes drawable tree exists as fairly plain, simple Ruby objects. And then a display-service drawable tree integrates with the display technology. This lets us use Ruby as our API while not tying it too closely to the limitations of Webview, WASM, LibUI, etc.

Choosing Display Services

Before running a Lacci app, you can set SCARPE_DISPLAY_SERVICE. If you set it to "whatever_service", Scarpe will require "scarpe/whatever_service", which can be supplied by the Scarpe gem or another Scarpe-based gem. Currently leaving the environment variable empty is equivalent to requesting local Webview.

Events

Events are a lot of what tie the Shoes drawables and the display service together.

Shoes drawables expect to operate in a fairly "hands off" mode where they record to an event queue to send to the display service, and the display service records events to send back.

When a Shoes handler takes an action (e.g. some_para.replace(),) the relevant call will be dispatched as a :display event, to be sent to the display service. And when a display-side event occurs (e.g. user pushes a button,) it will be dispatched as a :shoes event, to be sent to the Shoes tree of drawables.

Defined Under Namespace

Modules: Background, Builtins, Colors, Constants, Errors, Log, Spec Classes: App, Arc, Arrow, Border, Button, Changelog, Check, DisplayService, DocumentRoot, Drawable, EditBox, EditLine, Error, Flow, Image, Line, Link, LinkHover, Linkable, ListBox, LoggedWrapper, Oval, Para, Progress, Radio, Rect, Shape, Slot, SpecInstance, SpecProxy, Stack, Star, SubscriptionItem, TextDrawable, Video, Widget

Constant Summary collapse

LOG_LEVELS =
[:debug, :info, :warn, :error, :fatal].freeze
RELEASE_INFO =
changelog_instance.get_latest_release_info
RELEASE_NAME =
RELEASE_ID =
RELEASE_BUILD_DATE =
RELEASE_TYPE =

This isn't really a thing any more

"LOOSE_SHOES"
REVISION =

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.APPSObject

Returns the value of attribute APPS.



58
59
60
# File 'lacci/lib/shoes.rb', line 58

def APPS
  @APPS
end

.pending_app_classObject

Track the most recently defined Shoes subclass for the inheritance pattern e.g., class Book < Shoes; end; Shoes.app



62
63
64
# File 'lacci/lib/shoes.rb', line 62

def pending_app_class
  @pending_app_class
end

Class Method Details

.add_file_loader(loader) ⇒ Object



211
212
213
# File 'lacci/lib/shoes.rb', line 211

def add_file_loader(loader)
  file_loaders.prepend(loader)
end

.app(title: 'Shoes!', width: 480, height: 420, resizable: true, features: [], &app_code_body) ⇒ void

This method returns an undefined value.

Creates a Shoes app with a new window. The block parameter is used to create drawables and set up handlers. Arguments are passed to Shoes::App.new internally.

Examples:

Simple one-button app

Shoes.app(title: "Button!", width: 200, height: 200) do
  @p = para "Press it NOW!"
  button("clicky") { @p.replace("You pressed it! CELEBRATION!") }
end

Parameters:

  • title (String) (defaults to: 'Shoes!')

    The new app window title

  • width (Integer) (defaults to: 480)

    The new app window width

  • height (Integer) (defaults to: 420)

    The new app window height

  • resizable (Boolean) (defaults to: true)

    Whether the app window should be resizeable

  • features (Symbol, Array<Symbol>) (defaults to: [])

    Additional Shoes extensions requested by the app

See Also:

  • Shoes::App#new

Incompatibilities with Shoes:

  • In Shoes3, this method will return normally. In Scarpe, after the block is executed, the method will not return and Scarpe will retain control of execution until the window is closed and the app quits.

  • In Shoes3 the parameters were a hash of options, not keyword arguments.



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lacci/lib/shoes.rb', line 114

def app(
  title: 'Shoes!',
  width: 480,
  height: 420,
  resizable: true,
  features: [],
  &app_code_body
)
  f = [features].flatten # Make sure this is a list, not a single symbol
  app = Shoes::App.new(title:, width:, height:, resizable:, features: f, &app_code_body)

  # If there's a pending Shoes subclass (e.g., class Book < Shoes), use it
  if Shoes.pending_app_class
    subclass = Shoes.pending_app_class
    Shoes.pending_app_class = nil  # Clear it so it doesn't affect future apps

    # Include the subclass as a module to get its instance methods
    # This works because we're extending the singleton class
    methods_to_copy = subclass.instance_methods(false)

    methods_to_copy.each do |method_name|
      # Get source location and use eval to redefine - but that's fragile
      # Instead, let's use a delegation pattern with the app as context

      # Read the method's arity and create a proper wrapper
      um = subclass.instance_method(method_name)

      # Define a wrapper that will eval the original method body in app's context
      # This is a bit hacky but works: we store the subclass and call via instance_eval
      app.define_singleton_method(method_name) do |*args, &block|
        # Create a temporary subclass instance that delegates to app for Shoes methods
        temp = subclass.allocate
        temp.instance_variable_set(:@__shoes_app__, self)

        # Define method_missing on the temp to delegate Shoes DSL calls to the app
        temp.define_singleton_method(:method_missing) do |name, *a, **kw, &b|
          @__shoes_app__.send(name, *a, **kw, &b)
        end
        temp.define_singleton_method(:respond_to_missing?) { |*| true }

        # Call the original method on temp (which delegates DSL calls to app)
        temp.send(method_name, *args, &block)
      end
    end

    # Copy routes from the subclass to the app
    subclass.class_routes.each do |path, method_name|
      app.url(path, method_name)
    end
  end

  app.init
  app.run
  nil
end

.class_routesObject

Get the routes defined on this class



88
89
90
# File 'lacci/lib/shoes.rb', line 88

def class_routes
  @class_routes ||= {}
end

.default_file_loadersObject



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

def default_file_loaders
  [
    # By default we will always try to load any file, regardless of extension, as a Shoes Ruby file.
    proc do |path|
      load path
      true
    end
  ]
end

.default_text_drawable_with(element) ⇒ Object



98
99
100
101
102
103
104
105
106
107
# File 'lacci/lib/shoes/drawables/text_drawable.rb', line 98

def default_text_drawable_with(element)
  class_name = element.capitalize

  drawable_class = Class.new(Shoes::TextDrawable) do
    shoes_events # No specific events

    init_args # We're going to pass an empty array to super
  end
  Shoes.const_set class_name, drawable_class
end

.file_loadersObject



207
208
209
# File 'lacci/lib/shoes.rb', line 207

def file_loaders
  @file_loaders ||= default_file_loaders
end

.inherited(subclass) ⇒ Object

When someone does class MyApp < Shoes, track it



65
66
67
68
69
70
71
72
# File 'lacci/lib/shoes.rb', line 65

def inherited(subclass)
  # Only track direct subclasses of Shoes, not Shoes::App, Shoes::Drawable, etc.
  # Those have their own inheritance tracking
  if self == ::Shoes
    Shoes.pending_app_class = subclass
  end
  super
end

.reset_file_loadersObject



215
216
217
# File 'lacci/lib/shoes.rb', line 215

def reset_file_loaders
  @file_loaders = default_file_loaders
end

.run_app(relative_path) ⇒ void

This method returns an undefined value.

Load a Shoes app from a file. By default, this will load old-style Shoes apps from a .rb file with all the appropriate libraries loaded. By setting one or more loaders, a Lacci-based display library can accept new file formats as well, not just raw Shoes .rb files.

Parameters:

  • relative_path (String)

    The current-dir-relative path to the file

See Also:



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lacci/lib/shoes.rb', line 178

def run_app(relative_path)
  path = File.expand_path relative_path
  dir = File.dirname(path)

  # Shoes assumes we're starting from the app code's path
  Dir.chdir(dir)

  loaded = false
  file_loaders.each do |loader|
    if loader.call(path)
      loaded = true
      break
    end
  end
  raise "Could not find a file loader for #{path.inspect}!" unless loaded

  nil
end

.set_file_loaders(loaders) ⇒ Object



219
220
221
# File 'lacci/lib/shoes.rb', line 219

def set_file_loaders(loaders)
  @file_loaders = loaders
end

.url(path, method_name) ⇒ Object

Class-level url method for defining routes in Shoes subclasses e.g., class Book < Shoes; url '/', :index; end



76
77
78
79
80
81
82
83
84
85
# File 'lacci/lib/shoes.rb', line 76

def url(path, method_name)
  @class_routes ||= {}
  if path.is_a?(String) && path.include?('(')
    # Convert string patterns like '/page/(\d+)' to regex
    regex = Regexp.new("^#{path.gsub(/\(.*?\)/, '(.*?)')}$")
    @class_routes[regex] = method_name
  else
    @class_routes[path] = method_name
  end
end