metro-ld26 0.3.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +6 -0
- data/Gemfile +14 -0
- data/Guardfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +177 -0
- data/Rakefile +18 -0
- data/bin/metro +16 -0
- data/changelog.md +153 -0
- data/lib/assets/menu-movement.wav +0 -0
- data/lib/assets/menu-selection.wav +0 -0
- data/lib/assets/missing.ogg +0 -0
- data/lib/assets/missing.png +0 -0
- data/lib/assets/missing.wav +0 -0
- data/lib/assets/missing_animation.png +0 -0
- data/lib/commands/generate_game.rb +13 -0
- data/lib/commands/generate_model.rb +25 -0
- data/lib/commands/generate_scene.rb +36 -0
- data/lib/commands/generate_view.rb +21 -0
- data/lib/commands/thor.rb +83 -0
- data/lib/core_ext/class.rb +14 -0
- data/lib/core_ext/numeric.rb +59 -0
- data/lib/gosu_ext/color.rb +62 -0
- data/lib/gosu_ext/gosu_constants.rb +53 -0
- data/lib/locale/en.yml +35 -0
- data/lib/locale/locale.rb +1 -0
- data/lib/metro.rb +144 -0
- data/lib/metro/animation.rb +135 -0
- data/lib/metro/animation/after_interval_factory.rb +12 -0
- data/lib/metro/animation/animation_factory.rb +15 -0
- data/lib/metro/animation/easing/ease_in.rb +15 -0
- data/lib/metro/animation/easing/easing.rb +51 -0
- data/lib/metro/animation/easing/linear.rb +15 -0
- data/lib/metro/animation/has_animations.rb +70 -0
- data/lib/metro/animation/implicit_animation.rb +100 -0
- data/lib/metro/animation/on_update_operation.rb +96 -0
- data/lib/metro/animation/scene_animation.rb +16 -0
- data/lib/metro/asset_path.rb +97 -0
- data/lib/metro/events/control_definition.rb +11 -0
- data/lib/metro/events/controls.rb +42 -0
- data/lib/metro/events/event_data.rb +60 -0
- data/lib/metro/events/event_dictionary.rb +52 -0
- data/lib/metro/events/event_factory.rb +17 -0
- data/lib/metro/events/event_relay.rb +342 -0
- data/lib/metro/events/event_state_manager.rb +70 -0
- data/lib/metro/events/events.rb +3 -0
- data/lib/metro/events/has_events.rb +108 -0
- data/lib/metro/events/hit_list.rb +75 -0
- data/lib/metro/events/unknown_sender.rb +5 -0
- data/lib/metro/font.rb +69 -0
- data/lib/metro/game.rb +102 -0
- data/lib/metro/game/dsl.rb +68 -0
- data/lib/metro/image.rb +75 -0
- data/lib/metro/logging.rb +33 -0
- data/lib/metro/missing_scene.rb +21 -0
- data/lib/metro/models/audio/song.rb +33 -0
- data/lib/metro/models/draws.rb +86 -0
- data/lib/metro/models/key_value_coding.rb +38 -0
- data/lib/metro/models/model.rb +246 -0
- data/lib/metro/models/model_factory.rb +32 -0
- data/lib/metro/models/models.rb +62 -0
- data/lib/metro/models/properties/animation_property.rb +115 -0
- data/lib/metro/models/properties/array_property.rb +24 -0
- data/lib/metro/models/properties/boolean_property.rb +27 -0
- data/lib/metro/models/properties/color_property.rb +116 -0
- data/lib/metro/models/properties/dimensions_property.rb +84 -0
- data/lib/metro/models/properties/font_property.rb +130 -0
- data/lib/metro/models/properties/image_property.rb +96 -0
- data/lib/metro/models/properties/model_property.rb +84 -0
- data/lib/metro/models/properties/numeric_property.rb +29 -0
- data/lib/metro/models/properties/options_property/no_option.rb +29 -0
- data/lib/metro/models/properties/options_property/options.rb +98 -0
- data/lib/metro/models/properties/options_property/options_property.rb +125 -0
- data/lib/metro/models/properties/position_property.rb +90 -0
- data/lib/metro/models/properties/property.rb +221 -0
- data/lib/metro/models/properties/property_owner.rb +137 -0
- data/lib/metro/models/properties/sample_property.rb +84 -0
- data/lib/metro/models/properties/scale_property.rb +80 -0
- data/lib/metro/models/properties/song_property.rb +89 -0
- data/lib/metro/models/properties/text_property.rb +75 -0
- data/lib/metro/models/ui/animated_sprite.rb +85 -0
- data/lib/metro/models/ui/border.rb +95 -0
- data/lib/metro/models/ui/fps.rb +54 -0
- data/lib/metro/models/ui/generic.rb +66 -0
- data/lib/metro/models/ui/grid_drawer.rb +74 -0
- data/lib/metro/models/ui/image.rb +87 -0
- data/lib/metro/models/ui/label.rb +175 -0
- data/lib/metro/models/ui/menu.rb +214 -0
- data/lib/metro/models/ui/model_label.rb +65 -0
- data/lib/metro/models/ui/model_labeler.rb +79 -0
- data/lib/metro/models/ui/rectangle.rb +59 -0
- data/lib/metro/models/ui/sprite.rb +79 -0
- data/lib/metro/models/ui/tile_map.rb +132 -0
- data/lib/metro/models/ui/tmx/isometric_position.rb +43 -0
- data/lib/metro/models/ui/tmx/orthogonal_position.rb +15 -0
- data/lib/metro/models/ui/tmx/tile_layer.rb +78 -0
- data/lib/metro/models/ui/ui.rb +13 -0
- data/lib/metro/parameters/command_line_args_parser.rb +68 -0
- data/lib/metro/parameters/options.rb +25 -0
- data/lib/metro/parameters/parameters.rb +2 -0
- data/lib/metro/sample.rb +40 -0
- data/lib/metro/scene.rb +478 -0
- data/lib/metro/scenes.rb +154 -0
- data/lib/metro/song.rb +56 -0
- data/lib/metro/template_message.rb +60 -0
- data/lib/metro/transitions/edit_transition_scene.rb +100 -0
- data/lib/metro/transitions/fade_transition_scene.rb +66 -0
- data/lib/metro/transitions/scene_transitions.rb +44 -0
- data/lib/metro/transitions/transition_scene.rb +19 -0
- data/lib/metro/units/bounds.rb +8 -0
- data/lib/metro/units/calculation_validations.rb +74 -0
- data/lib/metro/units/dimensions.rb +60 -0
- data/lib/metro/units/point.rb +51 -0
- data/lib/metro/units/rectangle_bounds.rb +148 -0
- data/lib/metro/units/scale.rb +46 -0
- data/lib/metro/units/units.rb +6 -0
- data/lib/metro/version.rb +32 -0
- data/lib/metro/views/json_view.rb +60 -0
- data/lib/metro/views/no_view.rb +34 -0
- data/lib/metro/views/parsers.rb +42 -0
- data/lib/metro/views/scene_view.rb +107 -0
- data/lib/metro/views/view.rb +133 -0
- data/lib/metro/views/writers.rb +43 -0
- data/lib/metro/views/yaml_view.rb +94 -0
- data/lib/metro/window.rb +95 -0
- data/lib/setup_handlers/exit_if_dry_run.rb +26 -0
- data/lib/setup_handlers/game_execution.rb +65 -0
- data/lib/setup_handlers/load_game_configuration.rb +65 -0
- data/lib/setup_handlers/load_game_files.rb +101 -0
- data/lib/setup_handlers/move_to_game_directory.rb +25 -0
- data/lib/setup_handlers/reload_game_on_game_file_changes.rb +79 -0
- data/lib/templates/game/README.md.tt +43 -0
- data/lib/templates/game/assets/brand.jpg +0 -0
- data/lib/templates/game/assets/hero.png +0 -0
- data/lib/templates/game/lib/custom_easing.rb +32 -0
- data/lib/templates/game/metro.tt +63 -0
- data/lib/templates/game/models/hero.rb +62 -0
- data/lib/templates/game/scenes/brand_scene.rb +19 -0
- data/lib/templates/game/scenes/brand_to_title_scene.rb +13 -0
- data/lib/templates/game/scenes/first_scene.rb +28 -0
- data/lib/templates/game/scenes/game_scene.rb +43 -0
- data/lib/templates/game/scenes/title_scene.rb +15 -0
- data/lib/templates/game/views/brand.yaml +4 -0
- data/lib/templates/game/views/brand_to_title.yaml +8 -0
- data/lib/templates/game/views/first.yaml +26 -0
- data/lib/templates/game/views/title.yaml +11 -0
- data/lib/templates/message.erb +23 -0
- data/lib/templates/model.rb.tt +111 -0
- data/lib/templates/scene.rb.tt +140 -0
- data/lib/templates/view.yaml.tt +11 -0
- data/lib/tmx_ext/object.rb +26 -0
- data/lib/tmx_ext/tile_set.rb +41 -0
- data/metro.gemspec +57 -0
- data/metro.png +0 -0
- data/spec/core_ext/numeric_spec.rb +78 -0
- data/spec/core_ext/string_spec.rb +33 -0
- data/spec/gosu_ext/color_spec.rb +80 -0
- data/spec/metro/image_spec.rb +33 -0
- data/spec/metro/models/key_value_coding_spec.rb +61 -0
- data/spec/metro/models/properties/array_property_spec.rb +60 -0
- data/spec/metro/models/properties/color_property_spec.rb +85 -0
- data/spec/metro/models/properties/dimensions_spec.rb +29 -0
- data/spec/metro/models/properties/font_property_spec.rb +127 -0
- data/spec/metro/models/properties/numeric_property_spec.rb +46 -0
- data/spec/metro/models/properties/options_property/no_option_spec.rb +25 -0
- data/spec/metro/models/properties/options_property/options_property_spec.rb +133 -0
- data/spec/metro/models/properties/options_property/options_spec.rb +125 -0
- data/spec/metro/models/properties/position_property_spec.rb +90 -0
- data/spec/metro/models/ui/label_spec.rb +259 -0
- data/spec/metro/parameters/command_line_args_parser_spec.rb +42 -0
- data/spec/metro/scene_spec.rb +15 -0
- data/spec/metro/scene_views/json_view_spec.rb +27 -0
- data/spec/metro/scene_views/yaml_view_spec.rb +38 -0
- data/spec/metro/scenes_spec.rb +77 -0
- data/spec/metro/units/point_spec.rb +132 -0
- data/spec/metro/units/rectangle_bounds_spec.rb +56 -0
- data/spec/metro/views/view_spec.rb +53 -0
- data/spec/setup_handlers/exit_if_dry_run_spec.rb +27 -0
- data/spec/setup_handlers/reload_game_on_game_file_changes_spec.rb +68 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/tmx_ext/object_spec.rb +96 -0
- data/spec/tmx_ext/tile_set_spec.rb +24 -0
- 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
|