Module: Tuile::LayoutPass Private
- Defined in:
- lib/tuile/layout_pass.rb,
sig/tuile.rbs
Overview
This module is part of a private API. You should avoid using this module if possible, as it may be removed or be changed in the future.
Who may assign a rect right now. Component#rect= is refused outside the parent's Component#relayout, and this is the bracket that says when one is running:
LayoutPass.run(container) { relayout } # inside the container
A pass also records which children it has placed so far. Before the first, Component#invalidate_layout drops a mark the container makes on itself — the pass that would answer it is the one running — and until a child's turn, Component#rect_stale? reports its rect as the previous pass's (LayoutPass.unplaced?).
Thread-local rather than an ivar, because a tree with no Screen places children too, so there is no one object to hang the state on.
Tuile-internal: a member may change or vanish with no migration note. The rule it enforces is Component#rect='s and is documented there.
Constant Summary collapse
- PLACING =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
The thread-local naming what is placing children right now.
:tuile_placing- PLACED =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
The thread-local holding the children the current placer has assigned a rect so far — an identity
Set, ornilbefore the first. :tuile_placed- MAX_ROUNDS =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
Drain rounds a Screen#flush_layout or a detached Component#flush_layout runs before giving up. No container sizes itself from its children, so a tree settles in about its depth; one that is still marking past this is feeding a pass's output back into its input — a Component::Scroller whose
content_rowsthe app derives from the content's width, say — and would otherwise hang the UI thread. 50
Class Method Summary collapse
-
.before_first_placement?(component) ⇒ Boolean
private
Whether
componentis the running placer and has placed no child yet — the window in which a mark it makes on itself is redundant. -
.check(component) ⇒ void
private
@param
component— the one whose rect is being assigned. -
.drain(root, rounds = 0) ⇒ Integer
private
Runs every Component#relayout
root's tree owes, to a fixpoint — the one drain behind both Screen#flush_layout and a detached Component#flush_layout. -
.note_placement(component) ⇒ void
private
Records that the running pass has assigned
componentits rect. -
.placer(component) ⇒ Component, ...
private
What may assign
component's rect: its parent, whose Component#relayout does. -
.refuse_nested ⇒ void
private
The guard at the top of both drains, Screen#flush_layout and a detached Component#flush_layout.
-
.run(placer) ⇒ Object
private
Runs the block with
placeras the one thing whose children's rects may be assigned. -
.running? ⇒ Boolean
private
Whether any pass is placing children right now — what a drain refuses to run inside (LayoutPass.refuse_nested), and Screen#focused= defers its geometry under.
-
.unplaced?(component) ⇒ Boolean
private
Whether
component's placer is running its pass right now and has not reached it yet — so its rect is the previous pass's, though no flag says so: Component#perform_relayout clears the placer's before the body runs.
Class Method Details
.before_first_placement?(component) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Whether component is the running placer and has placed no child yet —
the window in which a mark it makes on itself is redundant.
@param component
163 164 165 |
# File 'lib/tuile/layout_pass.rb', line 163 def before_first_placement?(component) Thread.current[PLACING].equal?(component) && Thread.current[PLACED].nil? end |
.check(component) ⇒ void
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
This method returns an undefined value.
@param component — the one whose rect is being assigned.
93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/tuile/layout_pass.rb', line 93 def check(component) current = Thread.current[PLACING] return if !current.nil? && current.equal?(placer(component)) if component.parent.nil? raise Tuile::Error, "#{component} has no parent to place it; to size a detached tree, " \ "hold it in a Layout::Absolute (add(tree, rect), then flush_layout)" end raise Tuile::Error, "#{component}'s rect assigned outside #{component.parent}'s relayout; change " \ "what the parent places it by instead (Absolute#constrain, Box#constrain, " \ "Overlay#placement=)" end |
.drain(root, rounds = 0) ⇒ Integer
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Runs every Component#relayout root's tree owes, to a fixpoint — the one
drain behind both Screen#flush_layout and a detached
Component#flush_layout.
The dirty flags are the queue. Each round walks the tree pre-order and runs whatever is marked, so a parent lays out before the children whose rects it just wrote, and those children run in the same walk. A mark on something earlier in the order — a child's pass marking its parent — waits for the next round, and the drain ends on the first walk that finds nothing marked. A flag is cleared by the pass that answers it, so a container is never run twice for one mark, and a raise strands nothing.
@param root — the tree's root: the ScreenPane, or a detached root.
@param rounds — rounds already run in this flush, so a caller draining more than once — the screen, re-checking anchors — keeps one count.
@return — the rounds run in total.
122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/tuile/layout_pass.rb', line 122 def drain(root, rounds = 0) while drain_round(root) next rounds += 1 if rounds < MAX_ROUNDS culprits = [] root.walk_tree { culprits << _1 if _1.layout_dirty? } raise Tuile::Error, "layout did not settle after #{MAX_ROUNDS} rounds, still marking: " \ "#{culprits.first(5).map(&:inspect).join(", ")} — a relayout keeps changing its " \ "own input (content_rows derived from the content's width?)" end rounds end |
.note_placement(component) ⇒ void
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
This method returns an undefined value.
Records that the running pass has assigned component its rect. Past
the first, a mark on the placer is kept rather than dropped.
@param component
155 156 157 |
# File 'lib/tuile/layout_pass.rb', line 155 def note_placement(component) (Thread.current[PLACED] ||= Set.new.compare_by_identity) << component end |
.placer(component) ⇒ Component, ...
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
What may assign component's rect: its parent, whose
Component#relayout does. The exception is the ScreenPane, which has
no parent whose pass could place it, so the Screen does (D_tree_first
keeps the screen out of the tree). Those are the only two cases — this is
a closed rule rather than a hook a component may answer for itself.
@param component
86 87 88 |
# File 'lib/tuile/layout_pass.rb', line 86 def placer(component) component.is_a?(ScreenPane) ? component.screen : component.parent end |
.refuse_nested ⇒ void
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
This method returns an undefined value.
The guard at the top of both drains, Screen#flush_layout and a detached Component#flush_layout.
72 73 74 75 76 77 |
# File 'lib/tuile/layout_pass.rb', line 72 def refuse_nested return unless running? raise Tuile::Error, "flush_layout inside #{Thread.current[PLACING]}'s relayout: the running pass " \ "has not placed its children yet, so a nested drain would read stale rects" end |
.run(placer) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Runs the block with placer as the one thing whose children's rects may
be assigned. Nests: the enclosing pass is restored on the way out.
@param placer
@return — the block's value.
50 51 52 53 54 55 56 57 58 59 |
# File 'lib/tuile/layout_pass.rb', line 50 def run(placer) outer = Thread.current[PLACING] outer_placed = Thread.current[PLACED] Thread.current[PLACING] = placer Thread.current[PLACED] = nil yield ensure Thread.current[PLACING] = outer Thread.current[PLACED] = outer_placed end |
.running? ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Whether any pass is placing children right now — what a drain refuses to run inside (refuse_nested), and Screen#focused= defers its geometry under.
64 |
# File 'lib/tuile/layout_pass.rb', line 64 def running? = !Thread.current[PLACING].nil? |
.unplaced?(component) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Whether component's placer is running its pass right now and has not
reached it yet — so its rect is the previous pass's, though no flag says
so: Component#perform_relayout clears the placer's before the body runs.
@param component
172 173 174 175 176 177 178 |
# File 'lib/tuile/layout_pass.rb', line 172 def unplaced?(component) placing = Thread.current[PLACING] return false if placing.nil? || !placing.equal?(placer(component)) placed = Thread.current[PLACED] placed.nil? || !placed.include?(component) end |