Class: Tuile::Component::TextArea
- Inherits:
-
Tuile::Component
- Object
- Tuile::Component
- Tuile::Component::TextArea
- Defined in:
- lib/tuile/component/text_area.rb
Overview
A multi-line, word-wrapping text input.
Sized by the caller — #rect is fixed; the area does not grow with content. Text is wrapped to Rect#width columns and any text that doesn’t fit vertically is reached by scrolling: #top_display_row follows the caret so the line being edited stays visible. There is no horizontal scrolling.
The caret is a logical index in ‘0..text.length`. When the caret falls inside a whitespace run that was absorbed by a soft wrap, it displays at the end of the previous row (which is visually identical to the start of the next row in nearly all cases).
Currently only #on_change is wired; Enter inserts a newline as in any plain ‘<textarea>` or text editor. A future `on_enter`/`on_submit` callback may opt out of that by consuming Enter instead.
Constant Summary collapse
- ACTIVE_BG_SGR =
Same SGR palette as Tuile::Component::TextField for visual consistency.
TextField::ACTIVE_BG_SGR
- INACTIVE_BG_SGR =
TextField::INACTIVE_BG_SGR
- SGR_RESET =
TextField::SGR_RESET
Instance Attribute Summary collapse
-
#caret ⇒ Integer
Caret index in ‘0..text.length`.
-
#on_change ⇒ Proc, ...
Optional callback fired whenever #text changes.
-
#text ⇒ String
Current text contents (may contain embedded ‘n`).
-
#top_display_row ⇒ Integer
readonly
Index of the topmost display row currently visible.
Attributes inherited from Tuile::Component
Instance Method Summary collapse
- #cursor_position ⇒ Point?
- #focusable? ⇒ Boolean
- #handle_key(key) ⇒ Boolean
- #handle_mouse(event) ⇒ void
-
#initialize ⇒ TextArea
constructor
A new instance of TextArea.
- #repaint ⇒ void
- #tab_stop? ⇒ Boolean
Methods inherited from Tuile::Component
#active=, #active?, #attached?, #children, #content_size, #depth, #find_shortcut_component, #focus, #keyboard_hint, #on_child_removed, #on_focus, #on_tree, #root, #screen
Constructor Details
#initialize ⇒ TextArea
Returns a new instance of TextArea.
22 23 24 25 26 27 28 29 |
# File 'lib/tuile/component/text_area.rb', line 22 def initialize super @text = +"" @caret = 0 @top_display_row = 0 @on_change = nil @display_rows = nil end |
Instance Attribute Details
#caret ⇒ Integer
Returns caret index in ‘0..text.length`.
35 36 37 |
# File 'lib/tuile/component/text_area.rb', line 35 def caret @caret end |
#on_change ⇒ Proc, ...
45 46 47 |
# File 'lib/tuile/component/text_area.rb', line 45 def on_change @on_change end |
#text ⇒ String
Returns current text contents (may contain embedded ‘n`).
32 33 34 |
# File 'lib/tuile/component/text_area.rb', line 32 def text @text end |
#top_display_row ⇒ Integer (readonly)
Returns index of the topmost display row currently visible.
38 39 40 |
# File 'lib/tuile/component/text_area.rb', line 38 def top_display_row @top_display_row end |
Instance Method Details
#cursor_position ⇒ Point?
79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/tuile/component/text_area.rb', line 79 def cursor_position return nil if rect.empty? row, col = caret_to_display(@caret) screen_row = row - @top_display_row return nil if screen_row.negative? || screen_row >= rect.height # Cap so the hardware cursor never lands at rect.left+rect.width # (one past the rect). Terminals with auto-wrap interpret that as # column 0 of the row below; capping pins the cursor on the last # visible cell instead. Point.new(rect.left + col.clamp(0, rect.width - 1), rect.top + screen_row) end |
#focusable? ⇒ Boolean
74 |
# File 'lib/tuile/component/text_area.rb', line 74 def focusable? = true |
#handle_key(key) ⇒ Boolean
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/tuile/component/text_area.rb', line 95 def handle_key(key) return false unless active? return true if super case key when Keys::LEFT_ARROW then self.caret = @caret - 1 when Keys::RIGHT_ARROW then self.caret = @caret + 1 when Keys::CTRL_LEFT_ARROW then self.caret = word_left when Keys::CTRL_RIGHT_ARROW then self.caret = word_right when Keys::UP_ARROW then move_caret_vertical(-1) when Keys::DOWN_ARROW then move_caret_vertical(1) when *Keys::HOMES then move_caret_to_row_start when *Keys::ENDS_ then move_caret_to_row_end when *Keys::BACKSPACES then delete_before_caret when Keys::DELETE then delete_at_caret when Keys::ENTER then insert_char("\n") else return insert_char(key) if printable?(key) return false end true end |
#handle_mouse(event) ⇒ void
This method returns an undefined value.
121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/tuile/component/text_area.rb', line 121 def handle_mouse(event) super return unless event. == :left && rect.contains?(event.point) target_row = (event.y - rect.top) + @top_display_row target_col = event.x - rect.left rows = display_rows if target_row >= rows.size self.caret = @text.length else r = rows[target_row] self.caret = r[:start] + target_col.clamp(0, r[:length]) end end |
#repaint ⇒ void
This method returns an undefined value.
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
# File 'lib/tuile/component/text_area.rb', line 145 def repaint return if rect.empty? bg = active? ? ACTIVE_BG_SGR : INACTIVE_BG_SGR rows = display_rows (0...rect.height).each do |screen_row| row_idx = screen_row + @top_display_row line = if row_idx >= rows.size " " * rect.width else r = rows[row_idx] chunk = @text[r[:start], r[:length]] || "" chunk + (" " * (rect.width - r[:length])) end screen.print TTY::Cursor.move_to(rect.left, rect.top + screen_row), bg, line, SGR_RESET end end |
#tab_stop? ⇒ Boolean
76 |
# File 'lib/tuile/component/text_area.rb', line 76 def tab_stop? = true |