Class: Tuile::Component::TextView
- Inherits:
-
Tuile::Component
- Object
- Tuile::Component
- Tuile::Component::TextView
- Defined in:
- lib/tuile/component/text_view.rb
Overview
A read-only viewer for prose: chunks of formatted text that scroll vertically. Shape-wise a hybrid between Label (string-shaped content via #text=) and List (scroll keys, optional scrollbar, auto-scroll).
Text is modeled as a StyledString: embedded ‘n` are hard line breaks, lines wider than the viewport are word-wrapped via StyledString#wrap (style spans are preserved across wrap boundaries — unlike the older ANSI-as-bytes wrapping, color does not get dropped on continuation rows). #text= accepts a String (parsed via StyledString.parse, so embedded ANSI is honored) or a StyledString directly; #text always returns the StyledString. Use #append for incremental “log line” style updates; turn on #auto_scroll to keep the latest content in view.
TextView is meant to be the content of a Window — focus indication and keyboard-hint surfacing rely on the surrounding window chrome.
Instance Attribute Summary collapse
-
#auto_scroll ⇒ Boolean
If true, mutating the text scrolls the viewport so the last line stays in view.
-
#content_size ⇒ Size
readonly
Longest hard-line’s display width × number of hard lines.
-
#scrollbar_visibility ⇒ Symbol
‘:gone` or `:visible`.
-
#top_line ⇒ Integer
Index of the first visible physical line.
Attributes inherited from Tuile::Component
Instance Method Summary collapse
-
#append(str) ⇒ void
Appends ‘str` as a new physical line.
-
#clear ⇒ void
Clears the text.
- #focusable? ⇒ Boolean
- #handle_key(key) ⇒ Boolean
- #handle_mouse(event) ⇒ void
-
#initialize ⇒ TextView
constructor
A new instance of TextView.
-
#repaint ⇒ void
Paints the text into #rect.
- #tab_stop? ⇒ Boolean
-
#text ⇒ StyledString
The current text.
-
#text=(value) ⇒ void
Replaces the text.
Methods inherited from Tuile::Component
#active=, #active?, #attached?, #children, #cursor_position, #depth, #find_shortcut_component, #focus, #keyboard_hint, #on_child_removed, #on_focus, #on_tree, #root, #screen
Constructor Details
#initialize ⇒ TextView
Returns a new instance of TextView.
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# File 'lib/tuile/component/text_view.rb', line 22 def initialize super # `@hard_lines` is the logical model — one entry per `\n`-delimited # line of the original text, width-independent. `@physical_lines` is # the rendered view — each hard line word-wrapped to `wrap_width` # and padded with trailing blanks, so painting a row is a lookup. # Resizing rebuilds `@physical_lines` from `@hard_lines`; `#append` # extends both. @hard_lines = [] @physical_lines = [] @text = StyledString::EMPTY @content_size = Size::ZERO @blank_line = StyledString::EMPTY @top_line = 0 @auto_scroll = false @scrollbar_visibility = :gone end |
Instance Attribute Details
#auto_scroll ⇒ Boolean
Returns if true, mutating the text scrolls the viewport so the last line stays in view. Default ‘false`.
59 60 61 |
# File 'lib/tuile/component/text_view.rb', line 59 def auto_scroll @auto_scroll end |
#content_size ⇒ Size (readonly)
158 159 160 |
# File 'lib/tuile/component/text_view.rb', line 158 def content_size @content_size end |
#scrollbar_visibility ⇒ Symbol
Returns ‘:gone` or `:visible`.
55 56 57 |
# File 'lib/tuile/component/text_view.rb', line 55 def @scrollbar_visibility end |
#top_line ⇒ Integer
Returns index of the first visible physical line.
52 53 54 |
# File 'lib/tuile/component/text_view.rb', line 52 def top_line @top_line end |
Instance Method Details
#append(str) ⇒ void
This method returns an undefined value.
Appends ‘str` as a new physical line. If the current text is empty, behaves like `text = str`; otherwise prepends a newline so the new content lands on a fresh line. Accepts the same input forms as #text=.
Cost is O(appended) rather than O(total) — the existing wrapped buffer is reused, only the new hard line(s) are wrapped and padded, and ‘@content_size` is updated incrementally. The cached #text is invalidated and rebuilt on demand.
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/tuile/component/text_view.rb', line 90 def append(str) screen.check_locked appended = StyledString.parse(str) if @hard_lines.empty? self.text = appended return end new_hard_lines = appended.lines @text = nil @hard_lines.concat(new_hard_lines) new_width = new_hard_lines.map(&:display_width).max || 0 @content_size = Size.new( [@content_size.width, new_width].max, @content_size.height + new_hard_lines.size ) width = wrap_width new_hard_lines.each { |hl| append_physical_lines(hl, width) } update_top_line_if_auto_scroll invalidate end |
#clear ⇒ void
This method returns an undefined value.
Clears the text. Equivalent to ‘text = “”`.
114 115 116 |
# File 'lib/tuile/component/text_view.rb', line 114 def clear self.text = StyledString::EMPTY end |
#focusable? ⇒ Boolean
149 |
# File 'lib/tuile/component/text_view.rb', line 149 def focusable? = true |
#handle_key(key) ⇒ Boolean
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
# File 'lib/tuile/component/text_view.rb', line 162 def handle_key(key) return false unless active? return true if super case key when *Keys::DOWN_ARROWS then move_top_line_by(1) when *Keys::UP_ARROWS then move_top_line_by(-1) when Keys::PAGE_DOWN then move_top_line_by() when Keys::PAGE_UP then move_top_line_by(-) when Keys::CTRL_D then move_top_line_by( / 2) when Keys::CTRL_U then move_top_line_by(- / 2) when *Keys::HOMES, "g" then move_top_line_to(0) when *Keys::ENDS_, "G" then move_top_line_to(top_line_max) else return false end true end |
#handle_mouse(event) ⇒ void
This method returns an undefined value.
182 183 184 185 186 187 188 |
# File 'lib/tuile/component/text_view.rb', line 182 def handle_mouse(event) super case event. when :scroll_down then move_top_line_by(4) when :scroll_up then move_top_line_by(-4) end end |
#repaint ⇒ void
This method returns an undefined value.
Paints the text into Tuile::Component#rect.
Skips the Tuile::Component#repaint default’s auto-clear: every row is painted explicitly (with padded blanks past the last line), so the “fully draw over your rect” contract is met without an upfront wipe.
196 197 198 199 200 201 202 203 204 205 206 |
# File 'lib/tuile/component/text_view.rb', line 196 def repaint return if rect.empty? = if VerticalScrollBar.new(rect.height, line_count: @physical_lines.size, top_line: @top_line) end (0...rect.height).each do |row| line = paintable_line(row + @top_line, row, ) screen.print TTY::Cursor.move_to(rect.left, rect.top + row), line end end |
#tab_stop? ⇒ Boolean
151 |
# File 'lib/tuile/component/text_view.rb', line 151 def tab_stop? = true |
#text ⇒ StyledString
Returns the current text. Defaults to an empty StyledString. Internally the text is stored as an array of hard lines so #append can stay O(appended) instead of re-scanning the whole buffer; the joined StyledString returned here is reconstructed on first read after a mutation and cached, so repeated reads are O(1) but the first read after #append pays O(total spans).
47 48 49 |
# File 'lib/tuile/component/text_view.rb', line 47 def text @text ||= build_text end |
#text=(value) ⇒ void
This method returns an undefined value.
Replaces the text. Embedded ‘n` characters become hard line breaks. A `String` is parsed via StyledString.parse (so embedded ANSI is honored); a `StyledString` is used as-is; `nil` is coerced to an empty StyledString.
67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/tuile/component/text_view.rb', line 67 def text=(value) new_text = StyledString.parse(value) return if text == new_text @text = new_text @hard_lines = new_text.empty? ? [] : new_text.lines @content_size = compute_content_size rewrap update_top_line_if_auto_scroll invalidate end |