glimr 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/glimr.rb +2 -0
- data/lib/glimr/configurable.rb +37 -0
- data/lib/glimr/default_theme/button_bg.png +0 -0
- data/lib/glimr/default_theme/button_bg_down.png +0 -0
- data/lib/glimr/default_theme/button_cover.png +0 -0
- data/lib/glimr/default_theme/button_cover_down.png +0 -0
- data/lib/glimr/default_theme/button_focus.png +0 -0
- data/lib/glimr/default_theme/checkbox_bg.png +0 -0
- data/lib/glimr/default_theme/checkbox_checked_bg.png +0 -0
- data/lib/glimr/default_theme/font.ttf +0 -0
- data/lib/glimr/default_theme/hscroller_bg.png +0 -0
- data/lib/glimr/default_theme/radiobutton_bg.png +0 -0
- data/lib/glimr/default_theme/radiobutton_checked_bg.png +0 -0
- data/lib/glimr/default_theme/resizer_down.png +0 -0
- data/lib/glimr/default_theme/resizer_up.png +0 -0
- data/lib/glimr/default_theme/scroll_down_down.png +0 -0
- data/lib/glimr/default_theme/scroll_down_up.png +0 -0
- data/lib/glimr/default_theme/scroll_hknob_down.png +0 -0
- data/lib/glimr/default_theme/scroll_hknob_up.png +0 -0
- data/lib/glimr/default_theme/scroll_left_down.png +0 -0
- data/lib/glimr/default_theme/scroll_left_up.png +0 -0
- data/lib/glimr/default_theme/scroll_right_down.png +0 -0
- data/lib/glimr/default_theme/scroll_right_up.png +0 -0
- data/lib/glimr/default_theme/scroll_up_down.png +0 -0
- data/lib/glimr/default_theme/scroll_up_up.png +0 -0
- data/lib/glimr/default_theme/scroll_vknob_down.png +0 -0
- data/lib/glimr/default_theme/scroll_vknob_up.png +0 -0
- data/lib/glimr/default_theme/text_cursor.png +0 -0
- data/lib/glimr/default_theme/text_cursor_insert.png +0 -0
- data/lib/glimr/default_theme/text_input_bg.png +0 -0
- data/lib/glimr/default_theme/vscroller_bg.png +0 -0
- data/lib/glimr/event.rb +41 -0
- data/lib/glimr/eventlistener.rb +209 -0
- data/lib/glimr/layoutable.rb +520 -0
- data/lib/glimr/renderer.rb +2 -0
- data/lib/glimr/renderer/camera.rb +63 -0
- data/lib/glimr/renderer/geometry.rb +194 -0
- data/lib/glimr/renderer/glutwindow.rb +387 -0
- data/lib/glimr/renderer/light.rb +43 -0
- data/lib/glimr/renderer/material.rb +66 -0
- data/lib/glimr/renderer/model.rb +103 -0
- data/lib/glimr/renderer/orthoprojection.rb +21 -0
- data/lib/glimr/renderer/raw.rb +34 -0
- data/lib/glimr/renderer/sceneobject.rb +279 -0
- data/lib/glimr/renderer/shader.rb +14 -0
- data/lib/glimr/renderer/texture.rb +280 -0
- data/lib/glimr/renderer/transform.rb +322 -0
- data/lib/glimr/renderer/viewport.rb +349 -0
- data/lib/glimr/renderer_core.rb +10 -0
- data/lib/glimr/util.rb +247 -0
- data/lib/glimr/widget.rb +87 -0
- data/lib/glimr/widgets.rb +37 -0
- data/lib/glimr/widgets/button.rb +277 -0
- data/lib/glimr/widgets/checkbox.rb +82 -0
- data/lib/glimr/widgets/container.rb +84 -0
- data/lib/glimr/widgets/image.rb +82 -0
- data/lib/glimr/widgets/label.rb +91 -0
- data/lib/glimr/widgets/layout.rb +227 -0
- data/lib/glimr/widgets/list.rb +28 -0
- data/lib/glimr/widgets/radiogroup.rb +118 -0
- data/lib/glimr/widgets/resizer.rb +31 -0
- data/lib/glimr/widgets/scrollable_container.rb +67 -0
- data/lib/glimr/widgets/scrollbar.rb +496 -0
- data/lib/glimr/widgets/stretchable_image.rb +135 -0
- data/lib/glimr/widgets/text_editor.rb +349 -0
- data/tests/assets/datatowers_crop.jpg +0 -0
- data/tests/assets/download_progress_meter.png +0 -0
- data/tests/assets/metalwing2.png +0 -0
- data/tests/assets/redhairgreeneyes3.jpg +0 -0
- data/tests/demo_apps/spinning_ruby.rb +37 -0
- data/tests/integration_tests/run_all.rb +8 -0
- data/tests/integration_tests/test_button.rb +22 -0
- data/tests/integration_tests/test_checkbox.rb +21 -0
- data/tests/integration_tests/test_container.rb +22 -0
- data/tests/integration_tests/test_label.rb +12 -0
- data/tests/integration_tests/test_layout.rb +43 -0
- data/tests/integration_tests/test_radiogroup.rb +16 -0
- data/tests/integration_tests/test_renderer.rb +44 -0
- data/tests/integration_tests/test_renderer2.rb +36 -0
- data/tests/integration_tests/test_scrollable_container.rb +34 -0
- data/tests/integration_tests/test_scrollbar.rb +20 -0
- data/tests/integration_tests/test_stretchable_image.rb +34 -0
- data/tests/integration_tests/test_text_input.rb +20 -0
- data/tests/integration_tests/test_zsort.rb +18 -0
- data/tests/unit_tests/test_button.rb +93 -0
- data/tests/unit_tests/test_checkbox.rb +35 -0
- data/tests/unit_tests/test_label.rb +36 -0
- data/tests/unit_tests/test_layout.rb +229 -0
- data/tests/unit_tests/test_widget.rb +3 -0
- metadata +139 -0
data/lib/glimr.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module GlimR
|
2
|
+
|
3
|
+
# Hash-configurable object.
|
4
|
+
# Takes a hash and sets the setters named
|
5
|
+
# by the keys to the values.
|
6
|
+
# Use #default_config to define the default config.
|
7
|
+
#
|
8
|
+
# class MyConfigurable
|
9
|
+
# include Configurable
|
10
|
+
# attr_accessor :x, :y
|
11
|
+
# def default_config
|
12
|
+
# { :x => 'default x', :y => 'default y' }
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# mc = MyConfigurable.new :y => 6
|
17
|
+
# mc.x
|
18
|
+
# # => 'default x'
|
19
|
+
# mc.y
|
20
|
+
# # => 6
|
21
|
+
#
|
22
|
+
module Configurable
|
23
|
+
|
24
|
+
def initialize(config_hash = {}, &optional_config_block)
|
25
|
+
config = default_config.merge(config_hash)
|
26
|
+
config.each{|k,v| instance_variable_set("@#{k}", v) }
|
27
|
+
super
|
28
|
+
yield(self) if block_given?
|
29
|
+
end
|
30
|
+
|
31
|
+
def default_config
|
32
|
+
{}
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/lib/glimr/event.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module GlimR
|
4
|
+
|
5
|
+
# Implements the <a href="http://www.w3.org/TR/DOM-Level-2-Events">W3C DOM Event model</a>
|
6
|
+
# event interface.
|
7
|
+
#
|
8
|
+
# Much like the ECMAScript API.
|
9
|
+
#
|
10
|
+
class Event < OpenStruct
|
11
|
+
|
12
|
+
attr_reader :type, :cancelled, :stopped
|
13
|
+
attr_accessor :phase, :params
|
14
|
+
|
15
|
+
# Creates a new event of the given type, sets params to param hash.
|
16
|
+
# By default, the event bubbles and is not cancelable.
|
17
|
+
def initialize(type, params={:bubbles => true, :cancelable => false})
|
18
|
+
params = params.clone
|
19
|
+
params[:bubbles] = true if params[:bubbles].nil?
|
20
|
+
params[:cancelable] = false if params[:cancelable].nil?
|
21
|
+
params[:time] ||= Time.now.to_f
|
22
|
+
super(params)
|
23
|
+
@type = type.to_sym
|
24
|
+
@params = params
|
25
|
+
@phase = :capture
|
26
|
+
end
|
27
|
+
|
28
|
+
# Stops the handling of the event.
|
29
|
+
def stop_propagation
|
30
|
+
@stopped = true
|
31
|
+
end
|
32
|
+
|
33
|
+
# Cancels the event's possible default behaviour.
|
34
|
+
def prevent_default
|
35
|
+
@cancelled = true if cancelable
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,209 @@
|
|
1
|
+
require 'glimr/event'
|
2
|
+
|
3
|
+
|
4
|
+
class Array
|
5
|
+
|
6
|
+
def delete_first(item)
|
7
|
+
i = index(item)
|
8
|
+
delete_at i if i
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
module GlimR
|
16
|
+
|
17
|
+
|
18
|
+
# The EventListener mixin implements an event listening interface
|
19
|
+
# similar to <a href="http://www.w3.org/TR/DOM-Level-2-Events/">
|
20
|
+
# W3C's ECMAScript DOM event API</a>, with the addition of event multicasting.
|
21
|
+
#
|
22
|
+
# EventListener provides the following methods:
|
23
|
+
# #add_event_listener, #remove_event_listener,
|
24
|
+
# #dispatch_event and #multicast_event.
|
25
|
+
#
|
26
|
+
# The list of event listeners can be modified with #add_event_listener and #remove_event_listener.
|
27
|
+
#
|
28
|
+
# E.g.
|
29
|
+
# l = event_listener.add_event_listener(:hello){|listener, event|
|
30
|
+
# puts "Goodbye!"
|
31
|
+
# }
|
32
|
+
# event_listener.remove_event_listener(:hello, l)
|
33
|
+
# event_listener.add_event_listener(:hello){|listener, event|
|
34
|
+
# puts "Hello, World!"
|
35
|
+
# }
|
36
|
+
# another_hello_listener.add_event_listener(:hello){|l, e|
|
37
|
+
# puts "Hello from me too!"
|
38
|
+
# }
|
39
|
+
# event_listener.attach another_hello_listener
|
40
|
+
#
|
41
|
+
# Also provides a method_missing that captures method calls starting with on_ and
|
42
|
+
# parses them as requests to add event listener for the event type.
|
43
|
+
#
|
44
|
+
# E.g. to add a bubbling event listener for the event type :frame, it's possible to do
|
45
|
+
# obj.on_frame{|o,e| puts 'got frame event'}
|
46
|
+
#
|
47
|
+
# And for a capturing listener
|
48
|
+
# obj.on_frame(true){|o,e| puts 'got it first!'}
|
49
|
+
#
|
50
|
+
# To send a targeted event to the EventListener, use #dispatch_event.
|
51
|
+
#
|
52
|
+
# E.g.
|
53
|
+
# event_listener.dispatch_event(Event.new(:hello))
|
54
|
+
# # Hello, World!
|
55
|
+
#
|
56
|
+
# To broadcast an event to all listeners for the event type in the scene graph, use #multicast_event.
|
57
|
+
#
|
58
|
+
# E.g.
|
59
|
+
# event_listener.multicast_event(Event.new(:hello))
|
60
|
+
# # Hello from me too!
|
61
|
+
# # Hello, World!
|
62
|
+
#
|
63
|
+
module EventListener
|
64
|
+
|
65
|
+
attr_accessor :event_listeners
|
66
|
+
attr_reader :listener_count
|
67
|
+
|
68
|
+
def initialize(*a,&b)
|
69
|
+
@event_listeners = Hash.new{|h,k| h[k] = Hash.new{|h,k| h[k] = []} }
|
70
|
+
@listener_count = Hash.new{|h,k| h[k] = 0 }
|
71
|
+
end
|
72
|
+
|
73
|
+
# Adds an event listener for the given event type. If use_capture is true, listens on capture phase.
|
74
|
+
# Otherwise listens on bubble phase. If listener is nil or false, the given block is used as the listener
|
75
|
+
# instead.
|
76
|
+
def add_event_listener(type, listener=nil, use_capture=false, &block)
|
77
|
+
listener ||= block
|
78
|
+
type = type.to_sym
|
79
|
+
@event_listeners[type][(use_capture ? :capture : :bubble)] << listener
|
80
|
+
increment_listener_count type
|
81
|
+
end
|
82
|
+
|
83
|
+
# Removes the given event listener from the event type. If no listener is given, removes all event listeners
|
84
|
+
# from the event type. If use_capture is true, removes capturing event listener(s), if false, removes bubbling
|
85
|
+
# event listener(s).
|
86
|
+
def remove_event_listener(type, listener=nil, use_capture=false)
|
87
|
+
type = type.to_sym
|
88
|
+
if listener
|
89
|
+
success = @event_listeners[type][(use_capture ? :capture : :bubble)].delete_first(listener)
|
90
|
+
decrement_listener_count type if success
|
91
|
+
else
|
92
|
+
listeners = @event_listeners[type][(use_capture ? :capture : :bubble)]
|
93
|
+
sz = listeners.size
|
94
|
+
if sz > 0
|
95
|
+
listeners.clear
|
96
|
+
decrement_listener_count type, sz
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def method_missing(mn, *a, &b)
|
102
|
+
if mn.to_s[0,3] == "on_"
|
103
|
+
add_event_listener(mn.to_s[3..-1].gsub(/=$/,''), *a, &b)
|
104
|
+
else
|
105
|
+
super
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Sends the Event evt to first the capture listeners,
|
110
|
+
# then down to children, then to bubbling listeners.
|
111
|
+
#
|
112
|
+
# Returns immediately if there are no listeners for
|
113
|
+
# the event type in or below this node.
|
114
|
+
#
|
115
|
+
def multicast_event(evt)
|
116
|
+
return !evt.cancelled if @listener_count[evt.type] <= 0
|
117
|
+
evt.phase = :capture
|
118
|
+
process_event(evt)
|
119
|
+
return !evt.cancelled if evt.stopped
|
120
|
+
if evt.phase == :capture # still going down
|
121
|
+
children.each{|c|
|
122
|
+
c.multicast_event(evt) if c.is_a? EventListener
|
123
|
+
return !evt.cancelled if evt.stopped
|
124
|
+
}
|
125
|
+
end
|
126
|
+
return !evt.cancelled if evt.stopped
|
127
|
+
evt.phase = :bubble
|
128
|
+
process_event(evt)
|
129
|
+
return !evt.cancelled
|
130
|
+
end
|
131
|
+
|
132
|
+
# Sends the Event evt down to self and bubbles it up.
|
133
|
+
def dispatch_event(evt)
|
134
|
+
evt.target = self
|
135
|
+
ancestors = [self]
|
136
|
+
o = self
|
137
|
+
while o.parent and !o.parent.event_root and o.parent != evt.sender
|
138
|
+
ancestors << o.parent
|
139
|
+
o = o.parent
|
140
|
+
end
|
141
|
+
i = ancestors.size
|
142
|
+
ancestors.reverse.each{|a|
|
143
|
+
a.process_event(evt)
|
144
|
+
break if evt.stopped
|
145
|
+
i -= 1
|
146
|
+
break if evt.phase == :bubble
|
147
|
+
}
|
148
|
+
# Now either the event was turned around at i or reached the bottom (i==1).
|
149
|
+
# Continue by setting evt.phase to :bubble and sending it up from i.
|
150
|
+
i += 1 if evt.target == self # bubble already called for self
|
151
|
+
evt.phase = :bubble
|
152
|
+
(ancestors[i..-1] || []).each{|a|
|
153
|
+
break if evt.stopped
|
154
|
+
a.process_event(evt)
|
155
|
+
}
|
156
|
+
return !evt.cancelled
|
157
|
+
end
|
158
|
+
|
159
|
+
def event_root
|
160
|
+
false
|
161
|
+
end
|
162
|
+
|
163
|
+
# Processes the Event evt by calling all listeners on
|
164
|
+
# this object registered to handle the event.
|
165
|
+
def process_event(evt)
|
166
|
+
listeners_for(evt).each{|l|
|
167
|
+
break if evt.stopped
|
168
|
+
case l
|
169
|
+
when Symbol
|
170
|
+
__send__(l, self, evt)
|
171
|
+
else
|
172
|
+
l.call(self, evt)
|
173
|
+
end
|
174
|
+
}
|
175
|
+
!evt.cancelled
|
176
|
+
end
|
177
|
+
|
178
|
+
# Adds count to the subtree total listener count for type.
|
179
|
+
# Sends changes to parent to keep its subtree total listener count correct.
|
180
|
+
def increment_listener_count type, count=1
|
181
|
+
c = @listener_count[type]
|
182
|
+
raise ArgumentError, "Trying to decrement listener count too much." if -count > c
|
183
|
+
@listener_count[type] += count
|
184
|
+
parent.increment_listener_count type, count if parent.is_a? EventListener
|
185
|
+
end
|
186
|
+
|
187
|
+
# Subtracts count from the subtree total listener count for type.
|
188
|
+
# Sends changes to parent to keep its subtree total listener count correct.
|
189
|
+
def decrement_listener_count type, count=1
|
190
|
+
increment_listener_count type, -count
|
191
|
+
end
|
192
|
+
|
193
|
+
private
|
194
|
+
|
195
|
+
def listeners_for(evt)
|
196
|
+
if self == evt.target
|
197
|
+
evt.phase = :at
|
198
|
+
@event_listeners[evt.type].values.flatten
|
199
|
+
elsif evt.phase == :capture
|
200
|
+
@event_listeners[evt.type][:capture]
|
201
|
+
else
|
202
|
+
@event_listeners[evt.type][:bubble]
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
|
208
|
+
|
209
|
+
end
|
@@ -0,0 +1,520 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'glimr/util'
|
3
|
+
|
4
|
+
|
5
|
+
module GlimR
|
6
|
+
|
7
|
+
|
8
|
+
# Layoutable is a mixin for creating GlimR nodes that
|
9
|
+
# are suspectible to layout rules.
|
10
|
+
#
|
11
|
+
# Layoutable objects have dimensions, margins, and padding.
|
12
|
+
#
|
13
|
+
# The margins define how much space should be left around the
|
14
|
+
# Layoutable. Padding defines how much distance should be left
|
15
|
+
# between the borders of the Layoutable and its child nodes.
|
16
|
+
#
|
17
|
+
# They also have the following attributes that control their
|
18
|
+
# dynamic resizing behaviour: expand_width, expand_height,
|
19
|
+
# fit_children, min_width, min_height, max_width and max_height.
|
20
|
+
#
|
21
|
+
# Dynamic layout rules:
|
22
|
+
#
|
23
|
+
# An object expands in a dimension if either its corresponding expand
|
24
|
+
# attribute is set to true, or if its fit_children is set to true and one of
|
25
|
+
# its children expands in the dimension.
|
26
|
+
#
|
27
|
+
# If an object neither fits its children nor expands, it is a constant size
|
28
|
+
# object.
|
29
|
+
#
|
30
|
+
# When a layoutable object is attached to the scene graph
|
31
|
+
# it expands to its maximum dimensions if it is an expanding object.
|
32
|
+
#
|
33
|
+
# When a layoutable object changes size, it informs its parent
|
34
|
+
# if the parent is a layoutable. If the object was a constant size object,
|
35
|
+
# or the size change resulted from changing the object's
|
36
|
+
# dimension constraints (min_width, max_width, etc.), the expanding
|
37
|
+
# children of the object are also informed of the size change.
|
38
|
+
#
|
39
|
+
# When computing the dimensions of a Layoutable, dimension
|
40
|
+
# constraints take predecence over expanding, which takes
|
41
|
+
# predecence over fit_children.
|
42
|
+
#
|
43
|
+
# E.g. the final width of a layoutable node is computed as follows:
|
44
|
+
#
|
45
|
+
# 1. set width to max child width if fit_children
|
46
|
+
# 2. set width to the inner width of the parent node if expand_width
|
47
|
+
# 3. clamp the width between min_width and max_width if they're set
|
48
|
+
#
|
49
|
+
module Layoutable
|
50
|
+
|
51
|
+
def self.size_changing_accessor(*mnames)
|
52
|
+
attr_reader *mnames
|
53
|
+
mnames.each{|mn|
|
54
|
+
ivar_name = :"@#{mn}"
|
55
|
+
define_method("#{mn}=") do |value|
|
56
|
+
size_changing{ instance_variable_set(ivar_name, value) }
|
57
|
+
end
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
size_changing_accessor :width, :height, :depth
|
62
|
+
size_changing_accessor :margin_left, :margin_right,
|
63
|
+
:margin_top, :margin_bottom,
|
64
|
+
:margin_front, :margin_back
|
65
|
+
size_changing_accessor :padding_left, :padding_right,
|
66
|
+
:padding_top, :padding_bottom,
|
67
|
+
:padding_front, :padding_back
|
68
|
+
|
69
|
+
size_changing_accessor :expand_width, :expand_height, :fit_children,
|
70
|
+
:min_width, :min_height, :max_width, :max_height
|
71
|
+
|
72
|
+
attr_accessor :align, :valign
|
73
|
+
|
74
|
+
alias_method :fit_children?, :fit_children
|
75
|
+
|
76
|
+
DIMENSIONS = [:left, :right, :top, :bottom] #, :front, :back]
|
77
|
+
|
78
|
+
|
79
|
+
# horiz margin setters modify full_width
|
80
|
+
# vert margin setters modify full_height
|
81
|
+
# horiz padding setters modify full_width
|
82
|
+
# vert padding setters modify full_height
|
83
|
+
|
84
|
+
|
85
|
+
def default_config
|
86
|
+
super.merge(
|
87
|
+
:expand_width => false, :expand_height => false,
|
88
|
+
:fit_children => true, :align => :left, :valign => :top,
|
89
|
+
:min_width => 0, :min_height => 0,
|
90
|
+
:max_width => 1.0/0.0, :max_height => 1.0/0.0,
|
91
|
+
:width => 0, :height => 0, :depth => 0,
|
92
|
+
:margin_left => 0, :margin_right => 0,
|
93
|
+
:margin_top => 0, :margin_bottom => 0,
|
94
|
+
:margin_front => 0, :margin_back => 0,
|
95
|
+
:padding_left => 0, :padding_right => 0,
|
96
|
+
:padding_top => 0, :padding_bottom => 0,
|
97
|
+
:padding_front => 0, :padding_back => 0
|
98
|
+
)
|
99
|
+
end
|
100
|
+
|
101
|
+
def initialize(*a, &b)
|
102
|
+
super
|
103
|
+
@__resize_handler = lambda{|o,e| o.expand! }
|
104
|
+
self.margin = @margin if @margin
|
105
|
+
self.padding = @padding if @padding
|
106
|
+
end
|
107
|
+
|
108
|
+
# Sets x to the given integer value.
|
109
|
+
def x= nx
|
110
|
+
super nx.to_i
|
111
|
+
end
|
112
|
+
|
113
|
+
# Sets y to the given integer value.
|
114
|
+
def y= ny
|
115
|
+
super ny.to_i
|
116
|
+
end
|
117
|
+
|
118
|
+
# Sets align to a and relayouts.
|
119
|
+
def align= a
|
120
|
+
if @align != a
|
121
|
+
@align = a
|
122
|
+
layout
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Sets valign to v and relayouts.
|
127
|
+
def valign= v
|
128
|
+
if @valign != v
|
129
|
+
@valign = v
|
130
|
+
layout
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Sets min_width to mw and sets width to min_width
|
135
|
+
# if it's smaller than the new min_width.
|
136
|
+
def min_width= mw
|
137
|
+
@min_width = mw
|
138
|
+
self.width = mw if self.width < mw
|
139
|
+
fit_to_children! if fit_children?
|
140
|
+
end
|
141
|
+
|
142
|
+
# Sets min_height to mw and sets height to min_height
|
143
|
+
# if it's smaller than the new min_height.
|
144
|
+
def min_height= mh
|
145
|
+
@min_height = mh
|
146
|
+
self.height = mh if self.height < mh
|
147
|
+
fit_to_children! if fit_children?
|
148
|
+
end
|
149
|
+
|
150
|
+
# Sets max_width to mw and sets width to max_width
|
151
|
+
# if it's larger than the new max_width.
|
152
|
+
def max_width= mw
|
153
|
+
@max_width = mw
|
154
|
+
self.width = mw if self.width > mw
|
155
|
+
end
|
156
|
+
|
157
|
+
# Sets max_height to mw and sets height to max_height
|
158
|
+
# if it's larger than the new max_height.
|
159
|
+
def max_height= mh
|
160
|
+
@max_height = mh
|
161
|
+
self.height = mh if self.height > mh
|
162
|
+
end
|
163
|
+
|
164
|
+
# Returns current margins as an OpenStruct with #left, #right, #top, and #bottom.
|
165
|
+
def margin
|
166
|
+
OpenStruct.new(
|
167
|
+
:left => margin_left, :right => margin_right,
|
168
|
+
:top => margin_top, :bottom => margin_bottom,
|
169
|
+
:front => margin_front, :back => margin_back
|
170
|
+
)
|
171
|
+
end
|
172
|
+
|
173
|
+
# Sets margins to new_margin.
|
174
|
+
# If given a Numeric, sets all margins to the given value.
|
175
|
+
# If given a hash, sets the margins to the corresponding values in the hash.
|
176
|
+
# If given any other object, acts as if it is an OpenStruct returned by #margin.
|
177
|
+
#
|
178
|
+
def margin= new_margin
|
179
|
+
size_changing do
|
180
|
+
case new_margin
|
181
|
+
when Numeric
|
182
|
+
@margin_left = @margin_right = @margin_top = @margin_bottom = new_margin
|
183
|
+
touch!
|
184
|
+
when Hash
|
185
|
+
new_margin.each{|k,v|
|
186
|
+
__send__("margin_#{k}=", v)
|
187
|
+
}
|
188
|
+
else
|
189
|
+
DIMENSIONS.each{|d|
|
190
|
+
__send__("margin_#{d}=", new_margin.__send__(d))
|
191
|
+
}
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# Returns current padding as an OpenStruct with #left, #right, #top, and #bottom.
|
197
|
+
def padding
|
198
|
+
OpenStruct.new(
|
199
|
+
:left => padding_left, :right => padding_right,
|
200
|
+
:top => padding_top, :bottom => padding_bottom,
|
201
|
+
:front => padding_front, :back => padding_back
|
202
|
+
)
|
203
|
+
end
|
204
|
+
|
205
|
+
# Sets padding to new_padding.
|
206
|
+
# If given a Numeric, sets all paddings to the given value.
|
207
|
+
# If given a hash, sets the paddings to the corresponding values in the hash.
|
208
|
+
# If given any other object, acts as if it is an OpenStruct returned by #padding.
|
209
|
+
#
|
210
|
+
def padding= new_padding
|
211
|
+
size_changing do
|
212
|
+
case new_padding
|
213
|
+
when Numeric
|
214
|
+
@padding_left = @padding_right = @padding_top = @padding_bottom = new_padding
|
215
|
+
touch!
|
216
|
+
when Hash
|
217
|
+
new_padding.each{|k,v|
|
218
|
+
__send__("padding_#{k}=", v)
|
219
|
+
}
|
220
|
+
else
|
221
|
+
DIMENSIONS.each{|d|
|
222
|
+
__send__("padding_#{d}=", new_padding.__send__(d))
|
223
|
+
}
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# Returns an Array of all children that respond true to #is_a?(Layoutable).
|
229
|
+
def layoutable_children
|
230
|
+
children.find_all{|c| c.is_a? Layoutable }
|
231
|
+
end
|
232
|
+
|
233
|
+
# Whether the object expands to fill its parent's width or not.
|
234
|
+
#
|
235
|
+
# Returns true if either @expand_width is true or fit_children is true
|
236
|
+
# and any of the layoutable children returns true to #expand_width.
|
237
|
+
def expand_width
|
238
|
+
@expand_width or (fit_children and layoutable_children.any?{|c| c.expand_width })
|
239
|
+
end
|
240
|
+
|
241
|
+
def parent= p
|
242
|
+
super
|
243
|
+
ew = expand_width
|
244
|
+
eh = expand_height
|
245
|
+
viewport_expand = p.is_a?(Viewport) and (ew or eh)
|
246
|
+
constant_expand = (p.is_a?(Layoutable) and !p.fit_children?) and
|
247
|
+
((ew and !parent.expand_width) or (eh and !parent.expand_height))
|
248
|
+
expand! if (viewport_expand or constant_expand)
|
249
|
+
end
|
250
|
+
|
251
|
+
# Whether the object expands to fill its parent's height or not.
|
252
|
+
#
|
253
|
+
# Returns true if either @expand_height is true or fit_children is true
|
254
|
+
# and any of the layoutable children returns true to #expand_height.
|
255
|
+
def expand_height
|
256
|
+
@expand_height or (fit_children and
|
257
|
+
layoutable_children.any?{|c| c.expand_height })
|
258
|
+
end
|
259
|
+
|
260
|
+
# Whether the Layoutable object expands to fill its parent's width or height.
|
261
|
+
#
|
262
|
+
def expand?
|
263
|
+
expand_width or expand_height
|
264
|
+
end
|
265
|
+
|
266
|
+
# Returns true if the object's size is constant, i.e it does not expand and
|
267
|
+
# does not fit its children.
|
268
|
+
def constant_size?
|
269
|
+
not (expand? or fit_children?)
|
270
|
+
end
|
271
|
+
|
272
|
+
def attach(*a, &b)
|
273
|
+
size_changing{
|
274
|
+
super
|
275
|
+
children_change
|
276
|
+
}
|
277
|
+
end
|
278
|
+
|
279
|
+
def detach(*a, &b)
|
280
|
+
size_changing{
|
281
|
+
super
|
282
|
+
children_change
|
283
|
+
}
|
284
|
+
end
|
285
|
+
|
286
|
+
# Sets dimensions equal to the largest child + padding.
|
287
|
+
def fit_to_children!
|
288
|
+
size_changing do
|
289
|
+
fit_width! unless parent and expand_width
|
290
|
+
fit_height! unless parent and expand_height
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
# Sets width equal to the largest child + padding.
|
295
|
+
def fit_width!
|
296
|
+
self.width = layoutable_children.map{|c|
|
297
|
+
c.full_width + padding_left + padding_right
|
298
|
+
}.max unless layoutable_children.empty?
|
299
|
+
end
|
300
|
+
|
301
|
+
# Sets height equal to the largest child + padding.
|
302
|
+
def fit_height!
|
303
|
+
self.height = layoutable_children.map{|c|
|
304
|
+
c.full_height + padding_top + padding_bottom
|
305
|
+
}.max unless layoutable_children.empty?
|
306
|
+
end
|
307
|
+
|
308
|
+
# Yields to block and informs relevant parties if size changed during it.
|
309
|
+
#
|
310
|
+
# In detail:
|
311
|
+
#
|
312
|
+
# If the node turns into an expanding one, it is expanded. This may cause a size change.
|
313
|
+
#
|
314
|
+
# If the inner dimensions (caused by padding changes) are altered,
|
315
|
+
# children are told and relayouted.
|
316
|
+
#
|
317
|
+
# If the outer dimensions (caused by dimension changes and margin changes)
|
318
|
+
# are altered, children are told and relayouted and possible Layoutable parent is told
|
319
|
+
# (unless the node is an expanding one, in which case the parent already knows.)
|
320
|
+
#
|
321
|
+
def size_changing(&block)
|
322
|
+
return yield if @__doing_size_changing_operation
|
323
|
+
@__doing_size_changing_operation = true
|
324
|
+
ow, oh, iw, ih = full_width, full_height, inner_width, inner_height
|
325
|
+
cs = constant_size?
|
326
|
+
fc = fit_children?
|
327
|
+
ew = expand_width
|
328
|
+
eh = expand_height
|
329
|
+
rv = yield
|
330
|
+
fit_to_children! if fit_children? and ((!fc and fit_children?) or (ew and !expand_width) or (eh and !expand_height))
|
331
|
+
expand! if (!ew and expand_width) or (!eh and expand_height)
|
332
|
+
@width = [[min_width, width].max, max_width].min
|
333
|
+
@height = [[min_height, height].max, max_height].min
|
334
|
+
dw = (ow != full_width)
|
335
|
+
dh = (oh != full_height)
|
336
|
+
diw = (iw != inner_width)
|
337
|
+
dih = (ih != inner_height)
|
338
|
+
if dw or dh or diw or dih
|
339
|
+
tell_children_of_size_change if diw or dih
|
340
|
+
parent.children_change if parent.is_a?(Layoutable) and (dw or dh)
|
341
|
+
layout
|
342
|
+
dispatch_event(Event.new(:layout)) if respond_to? :dispatch_event
|
343
|
+
@width = [[min_width, width].max, max_width].min
|
344
|
+
@height = [[min_height, height].max, max_height].min
|
345
|
+
end
|
346
|
+
@__doing_size_changing_operation = false
|
347
|
+
rv
|
348
|
+
end
|
349
|
+
|
350
|
+
# Children changed, let's accommodate.
|
351
|
+
def children_change
|
352
|
+
fit_to_children! if fit_children?
|
353
|
+
layout
|
354
|
+
end
|
355
|
+
|
356
|
+
# Expands all expanding children.
|
357
|
+
def tell_children_of_size_change
|
358
|
+
layoutable_children.each{|c| c.expand! if c.expand? }
|
359
|
+
end
|
360
|
+
|
361
|
+
# Expands self to maximum available dimensions.
|
362
|
+
# Brought on by parent size change.
|
363
|
+
#
|
364
|
+
def expand!(given_width=nil,given_height=nil)
|
365
|
+
size_changing{
|
366
|
+
expand_to_max_width!(given_width) if expand_width
|
367
|
+
expand_to_max_height!(given_width) if expand_height
|
368
|
+
}
|
369
|
+
end
|
370
|
+
|
371
|
+
# Expands self, parent and children to
|
372
|
+
# maximum available width if expand_width is true.
|
373
|
+
#
|
374
|
+
# If expand_width is true, expands to
|
375
|
+
# either the given_width, the result of
|
376
|
+
# calling @parent.expand_to_max_width! or
|
377
|
+
# @viewport.width (in this order.)
|
378
|
+
#
|
379
|
+
# If width changes as a result of this
|
380
|
+
# call, calls each Layoutable child's
|
381
|
+
# expand_to_max_width! with the new width.
|
382
|
+
#
|
383
|
+
# Returns free_width.
|
384
|
+
#
|
385
|
+
def expand_to_max_width!(given_width=nil)
|
386
|
+
if expand_width
|
387
|
+
remove_event_listener(:resize, @__resize_handler)
|
388
|
+
if given_width
|
389
|
+
@width = given_width
|
390
|
+
elsif @parent.is_a? Layoutable
|
391
|
+
@width = @parent.expand_to_max_width!
|
392
|
+
elsif @viewport ||= (@parent && @parent.viewport)
|
393
|
+
on_resize(@__resize_handler)
|
394
|
+
@width = @viewport.width
|
395
|
+
end
|
396
|
+
end
|
397
|
+
free_width
|
398
|
+
end
|
399
|
+
|
400
|
+
# Expands self, parent and children to
|
401
|
+
# maximum available height if expand_height is true.
|
402
|
+
#
|
403
|
+
# If expand_height is true, expands to
|
404
|
+
# either the given_height, the result of
|
405
|
+
# calling @parent.expand_to_max_height! or
|
406
|
+
# @viewport.height (in this order.)
|
407
|
+
#
|
408
|
+
# If height changes as a result of this
|
409
|
+
# call, calls each Layoutable child's
|
410
|
+
# expand_to_max_height! with the new height.
|
411
|
+
#
|
412
|
+
# Returns free_height.
|
413
|
+
#
|
414
|
+
def expand_to_max_height!(given_height=nil)
|
415
|
+
if expand_height
|
416
|
+
remove_event_listener(:resize, @__resize_handler)
|
417
|
+
if given_height
|
418
|
+
@height = given_height
|
419
|
+
elsif @parent.is_a? Layoutable
|
420
|
+
@height = @parent.expand_to_max_height!
|
421
|
+
elsif @viewport ||= (@parent && @parent.viewport)
|
422
|
+
on_resize(@__resize_handler)
|
423
|
+
@height = @viewport.height
|
424
|
+
end
|
425
|
+
end
|
426
|
+
free_height
|
427
|
+
end
|
428
|
+
|
429
|
+
def layout
|
430
|
+
@layouted_width = @width
|
431
|
+
@layouted_height = @height
|
432
|
+
layoutable_children.each{|c|
|
433
|
+
case align
|
434
|
+
when :left
|
435
|
+
c.x = padding_left + c.margin_left
|
436
|
+
when :right
|
437
|
+
c.x = width - padding_right - c.full_width
|
438
|
+
when :center
|
439
|
+
c.x = padding_left + c.margin_left + ((inner_width - c.margin_left - c.margin_right) - c.width) / 2.0
|
440
|
+
end
|
441
|
+
case valign
|
442
|
+
when :top
|
443
|
+
c.y = padding_top + c.margin_top
|
444
|
+
when :bottom
|
445
|
+
c.y = height - padding_bottom - c.full_height
|
446
|
+
when :middle
|
447
|
+
c.y = padding_top + c.margin_top + ((inner_height - c.margin_top - c.margin_bottom) - c.height) / 2.0
|
448
|
+
end
|
449
|
+
}
|
450
|
+
end
|
451
|
+
|
452
|
+
# Returns the maximum width an expanding child can expand to.
|
453
|
+
def free_width
|
454
|
+
inner_width
|
455
|
+
end
|
456
|
+
|
457
|
+
# Returns the maximum height an expanding child can expand to.
|
458
|
+
def free_height
|
459
|
+
inner_height
|
460
|
+
end
|
461
|
+
|
462
|
+
# Width sans padding.
|
463
|
+
def inner_width
|
464
|
+
@width - @padding_left - @padding_right
|
465
|
+
end
|
466
|
+
|
467
|
+
# Height sans padding.
|
468
|
+
def inner_height
|
469
|
+
@height - @padding_bottom - @padding_top
|
470
|
+
end
|
471
|
+
|
472
|
+
# Depth sans padding.
|
473
|
+
def inner_depth
|
474
|
+
@depth - @padding_front - @padding_back
|
475
|
+
end
|
476
|
+
|
477
|
+
# Width, scaled, plus margins.
|
478
|
+
def full_width
|
479
|
+
case scale
|
480
|
+
when Numeric
|
481
|
+
width * scale
|
482
|
+
when Array
|
483
|
+
width * scale[0]
|
484
|
+
else
|
485
|
+
width
|
486
|
+
end + @margin_left + @margin_right
|
487
|
+
end
|
488
|
+
|
489
|
+
# Height, scaled, plus margins.
|
490
|
+
def full_height
|
491
|
+
case scale
|
492
|
+
when Numeric
|
493
|
+
height * scale
|
494
|
+
when Array
|
495
|
+
height * scale[1]
|
496
|
+
else
|
497
|
+
height
|
498
|
+
end + @margin_top + @margin_bottom
|
499
|
+
end
|
500
|
+
|
501
|
+
def full_depth
|
502
|
+
case scale
|
503
|
+
when Numeric
|
504
|
+
depth * scale
|
505
|
+
when Array
|
506
|
+
depth * scale[2]
|
507
|
+
else
|
508
|
+
depth
|
509
|
+
end + @margin_front + @margin_back
|
510
|
+
end
|
511
|
+
|
512
|
+
def inspect
|
513
|
+
super[0..-2] + "::w#@width:h#@height>"
|
514
|
+
end
|
515
|
+
|
516
|
+
end
|
517
|
+
|
518
|
+
|
519
|
+
end
|
520
|
+
|