whirled_peas 0.11.0 → 0.11.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -2
  3. data/CHANGELOG.md +5 -0
  4. data/README.md +7 -1156
  5. data/doc/application.md +92 -0
  6. data/doc/cli.md +115 -0
  7. data/doc/components.md +55 -0
  8. data/doc/easing.md +257 -0
  9. data/doc/elements.md +69 -0
  10. data/doc/screen_test.md +23 -0
  11. data/doc/settings.md +466 -0
  12. data/doc/template_factory.md +53 -0
  13. data/doc/themes.md +15 -0
  14. data/lib/data/themes.yaml +8 -4
  15. data/lib/whirled_peas/component/list_with_active.rb +3 -3
  16. data/lib/whirled_peas/graphics/container_coords.rb +2 -26
  17. data/lib/whirled_peas/graphics/container_dimensions.rb +24 -0
  18. data/lib/whirled_peas/graphics/container_painter.rb +26 -28
  19. data/lib/whirled_peas/graphics/debugger.rb +1 -0
  20. data/lib/whirled_peas/graphics/graph_painter.rb +31 -24
  21. data/lib/whirled_peas/graphics/scrollbar_helper.rb +31 -29
  22. data/lib/whirled_peas/settings/bg_color.rb +5 -1
  23. data/lib/whirled_peas/settings/border.rb +1 -1
  24. data/lib/whirled_peas/settings/color.rb +8 -4
  25. data/lib/whirled_peas/settings/debugger.rb +2 -0
  26. data/lib/whirled_peas/settings/element_settings.rb +10 -2
  27. data/lib/whirled_peas/settings/text_color.rb +5 -0
  28. data/lib/whirled_peas/settings/theme.rb +28 -7
  29. data/lib/whirled_peas/settings/theme_library.rb +10 -4
  30. data/lib/whirled_peas/version.rb +1 -1
  31. data/screen_test/components/list_with_active/l2r_position_start.frame +1 -1
  32. data/screen_test/components/list_with_active/l2r_separator.frame +1 -1
  33. data/screen_test/components/list_with_active/t2b_position_start.frame +1 -1
  34. data/screen_test/components/list_with_active/t2b_separator.frame +1 -1
  35. data/screen_test/elements/{graph.frame → graph_asc.frame} +1 -1
  36. data/screen_test/elements/{graph.rb → graph_asc.rb} +0 -0
  37. data/screen_test/elements/graph_desc.frame +1 -0
  38. data/screen_test/elements/graph_desc.rb +12 -0
  39. data/screen_test/elements/graph_horiz.frame +1 -0
  40. data/screen_test/elements/graph_horiz.rb +12 -0
  41. data/screen_test/elements/graph_sin.frame +1 -0
  42. data/screen_test/elements/graph_sin.rb +12 -0
  43. data/screen_test/elements/theme.frame +1 -1
  44. data/screen_test/settings/scroll/horiz_box.frame +1 -1
  45. data/screen_test/settings/scroll/vert_box.frame +1 -1
  46. metadata +19 -6
  47. data/bin/easing +0 -33
  48. data/lib/whirled_peas/graphics/mock_screen.rb +0 -26
