metro 0.2.5 → 0.2.6
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/changelog.md +11 -0
- data/lib/assets/menu-movement.wav +0 -0
- data/lib/assets/menu-selection.wav +0 -0
- data/lib/metro.rb +1 -0
- data/lib/metro/asset_path.rb +27 -9
- data/lib/metro/events/event_dictionary.rb +1 -2
- data/lib/metro/font.rb +66 -0
- data/lib/metro/image.rb +4 -3
- data/lib/metro/models/draws.rb +8 -7
- data/lib/metro/models/model.rb +8 -5
- data/lib/metro/models/model_factory.rb +4 -6
- data/lib/metro/models/properties/animation_property.rb +1 -1
- 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/font_property.rb +5 -29
- data/lib/metro/models/properties/options_property/no_option.rb +29 -0
- data/lib/metro/models/properties/options_property/options.rb +94 -0
- data/lib/metro/models/properties/options_property/options_property.rb +125 -0
- data/lib/metro/models/properties/property.rb +14 -5
- data/lib/metro/models/properties/property_owner.rb +1 -0
- data/lib/metro/models/ui/generic.rb +22 -5
- data/lib/metro/models/ui/grid_drawer.rb +25 -15
- data/lib/metro/models/ui/image.rb +7 -3
- data/lib/metro/models/ui/label.rb +25 -15
- data/lib/metro/models/ui/menu.rb +106 -95
- data/lib/metro/models/ui/rectangle.rb +21 -8
- data/lib/metro/version.rb +1 -1
- data/spec/metro/models/properties/array_property_spec.rb +60 -0
- data/spec/metro/models/properties/{color_spec.rb → color_property_spec.rb} +0 -0
- data/spec/metro/models/properties/{font_spec.rb → font_property_spec.rb} +16 -18
- 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/ui/label_spec.rb +56 -15
- metadata +29 -11
@@ -0,0 +1,94 @@
|
|
1
|
+
module Metro
|
2
|
+
class Model
|
3
|
+
class OptionsProperty
|
4
|
+
|
5
|
+
#
|
6
|
+
# Options maintains the list of menu options that would be displayed in a menu.
|
7
|
+
# Each option is a component that can be rendered during the draw phase.
|
8
|
+
#
|
9
|
+
# Also, options maintains the currently selected item and has the ability to manage
|
10
|
+
# the changes from the previous.
|
11
|
+
#
|
12
|
+
class Options < SimpleDelegator
|
13
|
+
|
14
|
+
#
|
15
|
+
# Generate an empty set of options.
|
16
|
+
#
|
17
|
+
def self.empty
|
18
|
+
new []
|
19
|
+
end
|
20
|
+
|
21
|
+
#
|
22
|
+
# Create options with the provided array of options.
|
23
|
+
#
|
24
|
+
# @param [Array] options the array of options that this object will maintain
|
25
|
+
# as it's list of options.
|
26
|
+
#
|
27
|
+
def initialize(options)
|
28
|
+
super(options)
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
# @return [Fixnum] the index of the current selected option.
|
33
|
+
#
|
34
|
+
def current_selected_index
|
35
|
+
@current_selected_index ||= 0
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# Set the index of the currently selected item. Values that exceed the possible
|
40
|
+
# count of options will reset to the beginning of the list of options.
|
41
|
+
# Values that proceed the start of of the list of options will fallback to the last option.
|
42
|
+
#
|
43
|
+
def current_selected_index=(value)
|
44
|
+
@current_selected_index = value || 0
|
45
|
+
@current_selected_index = 0 if @current_selected_index >= count
|
46
|
+
@current_selected_index = count - 1 if @current_selected_index <= -1
|
47
|
+
@current_selected_index
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# @return [Array] a list of all the options that are currently not selected.
|
52
|
+
#
|
53
|
+
def unselected
|
54
|
+
self - [ selected ]
|
55
|
+
end
|
56
|
+
|
57
|
+
#
|
58
|
+
# @return [Object] the currently selected option.
|
59
|
+
#
|
60
|
+
def selected
|
61
|
+
at(current_selected_index) || NoOption.new
|
62
|
+
end
|
63
|
+
|
64
|
+
#
|
65
|
+
# @return [Symbol] the action name of the currently selected option. If no
|
66
|
+
# option is currently selected then the NoOption 'missing_menu_action' will be
|
67
|
+
# returned.
|
68
|
+
#
|
69
|
+
def selected_action
|
70
|
+
if selected.respond_to?(:properties) && selected.properties[:action]
|
71
|
+
selected.properties[:action]
|
72
|
+
else
|
73
|
+
selected.to_sym
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
#
|
78
|
+
# Move the current selected item to the next item
|
79
|
+
#
|
80
|
+
def next!
|
81
|
+
self.current_selected_index += 1
|
82
|
+
end
|
83
|
+
|
84
|
+
#
|
85
|
+
# Move the current selected item to the previous item
|
86
|
+
#
|
87
|
+
def previous!
|
88
|
+
self.current_selected_index -= 1
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -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'
|
@@ -27,8 +27,11 @@ module Metro
|
|
27
27
|
end
|
28
28
|
|
29
29
|
# Define a filter block for getting a value of that type.
|
30
|
-
def self.get(
|
31
|
-
|
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
|
32
35
|
end
|
33
36
|
|
34
37
|
# All get filter blocks defined
|
@@ -44,8 +47,11 @@ module Metro
|
|
44
47
|
end
|
45
48
|
|
46
49
|
# Define a filter block for setting a value of the type.
|
47
|
-
def self.set(
|
48
|
-
|
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
|
49
55
|
end
|
50
56
|
|
51
57
|
# All set filter blocks defined
|
@@ -200,6 +206,8 @@ require_relative 'property_owner'
|
|
200
206
|
|
201
207
|
require_relative 'numeric_property'
|
202
208
|
require_relative 'text_property'
|
209
|
+
require_relative 'boolean_property'
|
210
|
+
require_relative 'array_property'
|
203
211
|
require_relative 'animation_property'
|
204
212
|
require_relative 'color_property'
|
205
213
|
require_relative 'dimensions_property'
|
@@ -208,4 +216,5 @@ require_relative 'image_property'
|
|
208
216
|
require_relative 'position_property'
|
209
217
|
require_relative 'scale_property'
|
210
218
|
require_relative 'song_property'
|
211
|
-
require_relative 'sample_property'
|
219
|
+
require_relative 'sample_property'
|
220
|
+
require_relative 'options_property/options_property'
|
@@ -66,6 +66,7 @@ module Metro
|
|
66
66
|
define_method "#{name}=" do |value|
|
67
67
|
instance_variable_set("@_property_parsed_#{name}",nil)
|
68
68
|
prepared_value = property_class.new(self,options).set(value)
|
69
|
+
send("#{name}_changed",prepared_value) if respond_to? "#{name}_changed"
|
69
70
|
properties[name] = prepared_value
|
70
71
|
end
|
71
72
|
|
@@ -2,18 +2,35 @@ module Metro
|
|
2
2
|
module UI
|
3
3
|
|
4
4
|
#
|
5
|
-
# Generic model is used when no model
|
6
|
-
#
|
5
|
+
# Generic model is used when no model can be found.
|
6
|
+
#
|
7
|
+
# @example Defining an actor in a scene without the model
|
8
|
+
#
|
9
|
+
# class IntroScene < GameScene
|
10
|
+
#
|
11
|
+
# draw :game_title, text: "Super Game Title", position: Game.center
|
12
|
+
# color: "rgba(255,255,255,1.0)"
|
13
|
+
#
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# The above `game_title` actor was likely suppose to be a `metro::ui::label`, but
|
17
|
+
# the model was not specified. So this warning would be generated.
|
7
18
|
#
|
8
19
|
class Generic < Model
|
9
20
|
|
21
|
+
attr_accessor :warned
|
22
|
+
|
10
23
|
def draw
|
11
|
-
|
24
|
+
log.warn cannot_draw_message unless warned
|
25
|
+
self.warned = true
|
12
26
|
end
|
13
27
|
|
28
|
+
private
|
29
|
+
|
14
30
|
def cannot_draw_message
|
15
|
-
[ "Unable to draw #{name} in #{scene}",
|
16
|
-
"The actor named '#{name}'
|
31
|
+
[ "Unable to draw #{name} in #{scene}", "",
|
32
|
+
" The actor named '#{name}' does not specify a suitable model so it could not be drawn in the scene.",
|
33
|
+
"", " " + properties.to_s, "" ].join("\n")
|
17
34
|
end
|
18
35
|
|
19
36
|
end
|
@@ -1,23 +1,26 @@
|
|
1
1
|
module Metro
|
2
2
|
module UI
|
3
|
-
|
3
|
+
|
4
|
+
#
|
5
|
+
# The grid drawer will draw a grid from the specified position out to the specified
|
6
|
+
# dimensions at the spacing interval provided. This is currently used when the edit
|
7
|
+
# mode is enabled.
|
8
|
+
#
|
9
|
+
# @example Drawing a white, translucent, 100 by 100 grid starting at (20,20).
|
10
|
+
#
|
11
|
+
# class BuildScene < GameScene
|
12
|
+
# draw :grid, model: "metro::ui::grid_drawer", position: "20,20",
|
13
|
+
# color: "rgba(255,255,255,0.5)", spacing: 20, dimensions: "100,100"
|
14
|
+
# end
|
15
|
+
#
|
4
16
|
class GridDrawer < Model
|
5
17
|
|
18
|
+
property :position, default: Point.at(20,20,100)
|
6
19
|
property :color, default: "rgba(255,255,255,0.1)"
|
7
20
|
property :spacing, type: :numeric, default: 10
|
8
21
|
|
9
|
-
|
10
|
-
|
11
|
-
def name
|
12
|
-
self.class.name
|
13
|
-
end
|
14
|
-
|
15
|
-
def height
|
16
|
-
@height || Game.height
|
17
|
-
end
|
18
|
-
|
19
|
-
def width
|
20
|
-
@width || Game.width
|
22
|
+
property :dimensions do
|
23
|
+
Dimensions.of Game.width, Game.height
|
21
24
|
end
|
22
25
|
|
23
26
|
def saveable?
|
@@ -33,20 +36,27 @@ module Metro
|
|
33
36
|
draw_vertical_lines
|
34
37
|
end
|
35
38
|
|
39
|
+
private
|
40
|
+
|
36
41
|
def draw_vertical_lines
|
37
42
|
xs = (width / spacing + 1).to_i.times.map {|segment| segment * spacing }
|
38
43
|
xs.each do |x|
|
39
|
-
|
44
|
+
draw_line(x,1,x,height)
|
40
45
|
end
|
41
46
|
end
|
42
47
|
|
43
48
|
def draw_horizontal_lines
|
44
49
|
ys = (height / spacing + 1).to_i.times.map {|segment| segment * spacing }
|
45
50
|
ys.each do |y|
|
46
|
-
|
51
|
+
draw_line(1,y,width,y)
|
47
52
|
end
|
48
53
|
end
|
49
54
|
|
55
|
+
def draw_line(start_x,start_y,finish_x,finish_y)
|
56
|
+
window.draw_line(position.x + start_x, position.y + start_y, color,
|
57
|
+
position.x + finish_x, position.y + finish_y, color, 0, :additive)
|
58
|
+
end
|
59
|
+
|
50
60
|
end
|
51
61
|
end
|
52
62
|
end
|
@@ -2,10 +2,14 @@ module Metro
|
|
2
2
|
module UI
|
3
3
|
|
4
4
|
#
|
5
|
-
#
|
5
|
+
# The image will draw an image with the specifie path, color, rotation, and scale.
|
6
6
|
#
|
7
|
-
# @example
|
8
|
-
#
|
7
|
+
# @example Drawing the 'player.png' image at (320,240)
|
8
|
+
#
|
9
|
+
# class MainScene < GameScene
|
10
|
+
# draw :player, model: "metro::ui::image", position: "320,240",
|
11
|
+
# image: "player.png"
|
12
|
+
# end
|
9
13
|
#
|
10
14
|
class Image < Model
|
11
15
|
|
@@ -2,10 +2,16 @@ module Metro
|
|
2
2
|
module UI
|
3
3
|
|
4
4
|
#
|
5
|
-
#
|
5
|
+
# The label will draw the specified text with the font, position, color,
|
6
|
+
# and alignment provided.
|
6
7
|
#
|
7
|
-
# @example
|
8
|
-
#
|
8
|
+
# @example Drawing a label
|
9
|
+
#
|
10
|
+
# class TitleScene < GameScene
|
11
|
+
# draw :game_title, model: "metro::ui::label", position: "320,240",
|
12
|
+
# vertical_align: "center", align: "center", color: "rgba(255,255,255,1.0)",
|
13
|
+
# text: "Super Awesome Game\n2"
|
14
|
+
# end
|
9
15
|
#
|
10
16
|
class Label < Model
|
11
17
|
|
@@ -28,7 +34,7 @@ module Metro
|
|
28
34
|
|
29
35
|
def draw
|
30
36
|
parsed_text.each_with_index do |line,index|
|
31
|
-
font.draw line, x_position, y_position(index), z_order, x_factor, y_factor, color
|
37
|
+
font.draw line, x_position(index), y_position(index), z_order, x_factor, y_factor, color
|
32
38
|
end
|
33
39
|
end
|
34
40
|
|
@@ -46,10 +52,18 @@ module Metro
|
|
46
52
|
font.height
|
47
53
|
end
|
48
54
|
|
55
|
+
def line_width(line)
|
56
|
+
font.text_width(line)
|
57
|
+
end
|
58
|
+
|
49
59
|
def half_line_height
|
50
60
|
line_height / 2
|
51
61
|
end
|
52
62
|
|
63
|
+
def longest_line
|
64
|
+
parsed_text.map { |line| line_width(line) }.max
|
65
|
+
end
|
66
|
+
|
53
67
|
def line_count
|
54
68
|
parsed_text.count
|
55
69
|
end
|
@@ -58,20 +72,16 @@ module Metro
|
|
58
72
|
text.split("\n")
|
59
73
|
end
|
60
74
|
|
61
|
-
def
|
62
|
-
parsed_text.map { |line| font.text_width(line) }.max
|
63
|
-
end
|
64
|
-
|
65
|
-
def x_left_alignment
|
75
|
+
def x_left_alignment(index)
|
66
76
|
x
|
67
77
|
end
|
68
78
|
|
69
|
-
def x_center_alignment
|
70
|
-
x -
|
79
|
+
def x_center_alignment(index)
|
80
|
+
x - line_width(parsed_text[index]) / 2
|
71
81
|
end
|
72
82
|
|
73
|
-
def x_right_alignment
|
74
|
-
x -
|
83
|
+
def x_right_alignment(index)
|
84
|
+
x - line_width(parsed_text[index])
|
75
85
|
end
|
76
86
|
|
77
87
|
def horizontal_alignments
|
@@ -80,9 +90,9 @@ module Metro
|
|
80
90
|
right: :x_right_alignment }
|
81
91
|
end
|
82
92
|
|
83
|
-
def x_position
|
93
|
+
def x_position(index)
|
84
94
|
alignment = horizontal_alignments[align.to_sym]
|
85
|
-
send(alignment)
|
95
|
+
send(alignment,index)
|
86
96
|
end
|
87
97
|
|
88
98
|
def y_top_alignment(index)
|