whirled_peas 0.10.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/README.md +88 -24
  4. data/examples/components.rb +35 -0
  5. data/examples/scrolling.rb +8 -19
  6. data/lib/whirled_peas.rb +30 -16
  7. data/lib/whirled_peas/command/config_command.rb +3 -2
  8. data/lib/whirled_peas/command/play.rb +3 -2
  9. data/lib/whirled_peas/command/themes.rb +3 -2
  10. data/lib/whirled_peas/component.rb +35 -0
  11. data/lib/whirled_peas/component/list_with_active.rb +88 -0
  12. data/lib/whirled_peas/graphics/box_painter.rb +4 -4
  13. data/lib/whirled_peas/graphics/composer.rb +4 -0
  14. data/lib/whirled_peas/graphics/container_coords.rb +24 -2
  15. data/lib/whirled_peas/graphics/container_painter.rb +9 -119
  16. data/lib/whirled_peas/graphics/debugger.rb +1 -7
  17. data/lib/whirled_peas/graphics/scrollbar_helper.rb +124 -0
  18. data/lib/whirled_peas/null_logger.rb +0 -1
  19. data/lib/whirled_peas/settings/container_settings.rb +12 -5
  20. data/lib/whirled_peas/settings/debugger.rb +1 -1
  21. data/lib/whirled_peas/settings/position.rb +17 -5
  22. data/lib/whirled_peas/version.rb +1 -1
  23. data/screen_test/components/list_with_active/l2r_active_color.frame +1 -0
  24. data/screen_test/components/list_with_active/l2r_active_color.rb +18 -0
  25. data/screen_test/components/list_with_active/l2r_position_end.frame +1 -0
  26. data/screen_test/components/list_with_active/l2r_position_end.rb +16 -0
  27. data/screen_test/components/list_with_active/l2r_position_middle.frame +1 -0
  28. data/screen_test/components/list_with_active/l2r_position_middle.rb +16 -0
  29. data/screen_test/components/list_with_active/l2r_position_start.frame +1 -0
  30. data/screen_test/components/list_with_active/l2r_position_start.rb +16 -0
  31. data/screen_test/components/list_with_active/l2r_separator.frame +1 -0
  32. data/screen_test/components/list_with_active/l2r_separator.rb +17 -0
  33. data/screen_test/components/list_with_active/t2b_active_color.frame +1 -0
  34. data/screen_test/components/list_with_active/t2b_active_color.rb +19 -0
  35. data/screen_test/components/list_with_active/t2b_position_end.frame +1 -0
  36. data/screen_test/components/list_with_active/t2b_position_end.rb +17 -0
  37. data/screen_test/components/list_with_active/t2b_position_middle.frame +1 -0
  38. data/screen_test/components/list_with_active/t2b_position_middle.rb +17 -0
  39. data/screen_test/components/list_with_active/t2b_position_start.frame +1 -0
  40. data/screen_test/components/list_with_active/t2b_position_start.rb +17 -0
  41. data/screen_test/components/list_with_active/t2b_separator.frame +1 -0
  42. data/screen_test/components/list_with_active/t2b_separator.rb +18 -0
  43. data/screen_test/elements/theme.rb +1 -1
  44. data/screen_test/settings/{position/box_top_negative.frame → content_start/box_bottom.frame} +0 -0
  45. data/screen_test/settings/content_start/box_bottom.rb +17 -0
  46. data/screen_test/settings/{position → content_start}/box_left.frame +0 -0
  47. data/screen_test/settings/{position/box_top.rb → content_start/box_left.rb} +1 -1
  48. data/screen_test/settings/{position → content_start}/box_left_negative.frame +0 -0
  49. data/screen_test/settings/{position → content_start}/box_left_negative.rb +1 -1
  50. data/screen_test/settings/content_start/box_right.frame +1 -0
  51. data/screen_test/settings/content_start/box_right.rb +17 -0
  52. data/screen_test/settings/{position → content_start}/box_top.frame +0 -0
  53. data/screen_test/settings/{position/box_left.rb → content_start/box_top.rb} +1 -1
  54. data/screen_test/settings/content_start/box_top_negative.frame +1 -0
  55. data/screen_test/settings/{position → content_start}/box_top_negative.rb +1 -1
  56. data/screen_test/settings/{position → content_start}/grid_left.frame +0 -0
  57. data/screen_test/settings/{position → content_start}/grid_left.rb +1 -1
  58. data/screen_test/settings/{position → content_start}/grid_left_negative.frame +0 -0
  59. data/screen_test/settings/{position → content_start}/grid_left_negative.rb +1 -1
  60. data/screen_test/settings/{position → content_start}/grid_top.frame +0 -0
  61. data/screen_test/settings/{position → content_start}/grid_top.rb +1 -1
  62. data/screen_test/settings/{position → content_start}/grid_top_negative.frame +0 -0
  63. data/screen_test/settings/{position → content_start}/grid_top_negative.rb +1 -1
  64. data/screen_test/settings/scroll/horiz_box.frame +1 -1
  65. data/screen_test/settings/scroll/horiz_box.rb +2 -4
  66. data/screen_test/settings/scroll/horiz_box_align_center.rb +2 -4
  67. data/screen_test/settings/scroll/horiz_box_align_right.rb +2 -4
  68. data/screen_test/settings/scroll/vert_box.frame +1 -1
  69. data/screen_test/settings/scroll/vert_box.rb +5 -7
  70. metadata +46 -18
