Fixed Scroll
This commit is contained in:
parent
29102c567c
commit
5700809299
@ -11,10 +11,11 @@ if (PHP_VERSION_ID < 80100) {
|
||||
}
|
||||
|
||||
$app = new \PHPNative\Framework\Application('ToDo Demo', 700, 500);
|
||||
$scrollContainer = new Container(style: 'bg-red m-4 p-4');
|
||||
$container = new Container('m-10 p-10 bg-lime-500');
|
||||
$scrollContainer = new Container(style: 'overflow-y-auto bg-red m-4 p-4');
|
||||
|
||||
// Add many items to trigger overflow
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
for ($i = 1; $i <= 200; $i++) {
|
||||
$item = new Container(style: 'bg-blue-500 m-2 p-3');
|
||||
$label = new Label(
|
||||
text: "Item {$i}",
|
||||
@ -23,5 +24,6 @@ for ($i = 1; $i <= 5; $i++) {
|
||||
$item->addComponent($label);
|
||||
$scrollContainer->addComponent($item);
|
||||
}
|
||||
$app->setRoot($scrollContainer);
|
||||
$container->addComponent($scrollContainer);
|
||||
$app->setRoot($container);
|
||||
$app->run();
|
||||
|
||||
@ -151,7 +151,6 @@ class Application
|
||||
|
||||
case RGFW_mouseScroll:
|
||||
$deltaY = $event[1] ?? 0;
|
||||
var_dump($event);
|
||||
|
||||
// Propagate wheel to root component
|
||||
if ($this->rootComponent) {
|
||||
|
||||
@ -35,6 +35,33 @@ abstract class Component
|
||||
return $this->viewport;
|
||||
}
|
||||
|
||||
public function getContentViewport(): Viewport
|
||||
{
|
||||
return $this->contentViewport;
|
||||
}
|
||||
|
||||
public function setContentViewport(Viewport $viewport): void
|
||||
{
|
||||
$this->contentViewport = $viewport;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply offset to viewport and all child viewports recursively
|
||||
*/
|
||||
public function applyScrollOffset(int $offsetX, int $offsetY): void
|
||||
{
|
||||
// Offset this component's viewports
|
||||
$this->viewport->x = (int) ($this->viewport->x - $offsetX);
|
||||
$this->viewport->y = (int) ($this->viewport->y - $offsetY);
|
||||
$this->contentViewport->x = (int) ($this->contentViewport->x - $offsetX);
|
||||
$this->contentViewport->y = (int) ($this->contentViewport->y - $offsetY);
|
||||
|
||||
// Recursively apply to all children
|
||||
foreach ($this->children as $child) {
|
||||
$child->applyScrollOffset($offsetX, $offsetY);
|
||||
}
|
||||
}
|
||||
|
||||
public function setWindow($window): void
|
||||
{
|
||||
$this->window = $window;
|
||||
|
||||
@ -45,6 +45,17 @@ class Container extends Component
|
||||
$flexStyle = $this->computedStyles[Flex::class] ?? null;
|
||||
$hasFlexGrow = $flexStyle && $flexStyle->type !== FlexTypeEnum::none;
|
||||
|
||||
// Check if overflow is set (if yes, container should not auto-expand)
|
||||
$overflow = $this->computedStyles[\PHPNative\Tailwind\Style\Overflow::class] ?? null;
|
||||
$hasOverflowX = $overflow && in_array($overflow->x, [
|
||||
\PHPNative\Tailwind\Style\OverflowEnum::scroll,
|
||||
\PHPNative\Tailwind\Style\OverflowEnum::auto,
|
||||
]);
|
||||
$hasOverflowY = $overflow && in_array($overflow->y, [
|
||||
\PHPNative\Tailwind\Style\OverflowEnum::scroll,
|
||||
\PHPNative\Tailwind\Style\OverflowEnum::auto,
|
||||
]);
|
||||
|
||||
// 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 =
|
||||
@ -55,10 +66,17 @@ class Container extends Component
|
||||
preg_match('/(?<![a-z-])flex(?![a-z-])/', $this->style)
|
||||
);
|
||||
|
||||
// If overflow is set, treat it as explicit size (don't auto-expand)
|
||||
$hasExplicitWidth =
|
||||
isset($this->computedStyles[Width::class]) || isset($this->computedStyles[Basis::class]) || $hasFlexGrow;
|
||||
isset($this->computedStyles[Width::class]) ||
|
||||
isset($this->computedStyles[Basis::class]) ||
|
||||
$hasFlexGrow ||
|
||||
$hasOverflowX;
|
||||
$hasExplicitHeight =
|
||||
isset($this->computedStyles[Height::class]) || isset($this->computedStyles[Basis::class]) || $hasFlexGrow;
|
||||
isset($this->computedStyles[Height::class]) ||
|
||||
isset($this->computedStyles[Basis::class]) ||
|
||||
$hasFlexGrow ||
|
||||
$hasOverflowY;
|
||||
|
||||
// Check if this container has flex layout
|
||||
if ($isFlexContainer) {
|
||||
@ -69,11 +87,14 @@ class Container extends Component
|
||||
|
||||
foreach ($this->children as $child) {
|
||||
// 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
|
||||
$childViewport = new Viewport(
|
||||
x: $this->contentViewport->x,
|
||||
y: (int) $currentY,
|
||||
width: $this->contentViewport->width,
|
||||
height: $this->contentViewport->height,
|
||||
windowWidth: $this->contentViewport->windowWidth,
|
||||
windowHeight: $this->contentViewport->windowHeight
|
||||
);
|
||||
|
||||
$child->setViewport($childViewport);
|
||||
$child->layout($textRenderer);
|
||||
@ -171,21 +192,27 @@ class Container extends Component
|
||||
$size = $childSize['flexGrow'] ? $flexGrowSize : $childSize['size'];
|
||||
|
||||
// Create viewport for child
|
||||
$childViewport = new Viewport();
|
||||
|
||||
if ($isRow) {
|
||||
// Flex row
|
||||
$childViewport->x = (int) $currentPosition;
|
||||
$childViewport->y = $this->contentViewport->y;
|
||||
$childViewport->width = $size;
|
||||
$childViewport->height = $this->contentViewport->height;
|
||||
$childViewport = new Viewport(
|
||||
x: (int) $currentPosition,
|
||||
y: $this->contentViewport->y,
|
||||
width: $size,
|
||||
height: $this->contentViewport->height,
|
||||
windowWidth: $this->contentViewport->windowWidth,
|
||||
windowHeight: $this->contentViewport->windowHeight
|
||||
);
|
||||
$currentPosition += $size;
|
||||
} else {
|
||||
// Flex column
|
||||
$childViewport->x = $this->contentViewport->x;
|
||||
$childViewport->y = (int) $currentPosition;
|
||||
$childViewport->width = $this->contentViewport->width;
|
||||
$childViewport->height = $size;
|
||||
$childViewport = new Viewport(
|
||||
x: $this->contentViewport->x,
|
||||
y: (int) $currentPosition,
|
||||
width: $this->contentViewport->width,
|
||||
height: $size,
|
||||
windowWidth: $this->contentViewport->windowWidth,
|
||||
windowHeight: $this->contentViewport->windowHeight
|
||||
);
|
||||
$currentPosition += $size;
|
||||
}
|
||||
|
||||
@ -270,32 +297,52 @@ class Container extends Component
|
||||
// Apply scroll offset to children
|
||||
if ($overflow['x'] || $overflow['y']) {
|
||||
// Enable scissor test for clipping
|
||||
rsgl_scissorStart(
|
||||
$this->window,
|
||||
(int) $this->contentViewport->x,
|
||||
(int) $this->contentViewport->y,
|
||||
(int) $this->contentViewport->width,
|
||||
(int) $this->contentViewport->height,
|
||||
);
|
||||
$scissorX = (int) $this->contentViewport->x;
|
||||
$scissorY = (int) $this->contentViewport->y;
|
||||
$scissorW = (int) $this->contentViewport->width;
|
||||
$scissorH = (int) $this->contentViewport->height;
|
||||
|
||||
// Render children with scroll offset
|
||||
// IMPORTANT: RSGL uses batch rendering!
|
||||
// We need to flush the batch before/after scissor to ensure correct clipping
|
||||
|
||||
// Step 1: Flush any pending draw calls before scissor
|
||||
rsgl_render($this->window);
|
||||
|
||||
// Step 2: Enable scissor test for clipping
|
||||
$windowHeight = $this->contentViewport->windowHeight;
|
||||
$invertedY = $windowHeight - ($scissorY + $scissorH);
|
||||
|
||||
rsgl_scissorStart($this->window, $scissorX, $invertedY, $scissorW, $scissorH);
|
||||
|
||||
// Step 3: Render children with scroll offset (batches draw calls)
|
||||
foreach ($this->children as $child) {
|
||||
$child->setWindow($this->window);
|
||||
|
||||
// Apply scroll offset
|
||||
// Apply scroll offset recursively to child and all its descendants
|
||||
$child->applyScrollOffset((int) $this->scrollX, (int) $this->scrollY);
|
||||
|
||||
// Performance optimization: skip completely invisible children
|
||||
$childViewport = $child->getViewport();
|
||||
$childViewport->x = (int) ($childViewport->x - $this->scrollX);
|
||||
$childViewport->y = (int) ($childViewport->y - $this->scrollY);
|
||||
$isVisible =
|
||||
$childViewport->x + $childViewport->width > $scissorX &&
|
||||
$childViewport->x < $scissorX + $scissorW &&
|
||||
$childViewport->y + $childViewport->height > $scissorY &&
|
||||
$childViewport->y < $scissorY + $scissorH;
|
||||
|
||||
$child->render($textRenderer);
|
||||
$child->renderContent($textRenderer);
|
||||
if ($isVisible) {
|
||||
// Render - batches draw calls
|
||||
$child->render($textRenderer);
|
||||
$child->renderContent($textRenderer);
|
||||
}
|
||||
|
||||
// Restore position
|
||||
$childViewport->x = (int) ($childViewport->x + $this->scrollX);
|
||||
$childViewport->y = (int) ($childViewport->y + $this->scrollY);
|
||||
// Restore by applying negative offset
|
||||
$child->applyScrollOffset((int) -$this->scrollX, (int) -$this->scrollY);
|
||||
}
|
||||
|
||||
// Disable scissor test
|
||||
// Step 4: Flush the batch while scissor is still active
|
||||
rsgl_render($this->window);
|
||||
|
||||
// Step 5: Disable scissor test
|
||||
rsgl_scissorEnd($this->window);
|
||||
|
||||
// Render scrollbars
|
||||
|
||||
Loading…
Reference in New Issue
Block a user