Class: Tuile::Component::Window
- Inherits:
-
Component
- Object
- Component
- Tuile::Component::Window
- Includes:
- HasCaption, HasContent
- Defined in:
- lib/tuile/component/window.rb,
sig/tuile.rbs
Overview
A window with a frame, a #caption and a content Tuile::Component. Doesn't support overlapping with other windows: it paints its entire contents and doesn't clip if there are other overlapping windows.
The window's content is unset by default; assign one via HasContent#content=.
Window is considered invisible if #rect is empty. The window won't draw when invisible. (Repaint of detached windows is short-circuited by #invalidate; subclasses don't need to re-check.)
Direct Known Subclasses
Instance Attribute Summary collapse
-
#footer_text ⇒ StyledString, ...
@return — optional chrome embedded into the bottom border line, mirroring #caption on the top line.
Attributes included from HasContent
Instance Method Summary collapse
-
#bottom_border(inner_w, fg) ⇒ StyledString
Builds the bottom border row.
-
#caption ⇒ StyledString
Read through this method, never
@caption— the ivar stays nil until the first non-empty set (#caption= short-circuits when unchanged). -
#caption= ⇒ void
Sets the caption and invalidates the component.
-
#content_rect ⇒ Rect
The interior the content fills: inside the border on three sides, and on the fourth only while there is a right border — #scrollbar= drops it so the content's own bar takes that column.
- #focusable? ⇒ Boolean
-
#footer ⇒ Component?
@return — optional focusable component occupying the bottom border row, always spanning the full inner width.
-
#footer=(new_footer) ⇒ void
Mounts a component in the bottom border row, spanning the full inner width and positioned automatically;
nilremoves it. - #handle_focus ⇒ void
-
#initialize(caption = nil) ⇒ Window
constructor
@param
caption— the border title, coerced the same way HasCaption#caption= coerces it. -
#inspect_details ⇒ ::Array[String]
Adds
caption="…"to #inspect, omitted while empty. -
#layout(content) ⇒ void
@param
content. -
#layout_footer ⇒ void
Positions the footer slot over the bottom border row, spanning the full inner width (the only dimension a bottom-row widget needs — the window already knows it).
-
#rect=(new_rect) ⇒ void
@param
new_rect. -
#repaint ⇒ void
Fully repaints the window: the border ring here, the interior through the content and footer it re-invalidates.
-
#repaint_border ⇒ void
Paints the window border via #draw_text/#draw_char, so the border cells inherit #effective_bg_color — a #bg_color on the window tints border and content alike.
-
#scrollbar=(value) ⇒ void
@param
value. -
#top_border(inner_w, fg) ⇒ StyledString
Builds the top border row: corners, #caption embedded at its own width, dashes filling the remainder.
Constructor Details
#initialize(caption = nil) ⇒ Window
@param caption — the border title, coerced the same way HasCaption#caption= coerces it.
20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/tuile/component/window.rb', line 20 def initialize(caption = nil) super() @border_right = 1 self.caption = caption @content = nil # The bottom row holds either a widget or chrome text, never both; see # the precedence note on #footer=. @footer_slot = Slot.new add_child(@footer_slot) # appended: the footer paints over the border row @footer_text = StyledString::EMPTY end |
Instance Attribute Details
#footer_text ⇒ StyledString, ...
41 42 43 |
# File 'lib/tuile/component/window.rb', line 41 def @footer_text end |
Instance Method Details
#bottom_border(inner_w, fg) ⇒ StyledString
Builds the bottom border row. The corners take the border color; the interior is plain dashes when a #footer component occupies the row (it overpaints them) or when there's no chrome, otherwise it carries #footer_text embedded at its own width — keeping the text's own styling — with dashes filling the remainder up to the inner width.
@param inner_w — the border's interior width.
@param fg — the active-border color, or nil when inactive.
180 181 182 183 184 185 186 187 188 189 |
# File 'lib/tuile/component/window.rb', line 180 def bottom_border(inner_w, fg) interior = if || @footer_text.empty? StyledString.styled("─" * inner_w, fg: fg) else = @footer_text.slice(0, inner_w) + StyledString.styled("─" * (inner_w - .display_width), fg: fg) end StyledString.styled("└", fg: fg) + interior + StyledString.styled("┘", fg: fg) end |
#caption ⇒ StyledString
Read through this method, never @caption — the ivar stays nil until
the first non-empty set (#caption= short-circuits when unchanged).
@return — the caption; empty when never set.
5567 |
# File 'sig/tuile.rbs', line 5567
def caption: () -> StyledString
|
#caption= ⇒ void
This method returns an undefined value.
Sets the caption and invalidates the component. No-op when unchanged. A
String is parsed via StyledString.parse (embedded ANSI is honored);
a StyledString is used as-is; nil clears it.
@param new_caption
5574 |
# File 'sig/tuile.rbs', line 5574
def caption=: ((String | StyledString)? new_caption) -> void
|
#content_rect ⇒ Rect
The interior the content fills: inside the border on three sides, and on the fourth only while there is a right border — #scrollbar= drops it so the content's own bar takes that column.
@return — may be empty, for a window too small to have an inside.
126 127 128 |
# File 'lib/tuile/component/window.rb', line 126 def content_rect Rect.new(rect.left + 1, rect.top + 1, rect.width - 1 - @border_right, rect.height - 2) end |
#focusable? ⇒ Boolean
32 |
# File 'lib/tuile/component/window.rb', line 32 def focusable? = true |
#footer ⇒ Component?
@return — optional focusable component occupying the bottom border row, always spanning the full inner width.
36 |
# File 'lib/tuile/component/window.rb', line 36 def = @footer_slot.content |
#footer=(new_footer) ⇒ void
This method returns an undefined value.
Mounts a component in the bottom border row, spanning the full inner
width and positioned automatically; nil removes it.
Precedence: a footer component present hides #footer_text; absent, the text embeds into the bottom border. No window needs both at once.
@param new_footer
67 68 69 70 71 |
# File 'lib/tuile/component/window.rb', line 67 def () @footer_slot.content = invalidate # repaint border row that the footer covers/uncovers end |
#handle_focus ⇒ void
This method returns an undefined value.
5579 |
# File 'sig/tuile.rbs', line 5579
def handle_focus: () -> void
|
#inspect_details ⇒ ::Array[String]
Adds caption="…" to Tuile::Component#inspect, omitted while empty.
5577 |
# File 'sig/tuile.rbs', line 5577
def inspect_details: () -> ::Array[String]
|
#layout(content) ⇒ void
This method returns an undefined value.
@param content
119 |
# File 'lib/tuile/component/window.rb', line 119 def layout(content) = content.rect = content_rect |
#layout_footer ⇒ void
This method returns an undefined value.
Positions the footer slot over the bottom border row, spanning the full inner width (the only dimension a bottom-row widget needs — the window already knows it).
An unoccupied slot gets an empty rect, not the row — a Slot clears whatever it is given, which would blank the border underneath.
200 201 202 203 204 205 206 207 208 |
# File 'lib/tuile/component/window.rb', line 200 def if .nil? || rect.empty? @footer_slot.rect = Rect.new(0, 0, 0, 0) return end width = [rect.width - 2, 0].max @footer_slot.rect = Rect.new(rect.left + 1, rect.top + rect.height - 1, width, 1) end |
#rect=(new_rect) ⇒ void
This method returns an undefined value.
@param new_rect
75 76 77 78 |
# File 'lib/tuile/component/window.rb', line 75 def rect=(new_rect) super end |
#repaint ⇒ void
This method returns an undefined value.
Fully repaints the window: the border ring here, the interior through the content and footer it re-invalidates.
Deliberately not super: the default would blank the whole rect
first, because the content slot is inset by the border and so never
tiles — and every one of those blanked border cells is one this method
is about to repaint identically, which marks it dirty and makes
Buffer#flush re-emit it. That cost 925 bytes on every unchanged
repaint of an 80×25 window, paid on each focus change
(D_component_contract). The ring is this window's own paint and the
interior is the content's, so the only cell nobody covers is an
interior with no content in it — cleared here, exactly.
107 108 109 110 111 112 113 |
# File 'lib/tuile/component/window.rb', line 107 def repaint return if rect.empty? clear_background(content_rect) if content.nil? && !content_rect.empty? invalidate_children repaint_border end |
#repaint_border ⇒ void
This method returns an undefined value.
Paints the window border via Tuile::Component#draw_text/Tuile::Component#draw_char, so the border cells inherit Tuile::Component#effective_bg_color — a Tuile::Component#bg_color on the window tints border and content alike. Both border rows are clipped by display width, so no caption overflows the box; when the window is active the whole border — the caption's own colors included — is drawn in Theme#active_border_color.
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/tuile/component/window.rb', line 137 def repaint_border return if rect.empty? w = rect.width h = rect.height top = rect.top left = rect.left inner_w = [w - 2, 0].max fg = active? ? screen.theme.active_border_color : nil = StyledString::Style.new(fg: fg) draw_text(left, top, top_border(inner_w, fg).slice(0, w)) (1..(h - 2)).each do |dy| draw_char(left, top + dy, "│", ) # Skipped once {#scrollbar=} has given that column to the content: the # bar would paint over the border anyway, and painting it first only # dirties the column into every frame's diff (`D_component_contract`). draw_char(left + w - 1, top + dy, "│", ) if @border_right.positive? end draw_text(left, top + h - 1, bottom_border(inner_w, fg).slice(0, w)) if h >= 2 end |
#scrollbar=(value) ⇒ void
This method returns an undefined value.
@param value
82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/tuile/component/window.rb', line 82 def (value) unless content.respond_to?(:scrollbar_visibility=) raise Tuile::Error, "scrollbar= requires a content component that supports scrollbar_visibility=, got #{content.inspect}" end content. = value ? :visible : :gone @border_right = value ? 0 : 1 invalidate layout(content) end |
#top_border(inner_w, fg) ⇒ StyledString
Builds the top border row: corners, #caption embedded at its own
width, dashes filling the remainder. The caption keeps its own styling
unless fg is set — an active window's border claims it.
@param inner_w — the border's interior width.
@param fg — the active-border color, or nil when inactive.
165 166 167 168 169 170 |
# File 'lib/tuile/component/window.rb', line 165 def top_border(inner_w, fg) title = caption.slice(0, inner_w) title = title.with_fg(fg) if fg dashes = StyledString.styled("─" * (inner_w - title.display_width), fg: fg) StyledString.styled("┌", fg: fg) + title + dashes + StyledString.styled("┐", fg: fg) end |