whirled_peas 0.4.1 → 0.8.0
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/.gitignore +1 -0
- data/.travis.yml +1 -0
- data/CHANGELOG.md +37 -0
- data/README.md +529 -156
- data/Rakefile +9 -3
- data/bin/reset_cursor +11 -0
- data/bin/screen_test +68 -0
- data/examples/intro.rb +52 -0
- data/examples/scrolling.rb +54 -0
- data/lib/whirled_peas.rb +6 -12
- data/lib/whirled_peas/animator.rb +5 -0
- data/lib/whirled_peas/animator/debug_consumer.rb +17 -0
- data/lib/whirled_peas/animator/easing.rb +72 -0
- data/lib/whirled_peas/animator/frame.rb +5 -0
- data/lib/whirled_peas/animator/frameset.rb +33 -0
- data/lib/whirled_peas/animator/producer.rb +35 -0
- data/lib/whirled_peas/animator/renderer_consumer.rb +31 -0
- data/lib/whirled_peas/command.rb +5 -0
- data/lib/whirled_peas/command/base.rb +86 -0
- data/lib/whirled_peas/command/config_command.rb +44 -0
- data/lib/whirled_peas/command/debug.rb +21 -0
- data/lib/whirled_peas/command/fonts.rb +22 -0
- data/lib/whirled_peas/command/frame_command.rb +34 -0
- data/lib/whirled_peas/command/frames.rb +24 -0
- data/lib/whirled_peas/command/help.rb +38 -0
- data/lib/whirled_peas/command/play.rb +108 -0
- data/lib/whirled_peas/command/record.rb +57 -0
- data/lib/whirled_peas/command/still.rb +29 -0
- data/lib/whirled_peas/command_line.rb +23 -228
- data/lib/whirled_peas/config.rb +56 -6
- data/lib/whirled_peas/device.rb +5 -0
- data/lib/whirled_peas/device/null_device.rb +8 -0
- data/lib/whirled_peas/device/output_file.rb +19 -0
- data/lib/whirled_peas/device/screen.rb +26 -0
- data/lib/whirled_peas/errors.rb +2 -0
- data/lib/whirled_peas/graphics.rb +19 -0
- data/lib/whirled_peas/graphics/box_painter.rb +101 -0
- data/lib/whirled_peas/graphics/canvas.rb +118 -0
- data/lib/whirled_peas/graphics/composer.rb +80 -0
- data/lib/whirled_peas/graphics/container_coords.rb +72 -0
- data/lib/whirled_peas/graphics/container_dimensions.rb +93 -0
- data/lib/whirled_peas/graphics/container_painter.rb +363 -0
- data/lib/whirled_peas/graphics/debugger.rb +52 -0
- data/lib/whirled_peas/graphics/grid_painter.rb +69 -0
- data/lib/whirled_peas/graphics/mock_screen.rb +26 -0
- data/lib/whirled_peas/graphics/painter.rb +33 -0
- data/lib/whirled_peas/graphics/renderer.rb +32 -0
- data/lib/whirled_peas/graphics/text_dimensions.rb +15 -0
- data/lib/whirled_peas/graphics/text_painter.rb +40 -0
- data/lib/whirled_peas/settings.rb +5 -0
- data/lib/whirled_peas/settings/alignment.rb +24 -0
- data/lib/whirled_peas/settings/bg_color.rb +24 -0
- data/lib/whirled_peas/settings/border.rb +101 -0
- data/lib/whirled_peas/settings/box_settings.rb +8 -0
- data/lib/whirled_peas/settings/color.rb +68 -0
- data/lib/whirled_peas/settings/container_settings.rb +223 -0
- data/lib/whirled_peas/settings/debugger.rb +96 -0
- data/lib/whirled_peas/settings/display_flow.rb +27 -0
- data/lib/whirled_peas/settings/element_settings.rb +61 -0
- data/lib/whirled_peas/settings/grid_settings.rb +19 -0
- data/lib/whirled_peas/settings/margin.rb +8 -0
- data/lib/whirled_peas/settings/padding.rb +8 -0
- data/lib/whirled_peas/settings/position.rb +15 -0
- data/lib/whirled_peas/settings/scrollbar.rb +15 -0
- data/lib/whirled_peas/settings/sizing.rb +19 -0
- data/lib/whirled_peas/settings/spacing.rb +58 -0
- data/lib/whirled_peas/settings/text_color.rb +21 -0
- data/lib/whirled_peas/settings/text_settings.rb +15 -0
- data/lib/whirled_peas/settings/vert_alignment.rb +24 -0
- data/lib/whirled_peas/utils/ansi.rb +19 -56
- data/lib/whirled_peas/utils/file_handler.rb +57 -0
- data/lib/whirled_peas/utils/formatted_string.rb +64 -0
- data/lib/whirled_peas/utils/title_font.rb +1 -1
- data/lib/whirled_peas/version.rb +1 -1
- data/screen_test/elements/box.frame +1 -0
- data/screen_test/elements/box.rb +20 -0
- data/screen_test/elements/grid.frame +1 -0
- data/screen_test/elements/grid.rb +13 -0
- data/screen_test/elements/screen_overflow_x.frame +1 -0
- data/screen_test/elements/screen_overflow_x.rb +9 -0
- data/screen_test/elements/screen_overflow_y.frame +1 -0
- data/screen_test/elements/screen_overflow_y.rb +9 -0
- data/screen_test/elements/text.frame +1 -0
- data/screen_test/elements/text.rb +9 -0
- data/screen_test/elements/text_multiline.frame +1 -0
- data/screen_test/elements/text_multiline.rb +9 -0
- data/screen_test/settings/align/box_around.frame +1 -0
- data/screen_test/settings/align/box_around.rb +16 -0
- data/screen_test/settings/align/box_between.frame +1 -0
- data/screen_test/settings/align/box_between.rb +16 -0
- data/screen_test/settings/align/box_center.frame +1 -0
- data/screen_test/settings/align/box_center.rb +21 -0
- data/screen_test/settings/align/box_default.frame +1 -0
- data/screen_test/settings/align/box_default.rb +20 -0
- data/screen_test/settings/align/box_evenly.frame +1 -0
- data/screen_test/settings/align/box_evenly.rb +16 -0
- data/screen_test/settings/align/box_left.frame +1 -0
- data/screen_test/settings/align/box_left.rb +21 -0
- data/screen_test/settings/align/box_right.frame +1 -0
- data/screen_test/settings/align/box_right.rb +21 -0
- data/screen_test/settings/align/children_center.frame +1 -0
- data/screen_test/settings/align/children_center.rb +15 -0
- data/screen_test/settings/align/children_left.frame +1 -0
- data/screen_test/settings/align/children_left.rb +15 -0
- data/screen_test/settings/align/children_right.frame +1 -0
- data/screen_test/settings/align/children_right.rb +15 -0
- data/screen_test/settings/align/grid_center.frame +1 -0
- data/screen_test/settings/align/grid_center.rb +18 -0
- data/screen_test/settings/align/grid_default.frame +1 -0
- data/screen_test/settings/align/grid_default.rb +17 -0
- data/screen_test/settings/align/grid_left.frame +1 -0
- data/screen_test/settings/align/grid_left.rb +18 -0
- data/screen_test/settings/align/grid_right.frame +1 -0
- data/screen_test/settings/align/grid_right.rb +18 -0
- data/screen_test/settings/ansi/bold.frame +1 -0
- data/screen_test/settings/ansi/bold.rb +14 -0
- data/screen_test/settings/ansi/color.frame +1 -0
- data/screen_test/settings/ansi/color.rb +37 -0
- data/screen_test/settings/ansi/underline.frame +1 -0
- data/screen_test/settings/ansi/underline.rb +14 -0
- data/screen_test/settings/border.frame +1 -0
- data/screen_test/settings/border.rb +13 -0
- data/screen_test/settings/flow/box_b2t.frame +1 -0
- data/screen_test/settings/flow/box_b2t.rb +26 -0
- data/screen_test/settings/flow/box_l2r.frame +1 -0
- data/screen_test/settings/flow/box_l2r.rb +26 -0
- data/screen_test/settings/flow/box_r2l.frame +1 -0
- data/screen_test/settings/flow/box_r2l.rb +26 -0
- data/screen_test/settings/flow/box_t2b.frame +1 -0
- data/screen_test/settings/flow/box_t2b.rb +26 -0
- data/screen_test/settings/flow/grid_b2t.frame +1 -0
- data/screen_test/settings/flow/grid_b2t.rb +14 -0
- data/screen_test/settings/flow/grid_l2r.frame +1 -0
- data/screen_test/settings/flow/grid_l2r.rb +14 -0
- data/screen_test/settings/flow/grid_r2l.frame +1 -0
- data/screen_test/settings/flow/grid_r2l.rb +14 -0
- data/screen_test/settings/flow/grid_t2b.frame +1 -0
- data/screen_test/settings/flow/grid_t2b.rb +14 -0
- data/screen_test/settings/height/box.frame +1 -0
- data/screen_test/settings/height/box.rb +13 -0
- data/screen_test/settings/height/box_border_sizing.frame +1 -0
- data/screen_test/settings/height/box_border_sizing.rb +15 -0
- data/screen_test/settings/height/grid.frame +1 -0
- data/screen_test/settings/height/grid.rb +14 -0
- data/screen_test/settings/height/overflow_box.frame +1 -0
- data/screen_test/settings/height/overflow_box.rb +13 -0
- data/screen_test/settings/height/overflow_box_l2r.frame +1 -0
- data/screen_test/settings/height/overflow_box_l2r.rb +17 -0
- data/screen_test/settings/height/overflow_box_t2b.frame +1 -0
- data/screen_test/settings/height/overflow_box_t2b.rb +16 -0
- data/screen_test/settings/height/overflow_grid.frame +1 -0
- data/screen_test/settings/height/overflow_grid.rb +16 -0
- data/screen_test/settings/margin.frame +1 -0
- data/screen_test/settings/margin.rb +16 -0
- data/screen_test/settings/padding.frame +1 -0
- data/screen_test/settings/padding.rb +13 -0
- data/screen_test/settings/position/box_left.frame +1 -0
- data/screen_test/settings/position/box_left.rb +17 -0
- data/screen_test/settings/position/box_left_negative.frame +1 -0
- data/screen_test/settings/position/box_left_negative.rb +17 -0
- data/screen_test/settings/position/box_top.frame +1 -0
- data/screen_test/settings/position/box_top.rb +17 -0
- data/screen_test/settings/position/box_top_negative.frame +1 -0
- data/screen_test/settings/position/box_top_negative.rb +17 -0
- data/screen_test/settings/position/grid_left.frame +1 -0
- data/screen_test/settings/position/grid_left.rb +18 -0
- data/screen_test/settings/position/grid_left_negative.frame +1 -0
- data/screen_test/settings/position/grid_left_negative.rb +18 -0
- data/screen_test/settings/position/grid_top.frame +1 -0
- data/screen_test/settings/position/grid_top.rb +18 -0
- data/screen_test/settings/position/grid_top_negative.frame +1 -0
- data/screen_test/settings/position/grid_top_negative.rb +18 -0
- data/screen_test/settings/scroll/horiz_box.frame +1 -0
- data/screen_test/settings/scroll/horiz_box.rb +17 -0
- data/screen_test/settings/scroll/horiz_box_align_center.rb +18 -0
- data/screen_test/settings/scroll/horiz_box_align_right.rb +18 -0
- data/screen_test/settings/scroll/vert_box.frame +1 -0
- data/screen_test/settings/scroll/vert_box.rb +20 -0
- data/screen_test/settings/title_font.frame +1 -0
- data/screen_test/settings/title_font.rb +12 -0
- data/screen_test/settings/valign/box_around.frame +1 -0
- data/screen_test/settings/valign/box_around.rb +17 -0
- data/screen_test/settings/valign/box_between.frame +1 -0
- data/screen_test/settings/valign/box_between.rb +17 -0
- data/screen_test/settings/valign/box_bottom.frame +1 -0
- data/screen_test/settings/valign/box_bottom.rb +17 -0
- data/screen_test/settings/valign/box_default.frame +1 -0
- data/screen_test/settings/valign/box_default.rb +16 -0
- data/screen_test/settings/valign/box_evenly.frame +1 -0
- data/screen_test/settings/valign/box_evenly.rb +17 -0
- data/screen_test/settings/valign/box_middle.frame +1 -0
- data/screen_test/settings/valign/box_middle.rb +17 -0
- data/screen_test/settings/valign/box_top.frame +1 -0
- data/screen_test/settings/valign/box_top.rb +17 -0
- data/screen_test/settings/valign/grid_bottom.frame +1 -0
- data/screen_test/settings/valign/grid_bottom.rb +15 -0
- data/screen_test/settings/valign/grid_default.frame +1 -0
- data/screen_test/settings/valign/grid_default.rb +14 -0
- data/screen_test/settings/valign/grid_middle.frame +1 -0
- data/screen_test/settings/valign/grid_middle.rb +15 -0
- data/screen_test/settings/valign/grid_top.frame +1 -0
- data/screen_test/settings/valign/grid_top.rb +15 -0
- data/screen_test/settings/width/box_border_sizing.frame +1 -0
- data/screen_test/settings/width/box_border_sizing.rb +15 -0
- data/screen_test/settings/width/box_content.frame +1 -0
- data/screen_test/settings/width/box_content.rb +15 -0
- data/screen_test/settings/width/box_default.frame +1 -0
- data/screen_test/settings/width/box_default.rb +14 -0
- data/screen_test/settings/width/grid.frame +1 -0
- data/screen_test/settings/width/grid.rb +14 -0
- data/screen_test/settings/width/overflow_align_center.frame +1 -0
- data/screen_test/settings/width/overflow_align_center.rb +14 -0
- data/screen_test/settings/width/overflow_align_right.frame +1 -0
- data/screen_test/settings/width/overflow_align_right.rb +14 -0
- data/screen_test/settings/width/overflow_box.frame +1 -0
- data/screen_test/settings/width/overflow_box.rb +13 -0
- data/screen_test/settings/width/overflow_box_l2r.frame +1 -0
- data/screen_test/settings/width/overflow_box_l2r.rb +16 -0
- data/screen_test/settings/width/overflow_box_t2b.frame +1 -0
- data/screen_test/settings/width/overflow_box_t2b.rb +17 -0
- data/screen_test/settings/width/overflow_grid.frame +1 -0
- data/screen_test/settings/width/overflow_grid.rb +14 -0
- data/tools/whirled_peas/tools/screen_tester.rb +285 -0
- metadata +213 -15
- data/bin/title_fonts +0 -6
- data/lib/whirled_peas/frame.rb +0 -7
- data/lib/whirled_peas/frame/event_loop.rb +0 -91
- data/lib/whirled_peas/frame/print_consumer.rb +0 -33
- data/lib/whirled_peas/frame/producer.rb +0 -61
- data/lib/whirled_peas/template.rb +0 -5
- data/lib/whirled_peas/template/element.rb +0 -230
- data/lib/whirled_peas/template/settings.rb +0 -530
- data/lib/whirled_peas/ui.rb +0 -5
- data/lib/whirled_peas/ui/canvas.rb +0 -68
- data/lib/whirled_peas/ui/painter.rb +0 -287
- data/lib/whirled_peas/ui/screen.rb +0 -63
- data/lib/whirled_peas/utils/color.rb +0 -101
| @@ -0,0 +1,118 @@ | |
| 1 | 
            +
            require 'whirled_peas/utils/formatted_string'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module WhirledPeas
         | 
