tuile 0.11.0 → 0.12.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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +32 -0
  3. data/DECISIONS.md +680 -8
  4. data/README.md +12 -13
  5. data/TERMINOLOGY.md +61 -0
  6. data/book/02-repaint.md +1 -1
  7. data/book/03-layout.md +1 -1
  8. data/book/06-theming.md +1 -1
  9. data/book/07-components.md +97 -27
  10. data/examples/file_commander.rb +5 -4
  11. data/examples/sampler.rb +38 -1
  12. data/ideas/new-components.md +9 -4
  13. data/lib/tuile/buffer.rb +7 -7
  14. data/lib/tuile/component/button.rb +1 -1
  15. data/lib/tuile/component/checkbox.rb +1 -1
  16. data/lib/tuile/component/checkbox_group.rb +31 -26
  17. data/lib/tuile/component/combo_box.rb +10 -7
  18. data/lib/tuile/component/info_window.rb +1 -1
  19. data/lib/tuile/component/label.rb +14 -14
  20. data/lib/tuile/component/list.rb +291 -216
  21. data/lib/tuile/component/list_dropdown.rb +14 -7
  22. data/lib/tuile/component/notification.rb +317 -0
  23. data/lib/tuile/component/picker_window.rb +3 -3
  24. data/lib/tuile/component/popup.rb +8 -10
  25. data/lib/tuile/component/progress_bar.rb +1 -1
  26. data/lib/tuile/component/radio_group.rb +32 -30
  27. data/lib/tuile/component/select.rb +7 -7
  28. data/lib/tuile/component/text_area/wrapped_text.rb +320 -0
  29. data/lib/tuile/component/text_area.rb +79 -273
  30. data/lib/tuile/component/text_field.rb +1 -1
  31. data/lib/tuile/component/text_view.rb +191 -177
  32. data/lib/tuile/component/window.rb +8 -8
  33. data/lib/tuile/component.rb +5 -5
  34. data/lib/tuile/screen.rb +1 -1
  35. data/lib/tuile/styled_string.rb +12 -12
  36. data/lib/tuile/version.rb +1 -1
  37. data/lib/tuile/vertical_scroll_bar.rb +6 -6
  38. data/sig/tuile.rbs +788 -377
  39. metadata +4 -1
@@ -24,7 +24,7 @@ module Tuile
24
24
  @content = nil
25
25
  # Optional bottom-row widget slot (e.g. a search field), spanning the
26
26
  # full inner width; and optional bottom-border chrome text embedded in
27
- # the border line (mutually exclusive — the component, when present,
27
+ # the border row (mutually exclusive — the component, when present,
28
28
  # occupies the row and hides the text).
29
29
  @footer = nil
30
30
  @footer_text = StyledString::EMPTY
@@ -53,7 +53,7 @@ module Tuile
53
53
  return if @footer_text == new_text
54
54
 
55
55
  @footer_text = new_text
56
- invalidate # repaint the bottom border line
56
+ invalidate # repaint the bottom border row
57
57
  end
58
58
 
59
59
  # Sets the bottom-row widget slot. The footer occupies the bottom border
@@ -147,10 +147,10 @@ module Tuile
147
147
  content.rect = Rect.new(rect.left + 1, rect.top + 1, rect.width - 1 - @border_right, rect.height - 2)
148
148
  end
149
149
 
150
- # Paints the window border via {Component#draw_line}/{Component#draw_char},
150
+ # Paints the window border via {Component#draw_text}/{Component#draw_char},
151
151
  # so the border cells inherit {Component#effective_bg_color} — a
152
152
  # {Component#bg_color} on the window tints border and content alike. Both
153
- # border lines are clipped by *display* width, so no caption overflows the
153
+ # border rows are clipped by *display* width, so no caption overflows the
154
154
  # box; when the window is active the whole border — the caption's own
155
155
  # colors included — is drawn in {Theme#active_border_color}.
156
156
  # @return [void]
@@ -165,15 +165,15 @@ module Tuile
165
165
 
166
166
  fg = active? ? screen.theme.active_border_color : nil
167
167
  bar = StyledString::Style.new(fg: fg)
168
- draw_line(left, top, top_border(inner_w, fg).slice(0, w))
168
+ draw_text(left, top, top_border(inner_w, fg).slice(0, w))
169
169
  (1..(h - 2)).each do |dy|
170
170
  draw_char(left, top + dy, "│", bar)
