whirled_peas 0.1.0 → 0.4.1
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.
- checksums.yaml +4 -4
- data/.travis.yml +2 -0
- data/CHANGELOG.md +29 -0
- data/Gemfile +1 -0
- data/README.md +213 -48
- data/bin/title_fonts +6 -0
- data/exe/whirled_peas +7 -0
- data/lib/whirled_peas.rb +12 -30
- data/lib/whirled_peas/command_line.rb +270 -0
- data/lib/whirled_peas/config.rb +21 -0
- data/lib/whirled_peas/errors.rb +5 -0
- data/lib/whirled_peas/frame.rb +0 -7
- data/lib/whirled_peas/frame/event_loop.rb +91 -0
- data/lib/whirled_peas/frame/print_consumer.rb +33 -0
- data/lib/whirled_peas/frame/producer.rb +35 -31
- data/lib/whirled_peas/template.rb +5 -0
- data/lib/whirled_peas/template/element.rb +230 -0
- data/lib/whirled_peas/{ui → template}/settings.rb +28 -10
- data/lib/whirled_peas/ui.rb +1 -3
- data/lib/whirled_peas/ui/canvas.rb +37 -4
- data/lib/whirled_peas/ui/painter.rb +22 -18
- data/lib/whirled_peas/ui/screen.rb +24 -23
- data/lib/whirled_peas/utils.rb +5 -0
- data/lib/whirled_peas/utils/ansi.rb +103 -0
- data/lib/whirled_peas/{ui/ansi.rb → utils/color.rb} +23 -76
- data/lib/whirled_peas/utils/title_font.rb +75 -0
- data/lib/whirled_peas/version.rb +1 -1
- data/whirled_peas.gemspec +4 -2
- metadata +22 -18
- data/lib/whirled_peas/frame/consumer.rb +0 -61
- data/lib/whirled_peas/frame/loop.rb +0 -56
- data/lib/whirled_peas/ui/element.rb +0 -199
- data/lib/whirled_peas/ui/stroke.rb +0 -29
- data/sandbox/auto.rb +0 -13
- data/sandbox/box.rb +0 -19
- data/sandbox/grid.rb +0 -13
- data/sandbox/sandbox.rb +0 -17
- data/sandbox/text.rb +0 -33
| @@ -0,0 +1,33 @@ | |
| 1 | 
            +
            require_relative '../null_logger'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module WhirledPeas
         | 
| 4 | 
            +
              module Frame
         | 
| 5 | 
            +
                class PrintConsumer
         | 
| 6 | 
            +
                  LOGGER_ID = 'EVENT LOOP'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def initialize(logger=NullLogger.new)
         | 
| 9 | 
            +
                    @logger = logger
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def enqueue(name, duration, args)
         | 
| 13 | 
            +
                    if name == Frame::EOF
         | 
| 14 | 
            +
                      puts "EOF frame detected"
         | 
| 15 | 
            +
                    else
         | 
| 16 | 
            +
                      displayed_for = duration ? "#{duration} second(s)" : '1 frame'
         | 
| 17 | 
            +
                      args_str = args.empty? ? '' : " (#{args.inspect})"
         | 
| 18 | 
            +
                      puts "Frame '#{name}' displayed for #{displayed_for}#{args_str}"
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def start
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  def stop
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  private
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  attr_reader :logger
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
            end
         | 
| @@ -1,57 +1,61 @@ | |
| 1 1 | 
             
            require 'socket'
         | 
| 2 2 | 
             
            require 'json'
         | 
| 3 3 |  | 
| 4 | 
            +
            require_relative '../null_logger'
         | 
| 5 | 
            +
             | 
| 4 6 | 
             
            module WhirledPeas
         | 
| 5 7 | 
             
              module Frame
         | 
| 6 8 | 
             
                class Producer
         | 
| 7 | 
            -
                   | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 9 | 
            +
                  LOGGER_ID = 'PRODUCER'
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  # Manages the EventLoop lifecycle and yields a Producer to send frames to the
         | 
| 12 | 
            +
                  # EventLoop
         | 
| 13 | 
            +
                  def self.produce(consumer, logger=NullLogger.new)
         | 
| 14 | 
            +
                    producer = new(consumer, logger)
         | 
| 15 | 
            +
                    consumer_thread = Thread.new do
         | 
| 16 | 
            +
                      Thread.current.report_on_exception = false
         | 
