fidgit 0.0.2alpha
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +8 -0
- data/.rspec +2 -0
- data/COPYING.txt +674 -0
- data/Gemfile +4 -0
- data/README.textile +138 -0
- data/Rakefile +38 -0
- data/config/default_schema.yml +180 -0
- data/examples/_all_examples.rb +9 -0
- data/examples/align_example.rb +56 -0
- data/examples/button_and_toggle_button_example.rb +27 -0
- data/examples/color_picker_example.rb +17 -0
- data/examples/color_well_example.rb +25 -0
- data/examples/combo_box_example.rb +24 -0
- data/examples/file_dialog_example.rb +42 -0
- data/examples/grid_packer_example.rb +29 -0
- data/examples/helpers/example_window.rb +17 -0
- data/examples/label_example.rb +17 -0
- data/examples/list_example.rb +23 -0
- data/examples/media/images/head_icon.png +0 -0
- data/examples/menu_pane_example.rb +27 -0
- data/examples/message_dialog_example.rb +65 -0
- data/examples/radio_button_example.rb +37 -0
- data/examples/readme_example.rb +32 -0
- data/examples/scroll_window_example.rb +49 -0
- data/examples/slider_example.rb +30 -0
- data/examples/splash_example.rb +42 -0
- data/examples/text_area_example.rb +28 -0
- data/fidgit.gemspec +28 -0
- data/lib/fidgit.rb +4 -0
- data/lib/fidgit/chingu_ext/window.rb +6 -0
- data/lib/fidgit/clipboard.rb +23 -0
- data/lib/fidgit/cursor.rb +38 -0
- data/lib/fidgit/elements/button.rb +68 -0
- data/lib/fidgit/elements/color_picker.rb +63 -0
- data/lib/fidgit/elements/color_well.rb +39 -0
- data/lib/fidgit/elements/combo_box.rb +85 -0
- data/lib/fidgit/elements/composite.rb +17 -0
- data/lib/fidgit/elements/container.rb +187 -0
- data/lib/fidgit/elements/element.rb +252 -0
- data/lib/fidgit/elements/file_browser.rb +152 -0
- data/lib/fidgit/elements/grid_packer.rb +219 -0
- data/lib/fidgit/elements/group.rb +66 -0
- data/lib/fidgit/elements/horizontal_packer.rb +12 -0
- data/lib/fidgit/elements/label.rb +77 -0
- data/lib/fidgit/elements/list.rb +47 -0
- data/lib/fidgit/elements/menu_pane.rb +149 -0
- data/lib/fidgit/elements/packer.rb +42 -0
- data/lib/fidgit/elements/radio_button.rb +86 -0
- data/lib/fidgit/elements/scroll_area.rb +75 -0
- data/lib/fidgit/elements/scroll_bar.rb +114 -0
- data/lib/fidgit/elements/scroll_window.rb +92 -0
- data/lib/fidgit/elements/slider.rb +119 -0
- data/lib/fidgit/elements/text_area.rb +351 -0
- data/lib/fidgit/elements/toggle_button.rb +67 -0
- data/lib/fidgit/elements/tool_tip.rb +35 -0
- data/lib/fidgit/elements/vertical_packer.rb +12 -0
- data/lib/fidgit/event.rb +99 -0
- data/lib/fidgit/gosu_ext/color.rb +123 -0
- data/lib/fidgit/history.rb +85 -0
- data/lib/fidgit/redirector.rb +83 -0
- data/lib/fidgit/schema.rb +123 -0
- data/lib/fidgit/selection.rb +106 -0
- data/lib/fidgit/standard_ext/hash.rb +21 -0
- data/lib/fidgit/states/dialog_state.rb +42 -0
- data/lib/fidgit/states/file_dialog.rb +24 -0
- data/lib/fidgit/states/gui_state.rb +301 -0
- data/lib/fidgit/states/message_dialog.rb +61 -0
- data/lib/fidgit/thumbnail.rb +29 -0
- data/lib/fidgit/version.rb +5 -0
- data/lib/fidgit/window.rb +19 -0
- data/media/images/arrow.png +0 -0
- data/media/images/file_directory.png +0 -0
- data/media/images/file_file.png +0 -0
- data/media/images/pixel.png +0 -0
- data/spec/fidgit/elements/helpers/helper.rb +3 -0
- data/spec/fidgit/elements/label_spec.rb +49 -0
- data/spec/fidgit/event_spec.rb +149 -0
- data/spec/fidgit/gosu_ext/color_spec.rb +130 -0
- data/spec/fidgit/gosu_ext/helpers/helper.rb +3 -0
- data/spec/fidgit/helpers/helper.rb +4 -0
- data/spec/fidgit/helpers/tex_play_helper.rb +9 -0
- data/spec/fidgit/history_spec.rb +144 -0
- data/spec/fidgit/redirector_spec.rb +78 -0
- data/spec/fidgit/schema_spec.rb +67 -0
- data/spec/fidgit/schema_test.yml +32 -0
- data/spec/fidgit/thumbnail_spec.rb +50 -0
- metadata +177 -0
data/lib/fidgit/event.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Fidgit
|
4
|
+
# Adds simple event handling methods to an object (subscribe/publish pattern).
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# class JumpingBean
|
8
|
+
# include Event
|
9
|
+
# event :jump
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# bean = JumpingBean.new
|
13
|
+
# bean.subscribe :jump do
|
14
|
+
# puts "Whee!"
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# bean.subscribe :jump do |object, direction, distance|
|
18
|
+
# puts "#{object.class.name} jumped #{distance} metres #{direction}"
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# bean.publish :jump, :up, 4
|
22
|
+
# # Whee!
|
23
|
+
# # JumpingBean jumped 4 metres up
|
24
|
+
#
|
25
|
+
module Event
|
26
|
+
# @overload subscribe(event, method)
|
27
|
+
# Add an event handler for an event, using a method.
|
28
|
+
# @return [nil]
|
29
|
+
#
|
30
|
+
# @overload subscribe(event, &block)
|
31
|
+
# Add an event handler for an event, using a block.
|
32
|
+
# @return [nil]
|
33
|
+
def subscribe(event, method = nil, &block)
|
34
|
+
raise ArgumentError, "Expected method or block for event handler" unless !block.nil? ^ !method.nil?
|
35
|
+
raise ArgumentError, "#{self.class} does not handle #{event.inspect}" unless events.include? event
|
36
|
+
|
37
|
+
@_event_handlers = Hash.new() { |hash, key| hash[key] = [] } unless @_event_handlers
|
38
|
+
@_event_handlers[event].push(method ? method : block)
|
39
|
+
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
|
43
|
+
# Publish an event to all previously added handlers in the order they were added.
|
44
|
+
# It will automatically call the publishing object with the method named after the event if it is defined
|
45
|
+
# (this will be done before the manually added handlers are called).
|
46
|
+
#
|
47
|
+
# If any handler returns :handled, then no further handlers will be called.
|
48
|
+
#
|
49
|
+
# @param [Symbol] event Name of the event to publish.
|
50
|
+
# @param [Array] args Arguments to pass to the event handlers.
|
51
|
+
# @return [Symbol, nil] :handled if any handler handled the event or nil if none did.
|
52
|
+
def publish(event, *args)
|
53
|
+
raise ArgumentError, "#{self.class} does not handle #{event.inspect}" unless events.include? event
|
54
|
+
|
55
|
+
# Do nothing if the object is disabled.
|
56
|
+
return if respond_to?(:enabled?) and not enabled?
|
57
|
+
|
58
|
+
if respond_to? event
|
59
|
+
return :handled if send(event, self, *args) == :handled
|
60
|
+
end
|
61
|
+
|
62
|
+
if @_event_handlers
|
63
|
+
@_event_handlers[event].reverse_each do |handler|
|
64
|
+
return :handled if handler.call(self, *args) == :handled
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
|
71
|
+
# The list of events that this object can publish/subscribe.
|
72
|
+
def events
|
73
|
+
self.class.events
|
74
|
+
end
|
75
|
+
|
76
|
+
# Add singleton methods to the class that includes Event.
|
77
|
+
def self.included(base)
|
78
|
+
class << base
|
79
|
+
def events
|
80
|
+
unless @events
|
81
|
+
# Copy the events already set up for your parent.
|
82
|
+
@events = if superclass.respond_to? :events
|
83
|
+
superclass.events.dup
|
84
|
+
else
|
85
|
+
[]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
@events
|
90
|
+
end
|
91
|
+
|
92
|
+
def event(event)
|
93
|
+
events.push event.to_sym unless events.include? event
|
94
|
+
event
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Gosu
|
4
|
+
class Color
|
5
|
+
# Is the color completely transparent?
|
6
|
+
def transparent?; alpha == 0; end
|
7
|
+
# Is the color completely opaque?
|
8
|
+
def opaque?; alpha == 255; end
|
9
|
+
|
10
|
+
# RGB in 0..255 format (Alpha assumed 255)
|
11
|
+
#
|
12
|
+
# @param [Integer] red
|
13
|
+
# @param [Integer] green
|
14
|
+
# @param [Integer] blue
|
15
|
+
# @return [Color]
|
16
|
+
def self.rgb(red, green, blue)
|
17
|
+
new(255, red, green, blue)
|
18
|
+
end
|
19
|
+
|
20
|
+
# RGBA in 0..255 format
|
21
|
+
#
|
22
|
+
# @param [Integer] red
|
23
|
+
# @param [Integer] green
|
24
|
+
# @param [Integer] blue
|
25
|
+
# @param [Integer] alpha
|
26
|
+
# @return [Color]
|
27
|
+
def self.rgba(red, green, blue, alpha)
|
28
|
+
new(alpha, red, green, blue)
|
29
|
+
end
|
30
|
+
|
31
|
+
# ARGB in 0..255 format (equivalent to Color.new, but explicit)
|
32
|
+
#
|
33
|
+
# @param [Integer] alpha
|
34
|
+
# @param (see Color.rgb)
|
35
|
+
# @return [Color]
|
36
|
+
def self.argb(alpha, red, green, blue)
|
37
|
+
new(alpha, red, green, blue)
|
38
|
+
end
|
39
|
+
|
40
|
+
# HSV format (alpha assumed to be 255)
|
41
|
+
#
|
42
|
+
# @param [Float] hue 0.0..360.0
|
43
|
+
# @param [Float] saturation 0.0..1.0
|
44
|
+
# @param [Float] value 0.0..1.0
|
45
|
+
# @return [Color]
|
46
|
+
def self.hsv(hue, saturation, value)
|
47
|
+
from_hsv(hue, saturation, value)
|
48
|
+
end
|
49
|
+
|
50
|
+
# HSVA format
|
51
|
+
#
|
52
|
+
# @param [Float] hue 0.0..360.0
|
53
|
+
# @param [Float] saturation 0.0..1.0
|
54
|
+
# @param [Float] value 0.0..1.0
|
55
|
+
# @param [Integer] alpha 1..255
|
56
|
+
# @return [Color]
|
57
|
+
def self.hsva(hue, saturation, value, alpha)
|
58
|
+
from_ahsv(alpha, hue, saturation, value)
|
59
|
+
end
|
60
|
+
|
61
|
+
class << self
|
62
|
+
alias_method :ahsv, :from_ahsv
|
63
|
+
end
|
64
|
+
|
65
|
+
# Convert from an RGBA array, as used by TexPlay.
|
66
|
+
#
|
67
|
+
# @param [Array<Float>] color TexPlay color [r, g, b, a] in range 0.0..1.0
|
68
|
+
# @return [Color]
|
69
|
+
def self.from_tex_play(color)
|
70
|
+
rgba(*color.map {|c| (c * 255).to_i })
|
71
|
+
end
|
72
|
+
|
73
|
+
# Convert to an RGBA array, as used by TexPlay.
|
74
|
+
#
|
75
|
+
# @return [Array<Float>] TexPlay color array [r, g, b, a] in range 0.0..1.0
|
76
|
+
def to_tex_play
|
77
|
+
[red / 255.0, green / 255.0, blue / 255.0, alpha / 255.0]
|
78
|
+
end
|
79
|
+
|
80
|
+
# Show the Color as <RGBA [0, 0, 0, 0]> or, if opaque, <RGB [0, 0, 0]> (Gosu default is '(ARGB:0/0/0/0)')
|
81
|
+
def to_s
|
82
|
+
if opaque?
|
83
|
+
"<RGB [#{red}, #{green}, #{blue}]>"
|
84
|
+
else
|
85
|
+
"<RGBA [#{red}, #{green}, #{blue}, #{alpha}]>"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def +(other)
|
90
|
+
raise ArgumentError, "Can only add another #{self.class}" unless other.is_a? Color
|
91
|
+
|
92
|
+
copy = Color.new(0)
|
93
|
+
|
94
|
+
copy.red = [red + other.red, 255].min
|
95
|
+
copy.green = [green + other.green, 255].min
|
96
|
+
copy.blue = [blue + other.blue, 255].min
|
97
|
+
copy.alpha = [alpha + other.alpha, 255].min
|
98
|
+
|
99
|
+
copy
|
100
|
+
end
|
101
|
+
|
102
|
+
def -(other)
|
103
|
+
raise ArgumentError, "Can only take away another #{self.class}" unless other.is_a? Color
|
104
|
+
|
105
|
+
copy = Color.new(0)
|
106
|
+
|
107
|
+
copy.red = [red - other.red, 0].max
|
108
|
+
copy.green = [green - other.green, 0].max
|
109
|
+
copy.blue = [blue - other.blue, 0].max
|
110
|
+
copy.alpha = [alpha - other.alpha, 0].max
|
111
|
+
|
112
|
+
copy
|
113
|
+
end
|
114
|
+
|
115
|
+
def ==(other)
|
116
|
+
if other.is_a? Color
|
117
|
+
red == other.red and green == other.green and blue == other.blue and alpha == other.alpha
|
118
|
+
else
|
119
|
+
false
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Fidgit
|
4
|
+
# Manages a history of actions, along with doing, undoing and redoing those actions.
|
5
|
+
class History
|
6
|
+
# Maximum number of actions in the History before Actions are deleted.
|
7
|
+
DEFAULT_MAX_SIZE = 250
|
8
|
+
|
9
|
+
# An action in the History. Inherit actions from this in order to add them to a History.
|
10
|
+
class Action
|
11
|
+
# Perform the action.
|
12
|
+
def do; raise NotImplementedError, "#{self.class} does not have a do method defined"; end
|
13
|
+
|
14
|
+
# Reverse the action.
|
15
|
+
def undo; raise NotImplementedError, "#{self.class} does not have an undo method defined"; end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Is there an action that can be undone?
|
19
|
+
def can_undo?; @last_done >= 0; end
|
20
|
+
|
21
|
+
# Is there an action that has been undone that can now be redone?
|
22
|
+
def can_redo?; @last_done < (@actions.size - 1); end
|
23
|
+
|
24
|
+
def initialize(max_size = DEFAULT_MAX_SIZE)
|
25
|
+
@max_size = max_size
|
26
|
+
@actions = []
|
27
|
+
@last_done = -1 # Last command that was performed.
|
28
|
+
end
|
29
|
+
|
30
|
+
# Perform a History::Action, adding it to the history.
|
31
|
+
# If there are currently any actions that have been undone, they will be permanently lost and cannot be redone.
|
32
|
+
#
|
33
|
+
# @param [History::Action] action Action to be performed
|
34
|
+
def do(action)
|
35
|
+
raise ArgumentError, "Parameter, 'action', expected to be a #{Action}, but received: #{action}" unless action.is_a? Action
|
36
|
+
|
37
|
+
# Remove all undone actions when a new one is performed.
|
38
|
+
@actions = @actions[0..@last_done] if can_redo?
|
39
|
+
|
40
|
+
# If history is too big, remove the oldest action.
|
41
|
+
if @actions.size >= @max_size
|
42
|
+
@actions.shift
|
43
|
+
end
|
44
|
+
|
45
|
+
@last_done = @actions.size
|
46
|
+
@actions << action
|
47
|
+
action.do
|
48
|
+
|
49
|
+
nil
|
50
|
+
end
|
51
|
+
|
52
|
+
# Perform a History::Action, replacing the last action that was performed.
|
53
|
+
#
|
54
|
+
# @param [History::Action] action Action to be performed
|
55
|
+
def replace_last(action)
|
56
|
+
raise ArgumentError, "Parameter, 'action', expected to be a #{Action}, but received: #{action}" unless action.is_a? Action
|
57
|
+
|
58
|
+
@actions[@last_done].undo
|
59
|
+
@actions[@last_done] = action
|
60
|
+
action.do
|
61
|
+
|
62
|
+
nil
|
63
|
+
end
|
64
|
+
|
65
|
+
# Undo the last action that was performed.
|
66
|
+
def undo
|
67
|
+
raise "Can't undo unless there are commands in past" unless can_undo?
|
68
|
+
|
69
|
+
@actions[@last_done].undo
|
70
|
+
@last_done -= 1
|
71
|
+
|
72
|
+
nil
|
73
|
+
end
|
74
|
+
|
75
|
+
# Redo the last action that was undone.
|
76
|
+
def redo
|
77
|
+
raise "Can't redo if there are no commands in the future" unless can_redo?
|
78
|
+
|
79
|
+
@last_done += 1
|
80
|
+
@actions[@last_done].do
|
81
|
+
|
82
|
+
nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Fidgit
|
2
|
+
# Redirects methods to an object, but does not mask methods and ivars from the calling context.
|
3
|
+
module RedirectorMethods
|
4
|
+
# Evaluate a block accessing methods and ivars from the calling context, but calling public methods
|
5
|
+
# (not ivars or non-public methods) on this object in preference.
|
6
|
+
def instance_methods_eval(&block)
|
7
|
+
raise ArgumentError, "block required" unless block_given?
|
8
|
+
|
9
|
+
context = eval('self', block.binding)
|
10
|
+
|
11
|
+
context.send :push_redirection_target, self
|
12
|
+
|
13
|
+
begin
|
14
|
+
yield context
|
15
|
+
ensure
|
16
|
+
context.send :pop_redirection_target
|
17
|
+
end
|
18
|
+
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
def push_redirection_target(target)
|
24
|
+
meta_class = class << self; self; end
|
25
|
+
base_methods = Object.public_instance_methods
|
26
|
+
|
27
|
+
# Redirect just the public methods of the target, less those that are on Object.
|
28
|
+
methods_to_redirect = target.public_methods - base_methods
|
29
|
+
|
30
|
+
# Only hide those public/private/protected methods that are being redirected.
|
31
|
+
methods_overridden = []
|
32
|
+
[:public, :protected, :private].each do |access|
|
33
|
+
methods_to_hide = meta_class.send("#{access}_instance_methods", false) & methods_to_redirect
|
34
|
+
methods_to_hide.each do |meth|
|
35
|
+
# Take a reference to the method we are about to override.
|
36
|
+
methods_overridden.push [meth, method(meth), access]
|
37
|
+
meta_class.send :remove_method, meth
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Add a method, to redirect calls to the target.
|
42
|
+
methods_to_redirect.each do |meth|
|
43
|
+
meta_class.send :define_method, meth do |*args, &block|
|
44
|
+
target.send meth, *args, &block
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
redirection_stack.push [target, methods_overridden, methods_to_redirect]
|
49
|
+
|
50
|
+
target
|
51
|
+
end
|
52
|
+
|
53
|
+
protected
|
54
|
+
def pop_redirection_target
|
55
|
+
meta_class = class << self; self; end
|
56
|
+
|
57
|
+
target, methods_to_recreate, methods_to_remove = redirection_stack.pop
|
58
|
+
|
59
|
+
# Remove the redirection methods
|
60
|
+
methods_to_remove.reverse_each do |meth|
|
61
|
+
meta_class.send :remove_method, meth
|
62
|
+
end
|
63
|
+
|
64
|
+
# Replace with the previous versions of the methods.
|
65
|
+
methods_to_recreate.reverse_each do |meth, reference, access|
|
66
|
+
meta_class.send :define_method, meth, reference
|
67
|
+
meta_class.send access, meth unless access == :public
|
68
|
+
end
|
69
|
+
|
70
|
+
target
|
71
|
+
end
|
72
|
+
|
73
|
+
# Direct access to the redirection stack.
|
74
|
+
private
|
75
|
+
def redirection_stack
|
76
|
+
@_redirection_stack ||= []
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class Object
|
82
|
+
include Fidgit::RedirectorMethods
|
83
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Fidgit
|
4
|
+
# An object that manages Schema values. Usually loaded from a YAML file.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# schema = Schema.new(YAML.load(file.read('default_schema.yml')))
|
8
|
+
# default_color = schema.default(Element, :disabled, :color)
|
9
|
+
# schema.merge_schema!(YAML.load(file.read('override_schema.yml'))
|
10
|
+
# overridden_color = schema.default(Element, :disabled, :color)
|
11
|
+
class Schema
|
12
|
+
CONSTANT_PREFIX = '?'
|
13
|
+
|
14
|
+
# @param [Hash<Symbol => Hash>] schema data containing
|
15
|
+
def initialize(schema)
|
16
|
+
@constants = {}
|
17
|
+
@elements = {}
|
18
|
+
|
19
|
+
merge_schema! schema
|
20
|
+
end
|
21
|
+
|
22
|
+
# Merge in a hash containing constant values.
|
23
|
+
#
|
24
|
+
# @param [Hash<Symbol => Hash>] constants_hash Containing :colors, :constants and :elements hashes.
|
25
|
+
def merge_schema!(schema)
|
26
|
+
merge_constants!(schema[:constants]) if schema[:constants]
|
27
|
+
merge_elements!(schema[:elements]) if schema[:elements]
|
28
|
+
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
# Merge in a hash containing constant values. Arrays will be resolved as colors in RGBA or RGB format.
|
33
|
+
#
|
34
|
+
# @param [Hash<Symbol => Object>] constants_hash
|
35
|
+
def merge_constants!(constants_hash)
|
36
|
+
constants_hash.each_pair do |name, value|
|
37
|
+
@constants[name] = case value
|
38
|
+
when Array
|
39
|
+
case value.size
|
40
|
+
when 3 then Gosu::Color.rgb(*value)
|
41
|
+
when 4 then Gosu::Color.rgba(*value)
|
42
|
+
else
|
43
|
+
raise "Colors must be in 0..255, RGB or RGBA array format"
|
44
|
+
end
|
45
|
+
else
|
46
|
+
value
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
# Merge in a hash containing default values for each element.
|
54
|
+
#
|
55
|
+
# @param [Hash<Symbol => Hash>] elements_hash
|
56
|
+
def merge_elements!(elements_hash)
|
57
|
+
elements_hash.each_pair do |klass_names, data|
|
58
|
+
klass = Fidgit
|
59
|
+
klass_names.to_s.split('::').each do |klass_name|
|
60
|
+
klass = klass.const_get klass_name
|
61
|
+
end
|
62
|
+
|
63
|
+
raise "elements must be names of classes derived from #{Element}" unless klass.ancestors.include? Fidgit::Element
|
64
|
+
@elements[klass] ||= {}
|
65
|
+
@elements[klass].deep_merge! data
|
66
|
+
end
|
67
|
+
|
68
|
+
self
|
69
|
+
end
|
70
|
+
|
71
|
+
# Get the constant value associated with +name+.
|
72
|
+
#
|
73
|
+
# @param [Symbol] name
|
74
|
+
# @return [Object]
|
75
|
+
def constant(name)
|
76
|
+
@constants[name]
|
77
|
+
end
|
78
|
+
|
79
|
+
# @param [Class] klass Class to look for defaults for.
|
80
|
+
# @param [Symbol, Array<Symbol>] names Hash names to search for in that class's schema.
|
81
|
+
def default(klass, names)
|
82
|
+
raise ArgumentError, "#{klass} is not a descendent of the #{Element} class" unless klass.ancestors.include? Element
|
83
|
+
value = default_internal(klass, Array(names), true)
|
84
|
+
raise("Failed to find named value #{names.inspect} for class #{klass}") unless value
|
85
|
+
value
|
86
|
+
end
|
87
|
+
|
88
|
+
protected
|
89
|
+
# @param [Class] klass Class to look for defaults for.
|
90
|
+
# @param [Array<Symbol>] names Hash names to search for in that class's schema.
|
91
|
+
# @param [Boolean] default_to_outer Whether to default to an outer value (used internally)
|
92
|
+
def default_internal(klass, names, default_to_outer)
|
93
|
+
# Find the value by moving through the nested hash via the names.
|
94
|
+
value = @elements[klass]
|
95
|
+
|
96
|
+
names.each do |name|
|
97
|
+
break unless value.is_a? Hash
|
98
|
+
value = value.has_key?(name) ? value[name] : nil
|
99
|
+
end
|
100
|
+
|
101
|
+
# Convert the value to a color/constant if they are symbols.
|
102
|
+
value = if value.is_a? String and value[0] == CONSTANT_PREFIX
|
103
|
+
str = value[1..-1]
|
104
|
+
constant(str.to_sym) || value # If the value isn't a constant, return the string.
|
105
|
+
else
|
106
|
+
value
|
107
|
+
end
|
108
|
+
|
109
|
+
# If we didn't find the value for this class, default to parent class value.
|
110
|
+
if value.nil? and klass != Element and klass.ancestors.include? Element
|
111
|
+
# Check if any ancestors define the fully named value.
|
112
|
+
value = default_internal(klass.superclass, names, false)
|
113
|
+
end
|
114
|
+
|
115
|
+
if value.nil? and default_to_outer and names.size > 1
|
116
|
+
# Check the outer values (e.g. if [:hover, :color] is not defined, try [:color]).
|
117
|
+
value = default_internal(klass, names[1..-1], true)
|
118
|
+
end
|
119
|
+
|
120
|
+
value
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|