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, #popup_max_height, #popup_min_height, #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 |
# File 'lib/tuile/component/list.rb', line 206 def handle_key(key) if 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.
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 |
# File 'lib/tuile/component/list.rb', line 253 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.
279 280 281 282 283 284 285 286 287 288 289 |
# File 'lib/tuile/component/list.rb', line 279 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.buffer.set_line(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.
239 240 241 |
# File 'lib/tuile/component/list.rb', line 239 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.
247 248 249 |
# File 'lib/tuile/component/list.rb', line 247 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 |