| 4 | 
            +
              module Graphics
         | 
| 5 | 
            +
                # Canvas represent the area of the screen a painter can paint on.
         | 
| 6 | 
            +
                class Canvas
         | 
| 7 | 
            +
                  attr_reader :left, :top, :width, :height
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  def self.unwritable
         | 
| 10 | 
            +
                    new(-1, -1, 0, 0)
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def initialize(left, top, width, height)
         | 
| 14 | 
            +
                    @left = left
         | 
| 15 | 
            +
                    @top = top
         | 
| 16 | 
            +
                    @width = width
         | 
| 17 | 
            +
                    @height = height
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  def writable?
         | 
| 21 | 
            +
                    width > 0 || height > 0
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  def child(child_left, child_top, child_width, child_height)
         | 
| 25 | 
            +
                    Graphics.debugger(
         | 
| 26 | 
            +
                      proc do
         | 
| 27 | 
            +
                        "Create child: #{self.inspect}.child(left=#{child_left}, top=#{child_top}, width=#{child_width}, height=#{child_height})"
         | 
| 28 | 
            +
                      end
         | 
| 29 | 
            +
                    )
         | 
| 30 | 
            +
                    if child_left >= left + width
         | 
| 31 | 
            +
                      self.class.unwritable
         | 
| 32 | 
            +
                    elsif child_left + child_width <= left
         | 
