Module: Thaum::Concerns::Layout

Included in:
App, Octagram
Defined in:
lib/thaum/concerns/layout.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#child_layoutsObject (readonly)

Returns the value of attribute child_layouts.



6
7
8
# File 'lib/thaum/concerns/layout.rb', line 6

def child_layouts
  @child_layouts
end

#leaf_sigilsObject (readonly)

Returns the value of attribute leaf_sigils.



6
7
8
# File 'lib/thaum/concerns/layout.rb', line 6

def leaf_sigils
  @leaf_sigils
end

#rectObject (readonly)

Returns the value of attribute rect.



6
7
8
# File 'lib/thaum/concerns/layout.rb', line 6

def rect
  @rect
end

#subtree_childrenObject (readonly)

Returns the value of attribute subtree_children.



6
7
8
# File 'lib/thaum/concerns/layout.rb', line 6

def subtree_children
  @subtree_children
end

#subtree_leavesObject (readonly)

Returns the value of attribute subtree_leaves.



6
7
8
# File 'lib/thaum/concerns/layout.rb', line 6

def subtree_leaves
  @subtree_leaves
end

Instance Method Details

#collect_octagramsObject

Walk this subtree and collect every Octagram node (excluding self).



85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/thaum/concerns/layout.rb', line 85

def collect_octagrams
  result = []
  (@subtree_children || []).each do |child|
    if child.is_a?(Octagram)
      result << child
      result.concat(child.collect_octagrams)
    elsif child.respond_to?(:collect_octagrams)
      result.concat(child.collect_octagrams)
    end
  end
  result
end

#effective_focus_orderObject

Build the Tab traversal order for this subtree, recursively expanding any nested Layout that itself has focus_order. When no focus_order is defined at any level, this falls through to left-to-right leaf order.



109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/thaum/concerns/layout.rb', line 109

def effective_focus_order
  order = focus_order
  return order if order

  result = []
  layout_subtree_in_order.each do |node|
    if node.is_a?(Sigil)
      result << node if node.focusable?
    else
      result.concat(node.effective_focus_order)
    end
  end
  result
end

#first_focusable_leafObject

Recursively descend into this Octagram (or plain Layout) to find its first focusable leaf — used when Tab enters an Octagram unit.



150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/thaum/concerns/layout.rb', line 150

def first_focusable_leaf
  (@subtree_children || []).each do |child|
    case child
    when Sigil
      return child if child.focusable?
    else
      leaf = child.first_focusable_leaf if child.respond_to?(:first_focusable_leaf)
      return leaf if leaf
    end
  end
  nil
end

#focus_orderObject

Override on any Layout node (including App) to specify a Tab traversal order for that subtree. Return an array of leaf Sigils. nil (default) means “use left-to-right leaf order.”



11
# File 'lib/thaum/concerns/layout.rb', line 11

def focus_order = nil

#focus_scope_unitsObject

Scope units for Tab cycling within THIS focus scope. A “scope” is the App or an Octagram. A unit is either a focusable Sigil (reached through plain Layouts only) or a nested Octagram (which appears as one unit and is itself a scope). Plain Layouts are transparent — their units are flattened into the parent scope.



133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/thaum/concerns/layout.rb', line 133

def focus_scope_units
  result = []
  (@subtree_children || []).each do |child|
    case child
    when Sigil
      result << child if child.focusable?
    when Octagram
      result << child if child.focusable_descendant?
    else
      result.concat(child.focus_scope_units) if child.respond_to?(:focus_scope_units)
    end
  end
  result
end

#focusable_descendant?Boolean

Returns:

  • (Boolean)


177
178
179
# File 'lib/thaum/concerns/layout.rb', line 177

def focusable_descendant?
  !first_focusable_leaf.nil?
end

#inset_for_partition(rect) ⇒ Object

Override on Octagram (or any Layout) to inset the rect that this node’s children partition into. Defaults to identity.



33
34
35
36
37
38
39
40
41
42
43
# File 'lib/thaum/concerns/layout.rb', line 33

def inset_for_partition(rect)
  return rect unless respond_to?(:partition_inset)

  inset = partition_inset || {}
  Rect.new(
    x:      rect.x + (inset[:left]   || 0),
    y:      rect.y + (inset[:top]    || 0),
    width:  [rect.width  - (inset[:left] || 0) - (inset[:right]  || 0), 0].max,
    height: [rect.height - (inset[:top]  || 0) - (inset[:bottom] || 0), 0].max
  )