@@ -0,0 +1,53 @@
1
+ ## Template Factory
2
+
3
+ To render the frame events sent by the application, the application requires a template factory. This factory will be called for each frame event, with the frame name and the arguments supplied by the application. A template factory can be an instance of ruby class and thus can maintain state. Whirled Peas provides a few basic building blocks to make simple, yet elegant terminal-based UIs.
4
+
5
+ To build a template in a template factory, use `WhirledPeas.template(theme)`, which takes an optional [theme](themes.md) and yields a composer (used to add [elements](elements.md) to the template) and [settings](settings.md) (used to configure the element).
6
+
7
+ ### Full example
8
+
9
+ ```ruby
10
+ class TemplateFactory
11
+ def build(frame, args)
12
+ set_state(frame, args)
13
+ WhirledPeas.template do |composer, settings|
14
+ settings.flow = :l2r
15
+ settings.align = :center
16
+
17
+ composer.add_box('Title', &method(:title))
18
+ composer.add_box('Sum', &method(:sum))
19
+ composer.add_grid('NumberGrid', &method(:number_grid))
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def set_state(frame, args)
26
+ @frame = frame
27
+ @numbers = args.key?(:numbers) ? args[:numbers] || []
28
+ @sum = args[:sum] if args.key?(:sum)
29
+ @low = args[:low] if args.key?(:low)
30
+ @high = args[:high] if args.key?(:high)
31
+ end
32
+
33
+ def title(_composer, settings)
34
+ settings.underline = true
35
+ "Pair Finder"
36
+ end
37
+
38
+ def sum(_composer, settings)
39
+ settings.color = @frame == 'found-pair' ? :green : :red
40
+ @sum ? "Sum: #{@sum}" : 'N/A'
41
+ end
42
+
43
+ def number_grid(composer, settings)
44
+ settings.full_border
45
+ @numbers.each.with_index do |num, index|
46
+ composer.add_text do |_, settings|
47
+ settings.bg_color = (@low == index || @high == index) ? :cyan : :white
48
+ num
49
+ end
50
+ end
51
+ end
52
+ end
53
+ ```
@@ -0,0 +1,15 @@
1
+ ## Themes
2
+
3
+ The template builder (`WhirledPeas.template`) takes an optional `theme` argument. You can provide the name of a predefined theme (run `whirled_peas themes` to see a list with samples) or define you own with the following
4
+
5
+ ```ruby
6
+ WhirledPeas.register_theme(:my_theme) do |theme|
7
+ theme.bg_color = :bright_white
8
+ theme.color = :blue
9
+ theme.border_color = :bright_green
10
+ theme.axis_color = :bright_red
11
+ theme.title_font = :default
12
+ end
13
+ ```
14
+
15
+ Theme settings will be used as default settings throughout the template, however theme settings can be overridden on any element.
@@ -1,13 +1,17 @@
1
1
  bright:
2
+ axis_color: :red
2
3
  bg_color: :bright_white
3
- color: :blue
4
4
  border_color: :bright_blue
5
- axis_color: :red
5
+ border_style: :soft
6
+ color: :blue
6
7
  title_font: :default
7
8
 
8
9
  dark:
10
+ axis_color: :bright_white
9
11
  bg_color: :black
10
- color: :white
11
12
  border_color: :gray
12
- axis_color: :bright_white
13
+ border_style: :double
14
+ color: :white
15
+ highlight_bg_color: :bright_red
16
+ highlight_color: :black
13
17
  title_font: :swan
@@ -1,7 +1,7 @@
1
1
  module WhirledPeas
2
2
  module Component
3
3
  class ListWithActive
4
- attr_accessor :active_index, :active_color, :active_bg_color, :separator
4
+ attr_accessor :active_index, :separator
5
5
 
6
6
  attr_reader :items
7
7
 
@@ -75,8 +75,8 @@ module WhirledPeas
75
75
  composer.add_text { separator } if !separator.nil? && index > 0
76
76
  composer.add_text do |_, settings|
77
77
  if index == active_index
78
- settings.bg_color = active_bg_color
79
- settings.color = active_color
78
+ settings.bg_color = :highlight
79
+ settings.color = :highlight
80
80
  end
81
81
  item
82
82
  end
@@ -33,7 +33,7 @@ module WhirledPeas
33
33
  end
34
34
 
35
35
  def content_left(col_index=0)
36
- padding_left + settings.padding.left + col_index * grid_width
36
+ padding_left + settings.padding.left + col_index * dimensions.grid_width
37
37
  end
38
38
 
39
39
  def offset_content_left(col_index=0)
@@ -48,7 +48,7 @@ module WhirledPeas
48
48
  end
49
49
 
50
50
  def content_top(row_index=0)
51
- padding_top + settings.padding.top + row_index * grid_height
51
+ padding_top + settings.padding.top + row_index * dimensions.grid_height
52
52
  end
53
53
 
54
54
  def offset_content_top(row_index=0)
@@ -62,30 +62,6 @@ module WhirledPeas
62
62
  end
63
63
  end
64
64
 
65
- def inner_grid_width
66
- settings.padding.left +
67
- dimensions.content_width +
68
- settings.padding.right
69
- end
70
-
71
- def grid_width
72
- (settings.border.inner_vert? ? 1 : 0) +
73
- inner_grid_width +
74
- (settings.scrollbar.vert? ? 1 : 0)
75
- end
76
-
77
- def inner_grid_height
78
- settings.padding.top +
79
- dimensions.content_height +
80
- settings.padding.bottom
81
- end
82
-
83
- def grid_height
84
- (settings.border.inner_horiz? ? 1 : 0) +
85
- inner_grid_height +
86
- (settings.scrollbar.horiz? ? 1 : 0)
87
- end
88
-
89
65
  private
90
66
 