@@ -55,8 +55,8 @@ module WhirledPeas
55
55
  private
56
56
 
57
57
  def paint_horizontally(canvas, canvas_coords, &block)
58
- stroke_left = canvas_coords.content_left
59
- stroke_top = canvas_coords.content_top
58
+ stroke_left = canvas_coords.offset_content_left
59
+ stroke_top = canvas_coords.offset_content_top
60
60
  children_width = 0
61
61
  each_child { |c| children_width += c.dimensions.outer_width }
62
62
  left_offset, spacing_offset = horiz_justify_offset(children_width)
@@ -76,8 +76,8 @@ module WhirledPeas
76
76
  end
77
77
 
78
78
  def paint_vertically(canvas, canvas_coords, &block)
79
- stroke_left = canvas_coords.content_left
80
- stroke_top = canvas_coords.content_top
79
+ stroke_left = canvas_coords.offset_content_left
80
+ stroke_top = canvas_coords.offset_content_top
81
81
  children_height = 0
82
82
  each_child { |c| children_height += c.dimensions.outer_height }
83
83
  top_offset, spacing_offset = vert_justify_offset(children_height)
@@ -71,6 +71,10 @@ module WhirledPeas
71
71
  painter.add_child(child)
72
72
  end
73
73
 
74
+ def add_component(component)
75
+ component.compose(self, settings)
76
+ end
77
+
74
78
  def add_box(name=self.class.next_name, &block)
75
79
  child_settings = Settings::BoxSettings.inherit(painter.settings)
76
80
  child = BoxPainter.new(name, child_settings)
@@ -9,11 +9,11 @@ module WhirledPeas
9
9
  end
10
10
 
11
11
  def left
12
- start_left + settings.position.left
12
+ start_left
13
13
  end
14
14
 
15
15
  def top
16
- start_top + settings.position.top
16
+ start_top
17
17
  end
18
18
 
19
19
  def border_left
@@ -36,10 +36,32 @@ module WhirledPeas
36
36
  padding_left + settings.padding.left + col_index * grid_width
37
37
  end
38
38
 
39
+ def offset_content_left(col_index=0)
40
+ if settings.content_start.left
41
+ content_left(col_index) + settings.content_start.left
42
+ elsif settings.content_start.right
43
+ left_offset = dimensions.content_width - dimensions.children_width
44
+ content_left(col_index) + left_offset - settings.content_start.right
45
+ else
46
+ content_left(col_index)
47
+ end
48
+ end
49
+
39
50
  def content_top(row_index=0)
40
51
  padding_top + settings.padding.top + row_index * grid_height
41
52
  end
42
53
 
54
+ def offset_content_top(row_index=0)
55
+ if settings.content_start.top
56
+ content_top(row_index) + settings.content_start.top
57
+ elsif settings.content_start.bottom
58
+ top_offset = dimensions.content_height - dimensions.children_height
59
+ content_top(row_index) + top_offset - settings.content_start.bottom
60
+ else
61
+ content_top(row_index)
62
+ end
63
+ end
64
+
43
65
  def inner_grid_width
44
66
  settings.padding.left +