| 17 | 
            +
                      consumer.start
         | 
| 18 | 
            +
                    end
         | 
| 12 19 | 
             
                    yield producer
         | 
| 13 | 
            -
                     | 
| 20 | 
            +
                    producer.send_frame(Frame::EOF)
         | 
| 21 | 
            +
                    producer.flush
         | 
| 14 22 | 
             
                  rescue => e
         | 
| 15 | 
            -
                     | 
| 16 | 
            -
                    logger. | 
| 17 | 
            -
                    logger.error( | 
| 23 | 
            +
                    consumer.stop if consumer
         | 
| 24 | 
            +
                    logger.warn(LOGGER_ID) { 'Exited with error' }
         | 
| 25 | 
            +
                    logger.error(LOGGER_ID) { e }
         | 
| 26 | 
            +
                    raise
         | 
| 18 27 | 
             
                  ensure
         | 
| 19 | 
            -
                     | 
| 28 | 
            +
                    consumer_thread.join if consumer_thread
         | 
| 20 29 | 
             
                  end
         | 
| 21 30 |  | 
| 22 | 
            -
                  def initialize( | 
| 23 | 
            -
                    @ | 
| 31 | 
            +
                  def initialize(consumer, logger=NullLogger.new)
         | 
| 32 | 
            +
                    @consumer = consumer
         | 
| 24 33 | 
             
                    @logger = logger
         | 
| 25 34 | 
             
                    @queue = Queue.new
         | 
| 26 35 | 
             
                  end
         | 
| 27 36 |  | 
| 28 | 
            -
                   | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 31 | 
            -
                   | 
| 32 | 
            -
             | 
| 33 | 
            -
                   | 
| 37 | 
            +
                  # Buffer a frame to be played for the given duration. `#flush` must be called
         | 
| 38 | 
            +
                  # for frames to get pushed to the EventLoop.
         | 
| 39 | 
            +
                  #
         | 
| 40 | 
            +
                  # @param name [String] name of frame, which is passed to #build of the
         | 
| 41 | 
            +
                  #   TemplateFactory
         | 
| 42 | 
            +
                  # @param duration [Float|Integer] duration in seconds the frame should be,
         | 
| 43 | 
            +
                  #   displayed (default is nil, which results in a duration of a single refresh
         | 
| 44 | 
            +
                  #   cycle)
         | 
| 45 | 
            +
                  # @param args [Hash] key/value pair of arguments, which is passed to #build of
         | 
| 46 | 
            +
                  #   the TemplateFactory
         | 
| 47 | 
            +
                  def send_frame(name, duration: nil, args: {})
         | 
| 34 48 | 
             
                    queue.push([name, duration, args])
         | 
| 35 49 | 
             
                  end
         | 
| 36 50 |  | 
| 51 | 
            +
                  # Send any buffered frames to the EventLoop
         | 
| 37 52 | 
             
                  def flush
         | 
| 38 | 
            -
                    while !queue.empty?
         | 
| 39 | 
            -
                      name, duration, args = queue.pop
         | 
| 40 | 
            -
                      send(name, duration: duration, args: args)
         | 
| 41 | 
            -
                    end
         | 
| 42 | 
            -
                  end
         | 
| 43 | 
            -
             | 
| 44 | 
            -
                  def stop
         | 
| 45 | 
            -
                    send(Frame::EOF)
         | 
| 46 | 
            -
                  end
         | 
| 47 | 
            -
             | 
| 48 | 
            -
                  def terminate
         | 
| 49 | 
            -
                    send(Frame::TERMINATE)
         | 
| 53 | 
            +
                    consumer.enqueue(*queue.pop) while !queue.empty?
         | 
| 50 54 | 
             
                  end
         | 
| 51 55 |  | 
| 52 56 | 
             
                  private
         | 
| 53 57 |  | 
| 54 | 
            -
                  attr_reader : | 
| 58 | 
            +
                  attr_reader :consumer, :logger, :queue
         | 
| 55 59 | 
             
                end
         | 
| 56 60 | 
             
              end
         | 
| 57 61 | 
             
            end
         | 
| @@ -0,0 +1,230 @@ | |
| 1 | 
            +
            require_relative '../utils/title_font'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative 'settings'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module WhirledPeas
         | 
| 6 | 
            +
              module UI
         | 
| 7 | 
            +
                STRINGALBE_CLASSES = [FalseClass, Float, Integer, NilClass, String, Symbol, TrueClass]
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                class Element
         | 
| 10 | 
            +
                  attr_accessor :preferred_width, :preferred_height
         | 
| 11 | 
            +
                  attr_reader :name, :settings
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def initialize(name, settings)
         | 
| 14 | 
            +
                    @name = name
         | 
| 15 | 
            +
                    @settings = settings
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
                private_constant :Element
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                class TextElement < Element
         | 
| 21 | 
            +
                  attr_reader :lines
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  def initialize(name, settings)
         | 
| 24 | 
            +
                    super(name, TextSettings.merge(settings))
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  def lines=(val)
         | 
| 28 | 
            +
                    unless STRINGALBE_CLASSES.include?(val.class)
         | 
| 29 | 
            +
                      raise ArgmentError, "Unsupported type for TextElement: #{val.class}"
         | 
| 30 | 
            +
                    end
         | 
| 31 | 
            +
                    if settings.title_font
         | 
| 32 | 
            +
                      @lines = Utils::TitleFont.to_s(val.to_s, settings.title_font).split("\n")
         | 
| 33 | 
            +
                    else
         | 
| 34 | 
            +
                      @lines = [val.to_s]
         | 
| 35 | 
            +
                    end
         | 
| 36 | 
            +
                    @preferred_width = settings.width || @lines.first.length
         | 
| 37 | 
            +
                    @preferred_height = @lines.length
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  def inspect(indent='')
         | 
| 41 | 
            +
                    dims = unless preferred_width.nil?
         | 
| 42 | 
            +
                      "#{indent + '  '}- Dimensions: #{preferred_width}x#{preferred_height}"
         | 
| 43 | 
            +
                    end
         | 
| 44 | 
            +
                    [
         | 
| 45 | 
            +
                      "#{indent}+ #{name} [#{self.class.name}]",
         | 
| 46 | 
            +
                      dims,
         | 
| 47 | 
            +
                      "#{indent + '  '}- Settings",
         | 
| 48 | 
            +
                      settings.inspect(indent + '    ')
         | 
| 49 | 
            +
                    ].compact.join("\n")
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                class ComposableElement < Element
         | 
| 54 | 
            +
                  class << self
         | 
| 55 | 
            +
                    def next_name
         | 
| 56 | 
            +
                      @counter ||= 0
         | 
| 57 | 
            +
                      @counter += 1
         | 
| 58 | 
            +
                      "Element-#{@counter}"
         | 
| 59 | 
            +
                    end
         | 
| 60 | 
            +
                  end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                  def initialize(name, settings)
         | 
| 63 | 
            +
                    super
         | 
| 64 | 
            +
                  end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                  def children
         | 
| 67 | 
            +
                    @children ||= []
         | 
| 68 | 
            +
                  end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                  def add_text(name=self.class.next_name, &block)
         | 
| 71 | 
            +
                    element = TextElement.new(name, settings)
         | 
| 72 | 
            +
                    element.lines = yield nil, element.settings
         | 
| 73 | 
            +
                    children << element
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                  def add_box(name=self.class.next_name, &block)
         | 
| 77 | 
            +
                    element = BoxElement.new(name, settings)
         | 
| 78 | 
            +
                    value = yield element, element.settings
         | 
| 79 | 
            +
                    children << element
         | 
| 80 | 
            +
                    if element.children.empty? && STRINGALBE_CLASSES.include?(value.class)
         | 
| 81 | 
            +
                      element.add_text { value.to_s }
         | 
| 82 | 
            +
                    end
         | 
| 83 | 
            +
                  end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                  def add_grid(name=self.class.next_name, &block)
         | 
| 86 | 
            +
                    element = GridElement.new(name, settings)
         | 
| 87 | 
            +
                    values = yield element, element.settings
         | 
| 88 | 
            +
                    children << element
         | 
| 89 | 
            +
                    if element.children.empty? && values.is_a?(Array)
         | 
| 90 | 
            +
                      values.each { |v| element.add_text { v.to_s } }
         | 
| 91 | 
            +
                    end
         | 
| 92 | 
            +
                  end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                  def inspect(indent='')
         | 
| 95 | 
            +
                    kids = children.map { |c| c.inspect(indent + '    ') }.join("\n")
         | 
| 96 | 
            +
                    dims = unless preferred_width.nil?
         | 
| 97 | 
            +
                      "#{indent + '  '}- Dimensions: #{preferred_width}x#{preferred_height}"
         | 
| 98 | 
            +
                    end
         | 
| 99 | 
            +
                    [
         | 
| 100 | 
            +
                      "#{indent}+ #{name} [#{self.class.name}]",
         | 
| 101 | 
            +
                      dims,
         | 
| 102 | 
            +
                      "#{indent + '  '}- Settings",
         | 
| 103 | 
            +
                      settings.inspect(indent + '    '),
         | 
| 104 | 
            +
                      "#{indent + '  '}- Children",
         | 
| 105 | 
            +
                      kids
         | 
| 106 | 
            +
                    ].compact.join("\n")
         | 
| 107 | 
            +
                  end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                  private
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                  def margin_width
         | 
| 112 | 
            +
                    settings.margin.left + settings.margin.right
         | 
| 113 | 
            +
                  end
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                  def margin_height
         | 
| 116 | 
            +
                    settings.margin.top + settings.margin.bottom
         | 
| 117 | 
            +
                  end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                  def outer_border_width
         | 
| 120 | 
            +
                    (settings.border.left? ? 1 : 0) + (settings.border.right? ? 1 : 0)
         | 
| 121 | 
            +
                  end
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                  def outer_border_height
         | 
| 124 | 
            +
                    (settings.border.top? ? 1 : 0) + (settings.border.bottom? ? 1 : 0)
         | 
| 125 | 
            +
                  end
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                  def inner_border_width
         | 
| 128 | 
            +
                    settings.border.inner_vert? ? 1 : 0
         | 
| 129 | 
            +
                  end
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                  def inner_border_height
         | 
| 132 | 
            +
                    settings.border.inner_horiz? ? 1 : 0
         | 
| 133 | 
            +
                  end
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                  def padding_width
         | 
| 136 | 
            +
                    settings.padding.left + settings.padding.right
         | 
| 137 | 
            +
                  end
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                  def padding_height
         | 
| 140 | 
            +
                    settings.padding.top + settings.padding.bottom
         | 
| 141 | 
            +
                  end
         | 
| 142 | 
            +
                end
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                class Template < ComposableElement
         | 
| 145 | 
            +
                  def initialize(settings=TemplateSettings.new)
         | 
| 146 | 
            +
                    super('TEMPLATE', settings)
         | 
| 147 | 
            +
                  end
         | 
| 148 | 
            +
                end
         | 
| 149 | 
            +
             | 
| 150 | 
            +
                class BoxElement < ComposableElement
         | 
| 151 | 
            +
                  attr_writer :content_width, :content_height
         | 
| 152 | 
            +
             | 
| 153 | 
            +
                  def initialize(name, settings)
         | 
| 154 | 
            +
                    super(name, BoxSettings.merge(settings))
         | 
| 155 | 
            +
                  end
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                  def self.from_template(template, width, height)
         | 
| 158 | 
            +
                    box = new(template.name, template.settings)
         | 
| 159 | 
            +
                    template.children.each { |c| box.children << c }
         | 
| 160 | 
            +
                    box.content_width = box.preferred_width = width
         | 
| 161 | 
            +
                    box.content_height = box.preferred_height = height
         | 
| 162 | 
            +
                    box
         | 
| 163 | 
            +
                  end
         | 
| 164 | 
            +
             | 
| 165 | 
            +
                  def content_width
         | 
| 166 | 
            +
                    @content_width ||= begin
         | 
| 167 | 
            +
                      child_widths = children.map(&:preferred_width)
         | 
| 168 | 
            +
                      width = settings.horizontal_flow? ? child_widths.sum : (child_widths.max || 0)
         | 
| 169 | 
            +
                      [width, *settings.width].max
         | 
| 170 | 
            +
                    end
         | 
| 171 | 
            +
                  end
         | 
| 172 | 
            +
             | 
| 173 | 
            +
                  def preferred_width
         | 
| 174 | 
            +
                    @preferred_width ||=
         | 
| 175 | 
            +
                      margin_width + outer_border_width + padding_width + content_width
         | 
| 176 | 
            +
                  end
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                  def content_height
         | 
| 179 | 
            +
                    @content_height ||= begin
         | 
| 180 | 
            +
                      child_heights = children.map(&:preferred_height)
         | 
| 181 | 
            +
                      settings.vertical_flow? ? child_heights.sum : (child_heights.max || 0)
         | 
| 182 | 
            +
                    end
         | 
| 183 | 
            +
                  end
         | 
| 184 | 
            +
             | 
| 185 | 
            +
                  def preferred_height
         | 
| 186 | 
            +
                    @preferred_height ||=
         | 
| 187 | 
            +
                      margin_height + outer_border_height + padding_height + content_height
         | 
| 188 | 
            +
                  end
         | 
| 189 | 
            +
                end
         | 
| 190 | 
            +
             | 
| 191 | 
            +
                class GridElement < ComposableElement
         | 
| 192 | 
            +
                  def initialize(name, settings)
         | 
| 193 | 
            +
                    super(name, GridSettings.merge(settings))
         | 
| 194 | 
            +
                  end
         | 
| 195 | 
            +
             | 
| 196 | 
            +
                  def col_width
         | 
| 197 | 
            +
                    return @col_width if @col_width
         | 
| 198 | 
            +
                    @col_width = 0
         | 
| 199 | 
            +
                    children.each do |child|
         | 
| 200 | 
            +
                      @col_width = child.preferred_width if child.preferred_width > @col_width
         | 
| 201 | 
            +
                    end
         | 
| 202 | 
            +
                    @col_width
         | 
| 203 | 
            +
                  end
         | 
| 204 | 
            +
             | 
| 205 | 
            +
                  def row_height
         | 
| 206 | 
            +
                    return @row_height if @row_height
         | 
| 207 | 
            +
                    @row_height = 0
         | 
| 208 | 
            +
                    children.each do |child|
         | 
| 209 | 
            +
                      @row_height = child.preferred_height if child.preferred_height > @row_height
         | 
| 210 | 
            +
                    end
         | 
| 211 | 
            +
                    @row_height
         | 
| 212 | 
            +
                  end
         | 
| 213 | 
            +
             | 
| 214 | 
            +
                  def preferred_width
         | 
| 215 | 
            +
                    margin_width +
         | 
| 216 | 
            +
                      outer_border_width +
         | 
| 217 | 
            +
                      settings.num_cols * (padding_width + col_width) +
         | 
| 218 | 
            +
                      (settings.num_cols - 1) * inner_border_width
         | 
| 219 | 
            +
                  end
         | 
| 220 | 
            +
             | 
| 221 | 
            +
                  def preferred_height
         | 
| 222 | 
            +
                    num_rows = (children.length / settings.num_cols).ceil
         | 
| 223 | 
            +
                    margin_height +
         | 
| 224 | 
            +
                      outer_border_height +
         | 
| 225 | 
            +
                      num_rows * (padding_height + row_height) +
         | 
| 226 | 
            +
                      (num_rows - 1) * inner_border_height
         | 
| 227 | 
            +
                  end
         | 
| 228 | 
            +
                end
         | 
| 229 | 
            +
              end
         | 
| 230 | 
            +
            end
         | 
| @@ -1,5 +1,8 @@ | |
| 1 1 | 
             
            require 'json'
         | 
| 2 2 |  | 
| 3 | 
            +
            require_relative '../utils/color'
         | 
| 4 | 
            +
            require_relative '../utils/title_font'
         | 
| 5 | 
            +
             | 
| 3 6 | 
             
            module WhirledPeas
         | 
| 4 7 | 
             
              module UI
         | 
| 5 8 | 
             
                module TextAlign
         | 
| @@ -305,11 +308,11 @@ module WhirledPeas | |
| 305 308 | 
             
                end
         | 
| 306 309 | 
             
                private_constant :ElementSettings
         | 
| 307 310 |  | 
| 308 | 
            -
                module  | 
| 311 | 
            +
                module WidthSettings
         | 
| 309 312 | 
             
                  attr_accessor :width
         | 
| 310 313 | 
             
                end
         | 
| 311 314 |  | 
| 312 | 
            -
                module  | 
| 315 | 
            +
                module AlignSettings
         | 
| 313 316 | 
             
                  def align
         | 
| 314 317 | 
             
                    @_align || TextAlign::LEFT
         | 
| 315 318 | 
             
                  end
         | 
| @@ -322,7 +325,7 @@ module WhirledPeas | |
| 322 325 | 
             
                    merged = super
         | 
| 323 326 | 
             
                    merged._align = if @_align
         | 
| 324 327 | 
             
                      @_align
         | 
| 325 | 
            -
                    elsif parent.is_a?( | 
| 328 | 
            +
                    elsif parent.is_a?(AlignSettings)
         | 
| 326 329 | 
             
                      parent._align
         | 
| 327 330 | 
             
                    end
         | 
| 328 331 | 
             
                    merged
         | 
| @@ -342,6 +345,11 @@ module WhirledPeas | |
| 342 345 | 
             
                    @_margin.bottom = bottom if bottom
         | 
| 343 346 | 
             
                  end
         | 
| 344 347 |  | 
| 348 | 
            +
                  def clear_margin
         | 
| 349 | 
            +
                    set_margin(left: 0, top: 0, right: 0, bottom: 0)
         | 
| 350 | 
            +
                    @_auto_margin = nil
         | 
| 351 | 
            +
                  end
         | 
| 352 | 
            +
             | 
| 345 353 | 
             
                  def margin
         | 
| 346 354 | 
             
                    @_margin || Margin.new
         | 
| 347 355 | 
             
                  end
         | 
| @@ -387,7 +395,7 @@ module WhirledPeas | |
| 387 395 | 
             
                    @_border.color = color unless color.nil?
         | 
| 388 396 | 
             
                  end
         | 
| 389 397 |  | 
| 390 | 
            -
                  def  | 
| 398 | 
            +
                  def clear_border
         | 
| 391 399 | 
             
                    set_border(
         | 
| 392 400 | 
             
                      left: false, top: false, right: false, bottom: false, inner_horiz: false, inner_vert: false
         | 
| 393 401 | 
             
                    )
         | 
| @@ -424,6 +432,10 @@ module WhirledPeas | |
| 424 432 | 
             
                    @_padding.bottom = bottom if bottom
         | 
| 425 433 | 
             
                  end
         | 
| 426 434 |  | 
| 435 | 
            +
                  def clear_padding
         | 
| 436 | 
            +
                    set_padding(left: 0, top: 0, right: 0, bottom: 0)
         | 
| 437 | 
            +
                  end
         | 
| 438 | 
            +
             | 
| 427 439 | 
             
                  def padding
         | 
| 428 440 | 
             
                    @_padding || Padding.new
         | 
| 429 441 | 
             
                  end
         | 
| @@ -444,8 +456,14 @@ module WhirledPeas | |
| 444 456 | 
             
                end
         | 
| 445 457 |  | 
| 446 458 | 
             
                class TextSettings < ElementSettings
         | 
| 447 | 
            -
                  include  | 
| 448 | 
            -
                  include  | 
| 459 | 
            +
                  include WidthSettings
         | 
| 460 | 
            +
                  include AlignSettings
         | 
| 461 | 
            +
             | 
| 462 | 
            +
                  attr_reader :title_font
         | 
| 463 | 
            +
             | 
| 464 | 
            +
                  def title_font=(font)
         | 
| 465 | 
            +
                    @title_font = Utils::TitleFont.validate!(font)
         | 
| 466 | 
            +
                  end
         | 
| 449 467 | 
             
                end
         | 
| 450 468 |  | 
| 451 469 | 
             
                class ContainerSettings < ElementSettings
         | 
| @@ -455,8 +473,8 @@ module WhirledPeas | |
| 455 473 | 
             
                end
         | 
| 456 474 |  | 
| 457 475 | 
             
                class BoxSettings < ContainerSettings
         | 
| 458 | 
            -
                  include  | 
| 459 | 
            -
                  include  | 
| 476 | 
            +
                  include WidthSettings
         | 
| 477 | 
            +
                  include AlignSettings
         | 
| 460 478 |  | 
| 461 479 | 
             
                  def flow=(flow)
         | 
| 462 480 | 
             
                    @_flow = DisplayFlow.validate!(flow)
         | 
| @@ -498,8 +516,8 @@ module WhirledPeas | |
| 498 516 | 
             
                end
         | 
| 499 517 |  | 
| 500 518 | 
             
                class GridSettings < ContainerSettings
         | 
| 501 | 
            -
                  include  | 
| 502 | 
            -
                  include  | 
| 519 | 
            +
                  include WidthSettings
         | 
| 520 | 
            +
                  include AlignSettings
         | 
| 503 521 |  | 
| 504 522 | 
             
                  attr_accessor :num_cols
         | 
| 505 523 | 
             
                  attr_writer :transpose
         |