First Scroll
This commit is contained in:
parent
4e510b2ac2
commit
29102c567c
63
examples/ButtonExample.php
Normal file
63
examples/ButtonExample.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use PHPNative\Framework\Application;
|
||||
use PHPNative\Ui\Widget\Container;
|
||||
use PHPNative\Ui\Widget\Button;
|
||||
use PHPNative\Ui\Widget\Label;
|
||||
|
||||
// Check PHP version
|
||||
if (PHP_VERSION_ID < 80100) {
|
||||
die("This demo requires PHP 8.1+ for Fiber support.\nYour version: " . PHP_VERSION . "\n");
|
||||
}
|
||||
|
||||
$app = new Application('Button Example', 800, 600);
|
||||
|
||||
// Main container
|
||||
$mainContainer = new Container(style: 'p-10 bg-gray-100');
|
||||
|
||||
// Title
|
||||
$title = new Label(
|
||||
text: 'Button Sizing Example',
|
||||
style: 'text-xl text-black'
|
||||
);
|
||||
$mainContainer->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();
|
||||
27
examples/TestStack.php
Normal file
27
examples/TestStack.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
use PHPNative\Ui\Widget\Container;
|
||||
use PHPNative\Ui\Widget\Label;
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
// Check PHP version
|
||||
if (PHP_VERSION_ID < 80100) {
|
||||
die("This demo requires PHP 8.1+ for Fiber support.\nYour version: " . PHP_VERSION . "\n");
|
||||
}
|
||||
|
||||
$app = new \PHPNative\Framework\Application('ToDo Demo', 700, 500);
|
||||
$scrollContainer = new Container(style: 'bg-red m-4 p-4');
|
||||
|
||||
// Add many items to trigger overflow
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
$item = new Container(style: 'bg-blue-500 m-2 p-3');
|
||||
$label = new Label(
|
||||
text: "Item {$i}",
|
||||
style: 'text-green-500',
|
||||
);
|
||||
$item->addComponent($label);
|
||||
$scrollContainer->addComponent($item);
|
||||
}
|
||||
$app->setRoot($scrollContainer);
|
||||
$app->run();
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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)
|
||||
*
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
36
src/Ui/Widget/Button.php
Normal file
36
src/Ui/Widget/Button.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Ui\Widget;
|
||||
|
||||
use PHPNative\Framework\TextRenderer;
|
||||
|
||||
class Button extends Container
|
||||
{
|
||||
private Label $label;
|
||||
|
||||
public function __construct(
|
||||
public string $text = '',
|
||||
public string $style = '',
|
||||
) {
|
||||
parent::__construct($style);
|
||||
|
||||
// Create label inside button
|
||||
$this->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;
|
||||
}
|
||||
}
|
||||
@ -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('/(?<![a-z-])flex(?![a-z-])/', $this->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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user