45
67
  dimensions.content_width +
@@ -2,6 +2,7 @@ require 'whirled_peas/utils/formatted_string'
2
2
 
3
3
  require_relative 'container_coords'
4
4
  require_relative 'painter'
5
+ require_relative 'scrollbar_helper'
5
6
 
6
7
  module WhirledPeas
7
8
  module Graphics
@@ -216,16 +217,12 @@ module WhirledPeas
216
217
  settings.border.style.right_vert,
217
218
  ) do
218
219
  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
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
+ )
229
226
  PADDING * canvas_coords.inner_grid_width + scrollbar_char
230
227
  else
231
228
  PADDING * canvas_coords.inner_grid_width
@@ -241,122 +238,15 @@ module WhirledPeas
241
238
  settings.border.style.right_vert,
242
239
  ) do
243
240
  canvas_coords.inner_grid_width.times.map do |col_within_cell|
244
- horiz_scroll_char(
241
+ ScrollbarHelper.horiz_char(
245
242
  dimensions.children_width + dimensions.padding_width,
246
243
  canvas_coords.inner_grid_width,
247
- -children.first.settings.position.left,
244
+ canvas_coords.left - canvas_coords.offset_content_left,
248
245
  col_within_cell
249
246
  )
250
247
  end.join
251
248
  end
252
249
  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
250
  end
361
251
  private_constant :ContainerPainter
362
252
  end
@@ -18,13 +18,7 @@ module WhirledPeas
18
18
  info << "#{indent + ' '}- Settings"
19
19
  info << Settings::Debugger.new(painter.settings).debug(indent + ' ')
20
20
  if painter.is_a?(TextPainter)
21
- content = if painter.content.length > 1
22
- '<multiline>'
23
- elsif painter.content.first.length > 12
24
- "#{painter.content.first[0..9]}...".inspect
25
- else
26
- painter.content.first.inspect
27
- end
21
+ content = painter.content
28
22
  info << "#{indent + ' '}- Content(value=#{content})"
29
23
  elsif painter.is_a?(ContainerPainter)
30
24
  info << "#{indent + ' '}- Children"
@@ -0,0 +1,124 @@
1
+ module WhirledPeas
2
+ module Graphics
3
+ module ScrollbarHelper
4
+ # Contants to paint scrollbars
5
+ HORIZONTAL = [' ', '▗', '▖', '▄']
6
+ VERTICAL = [
7
+ ' ',
8
+ '▗',
9
+ '▝',
10
+ '▐'
11
+ ]
12
+
13
+ # The number of units of scrollbar a single character can be divided into
14
+ SCROLL_HANDLE_SCALE = 2
15
+ private_constant :SCROLL_HANDLE_SCALE
16
+
17
+ class << self
18
+ # Determine the character to paint the horizontal scroll bar with for the given column
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)
23
+ end
24
+
25
+ # Determine the character to paint the vertical scroll bar with for the given row
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)
30
+ end
31
+
32
+ private
33
+
34
+ # Determine which character to paint a for a scrollbar
35
+ #
36
+ # @param total_count [Integer] total number of rows/columns in the content
37
+ # @param viewable_count [Integer] number of rows/columns visible in the viewport
38
+ # @param first_visible [Integer] zero-based index of the first row/column that is visible
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
49
+
50
+ # The scroll handle has the exact same relative size and position in the scroll gutter
51
+ # that the viewable content has in the total content area. For example, a content area
52
+ # that is 50 columns wide with a view port that is 20 columns wide might look like
53
+ #
54
+ # +---------1-----****2*********3******---4---------+
55
+ # | * * |
56
+ # | hidden * viewable * hidden |
57
+ # | * * |
58
+ # +---------1-----****2*********3******---4---------+
59
+ #
60
+ # The scoll gutter, would look like
61
+ #
62
+ # |......********.....|
63
+ #
64
+ # Scrolling all the way to the right results in
65
+ #
66
+ # +---------1---------2---------3*********4*********+
67
+ # | * *
68
+ # | hidden * viewable *
69
+ # | * *
70
+ # +---------1---------2---------3*********4*********+
71
+ # |...........********|
72
+ #
73
+ # Returning to the first example, we can match up the arguments to this method to the
74
+ # diagram
75
+ #
76
+ # total_count = 50
77
+ # |<----------------------------------------------->|
78
+ # | |
79
+ # | veiwable_count = 20 |
80
+ # | |<----------------->| |
81
+ # ↓ ↓ ↓ ↓
82
+ # +---------1-----****2*********3******---4---------+
83
+ # | * * |
84
+ # | hidden * viewable * hidden |
85
+ # | * * |
86
+ # +---------1-----****2*********3******---4---------+
87
+ # |......****?***.....|
88
+ # ↑ ↑
89
+ # first_visible = 16 |
90
+ # curr = 11
91
+
92
+ # Always use the same length for the scrollbar so it does not give an inchworm effect
93
+ # as it scrolls along. This will calculate the "ideal" length of the scroll bar if we
94
+ # could infinitely divide a character.
95
+ scrollbar_length = (viewable_count ** 2).to_f / total_count
96
+
97
+ # Round the length to the nearst "scaled" value
98
+ scrollbar_length = (SCROLL_HANDLE_SCALE * scrollbar_length).round.to_f / SCROLL_HANDLE_SCALE
99
+
100
+ # Ensure we have a scrollbar!
101
+ scrollbar_length = 1.0 / SCROLL_HANDLE_SCALE if scrollbar_length == 0
102
+
103
+ # Find the "ideal" position of where the scrollbar should start.
104
+ scrollbar_start = first_visible * viewable_count.to_f / total_count
105
+
106
+ # Round the start to the nearest "scaled" value
107
+ scrollbar_start = (SCROLL_HANDLE_SCALE * scrollbar_start).round.to_f / SCROLL_HANDLE_SCALE
108
+
109
+ # Make sure we didn't scroll off the page!
110
+ scrollbar_start -= scrollbar_length if scrollbar_start == viewable_count
111
+
112
+ # Create "scaled" indexes for the subdivided current character
113
+ curr_0 = curr
114
+ curr_1 = curr + 1.0 / SCROLL_HANDLE_SCALE
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
118
+
119
+ chars[second_half | first_half]
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -15,6 +15,5 @@ module WhirledPeas
15
15
  def warn(*)
