First Scroll
This commit is contained in:
parent
f3fe8934ef
commit
4e510b2ac2
64
examples/OverflowScroll.php
Normal file
64
examples/OverflowScroll.php
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../vendor/autoload.php';
|
||||||
|
|
||||||
|
use PHPNative\Framework\Application;
|
||||||
|
use PHPNative\Ui\Widget\Container;
|
||||||
|
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('Overflow Scroll Demo', 800, 600);
|
||||||
|
|
||||||
|
// Main container
|
||||||
|
$mainContainer = new Container(style: 'p-4 bg-gray-100');
|
||||||
|
|
||||||
|
// Title
|
||||||
|
$title = new Label(text: 'Overflow Scroll Demo', style: 'text-xl text-black p-2');
|
||||||
|
$mainContainer->addComponent($title);
|
||||||
|
|
||||||
|
// Example 1: Vertical scroll with overflow-y-auto
|
||||||
|
$scrollContainer = new Container(style: 'overflow-y-auto bg-white m-4 p-4 h-200');
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
$label = new Label(
|
||||||
|
text: "Item {$i} - Scroll vertically with mouse wheel or drag the scrollbar",
|
||||||
|
style: 'text-white'
|
||||||
|
);
|
||||||
|
$item->addComponent($label);
|
||||||
|
$scrollContainer->addComponent($item);
|
||||||
|
}
|
||||||
|
|
||||||
|
$mainContainer->addComponent($scrollContainer);
|
||||||
|
|
||||||
|
// Example 2: Horizontal scroll with overflow-x-auto
|
||||||
|
$label2 = new Label(text: 'Horizontal Scroll:', style: 'text-black p-2');
|
||||||
|
$mainContainer->addComponent($label2);
|
||||||
|
|
||||||
|
$horizontalScroll = new Container(style: 'flex flex-row overflow-x-auto bg-white m-4 p-4 h-100');
|
||||||
|
|
||||||
|
for ($i = 1; $i <= 10; $i++) {
|
||||||
|
$box = new Container(style: 'w-150 bg-green-500 m-2 p-3 rounded-lg');
|
||||||
|
$boxLabel = new Label(text: "Box {$i}", style: 'text-white');
|
||||||
|
$box->addComponent($boxLabel);
|
||||||
|
$horizontalScroll->addComponent($box);
|
||||||
|
}
|
||||||
|
|
||||||
|
$mainContainer->addComponent($horizontalScroll);
|
||||||
|
|
||||||
|
// Instructions
|
||||||
|
$instructions = new Container(style: 'bg-yellow-200 p-4 m-4 rounded-lg');
|
||||||
|
$instructionText = new Label(
|
||||||
|
text: 'Use mouse wheel to scroll. Click and drag scrollbars.',
|
||||||
|
style: 'text-black'
|
||||||
|
);
|
||||||
|
$instructions->addComponent($instructionText);
|
||||||
|
$mainContainer->addComponent($instructions);
|
||||||
|
|
||||||
|
$app->setRoot($mainContainer);
|
||||||
|
$app->run();
|
||||||
@ -1,5 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use PHPNative\Ui\Widget\Container;
|
||||||
use PHPNative\Ui\Widget\Label;
|
use PHPNative\Ui\Widget\Label;
|
||||||
|
|
||||||
require_once __DIR__ . '/../vendor/autoload.php';
|
require_once __DIR__ . '/../vendor/autoload.php';
|
||||||
@ -24,6 +25,19 @@ $label = new Label(
|
|||||||
style: 'text-red-500',
|
style: 'text-red-500',
|
||||||
);
|
);
|
||||||
$containerMenu->addComponent($label);
|
$containerMenu->addComponent($label);
|
||||||
|
$scrollContainer = new Container(style: 'overflow-y-auto bg-white 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');
|
||||||
|
$label = new Label(
|
||||||
|
text: "Item {$i} - Scroll vertically with mouse wheel or drag the scrollbar",
|
||||||
|
style: 'text-white',
|
||||||
|
);
|
||||||
|
$item->addComponent($label);
|
||||||
|
$scrollContainer->addComponent($item);
|
||||||
|
}
|
||||||
|
$containerMenu->addComponent($scrollContainer);
|
||||||
$container->addComponent($containerMenu);
|
$container->addComponent($containerMenu);
|
||||||
$app->setRoot($container);
|
$app->setRoot($container);
|
||||||
$app->run();
|
$app->run();
|
||||||
|
|||||||
@ -126,7 +126,7 @@ class Application
|
|||||||
|
|
||||||
// Propagate mouse move to root component
|
// Propagate mouse move to root component
|
||||||
if ($this->rootComponent) {
|
if ($this->rootComponent) {
|
||||||
// $this->rootComponent->handleMouseMove($this->mouseX, $this->mouseY);
|
$this->rootComponent->handleMouseMove($this->mouseX, $this->mouseY);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -138,6 +138,24 @@ class Application
|
|||||||
$this->rootComponent->handleMouseClick($this->mouseX, $this->mouseY, $button);
|
$this->rootComponent->handleMouseClick($this->mouseX, $this->mouseY, $button);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case RGFW_mouseButtonReleased:
|
||||||
|
$button = $event['button'] ?? 0;
|
||||||
|
|
||||||
|
// Propagate release to root component
|
||||||
|
if ($this->rootComponent) {
|
||||||
|
$this->rootComponent->handleMouseRelease($this->mouseX, $this->mouseY, $button);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RGFW_mouseScroll:
|
||||||
|
$deltaY = $event[1] ?? 0;
|
||||||
|
|
||||||
|
// Propagate wheel to root component
|
||||||
|
if ($this->rootComponent) {
|
||||||
|
$this->rootComponent->handleMouseWheel($this->mouseX, $this->mouseY, $deltaY);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,6 +29,11 @@ abstract class Component
|
|||||||
$this->viewport = $viewport;
|
$this->viewport = $viewport;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getViewport(): Viewport
|
||||||
|
{
|
||||||
|
return $this->viewport;
|
||||||
|
}
|
||||||
|
|
||||||
public function setWindow($window): void
|
public function setWindow($window): void
|
||||||
{
|
{
|
||||||
$this->window = $window;
|
$this->window = $window;
|
||||||
@ -49,18 +54,18 @@ abstract class Component
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (isset($this->computedStyles[Margin::class]) && ($m = $this->computedStyles[Margin::class])) {
|
if (isset($this->computedStyles[Margin::class]) && ($m = $this->computedStyles[Margin::class])) {
|
||||||
$this->viewport->x += $m->left;
|
$this->viewport->x = (int) ($this->viewport->x + $m->left);
|
||||||
$this->viewport->width -= $m->right + $m->left;
|
$this->viewport->width -= $m->right + $m->left;
|
||||||
$this->viewport->y += $m->top;
|
$this->viewport->y = (int) ($this->viewport->y + $m->top);
|
||||||
$this->viewport->height -= $m->bottom + $m->top;
|
$this->viewport->height -= $m->bottom + $m->top;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->contentViewport = clone $this->viewport;
|
$this->contentViewport = clone $this->viewport;
|
||||||
|
|
||||||
if (isset($this->computedStyles[Padding::class]) && ($p = $this->computedStyles[Padding::class])) {
|
if (isset($this->computedStyles[Padding::class]) && ($p = $this->computedStyles[Padding::class])) {
|
||||||
$this->contentViewport->x += $p->left;
|
$this->contentViewport->x = (int) ($this->contentViewport->x + $p->left);
|
||||||
$this->contentViewport->width -= $p->right + $p->left;
|
$this->contentViewport->width -= $p->right + $p->left;
|
||||||
$this->contentViewport->y += $p->top;
|
$this->contentViewport->y = (int) ($this->contentViewport->y + $p->top);
|
||||||
$this->contentViewport->height -= $p->bottom + $p->top;
|
$this->contentViewport->height -= $p->bottom + $p->top;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,4 +136,56 @@ abstract class Component
|
|||||||
{
|
{
|
||||||
$this->children[] = $component;
|
$this->children[] = $component;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle mouse click event
|
||||||
|
* @return bool True if event was handled
|
||||||
|
*/
|
||||||
|
public function handleMouseClick(float $mouseX, float $mouseY, int $button): bool
|
||||||
|
{
|
||||||
|
// Default implementation: propagate to children
|
||||||
|
foreach ($this->children as $child) {
|
||||||
|
if ($child->handleMouseClick($mouseX, $mouseY, $button)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle mouse move event
|
||||||
|
*/
|
||||||
|
public function handleMouseMove(float $mouseX, float $mouseY): void
|
||||||
|
{
|
||||||
|
// Default implementation: propagate to children
|
||||||
|
foreach ($this->children as $child) {
|
||||||
|
$child->handleMouseMove($mouseX, $mouseY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle mouse button release event
|
||||||
|
*/
|
||||||
|
public function handleMouseRelease(float $mouseX, float $mouseY, int $button): void
|
||||||
|
{
|
||||||
|
// Default implementation: propagate to children
|
||||||
|
foreach ($this->children as $child) {
|
||||||
|
$child->handleMouseRelease($mouseX, $mouseY, $button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle mouse wheel event
|
||||||
|
* @return bool True if event was handled
|
||||||
|
*/
|
||||||
|
public function handleMouseWheel(float $mouseX, float $mouseY, float $deltaY): bool
|
||||||
|
{
|
||||||
|
// Default implementation: propagate to children
|
||||||
|
foreach ($this->children as $child) {
|
||||||
|
if ($child->handleMouseWheel($mouseX, $mouseY, $deltaY)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,24 @@ use PHPNative\Ui\Viewport;
|
|||||||
|
|
||||||
class Container extends Component
|
class Container extends Component
|
||||||
{
|
{
|
||||||
|
// Scroll state
|
||||||
|
private float $scrollX = 0;
|
||||||
|
private float $scrollY = 0;
|
||||||
|
private float $contentWidth = 0;
|
||||||
|
private float $contentHeight = 0;
|
||||||
|
|
||||||
|
// Scrollbar state
|
||||||
|
private bool $isDraggingScrollbarX = false;
|
||||||
|
private bool $isDraggingScrollbarY = false;
|
||||||
|
private float $dragStartX = 0;
|
||||||
|
private float $dragStartY = 0;
|
||||||
|
private float $scrollStartX = 0;
|
||||||
|
private float $scrollStartY = 0;
|
||||||
|
|
||||||
|
// Scrollbar dimensions
|
||||||
|
private const SCROLLBAR_WIDTH = 12;
|
||||||
|
private const SCROLLBAR_MIN_SIZE = 20;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public string $style = '',
|
public string $style = '',
|
||||||
) {}
|
) {}
|
||||||
@ -25,12 +43,19 @@ class Container extends Component
|
|||||||
parent::layout($textRenderer);
|
parent::layout($textRenderer);
|
||||||
|
|
||||||
// Check if this container has flex layout
|
// Check if this container has flex layout
|
||||||
if (!isset($this->computedStyles[Flex::class])) {
|
if (isset($this->computedStyles[Flex::class])) {
|
||||||
return;
|
$flex = $this->computedStyles[Flex::class];
|
||||||
|
$this->layoutChildren($flex, $textRenderer);
|
||||||
|
} else {
|
||||||
|
// Non-flex layout: just layout children normally
|
||||||
|
foreach ($this->children as $child) {
|
||||||
|
$child->setViewport($this->contentViewport);
|
||||||
|
$child->layout($textRenderer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$flex = $this->computedStyles[Flex::class];
|
// Calculate total content size after children are laid out
|
||||||
$this->layoutChildren($flex, $textRenderer);
|
$this->calculateContentSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function layoutChildren(Flex $flex, null|TextRenderer $textRenderer): void
|
private function layoutChildren(Flex $flex, null|TextRenderer $textRenderer): void
|
||||||
@ -82,7 +107,7 @@ class Container extends Component
|
|||||||
|
|
||||||
// Calculate remaining space for flex-grow items
|
// Calculate remaining space for flex-grow items
|
||||||
$remainingSpace = max(0, $availableSpace - $usedSpace);
|
$remainingSpace = max(0, $availableSpace - $usedSpace);
|
||||||
$flexGrowSize = $flexGrowCount > 0 ? $remainingSpace / $flexGrowCount : 0;
|
$flexGrowSize = $flexGrowCount > 0 ? ($remainingSpace / $flexGrowCount) : 0;
|
||||||
|
|
||||||
// Second pass: assign sizes and position children
|
// Second pass: assign sizes and position children
|
||||||
$currentPosition = $isRow ? $this->contentViewport->x : $this->contentViewport->y;
|
$currentPosition = $isRow ? $this->contentViewport->x : $this->contentViewport->y;
|
||||||
@ -96,7 +121,7 @@ class Container extends Component
|
|||||||
|
|
||||||
if ($isRow) {
|
if ($isRow) {
|
||||||
// Flex row
|
// Flex row
|
||||||
$childViewport->x = $currentPosition;
|
$childViewport->x = (int) $currentPosition;
|
||||||
$childViewport->y = $this->contentViewport->y;
|
$childViewport->y = $this->contentViewport->y;
|
||||||
$childViewport->width = $size;
|
$childViewport->width = $size;
|
||||||
$childViewport->height = $this->contentViewport->height;
|
$childViewport->height = $this->contentViewport->height;
|
||||||
@ -104,7 +129,7 @@ class Container extends Component
|
|||||||
} else {
|
} else {
|
||||||
// Flex column
|
// Flex column
|
||||||
$childViewport->x = $this->contentViewport->x;
|
$childViewport->x = $this->contentViewport->x;
|
||||||
$childViewport->y = $currentPosition;
|
$childViewport->y = (int) $currentPosition;
|
||||||
$childViewport->width = $this->contentViewport->width;
|
$childViewport->width = $this->contentViewport->width;
|
||||||
$childViewport->height = $size;
|
$childViewport->height = $size;
|
||||||
$currentPosition += $size;
|
$currentPosition += $size;
|
||||||
@ -123,4 +148,322 @@ class Container extends Component
|
|||||||
Unit::Percent => ($availableSpace * $style->value) / 100,
|
Unit::Percent => ($availableSpace * $style->value) / 100,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function calculateContentSize(): void
|
||||||
|
{
|
||||||
|
if (empty($this->children)) {
|
||||||
|
$this->contentWidth = $this->contentViewport->width;
|
||||||
|
$this->contentHeight = $this->contentViewport->height;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$maxX = 0;
|
||||||
|
$maxY = 0;
|
||||||
|
|
||||||
|
foreach ($this->children as $child) {
|
||||||
|
$childViewport = $child->getViewport();
|
||||||
|
$maxX = max($maxX, ($childViewport->x + $childViewport->width) - $this->contentViewport->x);
|
||||||
|
$maxY = max($maxY, ($childViewport->y + $childViewport->height) - $this->contentViewport->y);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->contentWidth = max($this->contentViewport->width, $maxX);
|
||||||
|
$this->contentHeight = max($this->contentViewport->height, $maxY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function hasOverflow(): array
|
||||||
|
{
|
||||||
|
$overflow = $this->computedStyles[\PHPNative\Tailwind\Style\Overflow::class] ?? null;
|
||||||
|
if (!$overflow) {
|
||||||
|
return ['x' => false, 'y' => false];
|
||||||
|
}
|
||||||
|
|
||||||
|
$needsScrollX = $this->contentWidth > $this->contentViewport->width;
|
||||||
|
$needsScrollY = $this->contentHeight > $this->contentViewport->height;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'x' =>
|
||||||
|
|
||||||
|
$needsScrollX &&
|
||||||
|
in_array($overflow->x, [
|
||||||
|
\PHPNative\Tailwind\Style\OverflowEnum::scroll,
|
||||||
|
\PHPNative\Tailwind\Style\OverflowEnum::auto,
|
||||||
|
])
|
||||||
|
,
|
||||||
|
'y' =>
|
||||||
|
|
||||||
|
$needsScrollY &&
|
||||||
|
in_array($overflow->y, [
|
||||||
|
\PHPNative\Tailwind\Style\OverflowEnum::scroll,
|
||||||
|
\PHPNative\Tailwind\Style\OverflowEnum::auto,
|
||||||
|
])
|
||||||
|
,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function renderContent(null|TextRenderer $textRenderer = null): void
|
||||||
|
{
|
||||||
|
if (!$this->visible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$overflow = $this->hasOverflow();
|
||||||
|
|
||||||
|
// Save original viewport
|
||||||
|
$originalViewport = $this->contentViewport;
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Render children with scroll offset
|
||||||
|
foreach ($this->children as $child) {
|
||||||
|
$child->setWindow($this->window);
|
||||||
|
|
||||||
|
// Apply scroll offset
|
||||||
|
$childViewport = $child->getViewport();
|
||||||
|
$childViewport->x = (int) ($childViewport->x - $this->scrollX);
|
||||||
|
$childViewport->y = (int) ($childViewport->y - $this->scrollY);
|
||||||
|
|
||||||
|
$child->render($textRenderer);
|
||||||
|
$child->renderContent($textRenderer);
|
||||||
|
|
||||||
|
// Restore position
|
||||||
|
$childViewport->x = (int) ($childViewport->x + $this->scrollX);
|
||||||
|
$childViewport->y = (int) ($childViewport->y + $this->scrollY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable scissor test
|
||||||
|
rsgl_scissorEnd($this->window);
|
||||||
|
|
||||||
|
// Render scrollbars
|
||||||
|
$this->renderScrollbars($overflow);
|
||||||
|
} else {
|
||||||
|
// No overflow, render normally
|
||||||
|
parent::renderContent($textRenderer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderScrollbars(array $overflow): void
|
||||||
|
{
|
||||||
|
$scrollbarColor = [100, 100, 100, 200]; // Gray with some transparency
|
||||||
|
|
||||||
|
// Vertical scrollbar
|
||||||
|
if ($overflow['y']) {
|
||||||
|
$scrollbarHeight = $this->contentViewport->height;
|
||||||
|
$thumbHeight = max(
|
||||||
|
self::SCROLLBAR_MIN_SIZE,
|
||||||
|
($this->contentViewport->height / $this->contentHeight) * $scrollbarHeight,
|
||||||
|
);
|
||||||
|
$maxScroll = $this->contentHeight - $this->contentViewport->height;
|
||||||
|
$thumbY = $this->contentViewport->y + (($this->scrollY / $maxScroll) * ($scrollbarHeight - $thumbHeight));
|
||||||
|
|
||||||
|
$scrollbarX = ($this->contentViewport->x + $this->contentViewport->width) - self::SCROLLBAR_WIDTH;
|
||||||
|
|
||||||
|
// Track
|
||||||
|
rsgl_setColor($this->window, 200, 200, 200, 100);
|
||||||
|
rsgl_drawRectF(
|
||||||
|
$this->window,
|
||||||
|
(int) $scrollbarX,
|
||||||
|
(int) $this->contentViewport->y,
|
||||||
|
(int) self::SCROLLBAR_WIDTH,
|
||||||
|
(int) $scrollbarHeight,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Thumb
|
||||||
|
rsgl_setColor(
|
||||||
|
$this->window,
|
||||||
|
$scrollbarColor[0],
|
||||||
|
$scrollbarColor[1],
|
||||||
|
$scrollbarColor[2],
|
||||||
|
$scrollbarColor[3],
|
||||||
|
);
|
||||||
|
rsgl_drawRoundRectF(
|
||||||
|
$this->window,
|
||||||
|
(int) ($scrollbarX + 2),
|
||||||
|
(int) $thumbY,
|
||||||
|
(int) (self::SCROLLBAR_WIDTH - 4),
|
||||||
|
(int) $thumbHeight,
|
||||||
|
4,
|
||||||
|
4
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Horizontal scrollbar
|
||||||
|
if ($overflow['x']) {
|
||||||
|
$scrollbarWidth = $this->contentViewport->width;
|
||||||
|
$thumbWidth = max(
|
||||||
|
self::SCROLLBAR_MIN_SIZE,
|
||||||
|
($this->contentViewport->width / $this->contentWidth) * $scrollbarWidth,
|
||||||
|
);
|
||||||
|
$maxScroll = $this->contentWidth - $this->contentViewport->width;
|
||||||
|
$thumbX = $this->contentViewport->x + (($this->scrollX / $maxScroll) * ($scrollbarWidth - $thumbWidth));
|
||||||
|
|
||||||
|
$scrollbarY = ($this->contentViewport->y + $this->contentViewport->height) - self::SCROLLBAR_WIDTH;
|
||||||
|
|
||||||
|
// Track
|
||||||
|
rsgl_setColor($this->window, 200, 200, 200, 100);
|
||||||
|
rsgl_drawRectF(
|
||||||
|
$this->window,
|
||||||
|
(int) $this->contentViewport->x,
|
||||||
|
(int) $scrollbarY,
|
||||||
|
(int) $scrollbarWidth,
|
||||||
|
(int) self::SCROLLBAR_WIDTH,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Thumb
|
||||||
|
rsgl_setColor(
|
||||||
|
$this->window,
|
||||||
|
$scrollbarColor[0],
|
||||||
|
$scrollbarColor[1],
|
||||||
|
$scrollbarColor[2],
|
||||||
|
$scrollbarColor[3],
|
||||||
|
);
|
||||||
|
rsgl_drawRoundRectF(
|
||||||
|
$this->window,
|
||||||
|
(int) $thumbX,
|
||||||
|
(int) ($scrollbarY + 2),
|
||||||
|
(int) $thumbWidth,
|
||||||
|
(int) (self::SCROLLBAR_WIDTH - 4),
|
||||||
|
4,
|
||||||
|
4
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleMouseClick(float $mouseX, float $mouseY, int $button): bool
|
||||||
|
{
|
||||||
|
// Check if click is on scrollbar
|
||||||
|
$overflow = $this->hasOverflow();
|
||||||
|
|
||||||
|
if ($overflow['y']) {
|
||||||
|
$scrollbarX = ($this->contentViewport->x + $this->contentViewport->width) - self::SCROLLBAR_WIDTH;
|
||||||
|
if (
|
||||||
|
$mouseX >= $scrollbarX &&
|
||||||
|
$mouseX <= ($scrollbarX + self::SCROLLBAR_WIDTH) &&
|
||||||
|
$mouseY >= $this->contentViewport->y &&
|
||||||
|
$mouseY <= ($this->contentViewport->y + $this->contentViewport->height)
|
||||||
|
) {
|
||||||
|
$this->isDraggingScrollbarY = true;
|
||||||
|
$this->dragStartY = $mouseY;
|
||||||
|
$this->scrollStartY = $this->scrollY;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($overflow['x']) {
|
||||||
|
$scrollbarY = ($this->contentViewport->y + $this->contentViewport->height) - self::SCROLLBAR_WIDTH;
|
||||||
|
if (
|
||||||
|
$mouseY >= $scrollbarY &&
|
||||||
|
$mouseY <= ($scrollbarY + self::SCROLLBAR_WIDTH) &&
|
||||||
|
$mouseX >= $this->contentViewport->x &&
|
||||||
|
$mouseX <= ($this->contentViewport->x + $this->contentViewport->width)
|
||||||
|
) {
|
||||||
|
$this->isDraggingScrollbarX = true;
|
||||||
|
$this->dragStartX = $mouseX;
|
||||||
|
$this->scrollStartX = $this->scrollX;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Propagate to children if not on scrollbar
|
||||||
|
foreach ($this->children as $child) {
|
||||||
|
if (method_exists($child, 'handleMouseClick')) {
|
||||||
|
if ($child->handleMouseClick($mouseX + $this->scrollX, $mouseY + $this->scrollY, $button)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleMouseMove(float $mouseX, float $mouseY): void
|
||||||
|
{
|
||||||
|
if ($this->isDraggingScrollbarY) {
|
||||||
|
$deltaY = $mouseY - $this->dragStartY;
|
||||||
|
$scrollbarHeight = $this->contentViewport->height;
|
||||||
|
$thumbHeight = max(
|
||||||
|
self::SCROLLBAR_MIN_SIZE,
|
||||||
|
($this->contentViewport->height / $this->contentHeight) * $scrollbarHeight,
|
||||||
|
);
|
||||||
|
$maxScroll = $this->contentHeight - $this->contentViewport->height;
|
||||||
|
$scrollRatio = $deltaY / ($scrollbarHeight - $thumbHeight);
|
||||||
|
$this->scrollY = max(0, min($maxScroll, $this->scrollStartY + ($scrollRatio * $maxScroll)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isDraggingScrollbarX) {
|
||||||
|
$deltaX = $mouseX - $this->dragStartX;
|
||||||
|
$scrollbarWidth = $this->contentViewport->width;
|
||||||
|
$thumbWidth = max(
|
||||||
|
self::SCROLLBAR_MIN_SIZE,
|
||||||
|
($this->contentViewport->width / $this->contentWidth) * $scrollbarWidth,
|
||||||
|
);
|
||||||
|
$maxScroll = $this->contentWidth - $this->contentViewport->width;
|
||||||
|
$scrollRatio = $deltaX / ($scrollbarWidth - $thumbWidth);
|
||||||
|
$this->scrollX = max(0, min($maxScroll, $this->scrollStartX + ($scrollRatio * $maxScroll)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Propagate to children
|
||||||
|
foreach ($this->children as $child) {
|
||||||
|
if (method_exists($child, 'handleMouseMove')) {
|
||||||
|
$child->handleMouseMove($mouseX + $this->scrollX, $mouseY + $this->scrollY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleMouseRelease(float $mouseX, float $mouseY, int $button): void
|
||||||
|
{
|
||||||
|
$this->isDraggingScrollbarX = false;
|
||||||
|
$this->isDraggingScrollbarY = false;
|
||||||
|
|
||||||
|
// Propagate to children
|
||||||
|
foreach ($this->children as $child) {
|
||||||
|
if (method_exists($child, 'handleMouseRelease')) {
|
||||||
|
$child->handleMouseRelease($mouseX + $this->scrollX, $mouseY + $this->scrollY, $button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleMouseWheel(float $mouseX, float $mouseY, float $deltaY): bool
|
||||||
|
{
|
||||||
|
$overflow = $this->hasOverflow();
|
||||||
|
|
||||||
|
// Check if mouse is over this container
|
||||||
|
if (
|
||||||
|
$mouseX >= $this->contentViewport->x &&
|
||||||
|
$mouseX <= ($this->contentViewport->x + $this->contentViewport->width) &&
|
||||||
|
$mouseY >= $this->contentViewport->y &&
|
||||||
|
$mouseY <= ($this->contentViewport->y + $this->contentViewport->height)
|
||||||
|
) {
|
||||||
|
if ($overflow['y']) {
|
||||||
|
$maxScroll = $this->contentHeight - $this->contentViewport->height;
|
||||||
|
$this->scrollY = max(0, min($maxScroll, $this->scrollY + ($deltaY * 20)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($overflow['x']) {
|
||||||
|
$maxScroll = $this->contentWidth - $this->contentViewport->width;
|
||||||
|
$this->scrollX = max(0, min($maxScroll, $this->scrollX + ($deltaY * 20)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Propagate to children
|
||||||
|
foreach ($this->children as $child) {
|
||||||
|
if (method_exists($child, 'handleMouseWheel')) {
|
||||||
|
if ($child->handleMouseWheel($mouseX + $this->scrollX, $mouseY + $this->scrollY, $deltaY)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user