From 29102c567c3616bc94b5a7f33933ba4175777cbd Mon Sep 17 00:00:00 2001 From: Thomas Peterson Date: Thu, 23 Oct 2025 07:46:13 +0200 Subject: [PATCH] First Scroll --- examples/ButtonExample.php | 63 +++++++++++++++++++++++++++ examples/TestStack.php | 27 ++++++++++++ examples/Todo.php | 10 ++--- src/Framework/Application.php | 3 ++ src/Framework/TextRenderer.php | 29 +++++++++++++ src/Ui/Component.php | 19 +++++---- src/Ui/Widget/Button.php | 36 ++++++++++++++++ src/Ui/Widget/Container.php | 78 +++++++++++++++++++++++++++++----- src/Ui/Widget/Label.php | 25 +++++++++++ 9 files changed, 265 insertions(+), 25 deletions(-) create mode 100644 examples/ButtonExample.php create mode 100644 examples/TestStack.php create mode 100644 src/Ui/Widget/Button.php diff --git a/examples/ButtonExample.php b/examples/ButtonExample.php new file mode 100644 index 0000000..f77fb10 --- /dev/null +++ b/examples/ButtonExample.php @@ -0,0 +1,63 @@ +addComponent($title); + +// Button with m-10 p-10, should be: 12 (text) + 10 (padding-top) + 10 (padding-bottom) = 32px height +$button1 = new Button( + text: 'Click Me', + style: 'm-10 p-10 bg-blue-500 rounded-lg' +); +$mainContainer->addComponent($button1); + +// Button with different padding +$button2 = new Button( + text: 'Another Button', + style: 'm-5 p-15 bg-green-500 rounded-lg' +); +$mainContainer->addComponent($button2); + +// Button with no padding +$button3 = new Button( + text: 'No Padding', + style: 'm-10 bg-red-500 rounded-lg' +); +$mainContainer->addComponent($button3); + +// Container with multiple labels (should stack) +$labelContainer = new Container(style: 'm-10 p-10 bg-white rounded-lg'); +$labelContainer->addComponent(new Label(text: 'Label 1', style: 'text-black')); +$labelContainer->addComponent(new Label(text: 'Label 2', style: 'text-black')); +$labelContainer->addComponent(new Label(text: 'Label 3', style: 'text-black')); +$mainContainer->addComponent($labelContainer); + +// Info text +$info = new Label( + text: 'Containers should auto-size to their content', + style: 'text-black m-10' +); +$mainContainer->addComponent($info); + +$app->setRoot($mainContainer); +$app->run(); diff --git a/examples/TestStack.php b/examples/TestStack.php new file mode 100644 index 0000000..c5d3e1d --- /dev/null +++ b/examples/TestStack.php @@ -0,0 +1,27 @@ +addComponent($label); + $scrollContainer->addComponent($item); +} +$app->setRoot($scrollContainer); +$app->run(); diff --git a/examples/Todo.php b/examples/Todo.php index 9438db2..55a928c 100644 --- a/examples/Todo.php +++ b/examples/Todo.php @@ -25,14 +25,14 @@ $label = new Label( style: 'text-red-500', ); $containerMenu->addComponent($label); -$scrollContainer = new Container(style: 'overflow-y-auto bg-white m-4 p-4'); +$scrollContainer = new Container(style: 'overflow-y-auto bg-red m-4 p-4'); // Add many items to trigger overflow -for ($i = 1; $i <= 20; $i++) { - $item = new Container(style: 'bg-blue-500 m-2 p-3 rounded-lg'); +for ($i = 1; $i <= 100; $i++) { + $item = new Container(style: 'bg-blue-500 m-2 p-3'); $label = new Label( - text: "Item {$i} - Scroll vertically with mouse wheel or drag the scrollbar", - style: 'text-white', + text: "Item {$i}", + style: 'text-green-500', ); $item->addComponent($label); $scrollContainer->addComponent($item); diff --git a/src/Framework/Application.php b/src/Framework/Application.php index 3bcc435..703ef7f 100644 --- a/src/Framework/Application.php +++ b/src/Framework/Application.php @@ -21,6 +21,7 @@ class Application private $mouseY; private Viewport $viewport; private bool $shouldBeReLayouted = true; + private float $pixelRatio = 2; public function __construct(string $title, int $width = 800, int $height = 600) { @@ -150,6 +151,7 @@ class Application case RGFW_mouseScroll: $deltaY = $event[1] ?? 0; + var_dump($event); // Propagate wheel to root component if ($this->rootComponent) { @@ -191,6 +193,7 @@ class Application protected function layout(): void { // Render root component tree + if ($this->rootComponent && $this->shouldBeReLayouted) { $this->rootComponent->setViewport($this->viewport); $this->rootComponent->setWindow($this->window); diff --git a/src/Framework/TextRenderer.php b/src/Framework/TextRenderer.php index dff5673..df9bdaf 100644 --- a/src/Framework/TextRenderer.php +++ b/src/Framework/TextRenderer.php @@ -131,6 +131,35 @@ class TextRenderer rfont_setColor($this->window, $r, $g, $b, $a); } + /** + * Measure text dimensions + * + * @param string $text Text to measure + * @param int|null $size Font size + * @return array [width, height] + */ + public function measureText(string $text, null|int $size = null): array + { + if (!$this->initialized) { + return [0, 0]; + } + + if ($size === null) { + $size = $this->fontSize; + } + + // Try to use rfont_getTextSize if available, otherwise estimate + if (function_exists('rfont_getTextSize')) { + $dimensions = rfont_getTextSize($this->window, $text, $size); + return [(int) $dimensions[0], (int) $dimensions[1]]; + } + + // Fallback: estimate based on font size + $width = strlen($text) * ($size * 0.6); // Rough estimate + $height = $size * 1.2; // Line height + return [(int) $width, (int) $height]; + } + /** * Update framebuffer size (call on window resize) * diff --git a/src/Ui/Component.php b/src/Ui/Component.php index 124e276..d034117 100644 --- a/src/Ui/Component.php +++ b/src/Ui/Component.php @@ -14,6 +14,7 @@ abstract class Component protected $children = []; protected $window; + protected $pixelRatio; protected string $styles = ''; @@ -39,6 +40,11 @@ abstract class Component $this->window = $window; } + public function setPixelRatio($pixelRatio): void + { + $this->pixelRatio = $pixelRatio; + } + public function update(): void { foreach ($this->children as $child) { @@ -55,23 +61,18 @@ abstract class Component if (isset($this->computedStyles[Margin::class]) && ($m = $this->computedStyles[Margin::class])) { $this->viewport->x = (int) ($this->viewport->x + $m->left); - $this->viewport->width -= $m->right + $m->left; + $this->viewport->width = max(0, ($this->viewport->width - $m->right) - $m->left); $this->viewport->y = (int) ($this->viewport->y + $m->top); - $this->viewport->height -= $m->bottom + $m->top; + $this->viewport->height = max(0, ($this->viewport->height - $m->bottom) - $m->top); } $this->contentViewport = clone $this->viewport; if (isset($this->computedStyles[Padding::class]) && ($p = $this->computedStyles[Padding::class])) { $this->contentViewport->x = (int) ($this->contentViewport->x + $p->left); - $this->contentViewport->width -= $p->right + $p->left; + $this->contentViewport->width = max(0, ($this->contentViewport->width - $p->right) - $p->left); $this->contentViewport->y = (int) ($this->contentViewport->y + $p->top); - $this->contentViewport->height -= $p->bottom + $p->top; - } - - foreach ($this->children as $child) { - $child->setViewport($this->contentViewport); - $child->layout($textRenderer); + $this->contentViewport->height = max(0, ($this->contentViewport->height - $p->bottom) - $p->top); } } diff --git a/src/Ui/Widget/Button.php b/src/Ui/Widget/Button.php new file mode 100644 index 0000000..84d9309 --- /dev/null +++ b/src/Ui/Widget/Button.php @@ -0,0 +1,36 @@ +label = new Label( + text: $text, + style: '' + ); + + $this->addComponent($this->label); + } + + public function setText(string $text): void + { + $this->text = $text; + $this->label->setText($text); + } + + public function getText(): string + { + return $this->text; + } +} diff --git a/src/Ui/Widget/Container.php b/src/Ui/Widget/Container.php index ee95b4e..2ca566d 100644 --- a/src/Ui/Widget/Container.php +++ b/src/Ui/Widget/Container.php @@ -41,21 +41,75 @@ class Container extends Component { // Call parent to compute styles and setup viewports parent::layout($textRenderer); + // Check if container has explicit width/height or is a flex child + $flexStyle = $this->computedStyles[Flex::class] ?? null; + $hasFlexGrow = $flexStyle && $flexStyle->type !== FlexTypeEnum::none; + + // A container is a flex container if it has flex/flex-row/flex-col in style + // (but not flex-1, flex-auto, flex-none which make it a flex child) + $isFlexContainer = + $flexStyle && + ( + preg_match('/\bflex-row\b/', $this->style) || + preg_match('/\bflex-col\b/', $this->style) || + preg_match('/(?style) + ); + + $hasExplicitWidth = + isset($this->computedStyles[Width::class]) || isset($this->computedStyles[Basis::class]) || $hasFlexGrow; + $hasExplicitHeight = + isset($this->computedStyles[Height::class]) || isset($this->computedStyles[Basis::class]) || $hasFlexGrow; // Check if this container has flex layout - if (isset($this->computedStyles[Flex::class])) { - $flex = $this->computedStyles[Flex::class]; - $this->layoutChildren($flex, $textRenderer); + if ($isFlexContainer) { + $this->layoutChildren($flexStyle, $textRenderer); } else { - // Non-flex layout: just layout children normally + // Non-flex layout: stack children vertically + $currentY = $this->contentViewport->y; + foreach ($this->children as $child) { - $child->setViewport($this->contentViewport); + // Create a viewport for the child with available width but let height be determined by child + $childViewport = new Viewport(); + $childViewport->x = $this->contentViewport->x; + $childViewport->y = (int) $currentY; + $childViewport->width = $this->contentViewport->width; // Maximum available width + $childViewport->height = $this->contentViewport->height; // Maximum available height + + $child->setViewport($childViewport); $child->layout($textRenderer); + + // Get the actual height after layout + $actualHeight = $child->getViewport()->height; + $currentY += $actualHeight; } } // Calculate total content size after children are laid out $this->calculateContentSize(); + + // Adjust viewport to content size if no explicit size + // But limit to available parent size (for proper overflow/scrolling) + $padding = $this->computedStyles[\PHPNative\Tailwind\Style\Padding::class] ?? null; + $paddingX = $padding ? ($padding->left + $padding->right) : 0; + $paddingY = $padding ? ($padding->top + $padding->bottom) : 0; + + // Store original available size before adjustment + $availableWidth = $this->viewport->width; + $availableHeight = $this->viewport->height; + + if (!$hasExplicitWidth) { + // Set viewport to min(contentSize + padding, availableSize) + $desiredWidth = $this->contentWidth + $paddingX; + $this->viewport->width = (int) min($desiredWidth, $availableWidth); + $this->contentViewport->width = max(0, $this->viewport->width - ((int) $paddingX)); + } + + if (!$hasExplicitHeight) { + // Set viewport to min(contentSize + padding, availableSize) + $desiredHeight = $this->contentHeight + $paddingY; + $this->viewport->height = (int) min($desiredHeight, $availableHeight); + $this->contentViewport->height = max(0, $this->viewport->height - ((int) $paddingY)); + } } private function layoutChildren(Flex $flex, null|TextRenderer $textRenderer): void @@ -152,8 +206,9 @@ class Container extends Component private function calculateContentSize(): void { if (empty($this->children)) { - $this->contentWidth = $this->contentViewport->width; - $this->contentHeight = $this->contentViewport->height; + // No children: content size is 0 + $this->contentWidth = 0; + $this->contentHeight = 0; return; } @@ -166,8 +221,9 @@ class Container extends Component $maxY = max($maxY, ($childViewport->y + $childViewport->height) - $this->contentViewport->y); } - $this->contentWidth = max($this->contentViewport->width, $maxX); - $this->contentHeight = max($this->contentViewport->height, $maxY); + // Content size is the actual space used by children within contentViewport + $this->contentWidth = $maxX; + $this->contentHeight = $maxY; } private function hasOverflow(): array @@ -291,7 +347,7 @@ class Container extends Component (int) (self::SCROLLBAR_WIDTH - 4), (int) $thumbHeight, 4, - 4 + 4, ); } @@ -332,7 +388,7 @@ class Container extends Component (int) $thumbWidth, (int) (self::SCROLLBAR_WIDTH - 4), 4, - 4 + 4, ); } } diff --git a/src/Ui/Widget/Label.php b/src/Ui/Widget/Label.php index 16908bf..b1b4077 100644 --- a/src/Ui/Widget/Label.php +++ b/src/Ui/Widget/Label.php @@ -8,6 +8,9 @@ use PHPNative\Ui\Component; class Label extends Component { + private int $intrinsicWidth = 0; + private int $intrinsicHeight = 0; + public function __construct( public string $text = '', public string $style = '', @@ -18,6 +21,28 @@ class Label extends Component $this->text = $text; } + public function layout(null|TextRenderer $textRenderer = null): void + { + // Call parent to compute styles and setup viewports + parent::layout($textRenderer); + + // Measure text to get intrinsic size + if ($textRenderer !== null && $textRenderer->isInitialized()) { + $textStyle = $this->computedStyles[Text::class] ?? new Text(); + [$this->intrinsicWidth, $this->intrinsicHeight] = $textRenderer->measureText($this->text, $textStyle->size); + // Adjust viewport to fit text if no explicit size + if (!isset($this->computedStyles[\PHPNative\Tailwind\Style\Width::class])) { + $this->viewport->width = $this->intrinsicWidth; + $this->contentViewport->width = $this->intrinsicWidth; + } + + if (!isset($this->computedStyles[\PHPNative\Tailwind\Style\Height::class])) { + $this->viewport->height = $this->intrinsicHeight; + $this->contentViewport->height = $this->intrinsicHeight; + } + } + } + public function renderContent(null|TextRenderer $textRenderer = null): void { if (!$this->visible || $textRenderer === null || !$textRenderer->isInitialized()) {