X-Git-Url: https://projects.mako.cc/source/selectricity/blobdiff_plain/1a62f78f4f3818ab697bddf51f80a1ba150ce9e7..131cab23209cb1dfa81930240b8cf722d2302131:/lib/gruff-0.2.8/lib/gruff/scene.rb diff --git a/lib/gruff-0.2.8/lib/gruff/scene.rb b/lib/gruff-0.2.8/lib/gruff/scene.rb new file mode 100644 index 0000000..b0220eb --- /dev/null +++ b/lib/gruff-0.2.8/lib/gruff/scene.rb @@ -0,0 +1,197 @@ + +require "observer" +require File.dirname(__FILE__) + '/base' + +## +# A scene is a non-linear graph that assembles layers together to tell a story. +# Layers are folders with appropriately named files (see below). You can group +# layers and control them together or just set their values individually. +# +# Examples: +# +# * A city scene that changes with the time of day and the weather conditions. +# * A traffic map that shows red lines on streets that are crowded and green on free-flowing ones. +# +# Usage: +# +# g = Gruff::Scene.new("500x100", "artwork/city_scene") +# g.layers = %w(background haze sky clouds) +# g.weather_group = %w(clouds) +# g.time_group = %w(background sky) +# g.weather = "cloudy" +# g.time = Time.now +# g.haze = true +# g.write "hazy_daytime_city_scene.png" +# +# +# +# If there is a file named 'default.png', it will be selected (unless other values are provided to override it). +# +class Gruff::Scene < Gruff::Base + + # An array listing the foldernames that will be rendered, from back to front. + # + # g.layers = %w(sky clouds buildings street people) + # + attr_reader :layers + + def initialize(target_width, base_dir) + @base_dir = base_dir + @groups = {} + @layers = [] + super target_width + end + + def draw + # Join all the custom paths and filter out the empty ones + image_paths = @layers.map { |layer| layer.path }.select { |path| !path.empty? } + images = Magick::ImageList.new(*image_paths) + @base_image = images.flatten_images + end + + def layers=(ordered_list) + ordered_list.each do |layer_name| + @layers << Gruff::Layer.new(@base_dir, layer_name) + end + end + + # Group layers to input values + # + # g.weather_group = ["sky", "sea", "clouds"] + # + # Set input values + # + # g.weather = "cloudy" + # + def method_missing(method_name, *args) + case method_name.to_s + when /^(\w+)_group=$/ + add_group $1, *args + return + when /^(\w+)=$/ + set_input $1, args.first + return + end + super + end + +private + + def add_group(input_name, layer_names) + @groups[input_name] = Gruff::Group.new(input_name, @layers.select { |layer| layer_names.include?(layer.name) }) + end + + def set_input(input_name, input_value) + if not @groups[input_name].nil? + @groups[input_name].send_updates(input_value) + else + if chosen_layer = @layers.detect { |layer| layer.name == input_name } + chosen_layer.update input_value + end + end + end + +end + + +class Gruff::Group + + include Observable + attr_reader :name + + def initialize(folder_name, layers) + @name = folder_name + layers.each do |layer| + layer.observe self + end + end + + def send_updates(value) + changed + notify_observers value + end + +end + + +class Gruff::Layer + + attr_reader :name + + def initialize(base_dir, folder_name) + @base_dir = base_dir.to_s + @name = folder_name.to_s + @filenames = Dir.open(File.join(base_dir, folder_name)).entries.select { |file| file =~ /^[^.]+\.png$/ } + @selected_filename = select_default + end + + # Register this layer so it receives updates from the group + def observe(obj) + obj.add_observer self + end + + # Choose the appropriate filename for this layer, based on the input + def update(value) + @selected_filename = case value.to_s + when /^(true|false)$/ + select_boolean value + when /^(\w|\s)+$/ + select_string value + when /^-?(\d+\.)?\d+$/ + select_numeric value + when /(\d\d):(\d\d):\d\d/ + select_time "#{$1}#{$2}" + else + select_default + end + # Finally, try to use 'default' if we're still blank + @selected_filename ||= select_default + end + + # Returns the full path to the selected image, or a blank string + def path + unless @selected_filename.nil? || @selected_filename.empty? + return File.join(@base_dir, @name, @selected_filename) + end + '' + end + +private + + # Match "true.png" or "false.png" + def select_boolean(value) + file_exists_or_blank value.to_s + end + + # Match -5 to _5.png + def select_numeric(value) + file_exists_or_blank value.to_s.gsub('-', '_') + end + + def select_time(value) + times = @filenames.map { |filename| filename.gsub('.png', '') } + times.each_with_index do |time, index| + if (time > value) && (index > 0) + return "#{times[index - 1]}.png" + end + end + return "#{times.last}.png" + end + + # Match "partly cloudy" to "partly_cloudy.png" + def select_string(value) + file_exists_or_blank value.to_s.gsub(' ', '_') + end + + def select_default + @filenames.include?("default.png") ? "default.png" : '' + end + + # Returns the string "#{filename}.png", if it exists. + # + # Failing that, it returns default.png, or '' if that doesn't exist. + def file_exists_or_blank(filename) + @filenames.include?("#{filename}.png") ? "#{filename}.png" : select_default + end + +end