metro 0.2.5 → 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. data/changelog.md +11 -0
  2. data/lib/assets/menu-movement.wav +0 -0
  3. data/lib/assets/menu-selection.wav +0 -0
  4. data/lib/metro.rb +1 -0
  5. data/lib/metro/asset_path.rb +27 -9
  6. data/lib/metro/events/event_dictionary.rb +1 -2
  7. data/lib/metro/font.rb +66 -0
  8. data/lib/metro/image.rb +4 -3
  9. data/lib/metro/models/draws.rb +8 -7
  10. data/lib/metro/models/model.rb +8 -5
  11. data/lib/metro/models/model_factory.rb +4 -6
  12. data/lib/metro/models/properties/animation_property.rb +1 -1
  13. data/lib/metro/models/properties/array_property.rb +24 -0
  14. data/lib/metro/models/properties/boolean_property.rb +27 -0
  15. data/lib/metro/models/properties/font_property.rb +5 -29
  16. data/lib/metro/models/properties/options_property/no_option.rb +29 -0
  17. data/lib/metro/models/properties/options_property/options.rb +94 -0
  18. data/lib/metro/models/properties/options_property/options_property.rb +125 -0
  19. data/lib/metro/models/properties/property.rb +14 -5
  20. data/lib/metro/models/properties/property_owner.rb +1 -0
  21. data/lib/metro/models/ui/generic.rb +22 -5
  22. data/lib/metro/models/ui/grid_drawer.rb +25 -15
  23. data/lib/metro/models/ui/image.rb +7 -3
  24. data/lib/metro/models/ui/label.rb +25 -15
  25. data/lib/metro/models/ui/menu.rb +106 -95
  26. data/lib/metro/models/ui/rectangle.rb +21 -8
  27. data/lib/metro/version.rb +1 -1
  28. data/spec/metro/models/properties/array_property_spec.rb +60 -0
  29. data/spec/metro/models/properties/{color_spec.rb → color_property_spec.rb} +0 -0
  30. data/spec/metro/models/properties/{font_spec.rb → font_property_spec.rb} +16 -18
  31. data/spec/metro/models/properties/options_property/no_option_spec.rb +25 -0
  32. data/spec/metro/models/properties/options_property/options_property_spec.rb +133 -0
  33. data/spec/metro/models/properties/options_property/options_spec.rb +125 -0
  34. data/spec/metro/models/ui/label_spec.rb +56 -15
  35. 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(type=NilClass,&block)
31
- gets[type.to_s] = block
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(type=NilClass,&block)
48
- sets[type.to_s] = block
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 is cannot not be found
6
- # when mapping the view content
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
- raise cannot_draw_message
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}' could not find a suitable model so it could not be drawn in the scene #{scene}" ].join("\n")
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
- attr_writer :spacing, :height, :width
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
- window.draw_line(x, 1, color, x, height, color, 0, :additive)
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
- window.draw_line(1, y, color, width, y, color, 0, :additive)
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
- # Draws an Image
5
+ # The image will draw an image with the specifie path, color, rotation, and scale.
6
6
  #
7
- # @example Using the Image in a view file
8
- # model: "metro::ui::image"
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
- # Draws a string of text.
5
+ # The label will draw the specified text with the font, position, color,
6
+ # and alignment provided.
6
7
  #
7
- # @example Using the Label in a view file
8
- # model: "metro::ui::label"
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 longest_line
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 - width / 2
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 - width
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)