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