171
171
  draw_char(left + w - 1, top + dy, "│", bar)
172
172
  end
173
- draw_line(left, top + h - 1, bottom_border(inner_w, fg).slice(0, w)) if h >= 2
173
+ draw_text(left, top + h - 1, bottom_border(inner_w, fg).slice(0, w)) if h >= 2
174
174
  end
175
175
 
176
- # Builds the top border line: corners, {#caption} embedded at its own
176
+ # Builds the top border row: corners, {#caption} embedded at its own
177
177
  # width, dashes filling the remainder. The caption keeps its own styling
178
178
  # unless `fg` is set — an active window's border claims it.
179
179
  # @param inner_w [Integer] the border's interior width.
@@ -186,7 +186,7 @@ module Tuile
186
186
  StyledString.styled("┌", fg: fg) + title + dashes + StyledString.styled("┐", fg: fg)
187
187
  end
188
188
 
189
- # Builds the bottom border line. The corners take the border color; the
189
+ # Builds the bottom border row. The corners take the border color; the
190
190
  # interior is plain dashes when a {#footer} component occupies the row
191
191
  # (it overpaints them) or when there's no chrome, otherwise it carries
192
192
  # {#footer_text} embedded at its own width — keeping the text's own
@@ -211,7 +211,7 @@ module Tuile
211
211
  # Called on every attached component (pre-order, popups included) when
212
212
  # {Screen#theme} changes — at {Screen#theme=} / {Screen#theme_def=} and on
213
213
  # OS appearance flips. The hook exists for app *content* whose colors were
214
- # baked in from the old theme (a {Label#text} / {List#lines} {StyledString}
214
+ # baked in from the old theme (a {Label#text} / {List#lines=} {StyledString}
215
215
  # styled with `theme[:accent]`); rebuild it here by re-running the code that
216
216
  # rendered it. See book ch6 for why built-in accents need no such handling.
217
217
  #
@@ -444,7 +444,7 @@ module Tuile
444
444
  screen.buffer.fill(area, bg ? StyledString::Style.new(bg:) : StyledString::Style::DEFAULT)
445
445
  end
446
446
 
447
- # {Buffer#set_line} wrapper that fills {#effective_bg_color} behind any span
447
+ # {Buffer#set_text} wrapper that fills {#effective_bg_color} behind any span
448
448
  # with no bg of its own (via {StyledString#under_bg}), so an inherited
449
449
  # {#bg_color} shows through the content a component paints. A no-op layer
450
450
  # when nothing is inherited. Self-painters (those skipping the {#repaint}
@@ -453,11 +453,11 @@ module Tuile
453
453
  # @param y [Integer] row.
454
454
  # @param styled [StyledString]
455
455
  # @return [void]
456
- def draw_line(x, y, styled)
457
- screen.buffer.set_line(x, y, styled.under_bg(effective_bg_color))
456
+ def draw_text(x, y, styled)
457
+ screen.buffer.set_text(x, y, styled.under_bg(effective_bg_color))
458
458
  end
459
459
 
460
- # {#draw_line}'s single-grapheme counterpart: writes `grapheme` at `(x, y)`,
460
+ # {#draw_text}'s single-grapheme counterpart: writes `grapheme` at `(x, y)`,
461
461
  # filling {#effective_bg_color} when `style` carries no bg of its own.
462
462
  # @param x [Integer] column.
463
463
  # @param y [Integer] row.
data/lib/tuile/screen.rb CHANGED
@@ -114,7 +114,7 @@ module Tuile
114
114
  attr_reader :color_scheme
115
115
 
116
116
  # @return [Buffer] the back buffer components paint into
117
- # ({Buffer#set_line} / {Buffer#fill} / {Buffer#set_char}).
117
+ # ({Buffer#set_text} / {Buffer#fill} / {Buffer#set_char}).
118
118
  attr_reader :buffer
119
119
 
120
120
  # Handler invoked when a {StandardError} escapes an event handler inside
@@ -536,7 +536,7 @@ module Tuile
536
536
  result
537
537
  end
538
538
 
539
- # Word-wraps to physical lines that each fit within `width` display
539
+ # Word-wraps to rows that each fit within `width` display
540
540
  # columns, preserving spans and styles across breaks. Greedy word-wrap,
541
541
  # hard-break for words wider than `width`, leading whitespace dropped on
542
542
  # wrapped continuations, hard `"\n"` breaks preserved as separate output