91
67
  attr_reader :settings, :dimensions, :start_left, :start_top
@@ -31,6 +31,30 @@ module WhirledPeas
31
31
  end
32
32
  end
33
33
 
34
+ def inner_grid_width
35
+ settings.padding.left +
36
+ content_width +
37
+ settings.padding.right
38
+ end
39
+
40
+ def grid_width
41
+ (settings.border.inner_vert? ? 1 : 0) +
42
+ inner_grid_width +
43
+ (settings.scrollbar.vert? ? 1 : 0)
44
+ end
45
+
46
+ def inner_grid_height
47
+ settings.padding.top +
48
+ content_height +
49
+ settings.padding.bottom
50
+ end
51
+
52
+ def grid_height
53
+ (settings.border.inner_horiz? ? 1 : 0) +
54
+ inner_grid_height +
55
+ (settings.scrollbar.horiz? ? 1 : 0)
56
+ end
57
+
34
58
  def outer_width
35
59
  @outer_width ||= margin_width +
36
60
  outer_border_width +
@@ -35,11 +35,17 @@ module WhirledPeas
35
35
 
36
36
  # Paint the top border if the settings call for it
37
37
  if settings.border.top?
38
- canvas.stroke(stroke_left, stroke_top, top_border_stroke(canvas_coords), formatting, &block)
38
+ canvas.stroke(stroke_left, stroke_top, top_border_stroke, formatting, &block)
39
39
  stroke_top += 1
40
40
  end
41
41
  # Precalculate the middle border container grids with more than 1 row
42
- middle_border = dimensions.num_rows > 1 ? middle_border_stroke(canvas_coords) : ''
42
+ middle_border = dimensions.num_rows > 1 ? middle_border_stroke : ''
43
+
44
+ vert_scrollbar = settings.scrollbar.vert? ? ScrollbarHelper.vert(
45
+ dimensions.children_height + dimensions.padding_height,
46
+ dimensions.inner_grid_height,
47
+ canvas_coords.content_top - canvas_coords.offset_content_top,
48
+ ) : []
43
49
 
44
50
  # Paint each grid row by row
45
51
  dimensions.num_rows.times do |row_num|
@@ -53,8 +59,9 @@ module WhirledPeas
53
59
 
54
60
  # Paint the interior of each row (horizontal borders, veritical scroll bar and
55
61
  # background color for the padding and content area)
56
- canvas_coords.inner_grid_height.times do |row_within_cell|
57
- canvas.stroke(stroke_left, stroke_top, content_line_stroke(canvas_coords, row_within_cell), formatting, &block)
62
+ dimensions.inner_grid_height.times do |row_within_cell|
63
+ content_line = content_line_stroke(row_within_cell, vert_scrollbar)
64
+ canvas.stroke(stroke_left, stroke_top, content_line, formatting, &block)
58
65
  stroke_top += 1
59
66
  end
60
67
 
@@ -67,7 +74,7 @@ module WhirledPeas
67
74
 
68
75
  # Paint the bottom border if the settings call for it
69
76
  if settings.border.bottom?
70
- canvas.stroke(stroke_left, stroke_top, bottom_border_stroke(canvas_coords), formatting, &block)
77
+ canvas.stroke(stroke_left, stroke_top, bottom_border_stroke, formatting, &block)
71
78
  stroke_top += 1
72
79
  end
73
80
  end
@@ -177,55 +184,49 @@ module WhirledPeas
177
184
  end
178
185
 
179
186
  # Return the stroke for the top border
180
- def top_border_stroke(canvas_coords)
187
+ def top_border_stroke
181
188
  line_stroke(
182
189
  settings.border.style.top_left,
183
190
  settings.border.style.top_junc,
184
191
  settings.border.style.top_right
185
192
  ) do
186
- settings.border.style.top_horiz * (canvas_coords.inner_grid_width + (settings.scrollbar.vert? ? 1 : 0))
193
+ settings.border.style.top_horiz * (dimensions.inner_grid_width + (settings.scrollbar.vert? ? 1 : 0))
187
194
  end
188
195
  end
189
196
 
190
197
  # Return the stroke for an inner horizontal border
191
- def middle_border_stroke(canvas_coords)
198
+ def middle_border_stroke
192
199
  line_stroke(
193
200
  settings.border.style.left_junc,
194
201
  settings.border.style.cross_junc,
195
202
  settings.border.style.right_junc
196
203
  ) do
