Class: Tuile::Component::ListDropdown
- Defined in:
- lib/tuile/component/list_dropdown.rb,
sig/tuile.rbs
Overview
A borderless, tinted, non-focusable floating selection list — the dropdown a driver drops open, drives by forwarding movement keys, and commits a pick from: a non-modal Popup wrapping a List that never takes focus, so focus stays on the driver while the caller refills the rows, moves the highlight, and reads the pick.
drop = Component::ListDropdown.new
drop.on_item_chosen = ->(index, _line) { commit(index) } # caller commits
# …then, from the driver's key handler:
drop.lines = matches.map { |m| render(m) } # caller filters + renders
drop.anchor_to(rect, rows: matches.size) # below the driver, or flipped
drop.open
return true if drop.move(key) # Up/Down/PgUp/PgDn/^U/^D → list scroll
drop.choose if key == Keys::ENTER # commit the highlight
It owns only what every such dropdown shares — placement included, via #anchor_to. What stays with the driver: the width policy (#anchor_to measures nothing itself), filtering, row rendering, the commit action, and ESC/Enter handling. ESC and Enter carry driver-specific tails (ESC may revert a query; Enter may commit via #choose or via a separate submit path), so #move claims neither — the driver calls #choose and Popup#close from its own branches.
Theming
Borderless, told apart from the content beneath by a background tint —
Theme#input_bg_color by default, assigned as a live Theme::Ref so it
tracks light/dark flips with no hook. Reassign #bg_color= for a
different tint (a Theme.ref(:token) keeps the flip-tracking).
UI-thread-confined, like every component (see Screen).
Defined Under Namespace
Classes: Menu
Constant Summary collapse
- MOVE_KEYS =
Cursor-movement keys forwarded to the list by #move: the two vertical arrows, page up/down, and Ctrl+U/D half-page jumps. Deliberately excludes Home/End and
j/k— a jump to the first/last row is the driver's call, and both drivers decline it (ComboBox's field needs Home/End for the caret; Select would spend a branch on what a second arrow press already does) — and Enter/ESC, which carry driver-specific tails (see the class docs). [Keys::UP_ARROW, Keys::DOWN_ARROW, Keys::PAGE_UP, Keys::PAGE_DOWN, Keys::CTRL_U, Keys::CTRL_D].freeze
- MAX_VISIBLE_ROWS =
Most rows shown before the list scrolls; #anchor_to's
max_rowsdefault. 10
Instance Attribute Summary
Attributes inherited from Popup
Attributes included from HasContent
Instance Method Summary collapse
-
#anchor_to(anchor, rows:, width: anchor.width, max_rows: MAX_VISIBLE_ROWS) ⇒ Object
Sizes and places the dropdown against
anchor: directly beneath it, flipped above whenrowswon't fit below, clamped — with the list scrolling — when neither side has room. -
#choose ⇒ Boolean
Commits the highlighted row by firing Tuile::Component::List#on_item_chosen, exactly as pressing Enter on the focused list would — the driver calls this from its own Enter branch.
-
#cursor ⇒ List::Cursor
@return — the list's cursor (the current highlight).
-
#cursor=(cursor) ⇒ void
@param
cursor— the highlight; see Tuile::Component::List#cursor=. -
#initialize ⇒ ListDropdown
constructor
A new instance of ListDropdown.
-
#lines ⇒ ::Array[StyledString]
@return — the current rows.
-
#lines=(lines) ⇒ void
@param
lines— the rows to show; see Tuile::Component::List#lines=. -
#move(key) ⇒ Boolean
Forwards a cursor-movement key to the list.
-
#on_item_chosen=(proc) ⇒ void
@param
proc— commit callback; see Tuile::Component::List#on_item_chosen.
Methods inherited from Popup
#center, #close, #focusable?, #handle_key, #handle_mouse, #keyboard_hint, #layout, #modal?, #on_focus, #open, open, #open?, #rect=, #reposition
Methods included from HasContent
#handle_mouse, #on_focus, #rect=
Constructor Details
#initialize ⇒ ListDropdown
Returns a new instance of ListDropdown.
61 62 63 64 65 66 67 |
# File 'lib/tuile/component/list_dropdown.rb', line 61 def initialize @list = Menu.new @list.cursor = List::Cursor.new @list.show_cursor_when_inactive = true # highlight the selection though focus stays on the driver super(content: @list, modal: false) self.bg_color = Theme.ref(:input_bg_color) end |
Instance Method Details
#anchor_to(anchor, rows:, width: anchor.width, max_rows: MAX_VISIBLE_ROWS) ⇒ Object
Sizes and places the dropdown against anchor: directly beneath it,
flipped above when rows won't fit below, clamped — with the list
scrolling — when neither side has room. Horizontally the left edges line
up, sliding left only far enough to keep the panel on screen.
drop.anchor_to(field.rect, rows: matches.size) # field width
drop.anchor_to(rect, rows: items.size, width: measured) # own width
Vertical flips but horizontal slides because covering the driver would hide what is being chosen, while sharing its columns is the point.
@param anchor — the driver's rect; the dropdown never covers it.
@param rows — how many rows there are to show — the content count, not the height: more than fits turns the scrollbar on. 0 collapses the dropdown to an empty rect (drivers close instead).
@param width — the panel's width in columns, clamped to the screen. Defaults to the anchor's, which lines both edges up with a field; a driver that measured its labels passes its own. A label wider than the screen clips — Tuile::Component::List has no horizontal scrolling.
@param max_rows — rows shown before the list scrolls.
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/tuile/component/list_dropdown.rb', line 114 def anchor_to(anchor, rows:, width: anchor.width, max_rows: MAX_VISIBLE_ROWS) desired = [rows, max_rows].min below = screen.size.height - (anchor.top + 1) above = anchor.top if desired <= below top = anchor.top + 1 height = desired elsif above >= below height = [desired, above].min top = anchor.top - height else height = below top = anchor.top + 1 end width = [width, screen.size.width].min self.size = Size.new(width, height) self.rect = Rect.new([anchor.left, screen.size.width - width].min.clamp(0, nil), top, width, height) # After the geometry: the setter rebuilds the list's padded rows against # the width it can see, and the gutter takes a column off it. @list. = rows > height ? :visible : :gone end |
#choose ⇒ Boolean
Commits the highlighted row by firing Tuile::Component::List#on_item_chosen, exactly as pressing Enter on the focused list would — the driver calls this from its own Enter branch.
@return — true iff a row was chosen (false when the cursor is off-content).
154 |
# File 'lib/tuile/component/list_dropdown.rb', line 154 def choose = @list.handle_key(Keys::ENTER) |
#cursor ⇒ List::Cursor
@return — the list's cursor (the current highlight).
91 |
# File 'lib/tuile/component/list_dropdown.rb', line 91 def cursor = @list.cursor |
#cursor=(cursor) ⇒ void
This method returns an undefined value.
@param cursor — the highlight; see Tuile::Component::List#cursor=.
86 87 88 |
# File 'lib/tuile/component/list_dropdown.rb', line 86 def cursor=(cursor) @list.cursor = cursor end |
#lines ⇒ ::Array[StyledString]
@return — the current rows.
76 |
# File 'lib/tuile/component/list_dropdown.rb', line 76 def lines = @list.lines |
#lines=(lines) ⇒ void
This method returns an undefined value.
@param lines — the rows to show; see Tuile::Component::List#lines=.
71 72 73 |
# File 'lib/tuile/component/list_dropdown.rb', line 71 def lines=(lines) @list.lines = lines end |
#move(key) ⇒ Boolean
Forwards a cursor-movement key to the list. The driver calls this from its own key handler; a truthy return means "consumed — stop here", falsy means "not mine — proceed with normal editing/dispatch". Only MOVE_KEYS are claimed, and only while open.
@param key
@return — true iff the key was consumed.
142 143 144 145 146 147 |
# File 'lib/tuile/component/list_dropdown.rb', line 142 def move(key) return false unless open? && MOVE_KEYS.include?(key) @list.handle_key(key) true end |
#on_item_chosen=(proc) ⇒ void
This method returns an undefined value.
@param proc — commit callback; see Tuile::Component::List#on_item_chosen.
80 81 82 |
# File 'lib/tuile/component/list_dropdown.rb', line 80 def on_item_chosen=(proc) @list.on_item_chosen = proc end |