Class: Tuile::Component::Popup
- Defined in:
- lib/tuile/component/popup.rb,
sig/tuile.rbs
Overview
A modal dialog: an Overlay that centers itself on the screen,
grabs focus, scopes keys to its own subtree, blocks clicks beneath it, and
closes on ESC or q.
window = Component::Window.new("Help")
window.content = Component::List.new.tap { _1.lines = lines }
Component::Popup.new(content: window).open
Bare content also works (a Label, a List…), in which case the popup is borderless. For a floating layer that does not take focus or capture input — an autocomplete list anchored to a field, a toast — use Overlay directly.
The popup does not size itself to its content. Its box is set by #declared_size — a Fraction (resolved against the screen every layout pass, so it tracks resize) or an absolute Size (clamped to the screen). The default is Fraction::HALF: half the screen, centered. The wrapped content then fills that box and handles its own overflow by wrapping and scrolling, so use content that can — a TextView or TextArea — for anything longer than fits. A Label only truncates.
Implementation details
q and ESC close the popup — handled here, at the top of the popup's own
subtree, so the key only arrives after every component on the focus chain
declined it (see ScreenPane#handle_key). That's why typing q into a
nested TextField doesn't dismiss the popup: the field consumes
it first.
A left click outside the popup closes it too — see Overlay#close_on_outside_click? for the exact contract and Overlay#on_close= for the notice a driver hears when it happens.
UI-thread-confined, like every component (see Screen).
Instance Attribute Summary collapse
-
#declared_size ⇒ Size, Fraction
The box this popup asks for, as set — a Fraction comes back unresolved.
Attributes inherited from Overlay
#close_on_outside_click, #on_close, #owner
Attributes included from HasContent
Instance Method Summary collapse
-
#center ⇒ void
Recenters the popup on the screen, preserving its current width/height.
-
#focusable? ⇒ Boolean
@return — true — ScreenPane#add_popup focuses a popup on open, and focus repair falls back to it when its subtree has no tab stop.
-
#handle_key(key) ⇒ Boolean
qand ESC close the popup. -
#initialize(content: nil, declared_size: Fraction::HALF, close_on_outside_click: true) ⇒ Popup
constructor
@param
content— initial content; can be set later via HasContent#content=. -
#modal? ⇒ Boolean
@return — true — a Popup scopes key dispatch, grabs focus on open, and blocks clicks on the content beneath it.
-
#reposition ⇒ void
Re-resolves #declared_size against the current screen and recenters the popup itself (this is not laying out content — the popup's own rect).
Methods inherited from Overlay
#close, #close_on_outside_click?, #layout, #on_detached, #on_focus, #open, #open?, #rect=, #tab_stop?
Methods included from HasContent
Constructor Details
#initialize(content: nil, declared_size: Fraction::HALF, close_on_outside_click: true) ⇒ Popup
@param content — initial content; can be set later via HasContent#content=. The content fills the popup's Tuile::Component#rect; it does not determine the popup's size.
@param declared_size — the popup's box, applied top-down. A Fraction is resolved against the screen each layout pass; a Size is clamped to the screen. Defaults to Fraction::HALF.
@param close_on_outside_click — true (default) to dismiss on a left click that misses this popup. See Overlay#close_on_outside_click?.
50 51 52 53 54 |
# File 'lib/tuile/component/popup.rb', line 50 def initialize(content: nil, declared_size: Fraction::HALF, close_on_outside_click: true) super(content: content, close_on_outside_click: close_on_outside_click) @declared_size = declared_size reposition end |
Instance Attribute Details
#declared_size ⇒ Size, Fraction
The box this popup asks for, as set — a Fraction comes back
unresolved. Tuile::Component#rect is the resolved answer. Named apart from size
because a component's size is its rect.size, which this is not: it
is an input re-read on every layout pass, not a report of the current
geometry.
@return — see #declared_size=.
62 63 64 |
# File 'lib/tuile/component/popup.rb', line 62 def declared_size @declared_size end |
Instance Method Details
#center ⇒ void
This method returns an undefined value.
Recenters the popup on the screen, preserving its current width/height.
101 102 103 |
# File 'lib/tuile/component/popup.rb', line 101 def center self.rect = rect.centered(screen.size) end |
#focusable? ⇒ Boolean
@return — true — ScreenPane#add_popup focuses a popup on open, and focus repair falls back to it when its subtree has no tab stop.
70 |
# File 'lib/tuile/component/popup.rb', line 70 def focusable? = true |
#handle_key(key) ⇒ Boolean
q and ESC close the popup. The popup sits on the focus chain of
whatever it wraps, so the key reaches here by bubbling up from the
focused content after that content declined to handle it.
@param key
@return — true if the key was handled.
110 111 112 113 114 115 116 117 |
# File 'lib/tuile/component/popup.rb', line 110 def handle_key(key) if [Keys::ESC, "q"].include?(key) close true else false end end |
#modal? ⇒ Boolean
@return — true — a Popup scopes key dispatch, grabs focus on open, and blocks clicks on the content beneath it.
66 |
# File 'lib/tuile/component/popup.rb', line 66 def modal? = true |
#reposition ⇒ void
This method returns an undefined value.
Re-resolves #declared_size against the current screen and recenters the popup itself (this is not laying out content — the popup's own rect). Called on Overlay#open, on #declared_size=, and by the screen's layout pass, so a Fraction tracks SIGWINCH.
The final rect is computed and assigned in one step rather than sizing at the origin and then centering: the intermediate origin rect rarely covers the previous one, which would make Overlay#rect='s shrink/move detection fire a full repaint on every resize.
94 95 96 97 |
# File 'lib/tuile/component/popup.rb', line 94 def reposition size = @declared_size.is_a?(Fraction) ? @declared_size.resolve(screen.size) : @declared_size.clamp(screen.size) self.rect = Rect.new(rect.left, rect.top, size.width, size.height).centered(screen.size) end |