197
- settings.border.style.middle_horiz * (canvas_coords.inner_grid_width + (settings.scrollbar.vert? ? 1 : 0))
204
+ settings.border.style.middle_horiz * (dimensions.inner_grid_width + (settings.scrollbar.vert? ? 1 : 0))
198
205
  end
199
206
  end
200
207
 
201
208
  # Return the stroke for the bottom border
202
- def bottom_border_stroke(canvas_coords)
209
+ def bottom_border_stroke
203
210
  line_stroke(
204
211
  settings.border.style.bottom_left,
205
212
  settings.border.style.bottom_junc,
206
213
  settings.border.style.bottom_right
207
214
  ) do
208
- settings.border.style.bottom_horiz * (canvas_coords.inner_grid_width + (settings.scrollbar.vert? ? 1 : 0))
215
+ settings.border.style.bottom_horiz * (dimensions.inner_grid_width + (settings.scrollbar.vert? ? 1 : 0))
209
216
  end
210
217
  end
211
218
 
212
219
  # Return the stroke for a grid row between any borders
213
- def content_line_stroke(canvas_coords, row_within_cell)
220
+ def content_line_stroke(row_within_cell, vert_scrollbar)
214
221
  line_stroke(
215
222
  settings.border.style.left_vert,
216
223
  settings.border.style.middle_vert,
217
224
  settings.border.style.right_vert,
218
225
  ) do
219
226
  if settings.scrollbar.vert?
220
- scrollbar_char = ScrollbarHelper.vert_char(
221
- dimensions.children_height + dimensions.padding_height,
222
- canvas_coords.inner_grid_height,
223
- canvas_coords.top - canvas_coords.offset_content_top,
224
- row_within_cell
225
- )
226
- PADDING * canvas_coords.inner_grid_width + scrollbar_char
227
+ PADDING * dimensions.inner_grid_width + vert_scrollbar[row_within_cell]
227
228
  else
228
- PADDING * canvas_coords.inner_grid_width
229
+ PADDING * dimensions.inner_grid_width
229
230
  end
230
231
  end
231
232
  end
@@ -237,14 +238,11 @@ module WhirledPeas
237
238
  settings.border.style.middle_vert,
238
239
  settings.border.style.right_vert,
239
240
  ) do
240
- canvas_coords.inner_grid_width.times.map do |col_within_cell|
241
- ScrollbarHelper.horiz_char(
242
- dimensions.children_width + dimensions.padding_width,
243
- canvas_coords.inner_grid_width,
244
- canvas_coords.left - canvas_coords.offset_content_left,
245
- col_within_cell
246
- )
247
- end.join
241
+ ScrollbarHelper.horiz(
242
+ dimensions.children_width + dimensions.padding_width,
243
+ dimensions.inner_grid_width,
244
+ canvas_coords.content_left - canvas_coords.offset_content_left
245
+ ).join
248
246
  end
249
247
  end
250
248
  end
@@ -15,6 +15,7 @@ module WhirledPeas
15
15
  "#{indent}* #{painter.class}(#{painter.name})",
16
16
  ]
17
17
  info << "#{indent + ' '}- Dimensions(#{dimensions})"
18
+ info << "#{indent + ' '}- Theme=#{painter.settings.theme.inspect}" if indent == ''
18
19
  info << "#{indent + ' '}- Settings"
19
20
  info << Settings::Debugger.new(painter.settings).debug(indent + ' ')
20
21
  if painter.is_a?(TextPainter)
@@ -5,6 +5,10 @@ require_relative 'content_painter'
5
5
  module WhirledPeas
6
6
  module Graphics
7
7
  class GraphPainter < ContentPainter
8
+ # The number of units along the Y-axis a single character can be divided into
9
+ Y_AXIS_SCALE = 2
10
+ private_constant :Y_AXIS_SCALE
11
+
8
12
  def paint(canvas, left, top, &block)
9
13
  axis_formatting = [*settings.axis_color, *settings.bg_color]
10
14
  plot_formatting = [*settings.color, *settings.bg_color]
@@ -26,8 +30,8 @@ module WhirledPeas
26
30
  min_y = 1.0 / 0
27
31
  max_y = -1.0 / 0
28
32
  if settings.width
29
- interpolated = inner_width.times.map do |i|
30
- x = (i * (content.length - 1).to_f / inner_width).floor
33
+ interpolated = (inner_width + 1).times.map do |i|
34
+ x = (i * (content.length - 1).to_f / (inner_width + 1)).floor
31
35
  max_y = content[x] if content[x] > max_y
32
36
  min_y = content[x] if content[x] < min_y
