Class: Tuile::Component::Popup
- Inherits:
-
Component
- Object
- Component
- Tuile::Component::Popup
- Includes:
- HasContent
- Defined in:
- lib/tuile/component/popup.rb,
sig/tuile.rbs
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 holds a top-down #size the Screen applies.
The popup does not size itself to its content. Its box is declared by #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.
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 without taking focus or capturing input —
the caller positions it (via #rect=), sizes it, and drives it from app
code. That's the building block for an autocomplete/slash-command list
anchored to a text field's caret: typing keeps focus in the input while
the caller refills and drives the overlay.
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 — 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.
Direct Known Subclasses
Instance Attribute Summary collapse
-
#size ⇒ Size, Fraction
@return — the popup's declared size.
Attributes included from HasContent
Class Method Summary collapse
-
.open(content: nil, modal: true, size: Fraction::HALF) ⇒ 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.
- #focusable? ⇒ Boolean
-
#handle_key(key) ⇒ Boolean
qand ESC close the popup. -
#handle_mouse ⇒ void
@param
event. -
#initialize(content: nil, modal: true, size: Fraction::HALF) ⇒ Popup
constructor
@param
content— initial content; can be set later via HasContent#content=. -
#keyboard_hint ⇒ String
Hint for the status bar: own "q Close" plus the wrapped content's hint.
-
#layout(content) ⇒ void
Content fills the popup's full rect — Popup has no border to subtract.
-
#modal? ⇒ Boolean
@return — whether this popup is modal.
- #on_focus ⇒ void
- #open ⇒ void
-
#open? ⇒ Boolean
@return — 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.
-
#reposition ⇒ void
Re-resolves #size against the current screen and repositions the popup itself (this is not laying out content — the popup's own rect): a modal popup recenters; a non-modal overlay keeps its caller-assigned top-left (only its size follows the screen).
Constructor Details
#initialize(content: nil, modal: true, size: Fraction::HALF) ⇒ 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 modal — true (default) for a centered, focus-grabbing, input-capturing modal; false for a non-modal overlay the caller positions and drives (see the class docs).
@param size — the popup's size, applied top-down. A Fraction is resolved against the screen each layout pass; a Size is clamped to the screen. Defaults to Fraction::HALF.
55 56 57 58 59 60 61 62 |
# File 'lib/tuile/component/popup.rb', line 55 def initialize(content: nil, modal: true, size: Fraction::HALF) super() @modal = modal @size = size @content = nil self.content = content unless content.nil? reposition end |
Instance Attribute Details
Class Method Details
.open(content: nil, modal: true, size: Fraction::HALF) ⇒ Popup
Constructs and opens a popup in one call.
@param content
@param modal — see #initialize.
@param size — see #initialize.
@return — the opened popup.
112 113 114 |
# File 'lib/tuile/component/popup.rb', line 112 def self.open(content: nil, modal: true, size: Fraction::HALF) Popup.new(content: content, modal: modal, size: size).tap(&:open) end |
Instance Method Details
#center ⇒ void
This method returns an undefined value.
Recenters the popup on the screen, preserving its current width/height.
148 149 150 |
# File 'lib/tuile/component/popup.rb', line 148 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.
118 119 120 |
# File 'lib/tuile/component/popup.rb', line 118 def close screen.remove_popup(self) end |
#focusable? ⇒ Boolean
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.
165 166 167 168 169 170 171 172 |
# File 'lib/tuile/component/popup.rb', line 165 def handle_key(key) if [Keys::ESC, "q"].include?(key) close true else false end end |
#handle_mouse ⇒ void
This method returns an undefined value.
@param event
2158 |
# File 'sig/tuile.rbs', line 2158
def handle_mouse: (MouseEvent event) -> void
|
#keyboard_hint ⇒ String
Hint for the status bar: own "q Close" plus the wrapped content's hint.
154 155 156 157 158 |
# File 'lib/tuile/component/popup.rb', line 154 def keyboard_hint prefix = "q #{screen.theme.hint("Close")}" child_hint = @content&.keyboard_hint.to_s child_hint.empty? ? prefix : "#{prefix} #{child_hint}" end |
#layout(content) ⇒ void
This method returns an undefined value.
Content fills the popup's full rect — Popup has no border to subtract.
@param content
179 180 181 |
# File 'lib/tuile/component/popup.rb', line 179 def layout(content) content.rect = rect end |
#modal? ⇒ Boolean
@return — whether this popup is modal. See #initialize.
68 |
# File 'lib/tuile/component/popup.rb', line 68 def modal? = @modal |
#on_focus ⇒ void
This method returns an undefined value.
2160 |
# File 'sig/tuile.rbs', line 2160
def on_focus: () -> void
|
#open ⇒ void
102 103 104 105 |
# File 'lib/tuile/component/popup.rb', line 102 def open reposition screen.add_popup(self) end |
#open? ⇒ Boolean
@return — true if this popup is currently mounted on the screen.
123 124 125 |
# File 'lib/tuile/component/popup.rb', line 123 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.
@param new_rect
93 94 95 96 97 |
# File 'lib/tuile/component/popup.rb', line 93 def rect=(new_rect) old_rect = rect super screen.needs_full_repaint if open? && !new_rect.contains_rect?(old_rect) end |
#reposition ⇒ void
This method returns an undefined value.
Re-resolves #size against the current screen and repositions the popup itself (this is not laying out content — the popup's own rect): a modal popup recenters; a non-modal overlay keeps its caller-assigned top-left (only its size follows the screen). Called on #open, on #size=, and by the screen's layout pass (so a Fraction size 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 #rect='s shrink/move detection fire a full repaint on every resize.
139 140 141 142 143 144 |
# File 'lib/tuile/component/popup.rb', line 139 def reposition size = @size.is_a?(Fraction) ? @size.resolve(screen.size) : @size.clamp(screen.size) r = Rect.new(rect.left, rect.top, size.width, size.height) r = r.centered(screen.size) if modal? self.rect = r end |