diff --git a/examples/FlexLayout.php b/examples/FlexLayout.php new file mode 100644 index 0000000..b0446b6 --- /dev/null +++ b/examples/FlexLayout.php @@ -0,0 +1,72 @@ +addComponent($label1); + +$box2 = new Container(style: 'basis-1/3 bg-green-500 p-4'); +$label2 = new Label(text: '33% Basis', style: 'text-white'); +$box2->addComponent($label2); + +$box3 = new Container(style: 'basis-1/3 bg-red-500 p-4'); +$label3 = new Label(text: '33% Basis', style: 'text-white'); +$box3->addComponent($label3); + +$row1->addComponent($box1); +$row1->addComponent($box2); +$row1->addComponent($box3); + +// Example 2: Flex Row with flex-grow +$row2 = new Container(style: 'flex flex-row bg-white m-2 p-2'); + +$fixedBox = new Container(style: 'w-200 bg-purple-500 p-4'); +$fixedLabel = new Label(text: 'Fixed 200px', style: 'text-white'); +$fixedBox->addComponent($fixedLabel); + +$growBox = new Container(style: 'flex-1 bg-yellow-500 p-4'); +$growLabel = new Label(text: 'Flex Grow (rest)', style: 'text-black'); +$growBox->addComponent($growLabel); + +$row2->addComponent($fixedBox); +$row2->addComponent($growBox); + +// Example 3: Flex Column +$col1 = new Container(style: 'flex flex-col bg-white m-2 p-2 h-200'); + +$colBox1 = new Container(style: 'basis-1/2 bg-cyan-500 p-4'); +$colLabel1 = new Label(text: '50% Height', style: 'text-white'); +$colBox1->addComponent($colLabel1); + +$colBox2 = new Container(style: 'basis-1/2 bg-orange-500 p-4'); +$colLabel2 = new Label(text: '50% Height', style: 'text-white'); +$colBox2->addComponent($colLabel2); + +$col1->addComponent($colBox1); +$col1->addComponent($colBox2); + +// Add all examples to main container +$mainContainer->addComponent($row1); +$mainContainer->addComponent($row2); +$mainContainer->addComponent($col1); + +$app->setRoot($mainContainer); +$app->run(); diff --git a/examples/Todo.php b/examples/Todo.php index f109bbe..83220db 100644 --- a/examples/Todo.php +++ b/examples/Todo.php @@ -10,12 +10,19 @@ if (PHP_VERSION_ID < 80100) { } $app = new \PHPNative\Framework\Application('ToDo Demo', 700, 500); -$container = new \PHPNative\Ui\Widget\Container(style: 'm-10 p-10 rounded-xl bg-lime-200'); -$containerMenu = new \PHPNative\Ui\Widget\Container(style: 'bg-lime-700'); -$label = new Label( +$container = new \PHPNative\Ui\Widget\Container(style: 'm-10 p-10 flex-row rounded-xl bg-lime-200'); +$containerContent = new \PHPNative\Ui\Widget\Container(style: 'flex-1 bg-lime-700'); +$labelContent = new Label( text: 'Todo App', style: 'text-xl2 text-red-500', ); +$containerContent->addComponent($labelContent); +$container->addComponent($containerContent); +$containerMenu = new \PHPNative\Ui\Widget\Container(style: 'w-200 bg-lime-200'); +$label = new Label( + text: 'Menu App', + style: 'text-red-500', +); $containerMenu->addComponent($label); $container->addComponent($containerMenu); $app->setRoot($container); diff --git a/src/Ui/Widget/Container.php b/src/Ui/Widget/Container.php index 27a06a5..f273710 100644 --- a/src/Ui/Widget/Container.php +++ b/src/Ui/Widget/Container.php @@ -2,11 +2,125 @@ namespace PHPNative\Ui\Widget; +use PHPNative\Framework\TextRenderer; +use PHPNative\Tailwind\Style\Basis; +use PHPNative\Tailwind\Style\DirectionEnum; +use PHPNative\Tailwind\Style\Flex; +use PHPNative\Tailwind\Style\FlexTypeEnum; +use PHPNative\Tailwind\Style\Height; +use PHPNative\Tailwind\Style\Unit; +use PHPNative\Tailwind\Style\Width; use PHPNative\Ui\Component; +use PHPNative\Ui\Viewport; class Container extends Component { public function __construct( public string $style = '', ) {} + + public function layout(null|TextRenderer $textRenderer = null): void + { + // Call parent to compute styles and setup viewports + parent::layout($textRenderer); + + // Check if this container has flex layout + if (!isset($this->computedStyles[Flex::class])) { + return; + } + + $flex = $this->computedStyles[Flex::class]; + $this->layoutChildren($flex, $textRenderer); + } + + private function layoutChildren(Flex $flex, null|TextRenderer $textRenderer): void + { + if (empty($this->children)) { + return; + } + + $isRow = $flex->direction === DirectionEnum::row; + $availableSpace = $isRow ? $this->contentViewport->width : $this->contentViewport->height; + + // First pass: calculate fixed sizes and count flex-grow items + $childSizes = []; + $flexGrowCount = 0; + $usedSpace = 0; + + foreach ($this->children as $index => $child) { + // Parse child styles to get basis, width, height, and flex + $childStyles = \PHPNative\Tailwind\StyleParser::parse($child->style)->getValidStyles( + \PHPNative\Tailwind\Style\MediaQueryEnum::normal, + \PHPNative\Tailwind\Style\StateEnum::normal, + ); + + $childFlex = $childStyles[Flex::class] ?? null; + $basis = $childStyles[Basis::class] ?? null; + $width = $childStyles[Width::class] ?? null; + $height = $childStyles[Height::class] ?? null; + + $size = 0; + + // Check if child has flex-grow + if ($childFlex && $childFlex->type !== FlexTypeEnum::none) { + $flexGrowCount++; + $childSizes[$index] = ['size' => 0, 'flexGrow' => true]; + } else { + // Calculate fixed size from basis, width, or height + if ($basis) { + $size = $this->calculateSize($basis, $availableSpace); + } elseif ($isRow && $width) { + $size = $this->calculateSize($width, $availableSpace); + } elseif (!$isRow && $height) { + $size = $this->calculateSize($height, $availableSpace); + } + + $usedSpace += $size; + $childSizes[$index] = ['size' => $size, 'flexGrow' => false]; + } + } + + // Calculate remaining space for flex-grow items + $remainingSpace = max(0, $availableSpace - $usedSpace); + $flexGrowSize = $flexGrowCount > 0 ? $remainingSpace / $flexGrowCount : 0; + + // Second pass: assign sizes and position children + $currentPosition = $isRow ? $this->contentViewport->x : $this->contentViewport->y; + + foreach ($this->children as $index => $child) { + $childSize = $childSizes[$index]; + $size = $childSize['flexGrow'] ? $flexGrowSize : $childSize['size']; + + // Create viewport for child + $childViewport = new Viewport(); + + if ($isRow) { + // Flex row + $childViewport->x = $currentPosition; + $childViewport->y = $this->contentViewport->y; + $childViewport->width = $size; + $childViewport->height = $this->contentViewport->height; + $currentPosition += $size; + } else { + // Flex column + $childViewport->x = $this->contentViewport->x; + $childViewport->y = $currentPosition; + $childViewport->width = $this->contentViewport->width; + $childViewport->height = $size; + $currentPosition += $size; + } + + $child->setViewport($childViewport); + $child->layout($textRenderer); + } + } + + private function calculateSize(Width|Height|Basis $style, float $availableSpace): float + { + return match ($style->unit) { + Unit::Pixel => (float) $style->value, + Unit::Point => (float) $style->value, + Unit::Percent => ($availableSpace * $style->value) / 100, + }; + } }