metro-ld26 0.3.4

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.
Files changed (185) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +6 -0
  5. data/Gemfile +14 -0
  6. data/Guardfile +4 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +177 -0
  9. data/Rakefile +18 -0
  10. data/bin/metro +16 -0
  11. data/changelog.md +153 -0
  12. data/lib/assets/menu-movement.wav +0 -0
  13. data/lib/assets/menu-selection.wav +0 -0
  14. data/lib/assets/missing.ogg +0 -0
  15. data/lib/assets/missing.png +0 -0
  16. data/lib/assets/missing.wav +0 -0
  17. data/lib/assets/missing_animation.png +0 -0
  18. data/lib/commands/generate_game.rb +13 -0
  19. data/lib/commands/generate_model.rb +25 -0
  20. data/lib/commands/generate_scene.rb +36 -0
  21. data/lib/commands/generate_view.rb +21 -0
  22. data/lib/commands/thor.rb +83 -0
  23. data/lib/core_ext/class.rb +14 -0
  24. data/lib/core_ext/numeric.rb +59 -0
  25. data/lib/gosu_ext/color.rb +62 -0
  26. data/lib/gosu_ext/gosu_constants.rb +53 -0
  27. data/lib/locale/en.yml +35 -0
  28. data/lib/locale/locale.rb +1 -0
  29. data/lib/metro.rb +144 -0
  30. data/lib/metro/animation.rb +135 -0
  31. data/lib/metro/animation/after_interval_factory.rb +12 -0
  32. data/lib/metro/animation/animation_factory.rb +15 -0
  33. data/lib/metro/animation/easing/ease_in.rb +15 -0
  34. data/lib/metro/animation/easing/easing.rb +51 -0
  35. data/lib/metro/animation/easing/linear.rb +15 -0
  36. data/lib/metro/animation/has_animations.rb +70 -0
  37. data/lib/metro/animation/implicit_animation.rb +100 -0
  38. data/lib/metro/animation/on_update_operation.rb +96 -0
  39. data/lib/metro/animation/scene_animation.rb +16 -0
  40. data/lib/metro/asset_path.rb +97 -0
  41. data/lib/metro/events/control_definition.rb +11 -0
  42. data/lib/metro/events/controls.rb +42 -0
  43. data/lib/metro/events/event_data.rb +60 -0
  44. data/lib/metro/events/event_dictionary.rb +52 -0
  45. data/lib/metro/events/event_factory.rb +17 -0
  46. data/lib/metro/events/event_relay.rb +342 -0
  47. data/lib/metro/events/event_state_manager.rb +70 -0
  48. data/lib/metro/events/events.rb +3 -0
  49. data/lib/metro/events/has_events.rb +108 -0
  50. data/lib/metro/events/hit_list.rb +75 -0
  51. data/lib/metro/events/unknown_sender.rb +5 -0
  52. data/lib/metro/font.rb +69 -0
  53. data/lib/metro/game.rb +102 -0
  54. data/lib/metro/game/dsl.rb +68 -0
  55. data/lib/metro/image.rb +75 -0
  56. data/lib/metro/logging.rb +33 -0
  57. data/lib/metro/missing_scene.rb +21 -0
  58. data/lib/metro/models/audio/song.rb +33 -0
  59. data/lib/metro/models/draws.rb +86 -0
  60. data/lib/metro/models/key_value_coding.rb +38 -0
  61. data/lib/metro/models/model.rb +246 -0
  62. data/lib/metro/models/model_factory.rb +32 -0
  63. data/lib/metro/models/models.rb +62 -0
  64. data/lib/metro/models/properties/animation_property.rb +115 -0
  65. data/lib/metro/models/properties/array_property.rb +24 -0
  66. data/lib/metro/models/properties/boolean_property.rb +27 -0
  67. data/lib/metro/models/properties/color_property.rb +116 -0
  68. data/lib/metro/models/properties/dimensions_property.rb +84 -0
  69. data/lib/metro/models/properties/font_property.rb +130 -0
  70. data/lib/metro/models/properties/image_property.rb +96 -0
  71. data/lib/metro/models/properties/model_property.rb +84 -0
  72. data/lib/metro/models/properties/numeric_property.rb +29 -0
  73. data/lib/metro/models/properties/options_property/no_option.rb +29 -0
  74. data/lib/metro/models/properties/options_property/options.rb +98 -0
  75. data/lib/metro/models/properties/options_property/options_property.rb +125 -0
  76. data/lib/metro/models/properties/position_property.rb +90 -0
  77. data/lib/metro/models/properties/property.rb +221 -0
  78. data/lib/metro/models/properties/property_owner.rb +137 -0
  79. data/lib/metro/models/properties/sample_property.rb +84 -0
  80. data/lib/metro/models/properties/scale_property.rb +80 -0
  81. data/lib/metro/models/properties/song_property.rb +89 -0
  82. data/lib/metro/models/properties/text_property.rb +75 -0
  83. data/lib/metro/models/ui/animated_sprite.rb +85 -0
  84. data/lib/metro/models/ui/border.rb +95 -0
  85. data/lib/metro/models/ui/fps.rb +54 -0
  86. data/lib/metro/models/ui/generic.rb +66 -0
  87. data/lib/metro/models/ui/grid_drawer.rb +74 -0
  88. data/lib/metro/models/ui/image.rb +87 -0
  89. data/lib/metro/models/ui/label.rb +175 -0
  90. data/lib/metro/models/ui/menu.rb +214 -0
  91. data/lib/metro/models/ui/model_label.rb +65 -0
  92. data/lib/metro/models/ui/model_labeler.rb +79 -0
  93. data/lib/metro/models/ui/rectangle.rb +59 -0
  94. data/lib/metro/models/ui/sprite.rb +79 -0
  95. data/lib/metro/models/ui/tile_map.rb +132 -0
  96. data/lib/metro/models/ui/tmx/isometric_position.rb +43 -0
  97. data/lib/metro/models/ui/tmx/orthogonal_position.rb +15 -0
  98. data/lib/metro/models/ui/tmx/tile_layer.rb +78 -0
  99. data/lib/metro/models/ui/ui.rb +13 -0
  100. data/lib/metro/parameters/command_line_args_parser.rb +68 -0
  101. data/lib/metro/parameters/options.rb +25 -0
  102. data/lib/metro/parameters/parameters.rb +2 -0
  103. data/lib/metro/sample.rb +40 -0
  104. data/lib/metro/scene.rb +478 -0
  105. data/lib/metro/scenes.rb +154 -0
  106. data/lib/metro/song.rb +56 -0
  107. data/lib/metro/template_message.rb +60 -0
  108. data/lib/metro/transitions/edit_transition_scene.rb +100 -0
  109. data/lib/metro/transitions/fade_transition_scene.rb +66 -0
  110. data/lib/metro/transitions/scene_transitions.rb +44 -0
  111. data/lib/metro/transitions/transition_scene.rb +19 -0
  112. data/lib/metro/units/bounds.rb +8 -0
  113. data/lib/metro/units/calculation_validations.rb +74 -0
  114. data/lib/metro/units/dimensions.rb +60 -0
  115. data/lib/metro/units/point.rb +51 -0
  116. data/lib/metro/units/rectangle_bounds.rb +148 -0
  117. data/lib/metro/units/scale.rb +46 -0
  118. data/lib/metro/units/units.rb +6 -0
  119. data/lib/metro/version.rb +32 -0
  120. data/lib/metro/views/json_view.rb +60 -0
  121. data/lib/metro/views/no_view.rb +34 -0
  122. data/lib/metro/views/parsers.rb +42 -0
  123. data/lib/metro/views/scene_view.rb +107 -0
  124. data/lib/metro/views/view.rb +133 -0
  125. data/lib/metro/views/writers.rb +43 -0
  126. data/lib/metro/views/yaml_view.rb +94 -0
  127. data/lib/metro/window.rb +95 -0
  128. data/lib/setup_handlers/exit_if_dry_run.rb +26 -0
  129. data/lib/setup_handlers/game_execution.rb +65 -0
  130. data/lib/setup_handlers/load_game_configuration.rb +65 -0
  131. data/lib/setup_handlers/load_game_files.rb +101 -0
  132. data/lib/setup_handlers/move_to_game_directory.rb +25 -0
  133. data/lib/setup_handlers/reload_game_on_game_file_changes.rb +79 -0
  134. data/lib/templates/game/README.md.tt +43 -0
  135. data/lib/templates/game/assets/brand.jpg +0 -0
  136. data/lib/templates/game/assets/hero.png +0 -0
  137. data/lib/templates/game/lib/custom_easing.rb +32 -0
  138. data/lib/templates/game/metro.tt +63 -0
  139. data/lib/templates/game/models/hero.rb +62 -0
  140. data/lib/templates/game/scenes/brand_scene.rb +19 -0
  141. data/lib/templates/game/scenes/brand_to_title_scene.rb +13 -0
  142. data/lib/templates/game/scenes/first_scene.rb +28 -0
  143. data/lib/templates/game/scenes/game_scene.rb +43 -0
  144. data/lib/templates/game/scenes/title_scene.rb +15 -0
  145. data/lib/templates/game/views/brand.yaml +4 -0
  146. data/lib/templates/game/views/brand_to_title.yaml +8 -0
  147. data/lib/templates/game/views/first.yaml +26 -0
  148. data/lib/templates/game/views/title.yaml +11 -0
  149. data/lib/templates/message.erb +23 -0
  150. data/lib/templates/model.rb.tt +111 -0
  151. data/lib/templates/scene.rb.tt +140 -0
  152. data/lib/templates/view.yaml.tt +11 -0
  153. data/lib/tmx_ext/object.rb +26 -0
  154. data/lib/tmx_ext/tile_set.rb +41 -0
  155. data/metro.gemspec +57 -0
  156. data/metro.png +0 -0
  157. data/spec/core_ext/numeric_spec.rb +78 -0
  158. data/spec/core_ext/string_spec.rb +33 -0
  159. data/spec/gosu_ext/color_spec.rb +80 -0
  160. data/spec/metro/image_spec.rb +33 -0
  161. data/spec/metro/models/key_value_coding_spec.rb +61 -0
  162. data/spec/metro/models/properties/array_property_spec.rb +60 -0
  163. data/spec/metro/models/properties/color_property_spec.rb +85 -0
  164. data/spec/metro/models/properties/dimensions_spec.rb +29 -0
  165. data/spec/metro/models/properties/font_property_spec.rb +127 -0
  166. data/spec/metro/models/properties/numeric_property_spec.rb +46 -0
  167. data/spec/metro/models/properties/options_property/no_option_spec.rb +25 -0
  168. data/spec/metro/models/properties/options_property/options_property_spec.rb +133 -0
  169. data/spec/metro/models/properties/options_property/options_spec.rb +125 -0
  170. data/spec/metro/models/properties/position_property_spec.rb +90 -0
  171. data/spec/metro/models/ui/label_spec.rb +259 -0
  172. data/spec/metro/parameters/command_line_args_parser_spec.rb +42 -0
  173. data/spec/metro/scene_spec.rb +15 -0
  174. data/spec/metro/scene_views/json_view_spec.rb +27 -0
  175. data/spec/metro/scene_views/yaml_view_spec.rb +38 -0
  176. data/spec/metro/scenes_spec.rb +77 -0
  177. data/spec/metro/units/point_spec.rb +132 -0
  178. data/spec/metro/units/rectangle_bounds_spec.rb +56 -0
  179. data/spec/metro/views/view_spec.rb +53 -0
  180. data/spec/setup_handlers/exit_if_dry_run_spec.rb +27 -0
  181. data/spec/setup_handlers/reload_game_on_game_file_changes_spec.rb +68 -0
  182. data/spec/spec_helper.rb +20 -0
  183. data/spec/tmx_ext/object_spec.rb +96 -0
  184. data/spec/tmx_ext/tile_set_spec.rb +24 -0
  185. metadata +379 -0