| 33 | 
            +
                      self.class.unwritable
         | 
| 34 | 
            +
                    elsif child_top >= top + height
         | 
| 35 | 
            +
                      self.class.unwritable
         | 
| 36 | 
            +
                    elsif child_top + child_height <= top
         | 
| 37 | 
            +
                      self.class.unwritable
         | 
| 38 | 
            +
                    else
         | 
| 39 | 
            +
                      if child_left < left
         | 
| 40 | 
            +
                        child_width -= left - child_left
         | 
| 41 | 
            +
                        child_left = left
         | 
| 42 | 
            +
                      end
         | 
| 43 | 
            +
                      child_width = [width - (child_left - left), child_width].min
         | 
| 44 | 
            +
                      if child_top < top
         | 
| 45 | 
            +
                        child_height -= top - child_top
         | 
| 46 | 
            +
                        child_top = top
         | 
| 47 | 
            +
                      end
         | 
| 48 | 
            +
                      child_height = [height - (child_top - top), child_height].min
         | 
| 49 | 
            +
                      child_canvas = self.class.new(
         | 
| 50 | 
            +
                        child_left,
         | 
| 51 | 
            +
                        child_top,
         | 
| 52 | 
            +
                        child_width,
         | 
| 53 | 
            +
                        child_height,
         | 
| 54 | 
            +
                      )
         | 
| 55 | 
            +
                      Graphics.debugger(proc { "  -> #{child_canvas.inspect}" })
         | 
| 56 | 
            +
                      child_canvas
         | 
| 57 | 
            +
                    end
         | 
| 58 | 
            +
                  end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  # Yields a single line of formatted characters positioned on the canvas,
         | 
| 61 | 
            +
                  # verifying only characters within the canvas are included.
         | 
| 62 | 
            +
                  def stroke(stroke_left, stroke_top, raw, formatting=[], &block)
         | 
| 63 | 
            +
                    Graphics.debugger(
         | 
| 64 | 
            +
                      proc do
         | 
| 65 | 
            +
                        "Stroke: #{self.inspect}.stroke(left=#{stroke_left}, top=#{stroke_top}, length=#{raw.length})"
         | 
| 66 | 
            +
                      end
         | 
| 67 | 
            +
                    )
         | 
| 68 | 
            +
                    if stroke_left >= left + width
         | 
| 69 | 
            +
                      # The stroke starts to the right of the canvas
         | 
| 70 | 
            +
                      fstring = Utils::FormattedString.blank
         | 
| 71 | 
            +
                    elsif stroke_left + raw.length <= left
         | 
| 72 | 
            +
                      # The stroke ends to the left of the canvas
         | 
| 73 | 
            +
                      fstring = Utils::FormattedString.blank
         | 
| 74 | 
            +
                    elsif stroke_top < top
         | 
| 75 | 
            +
                      # The stroke is above the canvas
         | 
| 76 | 
            +
                      fstring = Utils::FormattedString.blank
         | 
| 77 | 
            +
                    elsif stroke_top >= top + height
         | 
| 78 | 
            +
                      # The stroke is below the canvas
         | 
| 79 | 
            +
                      fstring = Utils::FormattedString.blank
         | 
| 80 | 
            +
                    else
         | 
| 81 | 
            +
                      # In this section, we know that at least part of the stroke should be visible
         | 
| 82 | 
            +
                      # on the canvas. Chop off parts of the raw string that aren't within the
         | 
| 83 | 
            +
                      # canvas boundary and ensure the stroke start position is also within the
         | 
| 84 | 
            +
                      # canvas boundary
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                      # If the stroke starts to the left of the canvas, set the start index to the
         | 
| 87 | 
            +
                      # first value that will be on the canvas, then update stroke_left to be on
         | 
| 88 | 
            +
                      # the canvas
         | 
| 89 | 
            +
                      start_index = stroke_left < left ? left - stroke_left : 0
         | 
| 90 | 
            +
                      stroke_left = left if stroke_left <= left
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                      # Determine how many characters from the stroke will fit on the canvas
         | 
| 93 | 
            +
                      visible_length = [raw.length, width - (stroke_left - left)].min
         | 
| 94 | 
            +
                      end_index = start_index + visible_length - 1
         | 
| 95 | 
            +
                      fstring = Utils::FormattedString.new(raw[start_index..end_index], formatting)
         | 
| 96 | 
            +
                    end
         | 
