3 require File.dirname(__FILE__) + '/base'
6 # A scene is a non-linear graph that assembles layers together to tell a story.
7 # Layers are folders with appropriately named files (see below). You can group
8 # layers and control them together or just set their values individually.
12 # * A city scene that changes with the time of day and the weather conditions.
13 # * A traffic map that shows red lines on streets that are crowded and green on free-flowing ones.
17 # g = Gruff::Scene.new("500x100", "artwork/city_scene")
18 # g.layers = %w(background haze sky clouds)
19 # g.weather_group = %w(clouds)
20 # g.time_group = %w(background sky)
21 # g.weather = "cloudy"
24 # g.write "hazy_daytime_city_scene.png"
28 # If there is a file named 'default.png', it will be selected (unless other values are provided to override it).
30 class Gruff::Scene < Gruff::Base
32 # An array listing the foldernames that will be rendered, from back to front.
34 # g.layers = %w(sky clouds buildings street people)
38 def initialize(target_width, base_dir)
46 # Join all the custom paths and filter out the empty ones
47 image_paths = @layers.map { |layer| layer.path }.select { |path| !path.empty? }
48 images = Magick::ImageList.new(*image_paths)
49 @base_image = images.flatten_images
52 def layers=(ordered_list)
53 ordered_list.each do |layer_name|
54 @layers << Gruff::Layer.new(@base_dir, layer_name)
58 # Group layers to input values
60 # g.weather_group = ["sky", "sea", "clouds"]
64 # g.weather = "cloudy"
66 def method_missing(method_name, *args)
72 set_input $1, args.first
80 def add_group(input_name, layer_names)
81 @groups[input_name] = Gruff::Group.new(input_name, @layers.select { |layer| layer_names.include?(layer.name) })
84 def set_input(input_name, input_value)
85 if not @groups[input_name].nil?
86 @groups[input_name].send_updates(input_value)
88 if chosen_layer = @layers.detect { |layer| layer.name == input_name }
89 chosen_layer.update input_value
102 def initialize(folder_name, layers)
104 layers.each do |layer|
109 def send_updates(value)
111 notify_observers value
121 def initialize(base_dir, folder_name)
122 @base_dir = base_dir.to_s
123 @name = folder_name.to_s
124 @filenames = Dir.open(File.join(base_dir, folder_name)).entries.select { |file| file =~ /^[^.]+\.png$/ }
125 @selected_filename = select_default
128 # Register this layer so it receives updates from the group
130 obj.add_observer self
133 # Choose the appropriate filename for this layer, based on the input
135 @selected_filename = case value.to_s
136 when /^(true|false)$/
140 when /^-?(\d+\.)?\d+$/
142 when /(\d\d):(\d\d):\d\d/
143 select_time "#{$1}#{$2}"
147 # Finally, try to use 'default' if we're still blank
148 @selected_filename ||= select_default
151 # Returns the full path to the selected image, or a blank string
153 unless @selected_filename.nil? || @selected_filename.empty?
154 return File.join(@base_dir, @name, @selected_filename)
161 # Match "true.png" or "false.png"
162 def select_boolean(value)
163 file_exists_or_blank value.to_s
167 def select_numeric(value)
168 file_exists_or_blank value.to_s.gsub('-', '_')
171 def select_time(value)
172 times = @filenames.map { |filename| filename.gsub('.png', '') }
173 times.each_with_index do |time, index|
174 if (time > value) && (index > 0)
175 return "#{times[index - 1]}.png"
178 return "#{times.last}.png"
181 # Match "partly cloudy" to "partly_cloudy.png"
182 def select_string(value)
183 file_exists_or_blank value.to_s.gsub(' ', '_')
187 @filenames.include?("default.png") ? "default.png" : ''
190 # Returns the string "#{filename}.png", if it exists.
192 # Failing that, it returns default.png, or '' if that doesn't exist.
193 def file_exists_or_blank(filename)
194 @filenames.include?("#{filename}.png") ? "#{filename}.png" : select_default