test
This commit is contained in:
parent
899499f2e5
commit
6b021058c6
135
examples/simple_two_windows.php
Normal file
135
examples/simple_two_windows.php
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// Enable event debugging (optional - set to false to disable)
|
||||||
|
define('DEBUG_EVENTS', true);
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../vendor/autoload.php';
|
||||||
|
|
||||||
|
use PHPNative\Framework\Application;
|
||||||
|
use PHPNative\Ui\Widget\Button;
|
||||||
|
use PHPNative\Ui\Widget\Container;
|
||||||
|
use PHPNative\Ui\Widget\Label;
|
||||||
|
|
||||||
|
echo "Starting two-window test application...\n";
|
||||||
|
echo 'DEBUG_EVENTS: ' . (DEBUG_EVENTS ? 'enabled' : 'disabled') . "\n";
|
||||||
|
|
||||||
|
// Create application
|
||||||
|
$app = new Application();
|
||||||
|
|
||||||
|
// Frame counter for window 1
|
||||||
|
$window1FrameCount = 0;
|
||||||
|
|
||||||
|
echo "Creating Window 1...\n";
|
||||||
|
$window1 = $app->createWindow('Window 1', 400, 300, 100, 100);
|
||||||
|
$container1 = new Container('flex flex-col p-6 gap-4 bg-blue-100');
|
||||||
|
|
||||||
|
$title1 = new Label(
|
||||||
|
text: 'Window 1',
|
||||||
|
style: 'text-2xl text-blue-900',
|
||||||
|
);
|
||||||
|
|
||||||
|
$frameLabel1 = new Label(
|
||||||
|
text: 'Frame: 0',
|
||||||
|
style: 'text-base text-blue-700',
|
||||||
|
);
|
||||||
|
|
||||||
|
$button1 = new Button(
|
||||||
|
text: 'Click Me (Window 1)',
|
||||||
|
style: 'bg-blue-500 hover:bg-blue-700 text-white p-4 rounded',
|
||||||
|
);
|
||||||
|
|
||||||
|
$clickCount1 = 0;
|
||||||
|
$button1->setOnClick(function () use (&$clickCount1, $button1) {
|
||||||
|
$clickCount1++;
|
||||||
|
$button1->setText("Clicked {$clickCount1} times");
|
||||||
|
echo "Window 1 button clicked! Count: {$clickCount1}\n";
|
||||||
|
});
|
||||||
|
|
||||||
|
$container1->addComponent($title1);
|
||||||
|
$container1->addComponent($frameLabel1);
|
||||||
|
$container1->addComponent($button1);
|
||||||
|
$window1->setRoot($container1);
|
||||||
|
|
||||||
|
// Frame counter for window 2
|
||||||
|
$window2FrameCount = 0;
|
||||||
|
|
||||||
|
echo "Creating Window 2...\n";
|
||||||
|
$window2 = $app->createWindow('Window 2', 400, 300, 520, 100);
|
||||||
|
$container2 = new Container('flex flex-col p-6 gap-4 bg-green-100');
|
||||||
|
|
||||||
|
$title2 = new Label(
|
||||||
|
text: 'Window 2',
|
||||||
|
style: 'text-2xl text-green-900',
|
||||||
|
);
|
||||||
|
|
||||||
|
$frameLabel2 = new Label(
|
||||||
|
text: 'Frame: 0',
|
||||||
|
style: 'text-base text-green-700',
|
||||||
|
);
|
||||||
|
|
||||||
|
$button2 = new Button(
|
||||||
|
text: 'Click Me (Window 2)',
|
||||||
|
style: 'bg-green-500 hover:bg-green-700 text-white p-4 rounded',
|
||||||
|
);
|
||||||
|
|
||||||
|
$clickCount2 = 0;
|
||||||
|
$button2->setOnClick(function () use (&$clickCount2, $button2) {
|
||||||
|
$clickCount2++;
|
||||||
|
$button2->setText("Clicked {$clickCount2} times");
|
||||||
|
echo "Window 2 button clicked! Count: {$clickCount2}\n";
|
||||||
|
});
|
||||||
|
|
||||||
|
$container2->addComponent($title2);
|
||||||
|
$container2->addComponent($frameLabel2);
|
||||||
|
$container2->addComponent($button2);
|
||||||
|
$window2->setRoot($container2);
|
||||||
|
|
||||||
|
echo "Starting main loop...\n";
|
||||||
|
echo "Click the buttons to test interaction!\n";
|
||||||
|
echo "Close all windows to exit.\n\n";
|
||||||
|
|
||||||
|
// Custom run loop with frame counter updates
|
||||||
|
$running = true;
|
||||||
|
while ($running && count($app->getWindows()) > 0) {
|
||||||
|
// Update frame counters
|
||||||
|
$window1FrameCount++;
|
||||||
|
$window2FrameCount++;
|
||||||
|
$frameLabel1->setText("Frame: {$window1FrameCount}");
|
||||||
|
$frameLabel2->setText("Frame: {$window2FrameCount}");
|
||||||
|
|
||||||
|
// Layout all windows FIRST (sets window references and calculates positions)
|
||||||
|
foreach ($app->getWindows() as $windowId => $window) {
|
||||||
|
$window->layout();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle events for all windows (now that layout is done)
|
||||||
|
foreach ($app->getWindows() as $windowId => $window) {
|
||||||
|
$window->handleEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update async tasks (global)
|
||||||
|
PHPNative\Async\TaskManager::getInstance()->update();
|
||||||
|
|
||||||
|
// Update all windows
|
||||||
|
foreach ($app->getWindows() as $windowId => $window) {
|
||||||
|
$window->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render all windows
|
||||||
|
foreach ($app->getWindows() as $windowId => $window) {
|
||||||
|
$window->render();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove closed windows
|
||||||
|
$windowsCopy = $app->getWindows();
|
||||||
|
foreach ($windowsCopy as $windowId => $window) {
|
||||||
|
if ($window->shouldClose()) {
|
||||||
|
echo "Window {$windowId} closing...\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit frame rate to ~60 FPS
|
||||||
|
usleep(16666);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Application exited.\n";
|
||||||
@ -8,7 +8,7 @@ use PHPNative\Ui\Window;
|
|||||||
|
|
||||||
class Application
|
class Application
|
||||||
{
|
{
|
||||||
private static ?Application $instance = null;
|
private static null|Application $instance = null;
|
||||||
protected array $windows = [];
|
protected array $windows = [];
|
||||||
protected int $nextWindowId = 0;
|
protected int $nextWindowId = 0;
|
||||||
protected bool $running = true;
|
protected bool $running = true;
|
||||||
@ -16,12 +16,14 @@ class Application
|
|||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
self::$instance = $this;
|
self::$instance = $this;
|
||||||
|
|
||||||
|
rgfw_setQueueEvents(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get singleton instance
|
* Get singleton instance
|
||||||
*/
|
*/
|
||||||
public static function getInstance(): ?Application
|
public static function getInstance(): null|Application
|
||||||
{
|
{
|
||||||
return self::$instance;
|
return self::$instance;
|
||||||
}
|
}
|
||||||
@ -36,13 +38,8 @@ class Application
|
|||||||
* @param int $y Window Y position (default: 100)
|
* @param int $y Window Y position (default: 100)
|
||||||
* @return Window The created window instance
|
* @return Window The created window instance
|
||||||
*/
|
*/
|
||||||
public function createWindow(
|
public function createWindow(string $title, int $width = 800, int $height = 600, int $x = 100, int $y = 100): Window
|
||||||
string $title,
|
{
|
||||||
int $width = 800,
|
|
||||||
int $height = 600,
|
|
||||||
int $x = 100,
|
|
||||||
int $y = 100
|
|
||||||
): Window {
|
|
||||||
$window = new Window($title, $width, $height, $x, $y);
|
$window = new Window($title, $width, $height, $x, $y);
|
||||||
$windowId = $this->nextWindowId++;
|
$windowId = $this->nextWindowId++;
|
||||||
$this->windows[$windowId] = $window;
|
$this->windows[$windowId] = $window;
|
||||||
@ -72,7 +69,15 @@ class Application
|
|||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
while ($this->running && count($this->windows) > 0) {
|
while ($this->running && count($this->windows) > 0) {
|
||||||
// Handle events for all windows
|
// Layout all windows FIRST (sets window references and calculates positions)
|
||||||
|
foreach ($this->windows as $windowId => $window) {
|
||||||
|
$window->layout();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poll events globally for all windows (event queue mode, if available)
|
||||||
|
rgfw_pollEvents();
|
||||||
|
|
||||||
|
// Handle events for all windows (now that layout is done)
|
||||||
foreach ($this->windows as $windowId => $window) {
|
foreach ($this->windows as $windowId => $window) {
|
||||||
$window->handleEvents();
|
$window->handleEvents();
|
||||||
}
|
}
|
||||||
@ -82,18 +87,17 @@ class Application
|
|||||||
|
|
||||||
// Update all windows
|
// Update all windows
|
||||||
foreach ($this->windows as $windowId => $window) {
|
foreach ($this->windows as $windowId => $window) {
|
||||||
|
if (!$window->shouldClose()) {
|
||||||
$window->update();
|
$window->update();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Layout all windows
|
|
||||||
foreach ($this->windows as $windowId => $window) {
|
|
||||||
$window->layout();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render all windows
|
// Render all windows (skip windows that are closing)
|
||||||
foreach ($this->windows as $windowId => $window) {
|
foreach ($this->windows as $windowId => $window) {
|
||||||
|
if (!$window->shouldClose()) {
|
||||||
$window->render();
|
$window->render();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Remove closed windows
|
// Remove closed windows
|
||||||
foreach ($this->windows as $windowId => $window) {
|
foreach ($this->windows as $windowId => $window) {
|
||||||
|
|||||||
@ -13,6 +13,16 @@ class Background implements Parser
|
|||||||
preg_match_all('/bg-(.*)/', $style, $output_array);
|
preg_match_all('/bg-(.*)/', $style, $output_array);
|
||||||
if (count($output_array[0]) > 0) {
|
if (count($output_array[0]) > 0) {
|
||||||
$colorStyle = $output_array[1][0];
|
$colorStyle = $output_array[1][0];
|
||||||
|
|
||||||
|
// Skip gradient-related classes (gradient directions and color stops)
|
||||||
|
// These require special handling that's not yet implemented
|
||||||
|
if (str_starts_with($colorStyle, 'gradient-') ||
|
||||||
|
str_starts_with($colorStyle, 'from-') ||
|
||||||
|
str_starts_with($colorStyle, 'to-') ||
|
||||||
|
str_starts_with($colorStyle, 'via-')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
$color = Color::parse($colorStyle);
|
$color = Color::parse($colorStyle);
|
||||||
return new \PHPNative\Tailwind\Style\Background($color);
|
return new \PHPNative\Tailwind\Style\Background($color);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,15 +24,19 @@ class Color implements Parser
|
|||||||
preg_match_all('/(\w{1,8})/', $style, $output_array);
|
preg_match_all('/(\w{1,8})/', $style, $output_array);
|
||||||
if (count($output_array[0]) > 0) {
|
if (count($output_array[0]) > 0) {
|
||||||
$color = (string)$output_array[1][0];
|
$color = (string)$output_array[1][0];
|
||||||
|
if (isset($data[$color]['500'])) {
|
||||||
[$red, $green, $blue] = sscanf($data[$color]['500'], "#%02x%02x%02x");
|
[$red, $green, $blue] = sscanf($data[$color]['500'], "#%02x%02x%02x");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
preg_match_all('/(\w{1,8})-(\d{1,3})/', $style, $output_array);
|
preg_match_all('/(\w{1,8})-(\d{1,3})/', $style, $output_array);
|
||||||
if (count($output_array[0]) > 0) {
|
if (count($output_array[0]) > 0) {
|
||||||
$color = (string)$output_array[1][0];
|
$color = (string)$output_array[1][0];
|
||||||
$variant = (string)$output_array[2][0];
|
$variant = (string)$output_array[2][0];
|
||||||
|
if (isset($data[$color][$variant])) {
|
||||||
[$red, $green, $blue] = sscanf($data[$color][$variant], "#%02x%02x%02x");
|
[$red, $green, $blue] = sscanf($data[$color][$variant], "#%02x%02x%02x");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -69,6 +69,11 @@ abstract class Component
|
|||||||
public function setWindow($window): void
|
public function setWindow($window): void
|
||||||
{
|
{
|
||||||
$this->window = $window;
|
$this->window = $window;
|
||||||
|
|
||||||
|
// Recursively set window for all children
|
||||||
|
foreach ($this->children as $child) {
|
||||||
|
$child->setWindow($window);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setPixelRatio($pixelRatio): void
|
public function setPixelRatio($pixelRatio): void
|
||||||
|
|||||||
@ -65,6 +65,20 @@ class Button extends Container
|
|||||||
|
|
||||||
public function handleMouseClick(float $mouseX, float $mouseY, int $button): bool
|
public function handleMouseClick(float $mouseX, float $mouseY, int $button): bool
|
||||||
{
|
{
|
||||||
|
// Debug output
|
||||||
|
if (defined('DEBUG_EVENTS') && DEBUG_EVENTS) {
|
||||||
|
error_log(sprintf(
|
||||||
|
"[Button '%s'] Click at (%.1f, %.1f), bounds: (%.1f, %.1f) to (%.1f, %.1f)",
|
||||||
|
$this->text,
|
||||||
|
$mouseX,
|
||||||
|
$mouseY,
|
||||||
|
$this->viewport->x,
|
||||||
|
$this->viewport->y,
|
||||||
|
$this->viewport->x + $this->viewport->width,
|
||||||
|
$this->viewport->y + $this->viewport->height
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
// Check if click is within button bounds
|
// Check if click is within button bounds
|
||||||
if (
|
if (
|
||||||
$mouseX >= $this->viewport->x &&
|
$mouseX >= $this->viewport->x &&
|
||||||
@ -72,6 +86,10 @@ class Button extends Container
|
|||||||
$mouseY >= $this->viewport->y &&
|
$mouseY >= $this->viewport->y &&
|
||||||
$mouseY <= ($this->viewport->y + $this->viewport->height)
|
$mouseY <= ($this->viewport->y + $this->viewport->height)
|
||||||
) {
|
) {
|
||||||
|
if (defined('DEBUG_EVENTS') && DEBUG_EVENTS) {
|
||||||
|
error_log("[Button '{$this->text}'] Click INSIDE button - executing callback");
|
||||||
|
}
|
||||||
|
|
||||||
// Call async onClick callback if set
|
// Call async onClick callback if set
|
||||||
if ($this->onClickAsync !== null) {
|
if ($this->onClickAsync !== null) {
|
||||||
$task = TaskManager::getInstance()->runAsync($this->onClickAsync['task']);
|
$task = TaskManager::getInstance()->runAsync($this->onClickAsync['task']);
|
||||||
@ -91,7 +109,13 @@ class Button extends Container
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// Propagate to parent if click was outside button
|
|
||||||
return parent::handleMouseClick($mouseX, $mouseY, $button);
|
if (defined('DEBUG_EVENTS') && DEBUG_EVENTS) {
|
||||||
|
error_log("[Button '{$this->text}'] Click OUTSIDE button");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Click was outside button - don't handle it
|
||||||
|
// Don't call parent::handleMouseClick as that would cause double event propagation
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -165,23 +165,44 @@ class Container extends Component
|
|||||||
$height = $childStyles[Height::class] ?? null;
|
$height = $childStyles[Height::class] ?? null;
|
||||||
|
|
||||||
$size = 0;
|
$size = 0;
|
||||||
|
$hasExplicitSize = false;
|
||||||
|
|
||||||
// Check if child has flex-grow
|
// Check if child has flex-grow
|
||||||
if ($childFlex && $childFlex->type !== FlexTypeEnum::none) {
|
if ($childFlex && $childFlex->type !== FlexTypeEnum::none) {
|
||||||
$flexGrowCount++;
|
$flexGrowCount++;
|
||||||
$childSizes[$index] = ['size' => 0, 'flexGrow' => true];
|
$childSizes[$index] = ['size' => 0, 'flexGrow' => true, 'natural' => false];
|
||||||
} else {
|
} else {
|
||||||
// Calculate fixed size from basis, width, or height
|
// Calculate fixed size from basis, width, or height
|
||||||
if ($basis) {
|
if ($basis) {
|
||||||
$size = $this->calculateSize($basis, $availableSpace);
|
$size = $this->calculateSize($basis, $availableSpace);
|
||||||
|
$hasExplicitSize = true;
|
||||||
} elseif ($isRow && $width) {
|
} elseif ($isRow && $width) {
|
||||||
$size = $this->calculateSize($width, $availableSpace);
|
$size = $this->calculateSize($width, $availableSpace);
|
||||||
|
$hasExplicitSize = true;
|
||||||
} elseif (!$isRow && $height) {
|
} elseif (!$isRow && $height) {
|
||||||
$size = $this->calculateSize($height, $availableSpace);
|
$size = $this->calculateSize($height, $availableSpace);
|
||||||
|
$hasExplicitSize = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$hasExplicitSize) {
|
||||||
|
// Need to measure natural size - do a temporary layout
|
||||||
|
$tempViewport = new Viewport(
|
||||||
|
x: $this->contentViewport->x,
|
||||||
|
y: $this->contentViewport->y,
|
||||||
|
width: $isRow ? $availableSpace : $this->contentViewport->width,
|
||||||
|
height: $isRow ? $this->contentViewport->height : $availableSpace,
|
||||||
|
windowWidth: $this->contentViewport->windowWidth,
|
||||||
|
windowHeight: $this->contentViewport->windowHeight,
|
||||||
|
);
|
||||||
|
$child->setViewport($tempViewport);
|
||||||
|
$child->layout($textRenderer);
|
||||||
|
|
||||||
|
// Get natural size
|
||||||
|
$size = $isRow ? $child->getViewport()->width : $child->getViewport()->height;
|
||||||
}
|
}
|
||||||
|
|
||||||
$usedSpace += $size;
|
$usedSpace += $size;
|
||||||
$childSizes[$index] = ['size' => $size, 'flexGrow' => false];
|
$childSizes[$index] = ['size' => $size, 'flexGrow' => false, 'natural' => !$hasExplicitSize];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -481,9 +502,13 @@ class Container extends Component
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Propagate to children if not on scrollbar
|
// Propagate to children if not on scrollbar
|
||||||
|
// Only adjust coordinates if we have active scrolling
|
||||||
|
$adjustedMouseX = $overflow['x'] || $overflow['y'] ? $mouseX + $this->scrollX : $mouseX;
|
||||||
|
$adjustedMouseY = $overflow['x'] || $overflow['y'] ? $mouseY + $this->scrollY : $mouseY;
|
||||||
|
|
||||||
foreach ($this->children as $child) {
|
foreach ($this->children as $child) {
|
||||||
if (method_exists($child, 'handleMouseClick')) {
|
if (method_exists($child, 'handleMouseClick')) {
|
||||||
if ($child->handleMouseClick($mouseX + $this->scrollX, $mouseY + $this->scrollY, $button)) {
|
if ($child->handleMouseClick($adjustedMouseX, $adjustedMouseY, $button)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -495,6 +520,9 @@ class Container extends Component
|
|||||||
public function handleMouseMove(float $mouseX, float $mouseY): void
|
public function handleMouseMove(float $mouseX, float $mouseY): void
|
||||||
{
|
{
|
||||||
parent::handleMouseMove($mouseX, $mouseY);
|
parent::handleMouseMove($mouseX, $mouseY);
|
||||||
|
|
||||||
|
$overflow = $this->hasOverflow();
|
||||||
|
|
||||||
if ($this->isDraggingScrollbarY) {
|
if ($this->isDraggingScrollbarY) {
|
||||||
$deltaY = $mouseY - $this->dragStartY;
|
$deltaY = $mouseY - $this->dragStartY;
|
||||||
$scrollbarHeight = $this->contentViewport->height;
|
$scrollbarHeight = $this->contentViewport->height;
|
||||||
@ -519,10 +547,13 @@ class Container extends Component
|
|||||||
$this->scrollX = max(0, min($maxScroll, $this->scrollStartX + ($scrollRatio * $maxScroll)));
|
$this->scrollX = max(0, min($maxScroll, $this->scrollStartX + ($scrollRatio * $maxScroll)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Propagate to children
|
// Propagate to children - only adjust coordinates if we have active scrolling
|
||||||
|
$adjustedMouseX = $overflow['x'] || $overflow['y'] ? $mouseX + $this->scrollX : $mouseX;
|
||||||
|
$adjustedMouseY = $overflow['x'] || $overflow['y'] ? $mouseY + $this->scrollY : $mouseY;
|
||||||
|
|
||||||
foreach ($this->children as $child) {
|
foreach ($this->children as $child) {
|
||||||
if (method_exists($child, 'handleMouseMove')) {
|
if (method_exists($child, 'handleMouseMove')) {
|
||||||
$child->handleMouseMove($mouseX + $this->scrollX, $mouseY + $this->scrollY);
|
$child->handleMouseMove($adjustedMouseX, $adjustedMouseY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -532,10 +563,14 @@ class Container extends Component
|
|||||||
$this->isDraggingScrollbarX = false;
|
$this->isDraggingScrollbarX = false;
|
||||||
$this->isDraggingScrollbarY = false;
|
$this->isDraggingScrollbarY = false;
|
||||||
|
|
||||||
// Propagate to children
|
// Propagate to children - only adjust coordinates if we have active scrolling
|
||||||
|
$overflow = $this->hasOverflow();
|
||||||
|
$adjustedMouseX = $overflow['x'] || $overflow['y'] ? $mouseX + $this->scrollX : $mouseX;
|
||||||
|
$adjustedMouseY = $overflow['x'] || $overflow['y'] ? $mouseY + $this->scrollY : $mouseY;
|
||||||
|
|
||||||
foreach ($this->children as $child) {
|
foreach ($this->children as $child) {
|
||||||
if (method_exists($child, 'handleMouseRelease')) {
|
if (method_exists($child, 'handleMouseRelease')) {
|
||||||
$child->handleMouseRelease($mouseX + $this->scrollX, $mouseY + $this->scrollY, $button);
|
$child->handleMouseRelease($adjustedMouseX, $adjustedMouseY, $button);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -564,10 +599,13 @@ class Container extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Propagate to children
|
// Propagate to children - only adjust coordinates if we have active scrolling
|
||||||
|
$adjustedMouseX = $overflow['x'] || $overflow['y'] ? $mouseX + $this->scrollX : $mouseX;
|
||||||
|
$adjustedMouseY = $overflow['x'] || $overflow['y'] ? $mouseY + $this->scrollY : $mouseY;
|
||||||
|
|
||||||
foreach ($this->children as $child) {
|
foreach ($this->children as $child) {
|
||||||
if (method_exists($child, 'handleMouseWheel')) {
|
if (method_exists($child, 'handleMouseWheel')) {
|
||||||
if ($child->handleMouseWheel($mouseX + $this->scrollX, $mouseY + $this->scrollY, $deltaY)) {
|
if ($child->handleMouseWheel($adjustedMouseX, $adjustedMouseY, $deltaY)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ use PHPNative\Framework\TextRenderer;
|
|||||||
class Window
|
class Window
|
||||||
{
|
{
|
||||||
private mixed $window = null;
|
private mixed $window = null;
|
||||||
private ?Component $rootComponent = null;
|
private null|Component $rootComponent = null;
|
||||||
private TextRenderer $textRenderer;
|
private TextRenderer $textRenderer;
|
||||||
private float $mouseX = 0;
|
private float $mouseX = 0;
|
||||||
private float $mouseY = 0;
|
private float $mouseY = 0;
|
||||||
@ -15,13 +15,14 @@ class Window
|
|||||||
private bool $shouldBeReLayouted = true;
|
private bool $shouldBeReLayouted = true;
|
||||||
private float $pixelRatio = 2;
|
private float $pixelRatio = 2;
|
||||||
private bool $shouldClose = false;
|
private bool $shouldClose = false;
|
||||||
|
private bool $hasBeenLaidOut = false;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private string $title,
|
private string $title,
|
||||||
private int $width = 800,
|
private int $width = 800,
|
||||||
private int $height = 600,
|
private int $height = 600,
|
||||||
private int $x = 100,
|
private int $x = 100,
|
||||||
private int $y = 100
|
private int $y = 100,
|
||||||
) {
|
) {
|
||||||
// Create window
|
// Create window
|
||||||
$this->window = rgfw_createWindow($title, $x, $y, $width, $height, 0);
|
$this->window = rgfw_createWindow($title, $x, $y, $width, $height, 0);
|
||||||
@ -56,10 +57,16 @@ class Window
|
|||||||
{
|
{
|
||||||
$this->rootComponent = $component;
|
$this->rootComponent = $component;
|
||||||
$this->shouldBeReLayouted = true;
|
$this->shouldBeReLayouted = true;
|
||||||
|
$this->hasBeenLaidOut = false;
|
||||||
|
|
||||||
|
// Layout immediately to prevent black screen on first render
|
||||||
|
// This is especially important for windows created during event handling
|
||||||
|
$this->layout();
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRoot(): ?Component
|
public function getRoot(): null|Component
|
||||||
{
|
{
|
||||||
return $this->rootComponent;
|
return $this->rootComponent;
|
||||||
}
|
}
|
||||||
@ -99,7 +106,27 @@ class Window
|
|||||||
*/
|
*/
|
||||||
public function handleEvents(): void
|
public function handleEvents(): void
|
||||||
{
|
{
|
||||||
while ($event = rgfw_window_checkEvent($this->window)) {
|
// Limit events processed per frame to prevent one window from blocking others
|
||||||
|
// This is especially important in multi-window scenarios
|
||||||
|
$maxEventsPerFrame = 20;
|
||||||
|
$eventsProcessed = 0;
|
||||||
|
|
||||||
|
while ($event = rgfw_window_checkQueuedEvent($this->window)) {
|
||||||
|
$eventsProcessed++;
|
||||||
|
|
||||||
|
// Debug output - can be removed later
|
||||||
|
if (defined('DEBUG_EVENTS') && DEBUG_EVENTS) {
|
||||||
|
$eventTypes = [
|
||||||
|
RGFW_quit => 'QUIT',
|
||||||
|
RGFW_keyPressed => 'KEY_PRESSED',
|
||||||
|
RGFW_mouseButtonPressed => 'MOUSE_PRESSED',
|
||||||
|
RGFW_mouseButtonReleased => 'MOUSE_RELEASED',
|
||||||
|
RGFW_mousePosChanged => 'MOUSE_MOVE',
|
||||||
|
];
|
||||||
|
$typeName = $eventTypes[$event['type']] ?? ('UNKNOWN(' . $event['type'] . ')');
|
||||||
|
error_log("[{$this->title}] Event: {$typeName}");
|
||||||
|
}
|
||||||
|
|
||||||
switch ($event['type']) {
|
switch ($event['type']) {
|
||||||
case RGFW_quit:
|
case RGFW_quit:
|
||||||
$this->shouldClose = true;
|
$this->shouldClose = true;
|
||||||
@ -181,6 +208,17 @@ class Window
|
|||||||
*/
|
*/
|
||||||
public function update(): void
|
public function update(): void
|
||||||
{
|
{
|
||||||
|
// Update hover states based on current mouse position
|
||||||
|
// This ensures hover works even when the window doesn't have focus
|
||||||
|
if ($this->rootComponent && function_exists('rgfw_window_getMousePoint')) {
|
||||||
|
$mousePos = rgfw_window_getMousePoint($this->window);
|
||||||
|
if ($mousePos !== false) {
|
||||||
|
$this->mouseX = $mousePos[0] ?? $this->mouseX;
|
||||||
|
$this->mouseY = $mousePos[1] ?? $this->mouseY;
|
||||||
|
$this->rootComponent->handleMouseMove($this->mouseX, $this->mouseY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->rootComponent) {
|
if ($this->rootComponent) {
|
||||||
$this->rootComponent->update();
|
$this->rootComponent->update();
|
||||||
}
|
}
|
||||||
@ -191,11 +229,14 @@ class Window
|
|||||||
*/
|
*/
|
||||||
public function layout(): void
|
public function layout(): void
|
||||||
{
|
{
|
||||||
if ($this->rootComponent && $this->shouldBeReLayouted) {
|
if ($this->rootComponent) {
|
||||||
|
// Always layout for now - we can optimize this later by tracking what changed
|
||||||
|
// The shouldBeReLayouted flag was preventing proper layout updates
|
||||||
$this->rootComponent->setViewport($this->viewport);
|
$this->rootComponent->setViewport($this->viewport);
|
||||||
$this->rootComponent->setWindow($this->window);
|
$this->rootComponent->setWindow($this->window);
|
||||||
$this->rootComponent->layout($this->textRenderer);
|
$this->rootComponent->layout($this->textRenderer);
|
||||||
$this->shouldBeReLayouted = false;
|
$this->shouldBeReLayouted = false;
|
||||||
|
$this->hasBeenLaidOut = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,15 +245,17 @@ class Window
|
|||||||
*/
|
*/
|
||||||
public function render(): void
|
public function render(): void
|
||||||
{
|
{
|
||||||
rsgl_clear($this->window, 255, 255, 255, 0);
|
// Always clear the window to prevent black screens (white background, fully opaque)
|
||||||
|
rsgl_clear($this->window, 255, 255, 255, 255);
|
||||||
|
|
||||||
// Render root component tree
|
// Only render content if window has been laid out
|
||||||
if ($this->rootComponent) {
|
// This can happen when windows are created during async callbacks
|
||||||
|
if ($this->hasBeenLaidOut && $this->rootComponent) {
|
||||||
$this->rootComponent->render($this->textRenderer);
|
$this->rootComponent->render($this->textRenderer);
|
||||||
$this->rootComponent->renderContent($this->textRenderer);
|
$this->rootComponent->renderContent($this->textRenderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render to screen
|
// Always swap buffers to display the cleared window
|
||||||
rsgl_render($this->window);
|
rsgl_render($this->window);
|
||||||
rgfw_window_swapBuffers($this->window);
|
rgfw_window_swapBuffers($this->window);
|
||||||
}
|
}
|
||||||
|
|||||||
75
test_queue_events.php
Normal file
75
test_queue_events.php
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
/* Test multi-window with event queue mode */
|
||||||
|
|
||||||
|
// Enable event queue mode
|
||||||
|
rgfw_setQueueEvents(true);
|
||||||
|
|
||||||
|
$win1 = rgfw_createWindow("Window 1", 100, 100, 400, 300, 0);
|
||||||
|
$win2 = rgfw_createWindow("Window 2", 550, 100, 400, 300, 0);
|
||||||
|
|
||||||
|
rsgl_init($win1);
|
||||||
|
rsgl_init($win2);
|
||||||
|
|
||||||
|
echo "Event queue mode enabled. Testing with 2 windows...\n";
|
||||||
|
echo "Try clicking in each window to test mouse events\n";
|
||||||
|
|
||||||
|
$running = true;
|
||||||
|
$frameCount = 0;
|
||||||
|
|
||||||
|
while ($running) {
|
||||||
|
// Poll events for all windows
|
||||||
|
rgfw_pollEvents();
|
||||||
|
|
||||||
|
// Check events for window 1
|
||||||
|
while ($event = rgfw_window_checkQueuedEvent($win1)) {
|
||||||
|
echo "Window 1 - Event type: {$event['type']}\n";
|
||||||
|
|
||||||
|
if ($event['type'] == RGFW_quit) {
|
||||||
|
$running = false;
|
||||||
|
} elseif ($event['type'] == RGFW_mouseButtonPressed) {
|
||||||
|
echo " Window 1 - Mouse clicked! Button: {$event['button']}\n";
|
||||||
|
} elseif ($event['type'] == RGFW_mousePosChanged) {
|
||||||
|
echo " Window 1 - Mouse moved to: {$event[0]}, {$event[1]}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check events for window 2
|
||||||
|
while ($event = rgfw_window_checkQueuedEvent($win2)) {
|
||||||
|
echo "Window 2 - Event type: {$event['type']}\n";
|
||||||
|
|
||||||
|
if ($event['type'] == RGFW_quit) {
|
||||||
|
$running = false;
|
||||||
|
} elseif ($event['type'] == RGFW_mouseButtonPressed) {
|
||||||
|
echo " Window 2 - Mouse clicked! Button: {$event['button']}\n";
|
||||||
|
} elseif ($event['type'] == RGFW_mousePosChanged) {
|
||||||
|
echo " Window 2 - Mouse moved to: {$event[0]}, {$event[1]}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render window 1 (red background)
|
||||||
|
rsgl_clear($win1, 255, 0, 0, 255);
|
||||||
|
rsgl_drawRectF($win1, 150, 100, 100, 100);
|
||||||
|
rgfw_window_swapBuffers($win1);
|
||||||
|
|
||||||
|
// Render window 2 (blue background)
|
||||||
|
rsgl_clear($win2, 0, 0, 255, 255);
|
||||||
|
rsgl_drawRectF($win2, 150, 100, 100, 100);
|
||||||
|
rgfw_window_swapBuffers($win2);
|
||||||
|
|
||||||
|
$frameCount++;
|
||||||
|
if ($frameCount % 100 == 0) {
|
||||||
|
echo "Frame $frameCount - Both windows running\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if windows should close
|
||||||
|
if (rgfw_window_shouldClose($win1) || rgfw_window_shouldClose($win2)) {
|
||||||
|
$running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
usleep(16000); // ~60 FPS
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Test finished after $frameCount frames\n";
|
||||||
|
|
||||||
|
rgfw_window_close($win1);
|
||||||
|
rgfw_window_close($win2);
|
||||||
Loading…
Reference in New Issue
Block a user