Class: Tuile::Component::List
- Inherits:
-
Tuile::Component
- Object
- Tuile::Component
- Tuile::Component::List
- Defined in:
- lib/tuile/component/list.rb
Overview
A scrollable list of items with cursor support.
Items are modeled as StyledStrings and painted directly into the component’s #rect. Lines wider than the viewport are ellipsized via StyledString#ellipsize (span styles are preserved across the cut —unlike the older ANSI-as-bytes truncation, color does not get dropped on the surviving characters). Vertical scrolling is supported via #top_line; the list can also automatically scroll to the bottom if #auto_scroll is enabled.
Cursor is supported; call #cursor= to change cursor behavior. The cursor responds to arrows, ‘jk`, Home/End, Ctrl+U/D and scrolls the list automatically. The cursor highlight overlays a dark background while preserving each span’s foreground color.
Defined Under Namespace
Classes: Cursor
Constant Summary collapse
- CURSOR_BG =
256-color SGR index for the cursor-row background highlight. Matches what ‘Rainbow(…).bg(:darkslategray)` emits.
59
Instance Attribute Summary collapse
-
#auto_scroll ⇒ Boolean
If true and a line is added or new content is set, auto-scrolls to the bottom.
-
#cursor ⇒ Cursor
The list’s cursor.
-
#on_cursor_changed ⇒ Proc?
Callback fired when the ‘(index, line)` tuple under the cursor changes.
-
#on_item_chosen ⇒ Proc?
Callback fired when an item is chosen — by pressing Enter on the cursor’s item, or by left-clicking an item.
-
#scrollbar_visibility ⇒ Symbol
Scrollbar visibility: ‘:gone` or `:visible`.
-
#show_cursor_when_inactive ⇒ Boolean
When true, the cursor highlight is painted even while the list is inactive (e.g. when focus is on a sibling search field).
-
#top_line ⇒ Integer
Top line of the viewport.
Attributes inherited from Tuile::Component
Instance Method Summary collapse
-
#add_line(line) ⇒ void
Adds a line.
-
#add_lines(lines) ⇒ void
Appends given lines.
- #content_size ⇒ Size
- #focusable? ⇒ Boolean
-
#handle_key(key) ⇒ Boolean
True if the key was handled.
- #handle_mouse(event) ⇒ void
-
#initialize ⇒ List
constructor
A new instance of List.
-
#lines {|buffer| ... } ⇒ Array<StyledString>
Without a block, returns the current lines.
-
#lines=(lines) ⇒ void
Sets new lines.
-
#repaint ⇒ void
Paints the list items into #rect.
-
#select_next(query, include_current: false) ⇒ Boolean
Moves the cursor to the next line whose text contains ‘query` (case-insensitive substring match).
-
#select_prev(query, include_current: false) ⇒ Boolean
Mirror of #select_next that walks the list backwards.
- #tab_stop? ⇒ Boolean
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 ⇒ List
Returns a new instance of List.
25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# File 'lib/tuile/component/list.rb', line 25 def initialize super @lines = [] @padded_lines = [] @blank_padded = StyledString::EMPTY @auto_scroll = false @top_line = 0 @cursor = Cursor::None.new @scrollbar_visibility = :gone @show_cursor_when_inactive = false @on_item_chosen = nil @on_cursor_changed = nil @last_cursor_state = cursor_state end |
Instance Attribute Details
#auto_scroll ⇒ Boolean
Returns if true and a line is added or new content is set, auto-scrolls to the bottom.
59 60 61 |
# File 'lib/tuile/component/list.rb', line 59 def auto_scroll @auto_scroll end |
#cursor ⇒ Cursor
Returns the list’s cursor.
65 66 67 |
# File 'lib/tuile/component/list.rb', line 65 def cursor @cursor end |
#on_cursor_changed ⇒ Proc?
Returns callback fired when the ‘(index, line)` tuple under the cursor changes. Called as `proc.call(index, line)` where `line` is the StyledString at the cursor, or `nil` when the cursor is off-content (Tuile::Component::List::Cursor::None, empty list, or `index` past the last line). Fires on cursor moves (key, mouse, search), on #cursor=, and on #lines=/#add_lines when the line at the cursor’s index changes (or its in-range/out-of-range status flips). Useful for keeping a details pane in sync with the highlighted row.
55 56 57 |
# File 'lib/tuile/component/list.rb', line 55 def on_cursor_changed @on_cursor_changed end |
#on_item_chosen ⇒ Proc?
Returns callback fired when an item is chosen — by pressing Enter on the cursor’s item, or by left-clicking an item. Called as ‘proc.call(index, line)` with the chosen 0-based index and its StyledString line. Never fires when the cursor’s position is outside the content (e.g. Tuile::Component::List::Cursor::None, or empty content).
45 46 47 |
# File 'lib/tuile/component/list.rb', line 45 def on_item_chosen @on_item_chosen end |
#scrollbar_visibility ⇒ Symbol
Returns scrollbar visibility: ‘:gone` or `:visible`.
68 69 70 |
# File 'lib/tuile/component/list.rb', line 68 def @scrollbar_visibility end |
#show_cursor_when_inactive ⇒ Boolean
Returns when true, the cursor highlight is painted even while the list is inactive (e.g. when focus is on a sibling search field). Defaults to false.
73 74 75 |
# File 'lib/tuile/component/list.rb', line 73 def show_cursor_when_inactive @show_cursor_when_inactive end |
#top_line ⇒ Integer
Returns top line of the viewport. 0 or positive.
62 63 64 |
# File 'lib/tuile/component/list.rb', line 62 def top_line @top_line end |
Instance Method Details
#add_line(line) ⇒ void
This method returns an undefined value.
Adds a line.
169 170 171 |
# File 'lib/tuile/component/list.rb', line 169 def add_line(line) add_lines [line] end |
#add_lines(lines) ⇒ void
This method returns an undefined value.
Appends given lines. Each entry is parsed the same way as in #lines=: coerced to a StyledString, split on ‘n`, with trailing empty pieces dropped and trailing ASCII whitespace stripped.
179 180 181 182 183 184 185 186 187 188 |
# File 'lib/tuile/component/list.rb', line 179 def add_lines(lines) screen.check_locked new_lines = parse_input_lines(lines) @lines += new_lines @content_size = nil @padded_lines += new_lines.map { |line| pad_to_row(line) } update_top_line_if_auto_scroll notify_cursor_changed invalidate end |
#content_size ⇒ Size
191 192 193 194 195 196 197 |
# File 'lib/tuile/component/list.rb', line 191 def content_size @content_size ||= begin content_w = @lines.map(&:display_width).max || 0 width = @lines.empty? ? 0 : content_w + 2 Size.new(width, @lines.size) end end |
#focusable? ⇒ Boolean
199 |
# File 'lib/tuile/component/list.rb', line 199 def focusable? = true |
#handle_key(key) ⇒ Boolean
Returns true if the key was handled.
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
# File 'lib/tuile/component/list.rb', line 205 def handle_key(key) if !active? false elsif super true elsif key == Keys::PAGE_UP move_top_line_by(-) true elsif key == Keys::PAGE_DOWN move_top_line_by() true elsif key == Keys::ENTER && cursor_on_item? fire_item_chosen true elsif @cursor.handle_key(key, @lines.size, ) notify_cursor_changed invalidate true else false end end |
#handle_mouse(event) ⇒ void
This method returns an undefined value.
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 |
# File 'lib/tuile/component/list.rb', line 256 def handle_mouse(event) super if event. == :scroll_down move_top_line_by(4) elsif event. == :scroll_up move_top_line_by(-4) else return unless rect.contains?(event.point) line = event.y - rect.top + top_line if @cursor.handle_mouse(line, event, @lines.size) notify_cursor_changed invalidate end fire_item_chosen if event. == :left && line >= 0 && line < @lines.size && cursor_on_item? end end |
#lines {|buffer| ... } ⇒ Array<StyledString>
Without a block, returns the current lines. With a block, fully re-populates the list: “‘ruby list.lines do |buffer|
buffer << "Hello!"
end “‘
158 159 160 161 162 163 164 |
# File 'lib/tuile/component/list.rb', line 158 def lines return @lines unless block_given? buffer = [] yield buffer self.lines = buffer end |
#lines=(lines) ⇒ void
This method returns an undefined value.
Sets new lines. Each entry is coerced into a StyledString (a ‘String` is parsed via StyledString.parse, so embedded ANSI is honored; a StyledString is used as-is; anything else is stringified via `#to_s` first), then split on `n` into separate lines via StyledString#lines, with trailing empty pieces dropped and trailing ASCII whitespace stripped — symmetric with #add_lines, so the stored `@lines` is always `Array<StyledString>`.
134 135 136 137 138 139 140 141 142 143 |
# File 'lib/tuile/component/list.rb', line 134 def lines=(lines) raise TypeError, "expected Array, got #{lines.inspect}" unless lines.is_a? Array @lines = parse_input_lines(lines) @content_size = nil rebuild_padded_lines update_top_line_if_auto_scroll notify_cursor_changed invalidate end |
#repaint ⇒ void
This method returns an undefined value.
Paints the list items into Tuile::Component#rect.
Skips the Tuile::Component#repaint default’s auto-clear: every row of Tuile::Component#rect is painted below (with blank padding past the last item), so the parent contract — “fully draw over your rect” — is met without an upfront wipe.
282 283 284 285 286 287 288 289 290 291 292 |
# File 'lib/tuile/component/list.rb', line 282 def repaint return if rect.empty? = if VerticalScrollBar.new(rect.height, line_count: @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, row + rect.top), line end end |
#select_next(query, include_current: false) ⇒ Boolean
Moves the cursor to the next line whose text contains ‘query` (case-insensitive substring match). Search wraps around the end of the list. Only lines reachable by the current #cursor are considered. Matching uses the line’s plain text — span styles do not affect the match.
242 243 244 |
# File 'lib/tuile/component/list.rb', line 242 def select_next(query, include_current: false) search_and_go(query, include_current: include_current, reverse: false) end |
#select_prev(query, include_current: false) ⇒ Boolean
Mirror of #select_next that walks the list backwards.
250 251 252 |
# File 'lib/tuile/component/list.rb', line 250 def select_prev(query, include_current: false) search_and_go(query, include_current: include_current, reverse: true) end |
#tab_stop? ⇒ Boolean
201 |
# File 'lib/tuile/component/list.rb', line 201 def tab_stop? = true |