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.
- 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
|