33
37
  content[x]
@@ -39,31 +43,34 @@ module WhirledPeas
39
43
  min_y = y if y < min_y
40
44
  end
41
45
  end
46
+ if min_y == max_y
47
+ min_y -= 1
48
+ max_y += 1
49
+ end
42
50
  scaled = interpolated.map do |y|
43
- (2 * inner_height * (y - min_y).to_f / (max_y - min_y)).floor
51
+ inner_height * (y - min_y).to_f / (max_y - min_y)
44
52
  end
53
+
45
54
  @plot_lines = Array.new(inner_height) { '' }
46
- scaled.each.with_index do |y, x_index|
55
+ scaled[0..-2].each.with_index do |y, x_index|
56
+ last = x_index == scaled.length - 2
57
+
58
+ prev_y = x_index == 0 ? y : scaled[x_index - 1]
59
+ next_y = scaled[x_index + 1]
60
+
61
+ min_y = [(prev_y + y) / 2, y, last ? next_y : (y + next_y) / 2].min
62
+ min_y = (Y_AXIS_SCALE * min_y).round.to_f / Y_AXIS_SCALE
63
+
64
+ max_y = [(prev_y + y) / 2, y, last ? next_y : (y + next_y) / 2].max
65
+ max_y = (Y_AXIS_SCALE * max_y).round.to_f / Y_AXIS_SCALE
66
+
47
67
  @plot_lines.each.with_index do |row, row_index|
48
- bottom_half_index = 2 * (inner_height - row_index - 1)
49
- top_half_index = bottom_half_index + 1
50
-
51
- asc, next_y = if scaled.length == 1
52
- [true, y]
53
- elsif x_index == scaled.length - 1
54
- y >= scaled[x_index - 1]
55
- [true, y]
56
- else
57
- scaled[x_index + 1] >= y
58
- [true, scaled[x_index + 1]]
59
- end
60
- if asc
61
- top_half = y == top_half_index || (y...next_y).include?(top_half_index)
62
- bottom_half = y == bottom_half_index || (y...next_y).include?(bottom_half_index)
63
- else
64
- top_half = y == top_half_index || (next_y...y).include?(top_half_index)
65
- bottom_half = y == bottom_half_index || (next_y...y).include?(bottom_half_index)
66
- end
68
+ bottom_value = inner_height - row_index - 1
69
+ top_value = bottom_value + 1.0 / Y_AXIS_SCALE
70
+
71
+ top_half = min_y == top_value || (min_y <= top_value && top_value < max_y)
72
+ bottom_half = min_y == bottom_value || (min_y <= bottom_value && bottom_value < max_y)
73
+
67
74
  row << if top_half && bottom_half
68
75
  '█'
69
76
  elsif top_half
@@ -83,7 +90,7 @@ module WhirledPeas
83
90
  end
84
91
 
85
92
  def inner_width
86
- settings.width.nil? ? content.length : settings.width - 1
93
+ (settings.width.nil? ? content.length : settings.width) - 1
87
94
  end
88
95
 
89
96
  def axes_lines
@@ -15,18 +15,18 @@ module WhirledPeas
15
15
  private_constant :SCROLL_HANDLE_SCALE
16
16
 
17
17
  class << self
18
- # Determine the character to paint the horizontal scroll bar with for the given column
18
+ # Return the characters to paint the horizontal scroll bar with for the given column
19
19
  #
20
- # @see #scroll_char for more details
21
- def horiz_char(col_count, viewable_col_count, first_visible_col, curr_col)
22
- scroll_char(col_count, viewable_col_count, first_visible_col, curr_col, HORIZONTAL)
20
+ # @see #scroll_chars for more details
21
+ def horiz(col_count, viewable_col_count, first_visible_col)
22
+ scroll_chars(col_count, viewable_col_count, first_visible_col, HORIZONTAL)
23
23
  end
24
24
 
25
- # Determine the character to paint the vertical scroll bar with for the given row
25
+ # Return the characters to paint the vertical scroll bar with for the given row
26
26
  #
27
- # @see #scroll_char for more details
28
- def vert_char(row_count, viewable_row_count, first_visible_row, curr_row)
29
- scroll_char(row_count, viewable_row_count, first_visible_row, curr_row, VERTICAL)
27
+ # @see #scroll_chars for more details
28
+ def vert(row_count, viewable_row_count, first_visible_row)
29
+ scroll_chars(row_count, viewable_row_count, first_visible_row, VERTICAL)
30
30
  end
