Class: Tuile::Component::Window

Inherits:
Component
  • Object
show all
Includes:
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

InfoWindow, LogWindow, PickerWindow

Instance Attribute Summary collapse

Attributes included from HasContent

#content

Instance Method Summary collapse

Constructor Details

#initialize(caption = "") ⇒ Window

@param caption

Parameters:

  • caption (String) (defaults to: "")


18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/tuile/component/window.rb', line 18

def initialize(caption = "")
  super()
  @border_right = 1
  @caption = caption
  @content = nil
  # Optional bottom-row widget slot (e.g. a search field), spanning the
  # full inner width; and optional bottom-border chrome text embedded in
  # the border line (mutually exclusive — the component, when present,
  # occupies the row and hides the text).
  @footer = nil
  @footer_text = StyledString::EMPTY
end

Instance Attribute Details

#captionString

@return — the current caption, empty by default.

Returns:

  • (String)


126
127
128
# File 'lib/tuile/component/window.rb', line 126

def caption
  @caption
end

@return — optional focusable component occupying the bottom border row, always spanning the full inner width.

Returns:



35
36
37
# File 'lib/tuile/component/window.rb', line 35

def footer
  @footer
end

@return — optional chrome embedded into the bottom border line, mirroring #caption on the top line. Empty by default; hidden whenever a #footer component is present.

Returns:



40
41
42
# File 'lib/tuile/component/window.rb', line 40

def footer_text
  @footer_text
end

Instance Method Details

#bottom_border(inner_w, fg) ⇒ StyledString

Builds the bottom border line. 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.

Parameters:

  • inner_w (Integer)
  • fg (Color, nil)

Returns:



204
205
206
207
208
209
210
211
212
213
# File 'lib/tuile/component/window.rb', line 204

def bottom_border(inner_w, fg)
  interior =
    if @footer || @footer_text.empty?
      StyledString.styled("" * inner_w, fg: fg)
    else
      embedded = @footer_text.slice(0, inner_w)
      embedded + StyledString.styled("" * (inner_w - embedded.display_width), fg: fg)
    end
  StyledString.styled("", fg: fg) + interior + StyledString.styled("", fg: fg)
end

#childrenArray<Component>

Returns:



90
91
92
# File 'lib/tuile/component/window.rb', line 90

def children
  @footer.nil? ? super : super + [@footer]
end

#focusable?Boolean

Returns:

  • (Boolean)


31
# File 'lib/tuile/component/window.rb', line 31

def focusable? = true

#frame_captionString

The caption text as it appears in the rendered border, including the shortcut prefix when Tuile::Component#key_shortcut is set.

Returns:

  • (String)


218
219
220
221
# File 'lib/tuile/component/window.rb', line 218

def frame_caption
  c = @caption || ""
  key_shortcut.nil? ? c : "[#{key_shortcut}]-#{c}"
end

#handle_mouse(event) ⇒ void

This method returns an undefined value.

@param event

Parameters:



96
97
98
99
100
101
102
# File 'lib/tuile/component/window.rb', line 96

def handle_mouse(event)
  if @footer&.rect&.contains?(event.point)
    @footer.handle_mouse(event)
  else
    super
  end
end

#key_shortcut=(key) ⇒ void

This method returns an undefined value.

@param key

Parameters:

  • key (String, nil)


155
156
157
158
159
# File 'lib/tuile/component/window.rb', line 155

def key_shortcut=(key)
  super
  # The shortcut key is shown in the caption — repaint.
  invalidate
end

#layout(content) ⇒ void

This method returns an undefined value.

@param content

Parameters:



165
166
167
# File 'lib/tuile/component/window.rb', line 165

def layout(content)
  content.rect = Rect.new(rect.left + 1, rect.top + 1, rect.width - 1 - @border_right, rect.height - 2)
end

This method returns an undefined value.

Positions the footer over the bottom border row, spanning the full inner width (the only dimension a bottom-row widget needs — the window already knows it).



229
230
231
232
233
234
# File 'lib/tuile/component/window.rb', line 229

def layout_footer
  return if @footer.nil? || rect.empty?

  width = [rect.width - 2, 0].max
  @footer.rect = Rect.new(rect.left + 1, rect.top + rect.height - 1, width, 1)
end

#on_focusvoid

This method returns an undefined value.



2179
# File 'sig/tuile.rbs', line 2179

def on_focus: () -> void

#rect=(new_rect) ⇒ void

This method returns an undefined value.

@param new_rect

Parameters:



106
107
108
109
# File 'lib/tuile/component/window.rb', line 106

def rect=(new_rect)
  super
  layout_footer
end

#repaintvoid

This method returns an undefined value.

Fully repaints the window: both frame and contents.

Window deliberately paints over its entire rect (border around the edge, content/footer over the interior), so we don't need the Tuile::Component#repaint default's auto-clear — but we do still want its "re-invalidate children" effect, since the border overpaints whatever the content/footer drew on the perimeter. Calling super handles both: the auto-clear is harmless (we re-paint over it), and the invalidation queues content + footer for repaint in the same cycle.



146
147
148
149
150
151
# File 'lib/tuile/component/window.rb', line 146

def repaint
  return if rect.empty?

  super
  repaint_border
end

#repaint_bordervoid

This method returns an undefined value.

Paints the window border into the Screen#buffer. Title is clipped to the inner width so the box never overflows Tuile::Component#rect; when the window is active the whole border is drawn in Theme#active_border_color.



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/tuile/component/window.rb', line 173

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
  title = frame_caption.to_s
  title = title[0, inner_w] if title.length > inner_w
  dashes = "" * (inner_w - title.length)

  fg = active? ? screen.theme.active_border_color : nil
  bar = StyledString::Style.new(fg: fg)
  buf = screen.buffer
  buf.set_line(left, top, StyledString.styled("#{title}#{dashes}", fg: fg))
  (1..(h - 2)).each do |dy|
    buf.set_char(left, top + dy, "", bar)
    buf.set_char(left + w - 1, top + dy, "", bar)
  end
  buf.set_line(left, top + h - 1, bottom_border(inner_w, fg)) if h >= 2
end

#scrollbar=(value) ⇒ void

This method returns an undefined value.

@param value

Parameters:

  • value (Boolean)


113
114
115
116
117
118
119
120
121
122
123
# File 'lib/tuile/component/window.rb', line 113

def scrollbar=(value)
  unless content.respond_to?(:scrollbar_visibility=)
    raise Tuile::Error,
          "scrollbar= requires a content component that supports scrollbar_visibility=, got #{content.inspect}"
  end

  content.scrollbar_visibility = value ? :visible : :gone
  @border_right = value ? 0 : 1
  invalidate
  layout(content)
end