Class: Tuile::Component::Layout

Inherits:
Tuile::Component show all
Defined in:
lib/tuile/component/layout.rb

Overview

A layout doesn’t paint anything by itself: its job is to position child components.

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

Absolute

Defined Under Namespace

Classes: Absolute

Instance Attribute Summary

Attributes inherited from Tuile::Component

#key_shortcut, #parent, #rect

Instance Method Summary collapse

Methods inherited from Tuile::Component

#active=, #active?, #attached?, #cursor_position, #depth, #find_shortcut_component, #focus, #keyboard_hint, #on_child_removed, #on_tree, #repaint, #root, #screen, #tab_stop?

Constructor Details

#initializeLayout

Returns a new instance of Layout.



14
15
16
17
# File 'lib/tuile/component/layout.rb', line 14

def initialize
  super
  @children = []
end

Instance Method Details

#add(child) ⇒ void

This method returns an undefined value.

Adds a child component to this layout.

Parameters:



35
36
37
38
39
40
41
42
43
44
45
# File 'lib/tuile/component/layout.rb', line 35

def add(child)
  if child.is_a? Enumerable
    child.each { add(it) }
  else
    raise TypeError, "expected Component, got #{child.inspect}" unless child.is_a? Component
    raise ArgumentError, "#{child} already has a parent #{child.parent}" unless child.parent.nil?

    @children << child
    child.parent = self
  end
end

#childrenArray<Component>

Returns:



20
# File 'lib/tuile/component/layout.rb', line 20

def children = @children.to_a

#content_sizeSize

Returns:



60
61
62
63
64
65
66
# File 'lib/tuile/component/layout.rb', line 60

def content_size
  return Size::ZERO if @children.empty?

  right  = @children.map { |c| c.rect.left + c.rect.width  }.max
  bottom = @children.map { |c| c.rect.top  + c.rect.height }.max
  Size.new(right - rect.left, bottom - rect.top)
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 on_focus cascade forwards to a tab stop.

Returns:

  • (Boolean)


30
# File 'lib/tuile/component/layout.rb', line 30

def focusable? = true

#handle_key(key) ⇒ Boolean

Called when a character is pressed on the keyboard.

Parameters:

  • key (String)

    a key.

Returns:

  • (Boolean)

    true if the key was handled, false if not.



81
82
83
84
85
86
87
88
# File 'lib/tuile/component/layout.rb', line 81

def handle_key(key)
  return true if super

  sc = @children.find(&:active?)
  return false if sc.nil?

  sc.handle_key(key)
end

#handle_mouse(event) ⇒ void

This method returns an undefined value.

Dispatches the event to the child under the mouse cursor.

Parameters:



71
72
73
74
75
76
# File 'lib/tuile/component/layout.rb', line 71

def handle_mouse(event)
  super
  @children.each do |child|
    child.handle_mouse(event) if child.rect.contains?(event.point)
  end
end

#on_focusvoid

This method returns an undefined value.



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/tuile/component/layout.rb', line 91

def on_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}).
  first_tab_stop = nil
  on_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(&:focusable?)
    screen.focused = first_focusable unless first_focusable.nil?
  end
end

#remove(child) ⇒ void

This method returns an undefined value.

Parameters:

Raises:

  • (TypeError)


49
50
51
52
53
54
55
56
57
# File 'lib/tuile/component/layout.rb', line 49

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

  child.parent = nil
  @children.delete(child)
  invalidate if @children.empty?
  on_child_removed(child)
end