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 Theme#active_bg_color while preserving each span’s foreground color.
Defined Under Namespace
Classes: Cursor
Instance Attribute Summary collapse
-
#auto_scroll ⇒ Boolean
If true and a line is added or new content is set, auto-scrolls to the bottom — but only while the viewport is already pinned to the last line (see #following?).
-
#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
#content_size, #key_shortcut, #on_theme_changed, #parent, #rect
Instance Method Summary collapse
-
#add_line(line) ⇒ void
Adds a line.
-
#add_lines(lines) ⇒ void
Appends given lines.
- #focusable? ⇒ Boolean
-
#following? ⇒ Boolean
Whether #auto_scroll is currently tailing.
-
#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_content_size_changed, #on_child_removed, #on_focus, #on_tree, #root, #screen
Constructor Details
#initialize ⇒ List
Returns a new instance of List.
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# File 'lib/tuile/component/list.rb', line 20 def initialize super @lines = [] @padded_lines = [] @blank_padded = StyledString::EMPTY @auto_scroll = false @follow = true @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 — but only while the viewport is already pinned to the last line (see #following?). Scroll up to read older content and appends stop yanking you back down; scroll back to the bottom and tailing resumes.
58 59 60 |
# File 'lib/tuile/component/list.rb', line 58 def auto_scroll @auto_scroll end |
#cursor ⇒ Cursor
Returns the list’s cursor.
70 71 72 |
# File 'lib/tuile/component/list.rb', line 70 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.
51 52 53 |
# File 'lib/tuile/component/list.rb', line 51 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).
41 42 43 |
# File 'lib/tuile/component/list.rb', line 41 def on_item_chosen @on_item_chosen end |
#scrollbar_visibility ⇒ Symbol
Returns scrollbar visibility: ‘:gone` or `:visible`.
73 74 75 |
# File 'lib/tuile/component/list.rb', line 73 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.
78 79 80 |
# File 'lib/tuile/component/list.rb', line 78 def show_cursor_when_inactive @show_cursor_when_inactive end |
#top_line ⇒ Integer
Returns top line of the viewport. 0 or positive.
67 68 69 |
# File 'lib/tuile/component/list.rb', line 67 def top_line @top_line end |
Instance Method Details
#add_line(line) ⇒ void
This method returns an undefined value.
Adds a line.
177 178 179 180 181 |
# File 'lib/tuile/component/list.rb', line 177 def add_line(line) raise ArgumentError, "line is nil" if line.nil? 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.
189 190 191 192 193 194 195 196 197 198 |
# File 'lib/tuile/component/list.rb', line 189 def add_lines(lines) screen.check_locked new_lines = parse_input_lines(lines) @lines += new_lines @padded_lines += new_lines.map { |line| pad_to_row(line) } update_top_line_if_auto_scroll notify_cursor_changed invalidate grow_content_size(new_lines) end |
#focusable? ⇒ Boolean
200 |
# File 'lib/tuile/component/list.rb', line 200 def focusable? = true |
#following? ⇒ Boolean
Returns whether #auto_scroll is currently tailing. True while the viewport sits at the last line; flips to false the moment the user scrolls up, and back to true once they scroll to the bottom again. Only consulted when #auto_scroll is enabled.
64 |
# File 'lib/tuile/component/list.rb', line 64 def following? = @follow |
#handle_key(key) ⇒ Boolean
Returns true if the key was handled.
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 |
# File 'lib/tuile/component/list.rb', line 206 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.
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 |
# File 'lib/tuile/component/list.rb', line 257 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 “‘
166 167 168 169 170 171 172 |
# File 'lib/tuile/component/list.rb', line 166 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>`.
142 143 144 145 146 147 148 149 150 151 |
# File 'lib/tuile/component/list.rb', line 142 def lines=(lines) raise TypeError, "expected Array, got #{lines.inspect}" unless lines.is_a? Array @lines = parse_input_lines(lines) rebuild_padded_lines update_top_line_if_auto_scroll notify_cursor_changed invalidate self.content_size = compute_content_size 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.
283 284 285 286 287 288 289 290 291 292 293 |
# File 'lib/tuile/component/list.rb', line 283 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.
243 244 245 |
# File 'lib/tuile/component/list.rb', line 243 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.
251 252 253 |
# File 'lib/tuile/component/list.rb', line 251 def select_prev(query, include_current: false) search_and_go(query, include_current: include_current, reverse: true) end |
#tab_stop? ⇒ Boolean
202 |
# File 'lib/tuile/component/list.rb', line 202 def tab_stop? = true |