Class: Tuile::Component::Layout
- Inherits:
-
Component
- Object
- Component
- Tuile::Component::Layout
- Defined in:
- lib/tuile/component/layout.rb,
lib/tuile/component/layout/box.rb,
lib/tuile/component/layout/absolute.rb,
lib/tuile/component/layout/vertical.rb,
lib/tuile/component/layout/horizontal.rb,
sig/tuile.rbs
Overview
A layout doesn't paint anything by itself: its job is to position child components. Three ways to use one, all top-down (see book ch3):
-
Subclass Layout and compute every child's rectangle yourself in a #relayout override. Total control, and the base for anything unusual:
class SplitPane < Component::Layout def initialize super add(@sidebar = Component::List.new) add(@main = Component::Window.new("Main")) end protected def relayout left = width * 4 / 10 @sidebar.rect = Rect.new(0, 0, left, height) @main.rect = Rect.new(left, 0, width - left, height) end endThe framework runs it after a resize, an
addorremove, and a child's #visible=. -
Absolute — you give each child a fixed Rect and the layout assigns exactly that.
-
Box / Vertical / Horizontal — you declare each child's extent as a Fixed, Percent or Expand constraint — a Percent optionally bounded with Percent#clamp — and the layout does the arithmetic.
Children that fully tile the layout's rect repaint themselves and cover everything; children that leave gaps (e.g. a form with widgets of varying widths) trigger #repaint's default behavior — the background is cleared and children are re-invalidated so they paint over a clean surface.
Direct Known Subclasses
Defined Under Namespace
Modules: Constraint Classes: Absolute, Box, Clamp, Expand, Fixed, Horizontal, Insets, Percent, Vertical
Instance Method Summary collapse
-
#add(child) ⇒ void
Adds a child component to this layout.
-
#focusable? ⇒ Boolean
Layouts are focusable containers — like Window and Popup, they don't accept input themselves but they need to participate in the HasContent focus cascade so a Popup wrapping a Layout wrapping a TextField ends up focusing the field rather than parking focus on the popup.
- #handle_focus ⇒ void
-
#remove(child) ⇒ void
@param
child.
Instance Method Details
#add(child) ⇒ void
This method returns an undefined value.
Adds a child component to this layout.
@param child
301 302 303 304 305 306 307 |
# File 'lib/tuile/component/layout.rb', line 301 def add(child) if child.is_a? Enumerable child.each { add(_1) } else add_child(child) end end |
#focusable? ⇒ Boolean
Layouts are focusable containers — like Window and Popup, they don't accept input themselves but they need to participate in the HasContent focus cascade so a Popup wrapping a Layout wrapping a TextField ends up focusing the field rather than parking focus on the popup. Layouts don't paint any visible chrome of their own (the auto-cleared background is just blank space), so this has no mouse-routing consequences — clicks on a gap area land back on the Layout itself and the handle_focus cascade forwards to a tab stop.
296 |
# File 'lib/tuile/component/layout.rb', line 296 def focusable? = true |
#handle_focus ⇒ void
This method returns an undefined value.
320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 |
# File 'lib/tuile/component/layout.rb', line 320 def handle_focus super # Forward focus to the first interactive widget in the subtree so the # user can start typing / cursoring immediately. Prefer a {#tab_stop?} # descendant (TextField, List, Button…) so we skip past intermediate # containers like a {Window} or another {Layout}. Fall back to the # first focusable direct child for the rare case where the layout has # focusable but non-tab-stop children (e.g. an empty {Window}). # # Both halves skip hidden subtrees — this is the cascade that would # otherwise walk straight back into the pane just hidden. first_tab_stop = nil walk_shown_tree { |c| first_tab_stop ||= c if !c.equal?(self) && c.tab_stop? } if first_tab_stop screen.focused = first_tab_stop else first_focusable = @children.find { _1.visible? && _1.focusable? } screen.focused = first_focusable unless first_focusable.nil? end end |
#remove(child) ⇒ void
This method returns an undefined value.
@param child
311 312 313 314 315 316 317 |
# File 'lib/tuile/component/layout.rb', line 311 def remove(child) raise TypeError, "expected Component, got #{child.inspect}" unless child.is_a? Component raise ArgumentError, "#{child}'s parent is #{child.parent}, not this layout #{self}" if child.parent != self remove_child(child) invalidate if @children.empty? # nothing left to paint over the gap end |