| 97 | 
            +
                    Graphics.debugger(
         | 
| 98 | 
            +
                      proc do
         | 
| 99 | 
            +
                        "  -> Stroke(left=#{stroke_left}, top=#{stroke_top}, length=#{fstring.length})"
         | 
| 100 | 
            +
                      end
         | 
| 101 | 
            +
                    )
         | 
| 102 | 
            +
                    yield stroke_left, stroke_top, fstring
         | 
| 103 | 
            +
                  end
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                  def hash
         | 
| 106 | 
            +
                    [left, top, width, height].hash
         | 
| 107 | 
            +
                  end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                  def ==(other)
         | 
| 110 | 
            +
                    other.is_a?(self.class) && hash == other.hash
         | 
| 111 | 
            +
                  end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                  def inspect
         | 
| 114 | 
            +
                    "Canvas(left=#{left}, top=#{top}, width=#{width}, height=#{height})"
         | 
| 115 | 
            +
                  end
         | 
| 116 | 
            +
                end
         | 
| 117 | 
            +
              end
         | 
| 118 | 
            +
            end
         | 
| @@ -0,0 +1,80 @@ | |
| 1 | 
            +
            require 'whirled_peas/settings/box_settings'
         | 
| 2 | 
            +
            require 'whirled_peas/settings/grid_settings'
         | 
| 3 | 
            +
            require 'whirled_peas/settings/text_settings'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            require_relative 'box_painter'
         | 
| 6 | 
            +
            require_relative 'grid_painter'
         | 
| 7 | 
            +
            require_relative 'text_painter'
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            module WhirledPeas
         | 
| 10 | 
            +
              module Graphics
         | 
| 11 | 
            +
                class Composer
         | 
| 12 | 
            +
                  # List of classes that convert nicely to a string
         | 
| 13 | 
            +
                  STRINGALBE_CLASSES = [FalseClass, Float, Integer, NilClass, String, Symbol, TrueClass]
         | 
| 14 | 
            +
                  private_constant :STRINGALBE_CLASSES
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def self.stringable?(value)
         | 
| 17 | 
            +
                    STRINGALBE_CLASSES.include?(value.class)
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  def self.next_name
         | 
| 21 | 
            +
                    @counter ||= 0
         | 
| 22 | 
            +
                    @counter += 1
         | 
| 23 | 
            +
                    "Element-#{@counter}"
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def self.build
         | 
| 27 | 
            +
                    settings = Settings::BoxSettings.new
         | 
| 28 | 
            +
                    template = BoxPainter.new('TEMPLATE', settings)
         | 
| 29 | 
            +
                    composer = Composer.new(template)
         | 
| 30 | 
            +
                    value = yield composer, settings
         | 
| 31 | 
            +
                    if !template.children? && stringable?(value)
         | 
| 32 | 
            +
                      composer.add_text { value.to_s }
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
                    template
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  attr_reader :painter
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  def initialize(painter)
         | 
| 40 | 
            +
                    @painter = painter
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  def add_text(name=self.class.next_name, &block)
         | 
| 44 | 
            +
                    child_settings = Settings::TextSettings.inherit(painter.settings)
         | 
| 45 | 
            +
                    child = TextPainter.new(name, child_settings)
         | 
| 46 | 
            +
                    # TextPainters are not composable, so yield nil
         | 
| 47 | 
            +
                    content = yield nil, child_settings
         | 
| 48 | 
            +
                    unless self.class.stringable?(content)
         | 
| 49 | 
            +
                      raise ArgumentError, "Unsupported type for text: #{content.class}"
         | 
| 50 | 
            +
                    end
         | 
| 51 | 
            +
                    child.content = content.to_s
         | 
| 52 | 
            +
                    painter.add_child(child)
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  def add_box(name=self.class.next_name, &block)
         | 
| 56 | 
            +
                    child_settings = Settings::BoxSettings.inherit(painter.settings)
         | 
| 57 | 
            +
                    child = BoxPainter.new(name, child_settings)
         | 
| 58 | 
            +
                    composer = self.class.new(child)
         | 
| 59 | 
            +
                    value = yield composer, child.settings
         | 
| 60 | 
            +
                    painter.add_child(child)
         | 
| 61 | 
            +
                    if !child.children? && self.class.stringable?(value)
         | 
| 62 | 
            +
                      composer.add_text("#{name}-Text") { value.to_s }
         | 
| 63 | 
            +
                    end
         | 
| 64 | 
            +
                  end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                  def add_grid(name=self.class.next_name, &block)
         | 
| 67 | 
            +
                    child_settings = Settings::GridSettings.inherit(painter.settings)
         | 
| 68 | 
            +
                    child = GridPainter.new(name, child_settings)
         | 
| 69 | 
            +
                    composer = self.class.new(child)
         | 
| 70 | 
            +
                    values = yield composer, child.settings
         | 
| 71 | 
            +
                    painter.add_child(child)
         | 
| 72 | 
            +
                    if !child.children? && values.is_a?(Array)
         | 
| 73 | 
            +
                      values.each.with_index do |value, index|
         | 
| 74 | 
            +
                        composer.add_text("#{name}-Text-#{index}") { value.to_s }
         | 
| 75 | 
            +
                      end
         | 
| 76 | 
            +
                    end
         | 
| 77 | 
            +
                  end
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
              end
         | 
| 80 | 
            +
            end
         | 
| @@ -0,0 +1,72 @@ | |
| 1 | 
            +
            module WhirledPeas
         | 
| 2 | 
            +
              module Graphics
         | 
| 3 | 
            +
                class ContainerCoords
         | 
| 4 | 
            +
                  def initialize(dimensions, settings, start_left, start_top)
         | 
| 5 | 
            +
                    @dimensions = dimensions
         | 
| 6 | 
            +
                    @settings = settings
         | 
| 7 | 
            +
                    @start_left = start_left
         | 
| 8 | 
            +
                    @start_top = start_top
         | 
| 9 | 
            +
                  end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  def left
         | 
| 12 | 
            +
                    start_left + settings.position.left
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  def top
         | 
| 16 | 
            +
                    start_top + settings.position.top
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  def border_left
         | 
| 20 | 
            +
                    left + settings.margin.left
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  def border_top
         | 
| 24 | 
            +
                    top + settings.margin.top
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  def padding_left
         | 
| 28 | 
            +
                    border_left + (settings.border.left? ? 1 : 0)
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  def padding_top
         | 
| 32 | 
            +
                    border_top + (settings.border.top? ? 1 : 0)
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  def content_left(col_index=0)
         | 
| 36 | 
            +
                    padding_left + settings.padding.left + col_index * grid_width
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  def content_top(row_index=0)
         | 
| 40 | 
            +
                    padding_top + settings.padding.top + row_index * grid_height
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  def inner_grid_width
         | 
| 44 | 
            +
                    settings.padding.left +
         | 
| 45 | 
            +
                      dimensions.content_width +
         | 
| 46 | 
            +
                      settings.padding.right
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                  def grid_width
         | 
| 50 | 
            +
                    (settings.border.inner_vert? ? 1 : 0) +
         | 
| 51 | 
            +
                      inner_grid_width +
         | 
| 52 | 
            +
                      (settings.scrollbar.vert? ? 1 : 0)
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  def inner_grid_height
         | 
| 56 | 
            +
                    settings.padding.top +
         | 
