metro 0.2.5 → 0.2.6
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|