fidgit 0.0.2alpha
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/.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
|