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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +32 -0
- data/DECISIONS.md +680 -8
- data/README.md +12 -13
- data/TERMINOLOGY.md +61 -0
- data/book/02-repaint.md +1 -1
- data/book/03-layout.md +1 -1
- data/book/06-theming.md +1 -1
- data/book/07-components.md +97 -27
- data/examples/file_commander.rb +5 -4
- data/examples/sampler.rb +38 -1
- data/ideas/new-components.md +9 -4
- data/lib/tuile/buffer.rb +7 -7
- data/lib/tuile/component/button.rb +1 -1
- data/lib/tuile/component/checkbox.rb +1 -1
- data/lib/tuile/component/checkbox_group.rb +31 -26
- data/lib/tuile/component/combo_box.rb +10 -7
- data/lib/tuile/component/info_window.rb +1 -1
- data/lib/tuile/component/label.rb +14 -14
- data/lib/tuile/component/list.rb +291 -216
- data/lib/tuile/component/list_dropdown.rb +14 -7
- data/lib/tuile/component/notification.rb +317 -0
- data/lib/tuile/component/picker_window.rb +3 -3
- data/lib/tuile/component/popup.rb +8 -10
- data/lib/tuile/component/progress_bar.rb +1 -1
- data/lib/tuile/component/radio_group.rb +32 -30
- data/lib/tuile/component/select.rb +7 -7
- data/lib/tuile/component/text_area/wrapped_text.rb +320 -0
- data/lib/tuile/component/text_area.rb +79 -273
- data/lib/tuile/component/text_field.rb +1 -1
- data/lib/tuile/component/text_view.rb +191 -177
- data/lib/tuile/component/window.rb +8 -8
- data/lib/tuile/component.rb +5 -5
- data/lib/tuile/screen.rb +1 -1
- data/lib/tuile/styled_string.rb +12 -12
- data/lib/tuile/version.rb +1 -1
- data/lib/tuile/vertical_scroll_bar.rb +6 -6
- data/sig/tuile.rbs +788 -377
- 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
|
|
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
|
|
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#
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
data/lib/tuile/component.rb
CHANGED
|
@@ -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#
|
|
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
|
|
457
|
-
screen.buffer.
|
|
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
|
-
# {#
|
|
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#
|
|
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
|
data/lib/tuile/styled_string.rb
CHANGED
|
@@ -536,7 +536,7 @@ module Tuile
|
|
|
536
536
|
result
|
|
537
537
|
end
|
|
538
538
|
|
|
539
|
-
# Word-wraps to
|
|
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
|
|
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
|
-
|
|
569
|
-
return
|
|
568
|
+
input_lines = lines
|
|
569
|
+
return input_lines if width.nil? || width <= 0
|
|
570
570
|
|
|
571
571
|
result = []
|
|
572
|
-
|
|
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
|
|
720
|
+
# @param line [StyledString] one line — no embedded `"\n"`.
|
|
721
721
|
# @param width [Integer]
|
|
722
722
|
# @return [Array<StyledString>]
|
|
723
|
-
def wrap_one(
|
|
724
|
-
return [
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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
|
@@ -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
|
|
22
|
-
# @param
|
|
23
|
-
def initialize(height,
|
|
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
|
|
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 /
|
|
34
|
-
@handle_start = (height *
|
|
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
|