Class: Scarpe::Webview::Drawable

Inherits:
Shoes::Linkable show all
Includes:
Shoes::Log
Defined in:
lib/scarpe/wv/drawable.rb,
lib/scarpe/wv.rb

Overview

The Webview::Drawable parent class helps connect a Webview drawable with its Shoes equivalent, render itself to the Webview DOM, handle Javascript events and generally keep things working in Webview.

Constant Summary

Constants included from Shoes::Log

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

Instance Attribute Summary collapse

Attributes inherited from Shoes::Linkable

#linkable_id

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Shoes::Log

configure_logger, #log_init, logger

Methods inherited from Shoes::Linkable

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

Constructor Details

#initialize(properties) ⇒ Drawable

Set instance variables for the Shoes styles of this drawable. Bind Shoes events for changes of parent drawable and changes of property values.



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
# File 'lib/scarpe/wv/drawable.rb', line 40

def initialize(properties)
  log_init("Webview::Drawable")

  @shoes_style_names = properties.keys.map(&:to_s) - ["shoes_linkable_id"]

  # Call method, which looks up the parent
  @shoes_linkable_id = properties["shoes_linkable_id"] || properties[:shoes_linkable_id]
  unless @shoes_linkable_id
    raise Scarpe::MissingAttributeError, "Could not find property shoes_linkable_id in #{properties.inspect}!"
  end

  # Set the Shoes styles as instance variables
  properties.each do |k, v|
    next if k == "shoes_linkable_id"

    instance_variable_set("@" + k.to_s, v)
  end

  # Must call this before bind
  super(linkable_id: @shoes_linkable_id)

  bind_shoes_event(event_name: "parent", target: shoes_linkable_id) do |new_parent_id|
    display_parent = DisplayService.instance.query_display_drawable_for(new_parent_id)
    if @parent != display_parent
      set_parent(display_parent)
    end
  end

  # When Shoes drawables change properties, we get a change notification here
  bind_shoes_event(event_name: "prop_change", target: shoes_linkable_id) do |prop_changes|
    prop_changes.each do |k, v|
      instance_variable_set("@" + k, v)
    end
    properties_changed(prop_changes)
  end

  bind_shoes_event(event_name: "destroy", target: shoes_linkable_id) do
    destroy_self
  end
end

Instance Attribute Details

#childrenObject (readonly)

An array of Webview::Drawable children (possibly empty) of this drawable



36
37
38
# File 'lib/scarpe/wv/drawable.rb', line 36

def children
  @children
end

#parentObject (readonly)

The Webview::Drawable parent of this drawable



33
34
35
# File 'lib/scarpe/wv/drawable.rb', line 33

def parent
  @parent
end

#shoes_linkable_idObject (readonly)

The Shoes ID corresponding to the Shoes drawable for this Webview drawable



30
31
32
# File 'lib/scarpe/wv/drawable.rb', line 30

def shoes_linkable_id
  @shoes_linkable_id
end

Class Method Details

.display_class_for(scarpe_class_name) ⇒ Object

Return the corresponding Webview class for a particular Shoes class name



13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/scarpe/wv/drawable.rb', line 13

def display_class_for(scarpe_class_name)
  scarpe_class = Shoes.const_get(scarpe_class_name)
  unless scarpe_class.ancestors.include?(Shoes::Linkable)
    raise Scarpe::InvalidClassError, "Scarpe Webview can only get display classes for Shoes " +
      "linkable drawables, not #{scarpe_class_name.inspect}!"
  end

  klass = Scarpe::Webview.const_get(scarpe_class_name.split("::")[-1])
  if klass.nil?
    raise Scarpe::MissingClassError, "Couldn't find corresponding Scarpe Webview class for #{scarpe_class_name.inspect}!"
  end

  klass
end

Instance Method Details

#add_child(child) ⇒ Object (protected)

Do not call directly, use set_parent



138
139
140
141
142
143
144
# File 'lib/scarpe/wv/drawable.rb', line 138