| 57 | 
            +
                      dimensions.content_height +
         | 
| 58 | 
            +
                      settings.padding.bottom
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                  def grid_height
         | 
| 62 | 
            +
                    (settings.border.inner_horiz? ? 1 : 0) +
         | 
| 63 | 
            +
                      inner_grid_height +
         | 
| 64 | 
            +
                      (settings.scrollbar.horiz? ? 1 : 0)
         | 
| 65 | 
            +
                  end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                  private
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                  attr_reader :settings, :dimensions, :start_left, :start_top
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
              end
         | 
| 72 | 
            +
            end
         | 
| @@ -0,0 +1,93 @@ | |
| 1 | 
            +
            module WhirledPeas
         | 
| 2 | 
            +
              module Graphics
         | 
| 3 | 
            +
                class ContainerDimensions
         | 
| 4 | 
            +
                  attr_reader :children_width, :children_height, :num_cols, :num_rows
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  def initialize(settings, content_width, content_height, num_cols=1, num_rows=1)
         | 
| 7 | 
            +
                    @orig_content_width = content_width
         | 
| 8 | 
            +
                    @orig_content_height = content_height
         | 
| 9 | 
            +
                    @children_width = content_width
         | 
| 10 | 
            +
                    @children_height = content_height
         | 
| 11 | 
            +
                    @num_cols = num_cols
         | 
| 12 | 
            +
                    @num_rows = num_rows
         | 
| 13 | 
            +
                    @settings = settings
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def content_width
         | 
| 17 | 
            +
                    return orig_content_width unless settings.width
         | 
| 18 | 
            +
                    if settings.border_sizing?
         | 
| 19 | 
            +
                      settings.width - outer_border_width - scrollbar_width - padding_width
         | 
| 20 | 
            +
                    else
         | 
| 21 | 
            +
                      settings.width
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  def content_height
         | 
| 26 | 
            +
                    return orig_content_height unless settings.height
         | 
| 27 | 
            +
                    if settings.border_sizing?
         | 
| 28 | 
            +
                      settings.height - outer_border_height - scrollbar_height - padding_height
         | 
| 29 | 
            +
                    else
         | 
| 30 | 
            +
                      settings.height
         | 
| 31 | 
            +
                    end
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  def outer_width
         | 
| 35 | 
            +
                    @outer_width ||= margin_width +
         | 
| 36 | 
            +
                      outer_border_width +
         | 
| 37 | 
            +
                      num_cols * (padding_width + content_width + scrollbar_width) +
         | 
| 38 | 
            +
                      (num_cols - 1) * inner_border_width
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  def outer_height
         | 
| 42 | 
            +
                    @outer_height ||= margin_height +
         | 
| 43 | 
            +
                      outer_border_height +
         | 
| 44 | 
            +
                      num_rows * (padding_height + content_height + scrollbar_height) +
         | 
| 45 | 
            +
                      (num_rows - 1) * inner_border_height
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  def margin_width
         | 
| 49 | 
            +
                    settings.margin.left + settings.margin.right
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  def margin_height
         | 
| 53 | 
            +
                    settings.margin.top + settings.margin.bottom
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  def outer_border_width
         | 
| 57 | 
            +
                    (settings.border.left? ? 1 : 0) + (settings.border.right? ? 1 : 0)
         | 
| 58 | 
            +
                  end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  def outer_border_height
         | 
| 61 | 
            +
                    (settings.border.top? ? 1 : 0) + (settings.border.bottom? ? 1 : 0)
         | 
| 62 | 
            +
                  end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                  def inner_border_width
         | 
| 65 | 
            +
                    settings.border.inner_vert? ? 1 : 0
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                  def inner_border_height
         | 
| 69 | 
            +
                    settings.border.inner_horiz? ? 1 : 0
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  def padding_width
         | 
| 73 | 
            +
                    settings.padding.left + settings.padding.right
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                  def padding_height
         | 
| 77 | 
            +
                    settings.padding.top + settings.padding.bottom
         | 
| 78 | 
            +
                  end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                  def scrollbar_width
         | 
| 81 | 
            +
                    settings.scrollbar.vert? ? 1 : 0
         | 
| 82 | 
            +
                  end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                  def scrollbar_height
         | 
| 85 | 
            +
                    settings.scrollbar.horiz? ? 1 : 0
         | 
| 86 | 
            +
                  end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                  private
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                  attr_reader :settings, :orig_content_width, :orig_content_height
         | 
| 91 | 
            +
                end
         | 
| 92 | 
            +
              end
         | 
| 93 | 
            +
            end
         | 
| @@ -0,0 +1,363 @@ | |
| 1 | 
            +
            require 'whirled_peas/utils/formatted_string'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative 'container_coords'
         | 
| 4 | 
            +
            require_relative 'painter'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module WhirledPeas
         | 
| 7 | 
            +
              module Graphics
         | 
| 8 | 
            +
                # Abstract Painter for containers. Containers (as the name implies) contain other child
         | 
| 9 | 
            +
                # elements and must delegate painting of the children to the children themselves.
         | 
| 10 | 
            +
                class ContainerPainter < Painter
         | 
| 11 | 
            +
                  PADDING = ' '
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def initialize(name, settings)
         | 
| 14 | 
            +
                    super
         | 
| 15 | 
            +
                    @children = []
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  # Paint the common attributes of containers (e.g. border and background color). Any
         | 
| 19 | 
            +
                  # class that inherits from this one should call `super` at the start of its #paint
         | 
| 20 | 
            +
                  # method, before painting its children.
         | 
| 21 | 
            +
                  def paint(canvas, left, top, &block)
         | 
| 22 | 
            +
                    return unless canvas.writable?
         | 
| 23 | 
            +
                    return unless needs_printing?
         | 
| 24 | 
            +
                    canvas_coords = coords(left, top)
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                    # Paint the border, background color, and scrollbar starting from the top left
         | 
| 27 | 
            +
                    # border position, moving down row by row until we reach the bottom border
         | 
| 28 | 
            +
                    # position
         | 
| 29 | 
            +
                    stroke_left = canvas_coords.border_left
         | 
| 30 | 
            +
                    stroke_top = canvas_coords.border_top
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                    # All strokes will have the same formatting options
         | 
| 33 | 
            +
                    formatting = [*settings.border.color, *settings.bg_color]
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    # Paint the top border if the settings call for it
         | 
| 36 | 
            +
                    if settings.border.top?
         | 
| 37 | 
            +
                      canvas.stroke(stroke_left, stroke_top, top_border_stroke(canvas_coords), formatting, &block)
         | 
| 38 | 
            +
                      stroke_top += 1
         | 
| 39 | 
            +
                    end
         | 
| 40 | 
            +
                    # Precalculate the middle border container grids with more than 1 row
         | 
| 41 | 
            +
                    middle_border = dimensions.num_rows > 1 ? middle_border_stroke(canvas_coords) : ''
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                    # Paint each grid row by row
         | 
| 44 | 
            +
                    dimensions.num_rows.times do |row_num|
         | 
| 45 | 
            +
                      # In a grid with N rows, we will need to paint N - 1 inner horizontal borders.
         | 
| 46 | 
            +
                      # This code treats the inner horizontal border as the top of each row except for
         | 
