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.
-
#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
-
#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 |
# File 'lib/tuile/component/list.rb', line 20 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.
54 55 56 |
# File 'lib/tuile/component/list.rb', line 54 def auto_scroll @auto_scroll end |
#cursor ⇒ Cursor
Returns the list’s cursor.
60 61 62 |
# File 'lib/tuile/component/list.rb', line 60 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.
50 51 52 |
# File 'lib/tuile/component/list.rb', line 50 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).
40 41 42 |
# File 'lib/tuile/component/list.rb', line 40 def on_item_chosen @on_item_chosen end |
#scrollbar_visibility ⇒ Symbol
Returns scrollbar visibility: ‘:gone` or `:visible`.
63 64 65 |
# File 'lib/tuile/component/list.rb', line 63 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.
68 69 70 |
# File 'lib/tuile/component/list.rb', line 68 def show_cursor_when_inactive @show_cursor_when_inactive end |
#top_line ⇒ Integer
Returns top line of the viewport. 0 or positive.
57 58 59 |
# File 'lib/tuile/component/list.rb', line 57 def top_line @top_line end |
Instance Method Details
#add_line(line) ⇒ void
This method returns an undefined value.
Adds a line.
164 165 166 167 |
# File 'lib/tuile/component/list.rb', line 164 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.
175 176 177 178 179 180 181 182 183 184 |
# File 'lib/tuile/component/list.rb', line 175 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
186 |
# File 'lib/tuile/component/list.rb', line 186 def focusable? = true |
#handle_key(key) ⇒ Boolean
Returns true if the key was handled.
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
# File 'lib/tuile/component/list.rb', line 192 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.
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 |
# File 'lib/tuile/component/list.rb', line 243 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 “‘
153 154 155 156 157 158 159 |
# File 'lib/tuile/component/list.rb', line 153 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>`.
129 130 131 132 133 134 135 136 137 138 |
# File 'lib/tuile/component/list.rb', line 129 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.
269 270 271 272 273 274 275 276 277 278 279 |
# File 'lib/tuile/component/list.rb', line 269 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.
229 230 231 |
# File 'lib/tuile/component/list.rb', line 229 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.
237 238 239 |
# File 'lib/tuile/component/list.rb', line 237 def select_prev(query, include_current: false) search_and_go(query, include_current: include_current, reverse: true) end |
#tab_stop? ⇒ Boolean
188 |
# File 'lib/tuile/component/list.rb', line 188 def tab_stop? = true |