16
16
  end
17
17
  end
18
-
19
18
  private_constant :NullLogger
20
19
  end
@@ -175,13 +175,20 @@ module WhirledPeas
175
175
  @_padding ||= Padding.new
176
176
  end
177
177
 
178
- def set_position(left: nil, top: nil)
179
- position.left = left if left
180
- position.top = top if top
178
+ def set_content_start(left: nil, top: nil, right: nil, bottom: nil)
179
+ if left && right
180
+ raise ArgumentError, 'Setting left and right content_start is not supported'
181
+ elsif top && bottom
182
+ raise ArgumentError, 'Setting top and bottom content_start is not supported'
183
+ end
184
+ content_start.left = left if left
185
+ content_start.top = top if top
186
+ content_start.right = right if right
187
+ content_start.bottom = bottom if bottom
181
188
  end
182
189
 
183
- def position
184
- @_position ||= Position.new
190
+ def content_start
191
+ @_content_start ||= Position.new
185
192
  end
186
193
 
187
194
  def set_scrollbar(horiz: nil, vert: nil)
@@ -73,7 +73,7 @@ module WhirledPeas
73
73
  def border_value(border)
74
74
  values = non_defaults(
75
75
  border,
76
- Border.new,
76
+ Border.new(settings.theme),
77
77
  %i[left? top? right? bottom? inner_horiz? inner_vert? style color]
78
78
  )
79
79
  return if values == ''
@@ -1,14 +1,26 @@
1
1
  module WhirledPeas
2
2
  module Settings
3
3
  class Position
4
- attr_writer :left, :top
4
+ attr_reader :left, :top, :right, :bottom
5
5
 
6
- def left
7
- @left || 0
6
+ def left=(value)
7
+ @right = nil
8
+ @left = value
8
9
  end
9
10
 
10
- def top
11
- @top || 0
11
+ def top=(value)
12
+ @bottom = nil
13
+ @top = value
14
+ end
15
+
16
+ def right=(value)
17
+ @left = nil
18
+ @right = value
19
+ end
20
+
21
+ def bottom=(value)
22
+ @top = nil
23
+ @bottom = value
12
24
  end
13
25
  end
14
26
  end