| 47 | 
            +
                      # the first one.
         | 
| 48 | 
            +
                      if row_num > 0 && settings.border.inner_horiz?
         | 
| 49 | 
            +
                        canvas.stroke(stroke_left, stroke_top, middle_border, formatting, &block)
         | 
| 50 | 
            +
                        stroke_top += 1
         | 
| 51 | 
            +
                      end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                      # Paint the interior of each row (horizontal borders, veritical scroll bar and
         | 
| 54 | 
            +
                      # background color for the padding and content area)
         | 
| 55 | 
            +
                      canvas_coords.inner_grid_height.times do |row_within_cell|
         | 
| 56 | 
            +
                        canvas.stroke(stroke_left, stroke_top, content_line_stroke(canvas_coords, row_within_cell), formatting, &block)
         | 
| 57 | 
            +
                        stroke_top += 1
         | 
| 58 | 
            +
                      end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                      # Paint the horizontal scroll bar is the settings call for it
         | 
| 61 | 
            +
                      if settings.scrollbar.horiz?
         | 
| 62 | 
            +
                        canvas.stroke(stroke_left, stroke_top, bottom_scroll_stroke(canvas_coords), formatting, &block)
         | 
| 63 | 
            +
                        stroke_top += 1
         | 
| 64 | 
            +
                      end
         | 
| 65 | 
            +
                    end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                    # Paint the bottom border if the settings call for it
         | 
| 68 | 
            +
                    if settings.border.bottom?
         | 
| 69 | 
            +
                      canvas.stroke(stroke_left, stroke_top, bottom_border_stroke(canvas_coords), formatting, &block)
         | 
| 70 | 
            +
                      stroke_top += 1
         | 
| 71 | 
            +
                    end
         | 
| 72 | 
            +
                  end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                  # Tightly manage access to the children (rather than simply exposing the underlying
         | 
| 75 | 
            +
                  # array). This allows subclasses to easily modify behavior based on that element's
         | 
| 76 | 
            +
                  # specific settings.
         | 
| 77 | 
            +
                  def add_child(child)
         | 
| 78 | 
            +
                    children << child
         | 
| 79 | 
            +
                  end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                  def num_children
         | 
| 82 | 
            +
                    children.length
         | 
| 83 | 
            +
                  end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                  def children?
         | 
| 86 | 
            +
                    num_children > 0
         | 
| 87 | 
            +
                  end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                  def each_child(&block)
         | 
| 90 | 
            +
                    children.each(&block)
         | 
| 91 | 
            +
                  end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                  private
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                  attr_reader :children
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                  # Determine if there is anything to print for the container (this does not accont for
         | 
| 98 | 
            +
                  # children, just the border, scrollbar, and background color)
         | 
| 99 | 
            +
                  def needs_printing?
         | 
| 100 | 
            +
                    return true if settings.bg_color
         | 
| 101 | 
            +
                    return true if settings.border.outer?
         | 
| 102 | 
            +
                    return true if dimensions.num_cols > 1 && settings.border.inner_vert?
         | 
| 103 | 
            +
                    return true if dimensions.num_rows > 1 && settings.border.inner_horiz?
         | 
| 104 | 
            +
                    settings.scrollbar.horiz? || settings.scrollbar.vert?
         | 
| 105 | 
            +
                  end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                  # Return an object that allows easy access to important coordinates within the container,
         | 
| 108 | 
            +
                  # e.g. the left position where the left border is printed
         | 
| 109 | 
            +
                  def coords(left, top)
         | 
| 110 | 
            +
                    ContainerCoords.new(dimensions, settings, left, top)
         | 
| 111 | 
            +
                  end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                  # @return [Array<Integer>] a two-item array, the first being the amount of horizontal
         | 
| 114 | 
            +
                  #   spacing to paint *before the first* child and the second being the amount of spacing
         | 
| 115 | 
            +
                  #   to paint *between each* child
         | 
| 116 | 
            +
                  def horiz_justify_offset(containing_width)
         | 
| 117 | 
            +
                    if settings.align_center?
         | 
| 118 | 
            +
                      [(dimensions.content_width - containing_width) / 2, 0]
         | 
| 119 | 
            +
                    elsif settings.align_right?
         | 
| 120 | 
            +
                      [dimensions.content_width - containing_width, 0]
         | 
| 121 | 
            +
                    elsif settings.align_between?
         | 
| 122 | 
            +
                      return [0, 0] if num_children == 1
         | 
| 123 | 
            +
                      [0, (dimensions.content_width - containing_width) / (num_children - 1)]
         | 
| 124 | 
            +
                    elsif settings.align_around?
         | 
| 125 | 
            +
                      full_spacing = (dimensions.content_width - containing_width) / num_children
         | 
| 126 | 
            +
                      [full_spacing / 2, full_spacing]
         | 
| 127 | 
            +
                    elsif settings.align_evenly?
         | 
| 128 | 
            +
                      spacing = (dimensions.content_width - containing_width) / (num_children + 1)
         | 
| 129 | 
            +
                      [spacing, spacing]
         | 
| 130 | 
            +
                    else
         | 
| 131 | 
            +
                      [0, 0]
         | 
| 132 | 
            +
                    end
         | 
| 133 | 
            +
                  end
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                  # @return [Array<Integer>] a two-item array, the first being the amount of vertical
         | 
| 136 | 
            +
                  #   spacing to paint *above the first* child and the second being the amount of spacing
         | 
| 137 | 
            +
                  #   to paint *between each* child
         | 
| 138 | 
            +
                  def vert_justify_offset(containing_height)
         | 
| 139 | 
            +
                    if settings.valign_middle?
         | 
| 140 | 
            +
                      [(dimensions.content_height - containing_height) / 2, 0]
         | 
| 141 | 
            +
                    elsif settings.valign_bottom?
         | 
| 142 | 
            +
                      [dimensions.content_height - containing_height, 0]
         | 
| 143 | 
            +
                    elsif settings.valign_between?
         | 
| 144 | 
            +
                      return [0, 0] if num_children == 1
         | 
| 145 | 
            +
                      [0, (dimensions.content_height - containing_height) / (num_children - 1)]
         | 
| 146 | 
            +
                    elsif settings.valign_around?
         | 
| 147 | 
            +
                      full_spacing = (dimensions.content_height - containing_height) / num_children
         | 
| 148 | 
            +
                      [full_spacing / 2, full_spacing]
         | 
| 149 | 
            +
                    elsif settings.valign_evenly?
         | 
| 150 | 
            +
                      spacing = (dimensions.content_height - containing_height) / (num_children + 1)
         | 
| 151 | 
            +
                      [spacing, spacing]
         | 
| 152 | 
            +
                    else
         | 
| 153 | 
            +
                      [0, 0]
         | 
| 154 | 
            +
                    end
         | 
| 155 | 
            +
                  end
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                  # Return a stroke for one line of the container
         | 
| 158 | 
            +
                  #
         | 
| 159 | 
            +
                  # @param left_border [String] the character to print as the first character if there
         | 
| 160 | 
            +
                  #   is a left border
         | 
| 161 | 
            +
                  # @param junc_border [String] the character to print as the junction between two grid
         | 