31
31
 
32
32
  private
@@ -37,15 +37,16 @@ module WhirledPeas
37
37
  # @param viewable_count [Integer] number of rows/columns visible in the viewport
38
38
  # @param first_visible [Integer] zero-based index of the first row/column that is visible
39
39
  # in the viewport
40
- # @param curr [Integer] zero-based index of the row/column (relative to the first visible
41
- # row/column) that the painted character is being requested for
42
- # @param chars [Array<String>] an array with three 1-character strings, the frist is the
43
- # "second half" scrollbar character, the second is the "full" scrollbar character, and
44
- # the third is the "first half" scrollbar character.
45
- def scroll_char(total_count, viewable_count, first_visible, curr, chars)
46
- return chars[0] if total_count == 0
47
- return chars[0] if viewable_count == 0
48
- return chars[0] if viewable_count >= total_count
40
+ # @param chars [Array<String>] an array with four 1-character strings, the frist is the
41
+ # gutter (i.e. no scrolbar handle visible), then the "second half" scrollbar character,
42
+ # then second is the "first half" scrollbar character, and finally the "full"
43
+ def scroll_chars(total_count, viewable_count, first_visible, chars)
44
+ # Start by initializing the scroll back to to all gutters
45
+ scrollbar = Array.new(viewable_count) { chars[0] }
46
+
47
+ return scrollbar if total_count == 0
48
+ return scrollbar if viewable_count == 0
49
+ return scrollbar if viewable_count >= total_count
49
50
 
50
51
  # The scroll handle has the exact same relative size and position in the scroll gutter
51
52
  # that the viewable content has in the total content area. For example, a content area
@@ -92,31 +93,32 @@ module WhirledPeas
92
93
  # Always use the same length for the scrollbar so it does not give an inchworm effect
93
94
  # as it scrolls along. This will calculate the "ideal" length of the scroll bar if we
94
95
  # could infinitely divide a character.
95
- scrollbar_length = (viewable_count ** 2).to_f / total_count
96
+ length = (viewable_count ** 2).to_f / total_count
96
97
 
97
98
  # Round the length to the nearst "scaled" value
98
- scrollbar_length = (SCROLL_HANDLE_SCALE * scrollbar_length).round.to_f / SCROLL_HANDLE_SCALE
99
+ length = (SCROLL_HANDLE_SCALE * length).round.to_f / SCROLL_HANDLE_SCALE
99
100
 
100
101
  # Ensure we have a scrollbar!
101
- scrollbar_length = 1.0 / SCROLL_HANDLE_SCALE if scrollbar_length == 0
102
+ length = 1.0 / SCROLL_HANDLE_SCALE if length == 0
102
103
 
103
104
  # Find the "ideal" position of where the scrollbar should start.
104
- scrollbar_start = first_visible * viewable_count.to_f / total_count
105
+ start = first_visible * viewable_count.to_f / total_count
105
106
 
106
107
  # Round the start to the nearest "scaled" value
107
- scrollbar_start = (SCROLL_HANDLE_SCALE * scrollbar_start).round.to_f / SCROLL_HANDLE_SCALE
108
+ start = (SCROLL_HANDLE_SCALE * start).round.to_f / SCROLL_HANDLE_SCALE
108
109
 
109
110
  # Make sure we didn't scroll off the page!
110
- scrollbar_start -= scrollbar_length if scrollbar_start == viewable_count
111
+ start -= length if start == viewable_count
111
112
 
112
- # Create "scaled" indexes for the subdivided current character
113
- curr_0 = curr
114
- curr_1 = curr + 1.0 / SCROLL_HANDLE_SCALE
113
+ (start.floor..[(start + length).floor, viewable_count - 1].min).each do |curr_0|
114
+ curr_1 = curr_0 + 1.0 / SCROLL_HANDLE_SCALE
115
115
 
116
- first_half = scrollbar_start <= curr_0 && curr_0 < scrollbar_start + scrollbar_length ? 2 : 0
117
- second_half = scrollbar_start <= curr_1 && curr_1 < scrollbar_start + scrollbar_length ? 1 : 0
116
+ first_half = start <= curr_0 && curr_0 < start + length ? 2 : 0
117
+ second_half = start <= curr_1 && curr_1 < start + length ? 1 : 0
118
118
 
119
- chars[second_half | first_half]
119
+ scrollbar[curr_0] = chars[second_half | first_half]
120
+ end
121
+ scrollbar
120
122
  end
121
123
  end
122
124
  end