Class: Tuile::Component::Popup

Inherits:
Tuile::Component show all
Includes:
HasContent
Defined in:
lib/tuile/component/popup.rb

Overview

A modal overlay that wraps any Tuile::Component as its content. Popup itself paints nothing — it’s a transparent host that handles modality (#open / #close / #open?, ESC/q to close), centers itself on the screen, and auto-sizes to the wrapped content.

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

#content

Attributes inherited from Tuile::Component

#content_size, #key_shortcut, #on_theme_changed, #parent, #rect

Class Method Summary collapse

Instance Method Summary collapse

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, #repaint, #root, #screen, #tab_stop?

Constructor Details

#initialize(content: nil) ⇒ Popup

Returns a new instance of Popup.

Parameters:

  • content (Component, nil) (defaults to: nil)

    initial content; can be set later via #content=. When provided here, the popup auto-sizes to fit.



30
31
32
33
34
# File 'lib/tuile/component/popup.rb', line 30

def initialize(content: nil)
  super()
  @content = nil
  self.content = content unless content.nil?
end

Class Method Details

.open(content: nil) ⇒ Popup

Constructs and opens a popup in one call.

Parameters:

  • content (Component, nil) (defaults to: nil)

Returns:

  • (Popup)

    the opened popup.



65
66
67
# File 'lib/tuile/component/popup.rb', line 65

def self.open(content: nil)
  Popup.new(content: content).tap(&:open)
end

Instance Method Details

#centervoid

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.



84
85
86
# File 'lib/tuile/component/popup.rb', line 84

def center
  self.rect = rect.centered(screen.size)
end

#closevoid

This method returns an undefined value.

Removes this popup from the Screen. No-op if not currently open.



71
72
73
# File 'lib/tuile/component/popup.rb', line 71

def close
  screen.remove_popup(self)
end

#content=(new_content) ⇒ Object

Sets the popup’s content and auto-sizes the popup to fit.

Parameters:



94
95
96
97
# File 'lib/tuile/component/popup.rb', line 94

def content=(new_content)
  super
  update_rect unless new_content.nil?
end

#focusable?Boolean

Returns:

  • (Boolean)


36
# File 'lib/tuile/component/popup.rb', line 36

def focusable? = true

#handle_key(key) ⇒ Boolean

Returns true if the key was handled.

Parameters:

  • key (String)

Returns:

  • (Boolean)

    true if the key was handled.



119
120
121
122
123
124
125
126
127
128
# File 'lib/tuile/component/popup.rb', line 119

def handle_key(key)
  return true if super

  if [Keys::ESC, "q"].include?(key)
    close
    true
  else
    false
  end
end

#keyboard_hintString

Hint for the status bar: own “q Close” plus the wrapped content’s hint.

Returns:

  • (String)


111
112
113
114
115
# File 'lib/tuile/component/popup.rb', line 111

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_heightInteger

Returns max height the popup will grow to fit its content, defaults to 12. Override in a subclass to allow taller popups.

Returns:

  • (Integer)

    max height the popup will grow to fit its content, defaults to 12. Override in a subclass to allow taller popups.



90
# File 'lib/tuile/component/popup.rb', line 90

def max_height = 12

#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).

Parameters:



105
106
107
# File 'lib/tuile/component/popup.rb', line 105

def on_child_content_size_changed(_child)
  update_rect
end

#openvoid

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.



57
58
59
60
# File 'lib/tuile/component/popup.rb', line 57

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.

Returns:

  • (Boolean)

    true if this popup is currently mounted on the screen.



76
77
78
# File 'lib/tuile/component/popup.rb', line 76

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.

Parameters:



47
48
49
50
51
# File 'lib/tuile/component/popup.rb', line 47

def rect=(new_rect)
  old_rect = rect
  super
  screen.needs_full_repaint if open? && !new_rect.contains_rect?(old_rect)
end