| 162 | 
            +
                  #   columns if there is an inner vertical border
         | 
| 163 | 
            +
                  # @param right_border [String] the character to print as the last character if there
         | 
| 164 | 
            +
                  #   is a right border
         | 
| 165 | 
            +
                  # @block [String] the block should yield a string that represents the interior
         | 
| 166 | 
            +
                  #   (including padding) of a grid cell
         | 
| 167 | 
            +
                  def line_stroke(left_border, junc_border, right_border, &block)
         | 
| 168 | 
            +
                    stroke = ''
         | 
| 169 | 
            +
                    stroke += left_border if settings.border.left?
         | 
| 170 | 
            +
                    dimensions.num_cols.times do |col_num|
         | 
| 171 | 
            +
                      stroke += junc_border if col_num > 0 && settings.border.inner_vert?
         | 
| 172 | 
            +
                      stroke += yield
         | 
| 173 | 
            +
                    end
         | 
| 174 | 
            +
                    stroke += right_border if settings.border.right?
         | 
| 175 | 
            +
                    stroke
         | 
| 176 | 
            +
                  end
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                  # Return the stroke for the top border
         | 
| 179 | 
            +
                  def top_border_stroke(canvas_coords)
         | 
| 180 | 
            +
                    line_stroke(
         | 
| 181 | 
            +
                      settings.border.style.top_left,
         | 
| 182 | 
            +
                      settings.border.style.top_junc,
         | 
| 183 | 
            +
                      settings.border.style.top_right
         | 
| 184 | 
            +
                    ) do
         | 
| 185 | 
            +
                      settings.border.style.top_horiz * (canvas_coords.inner_grid_width + (settings.scrollbar.vert? ? 1 : 0))
         | 
| 186 | 
            +
                    end
         | 
| 187 | 
            +
                  end
         | 
| 188 | 
            +
             | 
| 189 | 
            +
                  # Return the stroke for an inner horizontal border
         | 
| 190 | 
            +
                  def middle_border_stroke(canvas_coords)
         | 
| 191 | 
            +
                    line_stroke(
         | 
| 192 | 
            +
                      settings.border.style.left_junc,
         | 
| 193 | 
            +
                      settings.border.style.cross_junc,
         | 
| 194 | 
            +
                      settings.border.style.right_junc
         | 
| 195 | 
            +
                    ) do
         | 
| 196 | 
            +
                      settings.border.style.middle_horiz * (canvas_coords.inner_grid_width + (settings.scrollbar.vert? ? 1 : 0))
         | 
| 197 | 
            +
                    end
         | 
| 198 | 
            +
                  end
         | 
| 199 | 
            +
             | 
| 200 | 
            +
                  # Return the stroke for the bottom border
         | 
| 201 | 
            +
                  def bottom_border_stroke(canvas_coords)
         | 
| 202 | 
            +
                    line_stroke(
         | 
| 203 | 
            +
                      settings.border.style.bottom_left,
         | 
| 204 | 
            +
                      settings.border.style.bottom_junc,
         | 
| 205 | 
            +
                      settings.border.style.bottom_right
         | 
| 206 | 
            +
                    ) do
         | 
| 207 | 
            +
                      settings.border.style.bottom_horiz * (canvas_coords.inner_grid_width + (settings.scrollbar.vert? ? 1 : 0))
         | 
| 208 | 
            +
                    end
         | 
| 209 | 
            +
                  end
         | 
| 210 | 
            +
             | 
| 211 | 
            +
                  # Return the stroke for a grid row between any borders
         | 
| 212 | 
            +
                  def content_line_stroke(canvas_coords, row_within_cell)
         | 
| 213 | 
            +
                    line_stroke(
         | 
| 214 | 
            +
                      settings.border.style.left_vert,
         | 
| 215 | 
            +
                      settings.border.style.middle_vert,
         | 
| 216 | 
            +
                      settings.border.style.right_vert,
         | 
| 217 | 
            +
                    ) do
         | 
| 218 | 
            +
                      if settings.scrollbar.vert?
         | 
| 219 | 
            +
                        if dimensions.children_height <= canvas_coords.grid_height || children.first.settings.position.top > 0
         | 
| 220 | 
            +
                          scrollbar_char = GUTTER
         | 
| 221 | 
            +
                        else
         | 
| 222 | 
            +
                          scrollbar_char = vert_scroll_char(
         | 
| 223 | 
            +
                            dimensions.children_height + dimensions.padding_height,
         | 
| 224 | 
            +
                            canvas_coords.inner_grid_height,
         | 
| 225 | 
            +
                            -children.first.settings.position.top,
         | 
| 226 | 
            +
                            row_within_cell
         | 
| 227 | 
            +
                          )
         | 
| 228 | 
            +
                        end
         | 
| 229 | 
            +
                        PADDING * canvas_coords.inner_grid_width + scrollbar_char
         | 
| 230 | 
            +
                      else
         | 
| 231 | 
            +
                        PADDING * canvas_coords.inner_grid_width
         | 
| 232 | 
            +
                      end
         | 
| 233 | 
            +
                    end
         | 
| 234 | 
            +
                  end
         | 
| 235 | 
            +
             | 
| 236 | 
            +
                  # Return the stroke for the horizontal scroll bar
         | 
| 237 | 
            +
                  def bottom_scroll_stroke(canvas_coords)
         | 
| 238 | 
            +
                    line_stroke(
         | 
| 239 | 
            +
                      settings.border.style.left_vert,
         | 
| 240 | 
            +
                      settings.border.style.middle_vert,
         | 
| 241 | 
            +
                      settings.border.style.right_vert,
         | 
| 242 | 
            +
                    ) do
         | 
| 243 | 
            +
                      canvas_coords.inner_grid_width.times.map do |col_within_cell|
         | 
| 244 | 
            +
                        horiz_scroll_char(
         | 
| 245 | 
            +
                          dimensions.children_width + dimensions.padding_width,
         | 
| 246 | 
            +
                          canvas_coords.inner_grid_width,
         | 
| 247 | 
            +
                          -children.first.settings.position.left,
         | 
| 248 | 
            +
                          col_within_cell
         | 
| 249 | 
            +
                        )
         | 
| 250 | 
            +
                      end.join
         | 
| 251 | 
            +
                    end
         | 
| 252 | 
            +
                  end
         | 
| 253 | 
            +
             | 
| 254 | 
            +
                  # Contants to paint scrollbars
         | 
| 255 | 
            +
                  GUTTER = ' '
         | 
| 256 | 
            +
                  HORIZONTAL = %w[▗ ▄ ▖]
         | 
| 257 | 
            +
                  VERTICAL = %w[
         | 
| 258 | 
            +
                    ▗
         | 
| 259 | 
            +
                    ▐
         | 
| 260 | 
            +
                    ▝
         | 
| 261 | 
            +
                  ]
         | 
| 262 | 
            +
             | 
| 263 | 
            +
                  # Determine the character to paint the horizontal scroll bar with for the given column
         | 
| 264 | 
            +
                  #
         | 
| 265 | 
            +
                  # @see #scroll_char for more details
         | 
| 266 | 
            +
                  def horiz_scroll_char(col_count, viewable_col_count, first_visible_col, curr_col)
         | 
