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 String items with cursor support.
Items are lines painted directly into the component’s #rect. Lines are automatically clipped horizontally. 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.
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
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<String>
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.
16 17 18 19 20 21 22 23 24 25 26 27 |
# File 'lib/tuile/component/list.rb', line 16 def initialize super @lines = [] @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.
48 49 50 |
# File 'lib/tuile/component/list.rb', line 48 def auto_scroll @auto_scroll end |
#cursor ⇒ Cursor
Returns the list’s cursor.
54 55 56 |
# File 'lib/tuile/component/list.rb', line 54 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 `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.
44 45 46 |
# File 'lib/tuile/component/list.rb', line 44 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 line. Never fires when the cursor’s position is outside the content (e.g. Tuile::Component::List::Cursor::None, or empty content).
34 35 36 |
# File 'lib/tuile/component/list.rb', line 34 def on_item_chosen @on_item_chosen end |
#scrollbar_visibility ⇒ Symbol
Returns scrollbar visibility: ‘:gone` or `:visible`.
57 58 59 |
# File 'lib/tuile/component/list.rb', line 57 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.
62 63 64 |
# File 'lib/tuile/component/list.rb', line 62 def show_cursor_when_inactive @show_cursor_when_inactive end |
#top_line ⇒ Integer
Returns top line of the viewport. 0 or positive.
51 52 53 |
# File 'lib/tuile/component/list.rb', line 51 def top_line @top_line end |
Instance Method Details
#add_line(line) ⇒ void
This method returns an undefined value.
Adds a line.
149 150 151 |
# File 'lib/tuile/component/list.rb', line 149 def add_line(line) add_lines [line] end |
#add_lines(lines) ⇒ void
This method returns an undefined value.
Appends given lines. Each entry is coerced via ‘#to_s`, split on `n` into separate lines, and trailing whitespace stripped — symmetric with #lines=.
158 159 160 161 162 163 164 165 |
# File 'lib/tuile/component/list.rb', line 158 def add_lines(lines) screen.check_locked @lines += lines.flat_map { it.to_s.split("\n") }.map(&:rstrip) @content_size = nil update_top_line_if_auto_scroll notify_cursor_changed invalidate end |
#content_size ⇒ Size
168 169 170 171 172 173 174 |
# File 'lib/tuile/component/list.rb', line 168 def content_size @content_size ||= begin content_width = @lines.map { |line| Unicode::DisplayWidth.of(Rainbow.uncolor(line)) }.max || 0 width = @lines.empty? ? 0 : content_width + 2 Size.new(width, @lines.size) end end |
#focusable? ⇒ Boolean
176 |
# File 'lib/tuile/component/list.rb', line 176 def focusable? = true |
#handle_key(key) ⇒ Boolean
Returns true if the key was handled.
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
# File 'lib/tuile/component/list.rb', line 182 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.
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 |
# File 'lib/tuile/component/list.rb', line 231 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<String>
Without a block, returns the current lines. With a block, fully re-populates the list: “‘ruby list.lines do |buffer|
buffer << "Hello!"
end “‘
138 139 140 141 142 143 144 |
# File 'lib/tuile/component/list.rb', line 138 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 via ‘#to_s`, split on `n` into separate lines, and trailing whitespace stripped — symmetric with #add_lines, so the stored `@lines` is always `Array<String>`.
117 118 119 120 121 122 123 124 125 |
# File 'lib/tuile/component/list.rb', line 117 def lines=(lines) raise TypeError, "expected Array, got #{lines.inspect}" unless lines.is_a? Array @lines = lines.flat_map { it.to_s.split("\n") }.map(&:rstrip) @content_size = nil 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 padded content past the last item), so the parent contract — “fully draw over your rect” — is met without an upfront wipe.
257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/tuile/component/list.rb', line 257 def repaint return if rect.empty? width = rect.width = if VerticalScrollBar.new(rect.height, line_count: @lines.size, top_line: @top_line) end (0..(rect.height - 1)).each do |line_no| line_index = line_no + @top_line line = paintable_line(line_index, line_no, width, ) screen.print TTY::Cursor.move_to(rect.left, line_no + 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.
217 218 219 |
# File 'lib/tuile/component/list.rb', line 217 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.
225 226 227 |
# File 'lib/tuile/component/list.rb', line 225 def select_prev(query, include_current: false) search_and_go(query, include_current: include_current, reverse: true) end |
#tab_stop? ⇒ Boolean
178 |
# File 'lib/tuile/component/list.rb', line 178 def tab_stop? = true |