end

#last_focusable_leafObject

Mirror of first_focusable_leaf for Shift-Tab entry.



164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/thaum/concerns/layout.rb', line 164

def last_focusable_leaf
  (@subtree_children || []).reverse_each do |child|
    case child
    when Sigil
      return child if child.focusable?
    else
      leaf = child.last_focusable_leaf if child.respond_to?(:last_focusable_leaf)
      return leaf if leaf
    end
  end
  nil
end

#layout_subtree_in_orderObject

The direct children of this Layout in declaration order — both Sigils and nested Layouts. Built during partition (see place_child).



126
# File 'lib/thaum/concerns/layout.rb', line 126

def layout_subtree_in_order = @subtree_children || []

#partitionObject

Override to specify the layout. Must call horizontal/vertical.



202
# File 'lib/thaum/concerns/layout.rb', line 202

def partition; end

#repartitionObject

Re-run partition for this node’s subtree using its current rect.



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/thaum/concerns/layout.rb', line 46

def repartition
  return unless @rect

  old_leaves    = @leaf_sigils || []
  old_octagrams = collect_octagrams

  new_leaves = []
  run_partition(rect: @rect, collector: new_leaves)

  new_octagrams = collect_octagrams

  # Fire on_unmount for removed Sigils and Octagrams.
  (old_leaves - new_leaves).each(&:on_unmount)
  (old_octagrams - new_octagrams).each(&:on_unmount)

  # Rewire handler parents across the (possibly restructured) subtree so
  # newly-added Octagrams and Sigils see the right chain BEFORE on_mount.
  # Only rewires when we can identify this node's role in the dispatch
  # chain — App (root) or Octagram (its own handler scope).
  app = thaum_app_ref || (is_a?(Octagram) ? @thaum_app : nil)
  wire_handler_parents(handler_parent: self, app: app) if app

  # Fire on_mount for newly-added Sigils and Octagrams.
  (new_octagrams - old_octagrams).each do |o|
    o.thaum_app = app if app
    o.on_mount
  end
  (new_leaves - old_leaves).each do |s|
    s.thaum_app = app if app
    s.on_mount
  end

  @leaf_sigils = new_leaves

  # Re-validate focus_order in this subtree after structural change.
  validate_focus_order_tree
end

#run_partition(rect:, collector: nil) ⇒ Object

Called by the run loop (or repartition) to assign geometry and walk the partition tree. Returns the flat list of leaf Sigils in render order.



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/thaum/concerns/layout.rb', line 15

def run_partition(rect:, collector: nil)
  @rect = rect
  collector ||= []
  start = collector.size
  @leaf_sigils     = collector
  @child_layouts   = []
  @subtree_children = []
  # An Octagram may inset the rect its children partition into so its
  # render hook (border, padding) survives. Plain Layout passes through.
  @rect = inset_for_partition(rect)
  partition
  @rect = rect
  @subtree_leaves = collector[start..] || []
  collector
end

#validate_focus_order_treeObject

Walk this Layout node and every descendant Layout, raising FocusOrderError if any defines a focus_order that does not exactly cover the focusable leaves in its subtree.



101
102
103
104
# File 'lib/thaum/concerns/layout.rb', line 101

def validate_focus_order_tree
  validate_focus_order_node
  (@child_layouts || []).each(&:validate_focus_order_tree)
end

#wire_handler_parents(handler_parent:, app:) ⇒ Object

Walk the subtree top-down, wiring each leaf Sigil and each Octagram. Leaves and Octagrams get _handler_parent set to the innermost enclosing Octagram (or the App when there is none). Plain Layout nodes are transparent — their children inherit the outer parent.



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/thaum/concerns/layout.rb', line 185

def wire_handler_parents(handler_parent:, app:)
  (@subtree_children || []).each do |child|
    case child
    when Sigil
      child.handler_parent = handler_parent
      child.thaum_app      = app
    when Octagram
      child.handler_parent = handler_parent
      child.thaum_app      = app
      child.wire_handler_parents(handler_parent: child, app: app)
    else
      child.wire_handler_parents(handler_parent:, app:) if child.respond_to?(:wire_handler_parents)
    end
  end
end