require_relative 'settings' module WhirledPeas module UI STRINGALBE_CLASSES = [FalseClass, Float, Integer, NilClass, String, Symbol, TrueClass] class Element attr_accessor :preferred_width, :preferred_height attr_reader :name, :settings def initialize(name, settings) @name = name @settings = settings end end private_constant :Element class TextElement < Element attr_reader :value def initialize(name, settings) super(name, TextSettings.merge(settings)) end def value=(val) unless STRINGALBE_CLASSES.include?(val.class) raise ArgmentError, "Unsupported type for TextElement: #{val.class}" end @value = val.to_s @preferred_width = settings.width || value.length @preferred_height = 1 end def inspect(indent='') dims = unless preferred_width.nil? "#{indent + ' '}- Dimensions: #{preferred_width}x#{preferred_height}" end [ "#{indent}+ #{name} [#{self.class.name}]", dims, "#{indent + ' '}- Settings", settings.inspect(indent + ' ') ].compact.join("\n") end end class ComposableElement < Element class << self def next_name @counter ||= 0 @counter += 1 "Element-#{@counter}" end end def initialize(name, settings) super end def children @children ||= [] end def add_text(name=self.class.next_name, &block) element = TextElement.new(name, settings) element.value = yield nil, element.settings children << element end def add_box(name=self.class.next_name, &block) element = BoxElement.new(name, settings) value = yield element, element.settings children << element if element.children.empty? && STRINGALBE_CLASSES.include?(value.class) element.add_text { value.to_s } end end def add_grid(name=self.class.next_name, &block) element = GridElement.new(name, settings) values = yield element, element.settings children << element if element.children.empty? && values.is_a?(Array) values.each { |v| element.add_text { v.to_s } } end end def inspect(indent='') kids = children.map { |c| c.inspect(indent + ' ') }.join("\n") dims = unless preferred_width.nil? "#{indent + ' '}- Dimensions: #{preferred_width}x#{preferred_height}" end [ "#{indent}+ #{name} [#{self.class.name}]", dims, "#{indent + ' '}- Settings", settings.inspect(indent + ' '), "#{indent + ' '}- Children", kids ].compact.join("\n") end private def margin_width settings.margin.left + settings.margin.right end def margin_height settings.margin.top + settings.margin.bottom end def outer_border_width (settings.border.left? ? 1 : 0) + (settings.border.right? ? 1 : 0) end def outer_border_height (settings.border.top? ? 1 : 0) + (settings.border.bottom? ? 1 : 0) end def inner_border_width settings.border.inner_vert? ? 1 : 0 end def inner_border_height settings.border.inner_horiz? ? 1 : 0 end def padding_width settings.padding.left + settings.padding.right end def padding_height settings.padding.top + settings.padding.bottom end end class Template < ComposableElement def initialize(settings=TemplateSettings.new) super('TEMPLATE', settings) end end class BoxElement < ComposableElement attr_writer :content_width, :content_height def initialize(name, settings) super(name, BoxSettings.merge(settings)) end def self.from_template(template, width, height) box = new(template.name, template.settings) template.children.each { |c| box.children << c } box.content_width = box.preferred_width = width box.content_height = box.preferred_height = height box end def content_width @content_width ||= begin child_widths = children.map(&:preferred_width) width = settings.horizontal_flow? ? child_widths.sum : (child_widths.max || 0) [width, *settings.width].max end end def preferred_width @preferred_width ||= margin_width + outer_border_width + padding_width + content_width end def content_height @content_height ||= begin child_heights = children.map(&:preferred_height) settings.vertical_flow? ? child_heights.sum : (child_heights.max || 0) end end def preferred_height @preferred_height ||= margin_height + outer_border_height + padding_height + content_height end end class GridElement < ComposableElement def initialize(name, settings) super(name, GridSettings.merge(settings)) end def col_width return @col_width if @col_width @col_width = 0 children.each do |child| @col_width = child.preferred_width if child.preferred_width > @col_width end @col_width end def row_height return @row_height if @row_height @row_height = 0 children.each do |child| @row_height = child.preferred_height if child.preferred_height > @row_height end @row_height end def preferred_width margin_width + outer_border_width + settings.num_cols * (padding_width + col_width) + (settings.num_cols - 1) * inner_border_width end def preferred_height num_rows = (children.length / settings.num_cols).ceil margin_height + outer_border_height + num_rows * (padding_height + row_height) + (num_rows - 1) * inner_border_height end end end end