Class: Tuile::Component::Popup
- Inherits:
-
Tuile::Component
- Object
- Tuile::Component
- Tuile::Component::Popup
- Includes:
- HasContent
- Defined in:
- lib/tuile/component/popup.rb
Overview
An overlay that wraps any Tuile::Component as its content. Popup itself paints nothing — it’s a transparent host that handles its lifecycle (#open / #close / #open?, ESC/q to close) and auto-sizes to the wrapped content.
Modal by default: it centers on the screen, grabs focus, eats keys, and blocks clicks beneath it. Pass ‘modal: false` for a non-modal overlay that floats above the content (still painted on top, still auto-sized) without taking focus or capturing input — the caller positions it (via #rect=) and drives it from app code. That is the building block for an autocomplete/slash-command list anchored to a TextField or TextArea caret: typing keeps focus (and the cursor) in the input, an TextInput#on_change listener refills the list, and an TextInput#on_key interceptor forwards Up/Down/Enter to it.
The wrapped content fills the popup’s full #rect; if you want a frame and caption, wrap a Window (or any subclass — including LogWindow) and let it draw its own border:
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.
‘q` and ESC close the popup. Any nested TextField that owns the hardware cursor swallows printable keys first via the standard cursor-owner suppression in #handle_key, so typing `q` into a text field doesn’t dismiss the popup.
Instance Attribute Summary
Attributes included from HasContent
Attributes inherited from Tuile::Component
#content_size, #key_shortcut, #on_theme_changed, #parent, #rect
Class Method Summary collapse
-
.open(content: nil) ⇒ Popup
Constructs and opens a popup in one call.
Instance Method Summary collapse
-
#center ⇒ void
Recenters the popup on the screen, preserving its current width/height.
-
#close ⇒ void
Removes this popup from the Screen.
-
#content=(new_content) ⇒ Object
Sets the popup’s content and auto-sizes the popup to fit.
- #focusable? ⇒ Boolean
-
#handle_key(key) ⇒ Boolean
‘q` and ESC close the popup.
-
#initialize(content: nil, modal: true) ⇒ Popup
constructor
A new instance of Popup.
-
#keyboard_hint ⇒ String
Hint for the status bar: own “q Close” plus the wrapped content’s hint.
-
#max_height ⇒ Integer
Max height the popup will grow to fit its content.
-
#min_height ⇒ Integer
Min height the popup occupies even when its content is shorter.
-
#modal? ⇒ Boolean
Whether this popup is modal.
-
#on_child_content_size_changed(_child) ⇒ void
Re-sizes (and recenters, when open) whenever the wrapped content’s natural size changes — e.g.
-
#open ⇒ void
Mounts this popup on the Screen.
-
#open? ⇒ Boolean
True if this popup is currently mounted on the screen.
-
#rect=(new_rect) ⇒ void
Reassigns the popup’s rect, escalating to a full scene repaint when an open popup shrinks or moves so its new rect no longer covers the cells it previously painted.
Methods included from HasContent
#children, #handle_mouse, #on_focus
Methods inherited from Tuile::Component
#active=, #active?, #attached?, #children, #cursor_position, #depth, #find_shortcut_component, #focus, #handle_mouse, #on_child_removed, #on_focus, #on_tree, #popup_max_height, #popup_min_height, #repaint, #root, #screen, #tab_stop?
Constructor Details
#initialize(content: nil, modal: true) ⇒ Popup
Returns a new instance of Popup.
43 44 45 46 47 48 |
# File 'lib/tuile/component/popup.rb', line 43 def initialize(content: nil, modal: true) super() @modal = modal @content = nil self.content = content unless content.nil? end |
Class Method Details
Instance Method Details
#center ⇒ void
This method returns an undefined value.
Recenters the popup on the screen, preserving its current width/height. Called automatically by the screen’s layout pass and by #content= when the popup is open.
101 102 103 |
# File 'lib/tuile/component/popup.rb', line 101 def center self.rect = rect.centered(screen.size) end |
#close ⇒ void
This method returns an undefined value.
Removes this popup from the Screen. No-op if not currently open.
88 89 90 |
# File 'lib/tuile/component/popup.rb', line 88 def close screen.remove_popup(self) end |
#content=(new_content) ⇒ Object
Sets the popup’s content and auto-sizes the popup to fit.
122 123 124 125 |
# File 'lib/tuile/component/popup.rb', line 122 def content=(new_content) super update_rect unless new_content.nil? end |
#focusable? ⇒ Boolean
53 |
# File 'lib/tuile/component/popup.rb', line 53 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.
150 151 152 153 154 155 156 157 |
# File 'lib/tuile/component/popup.rb', line 150 def handle_key(key) if [Keys::ESC, "q"].include?(key) close true else false end end |
#keyboard_hint ⇒ String
Hint for the status bar: own “q Close” plus the wrapped content’s hint.
139 140 141 142 143 |
# File 'lib/tuile/component/popup.rb', line 139 def keyboard_hint prefix = "q #{screen.theme.hint("Close")}" child_hint = @content&.keyboard_hint.to_s child_hint.empty? ? prefix : "#{prefix} #{child_hint}" end |
#max_height ⇒ Integer
Returns max height the popup will grow to fit its content. Defers to the content’s Tuile::Component#popup_max_height advice when it gives one, else defaults to 12. Override in a subclass to allow taller popups regardless of content.
109 |
# File 'lib/tuile/component/popup.rb', line 109 def max_height = @content&.popup_max_height || 12 |
#min_height ⇒ Integer
Returns min height the popup occupies even when its content is shorter. Defers to the content’s Tuile::Component#popup_min_height advice when it gives one, else defaults to 0 (size purely to content) — so a LogWindow stays readable while only a few lines are in without callers wiring up a subclass. Override in a subclass to keep any popup from collapsing to a couple of rows. Capped at the same 4/5-of-screen ceiling #update_rect applies.
118 |
# File 'lib/tuile/component/popup.rb', line 118 def min_height = @content&.popup_min_height || 0 |
#modal? ⇒ Boolean
Returns whether this popup is modal. See #initialize.
51 |
# File 'lib/tuile/component/popup.rb', line 51 def modal? = @modal |
#on_child_content_size_changed(_child) ⇒ void
This method returns an undefined value.
Re-sizes (and recenters, when open) whenever the wrapped content’s natural size changes — e.g. a Label‘s `text=`, a List’s ‘add_line`, or a nested Window whose own content grew (the window recomputes its Tuile::Component#content_size and the change bubbles here).
133 134 135 |
# File 'lib/tuile/component/popup.rb', line 133 def on_child_content_size_changed(_child) update_rect end |
#open ⇒ void
This method returns an undefined value.
Mounts this popup on the Screen. Recomputes the popup’s size from the current content first, so reopening a popup whose content has grown or shrunk while closed picks up the new size.
74 75 76 77 |
# File 'lib/tuile/component/popup.rb', line 74 def open update_rect unless @content.nil? screen.add_popup(self) end |
#open? ⇒ Boolean
Returns true if this popup is currently mounted on the screen.
93 94 95 |
# File 'lib/tuile/component/popup.rb', line 93 def open? screen.has_popup?(self) end |
#rect=(new_rect) ⇒ void
This method returns an undefined value.
Reassigns the popup’s rect, escalating to a full scene repaint when an open popup shrinks or moves so its new rect no longer covers the cells it previously painted. A popup overdraws the scene without clipping and nothing clears underneath it, so Screen#repaint‘s popup-only fast path would repaint into the new rect and leave the vacated cells showing stale content. When the new rect fully covers the old one (the popup only grew), the fast path is correct and the full repaint is skipped.
64 65 66 67 68 |
# File 'lib/tuile/component/popup.rb', line 64 def rect=(new_rect) old_rect = rect super screen.needs_full_repaint if open? && !new_rect.contains_rect?(old_rect) end |