def add_child(child)
  @children ||= []
  @children << child

  # If we add a child, we should redraw ourselves
  needs_update!
end

#bind(event) { ... } ⇒ Object

This binds a Scarpe JS callback, handled via a single dispatch point in the app

Parameters:

  • event (String)

    the Scarpe drawable event name

Yields:

  • the block to call when the event occurs

Raises:



223
224
225
226
227
# File 'lib/scarpe/wv/drawable.rb', line 223

def bind(event, &block)
  raise(Scarpe::MissingAttributeError, "Drawable has no linkable_id! #{inspect}") unless linkable_id

  DisplayService.instance.app.bind("#{linkable_id}-#{event}", &block)
end

#destroy_selfScarpe::Promise

Removes the element from both the Ruby Drawable tree and the HTML DOM. Unsubscribe from all Shoes events. Return a promise for when that HTML change will be visible.

Returns:

  • (Scarpe::Promise)

    a promise that is fulfilled when the HTML change is complete



234
235
236
237
238
# File 'lib/scarpe/wv/drawable.rb', line 234

def destroy_self
  @parent&.remove_child(self)
  unsub_all_shoes_events
  html_element.remove
end

#full_window_redraw!void

This method returns an undefined value.

Request a full redraw of the entire window, including the entire tree of drawables and the outer "empty page" frame.



244
245
246
# File 'lib/scarpe/wv/drawable.rb', line 244

def full_window_redraw!
  DisplayService.instance.app.request_redraw!
end

#handler_js_code(handler_function_name, *args) ⇒ String

Generate JS code to trigger a specific event name on this drawable with the supplies arguments.

Parameters:

  • handler_function_name (String)

    the event name - @see #bind

  • args (Array)

    additional arguments that will be passed to the event in the generated JS

Returns:

  • (String)

    the generated JS code

Raises:



266
267
268
269
270
271
# File 'lib/scarpe/wv/drawable.rb', line 266

def handler_js_code(handler_function_name, *args)
  raise(Scarpe::MissingAttributeError, "Drawable has no linkable_id! #{inspect}") unless linkable_id

  js_args = ["'#{linkable_id}-#{handler_function_name}'", *args].join(", ")
  "scarpeHandler(#{js_args})"
end

#html_elementScarpe::WebWrangler::ElementWrangler

This gets an accessor for just this element's HTML ID. It is normally called by the drawable itself to do its DOM management. Drawables are required to use their html_id for their outermost element, to make sure that remove(), hidden() etc. affect every part of the drawable.

Returns:

  • (Scarpe::WebWrangler::ElementWrangler)

    a DOM object manager



189
190
191
# File 'lib/scarpe/wv/drawable.rb', line 189

def html_element
  @elt_wrangler ||= Scarpe::Webview::WebWrangler::ElementWrangler.new(html_id)
end

#html_idString

Get the object's HTML ID

Returns:

  • (String)

    the HTML ID



205
206
207
# File 'lib/scarpe/wv/drawable.rb', line 205

def html_id
  @linkable_id.to_s
end

#inspectObject

A shorter inspect text for prettier irb output



121
122
123
# File 'lib/scarpe/wv/drawable.rb', line 121

def inspect
  "#<#{self.class}:#{self.object_id} @shoes_linkable_id=#{@shoes_linkable_id} @children=#{@children.inspect}>"
end

#needs_update!void

This method returns an undefined value.

Request a full redraw of this drawable, including all its children. Can be overridden in drawable subclasses if needed. An override would normally only be needed if re-rendering the element with the given html_id wasn't enough (and then remove would also need to be overridden.)

This occurs by default if a property is changed and the drawable doesn't remove its change in property_changed.



257
258
259
# File 'lib/scarpe/wv/drawable.rb', line 257

def needs_update!
  html_element.outer_html = to_html
end

#promise_updateScarpe::Promise

Return a promise that guarantees all currently-requested changes have completed