@@ -560,16 +560,16 @@ module Tuile
560
560
  # @param width [Integer, nil] target column width. `nil` or `<= 0` skips
561
561
  # wrapping and returns each hard-line as-is, so callers can pass a
562
562
  # stale viewport width without crashing.
563
- # @return [Array<StyledString>] one entry per physical (output) line.
563
+ # @return [Array<StyledString>] one entry per output row.
564
564
  # An empty receiver returns `[]`.
565
565
  def wrap(width)
566
566
  return [] if empty?
567
567
 
568
- hard_lines = lines
569
- return hard_lines if width.nil? || width <= 0
568
+ input_lines = lines
569
+ return input_lines if width.nil? || width <= 0
570
570
 
571
571
  result = []
572
- hard_lines.each { |hl| result.concat(wrap_one(hl, width)) }
572
+ input_lines.each { |line| result.concat(wrap_one(line, width)) }
573
573
  result
574
574
  end
575
575
 
@@ -717,17 +717,17 @@ module Tuile
717
717
  self.class.new(out)
718
718
  end
719
719
 
720
- # @param hard_line [StyledString] one hard-broken line — no embedded `"\n"`.
720
+ # @param line [StyledString] one line — no embedded `"\n"`.
721
721
  # @param width [Integer]
722
722
  # @return [Array<StyledString>]
723
- def wrap_one(hard_line, width)
724
- return [hard_line] if hard_line.empty?
723
+ def wrap_one(line, width)
724
+ return [line] if line.empty?
725
725
 
726
726
  result = []
727
727
  line_glyphs = []
728
728
  line_w = 0
729
729
 
730
- tokenize_for_wrap(hard_line).each do |type, glyphs, w|
730
+ tokenize_for_wrap(line).each do |type, glyphs, w|
731
731
  if type == :space
732
732
  if line_w.zero? && (!result.empty? || w > width)
733
733
  # Nothing to emit: a continuation's leading run was consumed by the
@@ -763,18 +763,18 @@ module Tuile
763
763
  # a cluster is the unit a terminal draws, so measuring its parts separately
764
764
  # would both mis-total an emoji sequence and let a wrap break a letter away
765
765
  # from its combining mark.
766
- # @param hard_line [StyledString]
766
+ # @param line [StyledString]
767
767
  # @return [Array<Array>] tokens shaped `[type, glyphs, w]` where `type` is
768
768
  # `:space` or `:word`, `glyphs` is an `Array<[String, Style, Integer]>`
769
769
  # (grapheme cluster, style, display width), and `w` is the token's total
770
770
  # width.
771
- def tokenize_for_wrap(hard_line)
771
+ def tokenize_for_wrap(line)
772
772
  tokens = []
773
773
  current_glyphs = []
774
774
  current_w = 0
775
775
  current_type = nil
776
776
 
777
- each_glyph_with_style(hard_line) do |g, s|
777
+ each_glyph_with_style(line) do |g, s|
778
778
  type = [" ", "\t"].include?(g) ? :space : :word
779
779
  gw = Buffer.display_width(g)
780
780
  if current_type && current_type != type
data/lib/tuile/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Tuile
4
4
  # @return [String]
5
- VERSION = "0.11.0"
5
+ VERSION = "0.12.0"
6
6
  end
@@ -18,20 +18,20 @@ module Tuile
18
18
 
19
19
  # @param height [Integer] number of rows in the scrollbar (== viewport
20
20
  # height).
21
- # @param line_count [Integer] total number of content lines.
22
- # @param top_line [Integer] index of the first visible content line.
23
- def initialize(height, line_count:, top_line:)
21
+ # @param row_count [Integer] total number of content rows.
22
+ # @param scroll_top_row [Integer] index of the first visible content row.
23
+ def initialize(height, row_count:, scroll_top_row:)
24
24
  @height = height
25
25
 
26
26
  return unless height >= 1
27
27
 
28
- if line_count <= height
28
+ if row_count <= height
29
29
  @handle_height = height
30
30
  @handle_start = 0
31
31
  @handle_end = height - 1
32
32
  else
33
- @handle_height = [(height * height / line_count.to_f).ceil, 1].max
34
- @handle_start = (height * top_line / line_count.to_f).floor
33
+ @handle_height = [(height * height / row_count.to_f).ceil, 1].max
34
+ @handle_start = (height * scroll_top_row / row_count.to_f).floor
35
35
  @handle_end = @handle_start + @handle_height - 1
36
36
  end
37
37
  end