@@ -0,0 +1,125 @@
1
+ module Metro
2
+ class Model
3
+
4
+ #
5
+ # An options property is a property that takes scene or view defined values
6
+ # and converts them into an Options object.
7
+ #
8
+ # @see Metro::Model::OptionsProperty::Options
9
+ #
10
+ # @example A simple array of option names
11
+ #
12
+ # options: [ 'Start Game', 'Exit' ]
13
+ #
14
+ #
15
+ # @example A set of option names with a selection
16
+ #
17
+ # options:
18
+ # selected: 0
19
+ # items: [ 'Start Game', 'Exit' ]
20
+ #
21
+ # @example A complex set of options
22
+ #
23
+ # options:
24
+ # selected: 1
25
+ # items:
26
+ # -
27
+ # model: "metro::ui::label"
28
+ # text: "Start Game"
29
+ # action: start_game
30
+ # -
31
+ # model: metro::ui::label
32
+ # text: Exit
33
+ # action: exit_game
34
+ #
35
+ class OptionsProperty < Property
36
+
37
+ get do |options|
38
+ Options.empty
39
+ end
40
+
41
+ # This is the basic set of options
42
+ get Array do |options|
43
+ parse_array(options)
44
+ end
45
+
46
+ # This is the complex set of options
47
+ get Hash do |options|
48
+ parse(options)
49
+ end
50
+
51
+ # This is setting the options with the basic set of options
52
+ set Array do |array|
53
+ array
54
+ end
55
+
56
+ # This is setting the options with the complex options
57
+ set Hash, HashWithIndifferentAccess do |hash|
58
+ hash.to_hash
59
+ end
60
+
61
+ private
62
+
63
+ def parse_array(array)
64
+ hash = { items: array }
65
+ parse(hash)
66
+ end
67
+
68
+ #
69
+ # Convert the hash of parameters into an Options object which contains the visual
70
+ # representation of each item within the menu as well as the current selected item.
71
+ #
72
+ def parse(hash)
73
+ hash = hash.with_indifferent_access
74
+ options = create_options(hash[:items])
75
+ options.current_selected_index = hash[:selected]
76
+ options
77
+ end
78
+
79
+ def create_options(items)
80
+ create_options_with_items convert_simple_items(items)
81
+ end
82
+
83
+ #
84
+ # As menu options can be defined in many ways, this method will convert the simple
85
+ # versions of the content to match the full robust version by converting them to a
86
+ # hash and adding the remaining fields.
87
+ #
88
+ def convert_simple_items(items)
89
+ return items if items.first.is_a?(Hash)
90
+ items.map { |text| { model: "metro::ui::label", text: text } }
91
+ end
92
+
93
+ #
94
+ # Generate an Options object with the options specified.
95
+ #
96
+ # @see Options
97
+ #
98
+ def create_options_with_items(options)
99
+ Options.new options.map { |option| create_model_from_item(option) }
100
+ end
101
+
102
+ #
103
+ # From the options provided create the models that will be managed by the Options
104
+ # object.
105
+ #
106
+ def create_model_from_item(options)
107
+ options = options.symbolize_keys
108
+ options[:action] = actionize(options[:action] || options[:text])
109
+ model.create options[:model], options
110
+ end
111
+
112
+ #
113
+ # @return [Symbol] convert a text string into a name which would be a sane method name.
114
+ #
115
+ def actionize(text)
116
+ text.to_s.downcase.gsub(/\s/,'_').gsub(/^[^a-zA-Z]*/,'').gsub(/[^a-zA-Z0-9\s_]/,'').to_sym
117
+ end
118
+
119
+ end
120
+
121
+ end
122
+ end
123
+
124
+ require_relative 'options'
125
+ require_relative 'no_option'
@@ -0,0 +1,90 @@
1
+ module Metro
2
+ class Model
3
+
4
+ #
5
+ # A position property maintains a 2D Point.
6
+ #
7
+ # A position property also defines an `x` property and a `y` property which allows a
8
+ # more direct interface.
9
+ #
10
+ # A position is stored as a string in the properties and retrieved as a Point object.
11
+ #
12
+ # @example Defining a position property, using the x, y and z_order properties
13
+ #
14
+ # class Enemy < Metro::Model
15
+ # property :position
16
+ #
17
+ # def draw
18
+ # # image.draw position.x, position.y, z_order, x_factor, y_factor, color, :add)
19
+ # image.draw x, y, z_order, x_factor, y_factor, color, :add)
20
+ # end
21
+ # end
22
+ #
23
+ # @example Defining a position property providing a default
24
+ #
25
+ # class Enemy < Metro::Model
26
+ # property :position, default: Point.at(44,66)
27
+ # end
28
+ #
29
+ # @example Using a position property with a different property name
30
+ #
31
+ # class Hero < Metro::Model
32
+ # property :pos, type: :position, default: Point.zero
33
+ #
34
+ # def draw
35
+ # # image.draw pos.x, pos.y, pos.z_order, x_factor, y_factor, color, :add)
36
+ # image.draw pos_x, pos_y, pos_z_order, x_factor, y_factor, color, :add)
37
+ # end
38
+ # end
39
+ #
40
+ class PositionProperty < Property
41
+
42
+ # Define an x position property
43
+ define_property :x
44
+
45
+ # Define a y position property
46
+ define_property :y
47
+
48
+ # Define a z position property which is used for the z order
49
+ define_property :z
50
+ define_property :z_order
51
+
52
+ # When no getters match the specified value return the default point
53
+ get do
54
+ default_point
55
+ end
56
+
57
+ # When getting a point, then simply send that point on. This is often
58
+ # the case when the property is initailized by a scene.
59
+ get Point do |point|
60
+ point
61
+ end
62
+
63
+ # When getting a string convert it to a point.
64
+ get String do |value|
65
+ Point.parse(value)
66
+ end
67
+
68
+ # When no setters match save the default point.
69
+ set do
70
+ default_point.to_s
71
+ end
72
+
73
+ # When given a string, it is assumed to be a well formated point
74
+ set String do |value|
75
+ Point.parse(value).to_s
76
+ end
77
+
78
+ # When given a point save the the string value of it.
79
+ set Point do |value|
80
+ value.to_s
81
+ end
82
+
83
+ def default_point
84
+ Point.parse(options[:default])
85
+ end
86
+
87
+ end
88
+
89
+ end
90
+ end
@@ -0,0 +1,221 @@
1
+ module Metro
2
+ class Model
3
+
4
+ #
5
+ # A property is a value filter that allows you to specify filtering options
6
+ # both on the setting and the getting of a value. The property itself does
7
+ # not maintain any of these values but keeps in mind the particular model
8
+ # and options that it is created when applying the filtering.
9
+ #
10
+ # @note Property is intented to be subclassed to provide filtering of particular types.
11
+ #
12
+ class Property
13
+ include Units
14
+
15
+ attr_reader :model, :options, :block
16
+
17
+ #
18
+ # @param [Model] model the model associated with this property.
19
+ # @param [Types] options the additional options that may be set with
20
+ # this property. This may be default values or options on how
21
+ # this properby should behave.
22
+ #
23
+ def initialize(model,options={},&block)
24
+ @model = model
25
+ @options = options
26
+ @block = block
27
+ end
28
+
29
+ # Define a filter block for getting a value of that type.
30
+ def self.get(*types,&block)
31
+ types = [ NilClass ] if types.empty?
32
+ types.each do |type|
33
+ gets[type.to_s] = block
34
+ end
35
+ end
36
+
37
+ # All get filter blocks defined
38
+ def self.gets
39
+ @gets ||= hash_with_default_to_nil
40
+ end
41
+
42
+ # Perform a get of the value, running it through the get
43
+ # filter block that matches the type.
44
+ def get(value)
45
+ get_block = self.class.gets[value.class.to_s]
46
+ instance_exec(value,&get_block)
47
+ end
48
+
49
+ # Define a filter block for setting a value of the type.
50
+ def self.set(*types,&block)
51
+ types = [ NilClass ] if types.empty?
52
+ types.each do |type|
53
+ sets[type.to_s] = block
54
+ end
55
+ end
56
+
57
+ # All set filter blocks defined
58
+ def self.sets
59
+ @sets ||= hash_with_default_to_nil
60
+ end
61
+
62
+ # Perform a set of the value, running it through the set
63
+ # filter block that matches the type.
64
+ def set(value)
65
+ set_block = self.class.sets[value.class.to_s]
66
+ instance_exec(value,&set_block)
67
+ end
68
+
69
+ # Define a filter block that applies to both the setting and getting of a property.
70
+ def self.get_or_set(type=NilClass,&block)
71
+ gets[type.to_s] = block
72
+ sets[type.to_s] = block
73
+ end
74
+
75
+ #
76
+ # Allow a property to define a sub-property.
77
+ #
78
+ # A sub-property that is defined can be any type of exiting properties. Which
79
+ # may also define more sub-properties. These sub-properties will be available as properties
80
+ # through the names provided.
81
+ #
82
+ # @example DimensionProperty defining two numeric sub-properties for height and width
83
+ #
84
+ # class DimensionsProperty < Property
85
+ # define_property :width
86
+ # define_property :height
87
+ # end
88
+ #
89
+ # class Frogger < Metro::Model
90
+ # property :dimensions
91
+ #
92
+ # def after_initialize
93
+ # puts "Frog dimensions are #{dimensions}"
94
+ # puts "(#{dimensions.width},#{dimensions.height}) == (#{width},#{height})"
95
+ # end
96
+ # end
97
+ #
98
+ # The sub-properties can also be prefixed with the parent property name:
99
+ #
100
+ # @example FontProperty defines sub-properties which include the parent propery prefix
101
+ #
102
+ # class FontProperty < Property
103
+ # define_property :size, prefix: true
104
+ # define_property :name, type: :text, prefix: true
105
+ # end
106
+ #
107
+ # class MyLabel < Metro::Model
108
+ # property :font
109
+ #
110
+ # def after_initialize
111
+ # puts "Font is: #{font} - #{font_name}:#{font_size}"
112
+ # end
113
+ # end
114
+ #
115
+ # If you define a property with the non-default name it will automatically add the prefix
116
+ # to all the sub-properties. This is to prevent any getter/setter method name collisions.
117
+ #
118
+ # @example DimensionProperty defining two numeric sub-properties for height and width
119
+ #
120
+ # class DimensionsProperty < Property
121
+ # define_property :width
122
+ # define_property :height
123
+ # end
124
+ #
125
+ # class Frogger < Metro::Model
126
+ # property :dims, type: :dimensions
127
+ #
128
+ # def after_initialize
129
+ # puts "Frog dimensions are #{dims}"
130
+ # puts "(#{dims.width},#{dims.height}) == (#{dims_width},#{dims_height})"
131
+ # end
132
+ # end
133
+ #
134
+ def self.define_property(name,options = {})
135
+ defined_properties.push PropertyDefinition.new name, options
136
+ end
137
+
138
+ #
139
+ # @return an array of all the defined properties.
140
+ #
141
+ def self.defined_properties
142
+ @defined_properties ||= []
143
+ end
144
+
145
+ #
146
+ # Capture all the subclassed properties and add them to the availble list of
147
+ # properties.
148
+ #
149
+ def self.inherited(subclass)
150
+ property_name = subclass.to_s.gsub(/Property$/,'').split("::").last.underscore
151
+ properties_hash[property_name] = subclass.to_s
152
+ end
153
+
154
+ def self.properties
155
+ @properties ||= []
156
+ end
157
+
158
+ #
159
+ # @return a Property Class that matches the specified name.
160
+ #
161
+ def self.property(name)
162
+ property_classname = properties_hash[name]
163
+ property_classname.constantize
164
+ end
165
+
166
+ #
167
+ # Generate a properties hash. This properties will default to the numeric key when the property
168
+ # type cannot be found.
169
+ #
170
+ def self.properties_hash
171
+ @properties_hash ||= HashWithIndifferentAccess.new { |hash,key| hash[:numeric] }
172
+ end
173
+
174
+ #
175
+ # Generate a hash that will default all missing keys to the NilClass key and the NilClass key will
176
+ # return a cource of action that will raise an exception. This means by default that if it is not
177
+ # overridden an error is generated.
178
+ #
179
+ def self.hash_with_default_to_nil
180
+ hash = HashWithIndifferentAccess.new { |hash,key| hash["NilClass"] }
181
+ hash["NilClass"] = lambda { |value| raise "#{self} is not able to translate the #{value} (#{value.class})" }
182
+ hash
183
+ end
184
+
185
+ end
186
+
187
+ end
188
+
189
+ #
190
+ # A property definition contains the name of the property and the options specified with it.
191
+ # This is used internally to define properties and sub-properties.
192
+ #
193
+ class PropertyDefinition
194
+
195
+ attr_reader :name, :options
196
+
197
+ def initialize(name,options)
198
+ @name = name
199
+ @options = options
200
+ end
201
+ end
202
+
203
+ end
204
+
205
+ require_relative 'property_owner'
206
+
207
+ require_relative 'numeric_property'
208
+ require_relative 'text_property'
209
+ require_relative 'boolean_property'
210
+ require_relative 'array_property'
211
+ require_relative 'animation_property'
212
+ require_relative 'color_property'
213
+ require_relative 'dimensions_property'
214
+ require_relative 'font_property'
215
+ require_relative 'image_property'
216
+ require_relative 'position_property'
217
+ require_relative 'scale_property'
218
+ require_relative 'song_property'
219
+ require_relative 'sample_property'
220
+ require_relative 'options_property/options_property'
221
+ require_relative 'model_property'
@@ -0,0 +1,137 @@
1
+ module Metro
2
+
3
+ module PropertyOwner
4
+
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+ end
8
+
9
+ #
10
+ # The raw properties of the model. Most properties defined through the `property`
11
+ # class method will have getters/setters that will do the appropriate translation
12
+ # so this should in cases when you need access to the raw properties or there
13
+ # is not a property accessors defined.
14
+ #
15
+ # @return [Hash] the raw properties of the model.
16
+ #
17
+ def properties
18
+ @properties ||= {}
19
+ end
20
+
21
+ module ClassMethods
22
+
23
+ #
24
+ # Define a property for the model. A property has a name and then can optionally specify
25
+ # a property type which will receive additional options.
26
+ #
27
+ # @example Defining various propertys for a model
28
+ #
29
+ # class Player
30
+ # property :position
31
+ # property :angle, default: 0.0
32
+ # property :turn_amount, default: 4.5
33
+ # property :image, path: "player.png"
34
+ # property :motto, type: :text, default: 'Hometown Heroes!'
35
+ # end
36
+ #
37
+ # When the property name matches a property definition with that name they will be used. This is what
38
+ # happens for the 'position' and 'image' properties defined above. Both of those map to respective
39
+ # properties with matching names.
40
+ #
41
+ # Properties by default are assumed to be numeric properties so the types does not have to be stated.
42
+ # This is the case for 'angle' and 'turn_amount' properties.
43
+ #
44
+ # You may use any particular name for your properties as long as you specify the type. This is the case
45
+ # for the 'motto' property.
46
+ #
47
+ def property(name,options={},&block)
48
+
49
+ # Use the name as the property type if one has not been provided.
50
+
51
+ property_type = options[:type] || name
52
+
53
+ property_class = Model::Property.property(property_type)
54
+
55
+ define_method name do
56
+ raw_value = properties[name]
57
+
58
+ unless parsed_value = instance_variable_get("@_property_parsed_#{name}")
59
+ parsed_value = property_class.new(self,options,&block).get(raw_value)
60
+ instance_variable_set("@_property_parsed_#{name}",parsed_value)
61
+ end
62
+
63
+ parsed_value
64
+ end
65
+
66
+ define_method "#{name}=" do |value|
67
+ instance_variable_set("@_property_parsed_#{name}",nil)
68
+ prepared_value = property_class.new(self,options).set(value)
69
+ send("#{name}_changed",prepared_value) if respond_to? "#{name}_changed"
70
+ properties[name] = prepared_value
71
+ end
72
+
73
+ # Define any sub-properties defined on this property
74
+
75
+ # When the name does not match the property type then we want to force
76
+ # the prefixing to be on for our sub-properties. This is to make sure
77
+ # that when people define multiple fonts and colors that they do not
78
+ # overlap.
79
+
80
+ override_prefix = !(name == property_type)
81
+
82
+ property_class.defined_properties.each do |subproperty|
83
+ sub_options = { prefix: override_prefix }.merge(subproperty.options)
84
+ sub_options = sub_options.merge(parents: (Array(sub_options[:parents]) + [name]))
85
+ _sub_property subproperty.name, sub_options
86
+ end
87
+
88
+ end
89
+
90
+ #
91
+ # Defines the sub-properties defined within the property. This is to be used internally
92
+ # by the #property method.
93
+ #
94
+ def _sub_property(name,options={},&block)
95
+
96
+ # Use the name as the property type if one has not been provided.
97
+
98
+ property_type = options[:type] || name
99
+
100
+ property_class = Model::Property.property(property_type)
101
+
102
+ parents = Array(options[:parents])
103
+
104
+ method_name = name
105
+
106
+ if options[:prefix]
107
+ method_name = (parents + [name]).join("_")
108
+ end
109
+
110
+ # Define a getter for the sub-property that will traverse the
111
+ # parent properties, finally returning the filtered value
112
+
113
+ define_method method_name do
114
+ raw_value = (parents + [name]).inject(self) {|current,method| current.send(method) }
115
+ property_class.new(self,options).get raw_value
116
+ end
117
+
118
+ # Define a setter for the sub-property that will find the parent
119
+ # value and set itself on that with the filtered value. The parent
120
+ # is then set.
121
+ #
122
+ # @TODO: If getters return dups and not instances of the original object then a very
123
+ # deep setter will not be valid.
124
+ #
125
+ define_method "#{method_name}=" do |value|
126
+ parent_value = parents.inject(self) {|current,method| current.send(method) }
127
+
128
+ prepared_value = property_class.new(self,options,&block).set(value)
129
+ parent_value.send("#{name}=",prepared_value)
130
+
131
+ send("#{parents.last}=",parent_value)
132
+ end
133
+ end
134
+ end
135
+
136
+ end
137
+ end