Returns:

  • (Scarpe::Promise)

    a promise that will be fulfilled when all pending changes have finished



196
197
198
199
200
# File 'lib/scarpe/wv/drawable.rb', line 196

def promise_update
  # Doesn't matter what ElementWrangler we use -- they all return an update promise
  # that includes all pending updates, no matter who they're for.
  html_element.promise_update
end

#properties_changed(changes) ⇒ Object

Properties_changed will be called automatically when properties change. The drawable should delete any changes from the Hash that it knows how to incrementally handle, and pass the rest to super. If any changes go entirely un-handled, a full redraw will be scheduled. This exists to be overridden by children watching for changes.

Parameters:

  • changes (Hash)

    a Hash of new values for properties that have changed



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/scarpe/wv/drawable.rb', line 96

def properties_changed(changes)
  # If a drawable does something really nonstandard with its html_id or element, it will
  # need to override to prevent this from happening. That's easy enough, though.
  if changes.key?("hidden")
    hidden = changes.delete("hidden")
    if hidden
      html_element.set_style("display", "none")
    else
      new_style = style # Get current display CSS property, which may vary by subclass
      disp = new_style[:display]
      html_element.set_style("display", disp || "block")
    end
  end

  needs_update! unless changes.empty?
end

#remove_child(child) ⇒ Object (protected)

Do not call directly, use set_parent



128
129
130
131
132
133
134
135
# File 'lib/scarpe/wv/drawable.rb', line 128

def remove_child(child)
  @children ||= []
  unless @children.include?(child)
    @log.error("remove_child: no such child(#{child.inspect}) for"\
      " parent(#{parent.inspect})!")
  end
  @children.delete(child)
end

#rgb_to_hex(color) ⇒ Object (protected)

Convert an [r, g, b, a] array to an HTML hex color code Arrays support alpha. HTML hex does not. So premultiply.



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/scarpe/wv/drawable.rb', line 148

def rgb_to_hex(color)
  return color if color.nil?

  r, g, b, a = *color
  if r.is_a?(Float)
    a ||= 1.0
    r_float = r * a
    g_float = g * a
    b_float = b * a
  else
    a ||= 255
    a_float = (a / 255.0)
    r_float = (r.to_f / 255.0) * a_float
    g_float = (g.to_f / 255.0) * a_float
    b_float = (b.to_f / 255.0) * a_float
  end

  r_int = (r_float * 255.0).to_i.clamp(0, 255)
  g_int = (g_float * 255.0).to_i.clamp(0, 255)
  b_int = (b_float * 255.0).to_i.clamp(0, 255)

  "#%0.2X%0.2X%0.2X" % [r_int, g_int, b_int]
end

#set_parent(new_parent) ⇒ Object

Give this drawable a new parent, including managing the appropriate child lists for parent drawables.



114
115
116
117
118
# File 'lib/scarpe/wv/drawable.rb', line 114

def set_parent(new_parent)
  @parent&.remove_child(self)
  new_parent&.add_child(self)
  @parent = new_parent
end

#shoes_stylesObject



81
82
83
84
85
86
87
# File 'lib/scarpe/wv/drawable.rb', line 81

def shoes_styles
  p = {}
  @shoes_style_names.each do |prop_name|
    p[prop_name] = instance_variable_get("@#{prop_name}")
  end
  p
end

#styleObject (protected)

CSS styles



173
174
175
176
177
178
179
# File 'lib/scarpe/wv/drawable.rb', line 173

def style
  styles = {}
  if @hidden
    styles[:display] = "none"
  end
  styles
end

#to_htmlString

to_html is intended to get the HTML DOM rendering of this object and its children. Calling it should be side-effect-free and NOT update the webview.

Returns:

  • (String)

    the rendered HTML



213
214
215
216
217
# File 'lib/scarpe/wv/drawable.rb', line 213

def to_html
  @children ||= []
  child_markup = @children.map(&:to_html).join
  element { child_markup }
end