| 267 | 
            +
                    scroll_char(col_count, viewable_col_count, first_visible_col, curr_col, HORIZONTAL)
         | 
| 268 | 
            +
                  end
         | 
| 269 | 
            +
             | 
| 270 | 
            +
                  # Determine the character to paint the vertical scroll bar with for the given row
         | 
| 271 | 
            +
                  #
         | 
| 272 | 
            +
                  # @see #scroll_char for more details
         | 
| 273 | 
            +
                  def vert_scroll_char(row_count, viewable_row_count, first_visible_row, curr_row)
         | 
| 274 | 
            +
                    scroll_char(row_count, viewable_row_count, first_visible_row, curr_row, VERTICAL)
         | 
| 275 | 
            +
                  end
         | 
| 276 | 
            +
             | 
| 277 | 
            +
                  private
         | 
| 278 | 
            +
             | 
| 279 | 
            +
                  # Determine which character to paint a for a scrollbar
         | 
| 280 | 
            +
                  #
         | 
| 281 | 
            +
                  # @param total_count [Integer] total number of rows/columns in the content
         | 
| 282 | 
            +
                  # @param viewable_count [Integer] number of rows/columns visible in the viewport
         | 
| 283 | 
            +
                  # @param first_visible [Integer] zero-based index of the first row/column that is visible
         | 
| 284 | 
            +
                  #   in the viewport
         | 
| 285 | 
            +
                  # @param curr [Integer] zero-based index of the row/column (relative to the first visible
         | 
| 286 | 
            +
                  #   row/column) that the painted character is being requested for
         | 
| 287 | 
            +
                  # @param chars [Array<String>] an array with three 1-character strings, the frist is the
         | 
| 288 | 
            +
                  #   "second half" scrollbar character, the second is the "full" scrollbar character, and
         | 
| 289 | 
            +
                  #   the third is the "first half" scrollbar character.
         | 
| 290 | 
            +
                  def scroll_char(total_count, viewable_count, first_visible, curr, chars)
         | 
| 291 | 
            +
                    return GUTTER unless total_count > 0 && viewable_count > 0
         | 
| 292 | 
            +
                    # The scroll handle has the exact same relative size and position in the scroll gutter
         | 
| 293 | 
            +
                    # that the viewable content has in the total content area. For example, a content area
         | 
| 294 | 
            +
                    # that is 50 columns wide with a view port that is 20 columns wide might look like
         | 
| 295 | 
            +
                    #
         | 
| 296 | 
            +
                    #    +---------1-----****2*********3******---4---------+
         | 
| 297 | 
            +
                    #    |               *                   *             |
         | 
| 298 | 
            +
                    #    |   hidden      *     viewable      *   hidden    |
         | 
| 299 | 
            +
                    #    |               *                   *             |
         | 
| 300 | 
            +
                    #    +---------1-----****2*********3******---4---------+
         | 
| 301 | 
            +
                    #
         | 
| 302 | 
            +
                    # The scoll gutter, would look like
         | 
| 303 | 
            +
                    #
         | 
| 304 | 
            +
                    #                    |......********.....|
         | 
| 305 | 
            +
                    #
         | 
| 306 | 
            +
                    # Scrolling all the way to the right results in
         | 
| 307 | 
            +
                    #
         | 
| 308 | 
            +
                    #    +---------1---------2---------3*********4*********+
         | 
| 309 | 
            +
                    #    |                             *                   *
         | 
| 310 | 
            +
                    #    |            hidden           *     viewable      *
         | 
| 311 | 
            +
                    #    |                             *                   *
         | 
| 312 | 
            +
                    #    +---------1---------2---------3*********4*********+
         | 
| 313 | 
            +
                    #                                  |...........********|
         | 
| 314 | 
            +
                    #
         | 
| 315 | 
            +
                    # Returning to the first example, we can match up the arguments to this method to the
         | 
| 316 | 
            +
                    # diagram
         | 
| 317 | 
            +
                    #
         | 
| 318 | 
            +
                    #                       total_count = 50
         | 
| 319 | 
            +
                    #    |<----------------------------------------------->|
         | 
| 320 | 
            +
                    #    |                                                 |
         | 
| 321 | 
            +
                    #    |                veiwable_count = 20              |
         | 
| 322 | 
            +
                    #    |               |<----------------->|             |
         | 
| 323 | 
            +
                    #    ↓               ↓                   ↓             ↓
         | 
| 324 | 
            +
                    #    +---------1-----****2*********3******---4---------+
         | 
| 325 | 
            +
                    #    |               *                   *             |
         | 
| 326 | 
            +
                    #    |   hidden      *     viewable      *   hidden    |
         | 
| 327 | 
            +
                    #    |               *                   *             |
         | 
| 328 | 
            +
                    #    +---------1-----****2*********3******---4---------+
         | 
| 329 | 
            +
                    #                    |......****?***.....|
         | 
| 330 | 
            +
                    #                    ↑          ↑
         | 
| 331 | 
            +
                    #      first_visible = 16       |
         | 
| 332 | 
            +
                    #                             curr = 11
         | 
| 333 | 
            +
             | 
| 334 | 
            +
                    # The first task of determining how much of the handle is visible in a row/column is to
         | 
| 335 | 
            +
                    # calculate the range (as a precentage of the total) of viewable items
         | 
| 336 | 
            +
                    viewable_start = first_visible.to_f / total_count
         | 
| 337 | 
            +
                    viewable_end = (first_visible + viewable_count).to_f / total_count
         | 
| 338 | 
            +
             | 
| 339 | 
            +
                    # Always use the same length for the scroll bar so it does not give an inchworm effect
         | 
| 340 | 
            +
                    # as it scrolls along.
         | 
| 341 | 
            +
                    #
         | 
| 342 | 
            +
                    # Also, double the value now to get granularity for half width
         | 
| 343 | 
            +
                    # scrollbar characters.
         | 
| 344 | 
            +
                    scrollbar_length = ((2 * viewable_count ** 2).to_f / total_count).ceil
         | 
| 345 | 
            +
                    scrollbar_start = ((2 * first_visible * viewable_count).to_f / total_count).floor
         | 
| 346 | 
            +
             | 
| 347 | 
            +
                    first_half = (scrollbar_start...scrollbar_start + scrollbar_length).include?(2 * curr)
         | 
| 348 | 
            +
                    second_half = (scrollbar_start...scrollbar_start + scrollbar_length).include?(2 * curr + 1)
         | 
| 349 | 
            +
             | 
| 350 | 
            +
                    if first_half && second_half
         | 
| 351 | 
            +
                      chars[1]
         | 
| 352 | 
            +
                    elsif first_half
         | 
| 353 | 
            +
                      chars[2]
         | 
| 354 | 
            +
                    elsif second_half
         | 
| 355 | 
            +
                      chars[0]
         | 
| 356 | 
            +
                    else
         | 
| 357 | 
            +
                      GUTTER
         | 
| 358 | 
            +
                    end
         | 
| 359 | 
            +
                  end
         | 
| 360 | 
            +
                end
         | 
| 361 | 
            +
                private_constant :ContainerPainter
         | 
| 362 | 
            +
              end
         | 
| 363 | 
            +
            end
         |