/
githubmirror
/
symfony
Обзор
Документация
Войти
/
githubmirror
/
symfony
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
8.2
src/Symfony/Component/Tui/Render/PositionTracker.php
195 строк
5 KB
Fabien Potencier
[Tui] Fix descendant position tracking in horizontal layouts
11 авг 2026, 15:57
11 авг 2026, 15:57
8e6de60
Код
Авторство
О чём код?
<?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Tui\Render; use Symfony\Component\Tui\Widget\AbstractWidget; use Symfony\Component\Tui\Widget\ParentInterface; /** * Tracks absolute positions of rendered widgets on screen. * * Maintains a stack of absolute [row, col] offsets for the current rendering * context and records each widget's final position as a WidgetRect. * * @experimental * * @internal * * @author Fabien Potencier <fabien@symfony.com> */ final class PositionTracker { /** @var \WeakMap<AbstractWidget, WidgetRect> */ private \WeakMap $widgetPositions; /** * Stack of absolute [row, col] offsets for the current rendering context. * * @var list<array{int, int}> */ private array $positionStack = []; public function __construct() { $this->widgetPositions = new \WeakMap(); } /** * Reset the position stack for a new render pass. * * Previous widget positions are preserved so that cached subtrees * (which skip re-rendering) keep their tracked rects. Any widget * whose parent is re-rendered gets a fresh rect, replacing the old * entry. Removed widgets are garbage-collected by the WeakMap. */ public function reset(): void { $this->positionStack = [[0, 0]]; } /** * Get the tracked position of a widget from the last render pass. */ public function getWidgetRect(AbstractWidget $widget): ?WidgetRect { return $this->widgetPositions[$widget] ?? null; } /** * Record a widget's absolute position. */ public function setWidgetRect(AbstractWidget $widget, WidgetRect $rect): void { $this->widgetPositions[$widget] = $rect; } /** * Move a previously tracked widget and its descendants to a new position. */ public function moveSubtree(AbstractWidget $widget, WidgetRect $rect): bool { if (!isset($this->widgetPositions[$widget])) { return false; } $previous = $this->widgetPositions[$widget]; $rowOffset = $rect->row - $previous->row; $colOffset = $rect->col - $previous->col; if (0 !== $rowOffset || 0 !== $colOffset) { $this->shiftSubtree($widget, $rowOffset, $colOffset); } $this->widgetPositions[$widget] = $rect; return true; } /** * Whether position tracking is active (has a non-empty stack). */ public function isActive(): bool { return (bool) $this->positionStack; } /** * Get the current absolute [row, col] offset from the top of the stack. * * @return array{int, int} */ public function currentOffset(): array { return $this->positionStack[\count($this->positionStack) - 1]; } /** * Push a new absolute [row, col] offset onto the stack. */ public function push(int $row, int $col): void { $this->positionStack[] = [$row, $col]; } /** * Pop the top offset from the stack. */ public function pop(): void { if (\count($this->positionStack) > 1) { array_pop($this->positionStack); } } /** * Save the position stack and replace it with an empty one. * * Used to suppress position tracking during measurement passes. * * @return list<array{int, int}> */ public function suppressStack(): array { $saved = $this->positionStack; $this->positionStack = []; return $saved; } /** * Restore a previously saved position stack. * * @param list<array{int, int}> $stack */ public function restoreStack(array $stack): void { $this->positionStack = $stack; } /** * Shift the tracked positions of laid-out content by the given offsets. * * Called when alignment moves a container's content after layout, so the * children and their descendants keep matching where they are drawn. * * @param AbstractWidget[] $children */ public function shiftContentPositions(array $children, int $colOffset, int $rowOffset = 0): void { if (0 === $colOffset && 0 === $rowOffset) { return; } foreach ($children as $child) { $this->shiftSubtree($child, $rowOffset, $colOffset); } } private function shiftSubtree(AbstractWidget $widget, int $rowOffset, int $colOffset): void { if (isset($this->widgetPositions[$widget])) { $rect = $this->widgetPositions[$widget]; $this->widgetPositions[$widget] = new WidgetRect( $rect->row + $rowOffset, $rect->col + $colOffset, $rect->columns, $rect->rows, ); } if (!$widget instanceof ParentInterface) { return; } foreach ($widget->all() as $child) { $this->shiftSubtree($child, $rowOffset, $colOffset); } } }