first try

This commit is contained in:
Thomas Peterson 2025-10-23 10:40:07 +02:00
parent 11767487ef
commit 899499f2e5
6 changed files with 735 additions and 190 deletions

View File

@ -0,0 +1,189 @@
<?php
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;
// Create application
$app = new Application();
// Create main window
$mainWindow = $app->createWindow('Advanced Multi-Window Demo', 700, 500, 100, 100);
$windowCounter = 1;
// Main UI
$mainContainer = new Container('flex flex-col p-6 gap-4 bg-gray-50');
$title = new Label(
text: 'Advanced Multi-Window Demo',
style: 'text-2xl text-gray-900'
);
$subtitle = new Label(
text: 'Windows mit asynchronen Operationen',
style: 'text-base text-gray-600'
);
// Button to create window with async data loading
$createAsyncWindowButton = new Button(
text: 'Window mit Async-Datenladung',
style: 'bg-purple-500 hover:bg-purple-700 text-white p-4 rounded-lg'
);
$createAsyncWindowButton->setOnClickAsync(
onClickAsync: function() use (&$windowCounter) {
// Simulate loading data in background
sleep(1);
return [
'windowId' => $windowCounter++,
'data' => 'Daten erfolgreich geladen!',
'timestamp' => date('H:i:s'),
'items' => ['Item 1', 'Item 2', 'Item 3']
];
},
onComplete: function($result) use ($app) {
// Create window with loaded data
$windowId = $result['windowId'];
$newWindow = $app->createWindow(
"Data Window #$windowId",
500,
350,
150 + ($windowId * 30),
150 + ($windowId * 30)
);
$container = new Container('flex flex-col p-6 gap-3 bg-green-50');
$titleLabel = new Label(
text: "Data Window #$windowId",
style: 'text-xl text-gray-900'
);
$dataLabel = new Label(
text: $result['data'],
style: 'text-base text-green-700 p-2 bg-white rounded'
);
$timeLabel = new Label(
text: "Geladen um: " . $result['timestamp'],
style: 'text-sm text-gray-600'
);
$itemsLabel = new Label(
text: "Items:\n" . implode("\n", $result['items']),
style: 'text-sm text-gray-700 p-2 bg-white rounded'
);
$closeButton = new Button(
text: 'Schließen',
style: 'bg-red-500 hover:bg-red-700 text-white p-3 rounded'
);
$closeButton->setOnClick(function() use ($newWindow) {
$newWindow->close();
});
$container->addComponent($titleLabel);
$container->addComponent($dataLabel);
$container->addComponent($timeLabel);
$container->addComponent($itemsLabel);
$container->addComponent($closeButton);
$newWindow->setRoot($container);
},
onError: function($error) {
error_log("Error creating window: " . $error->getMessage());
}
);
// Button to create multiple windows at once
$createMultipleButton = new Button(
text: '3 Windows auf einmal erstellen',
style: 'bg-orange-500 hover:bg-orange-700 text-white p-4 rounded-lg'
);
$createMultipleButton->setOnClick(function() use ($app, &$windowCounter) {
for ($i = 0; $i < 3; $i++) {
$currentId = $windowCounter++;
$offset = $currentId * 40;
$newWindow = $app->createWindow(
"Batch Window #$currentId",
400,
250,
200 + $offset,
200 + ($i * 50)
);
$container = new Container('flex flex-col p-4 gap-2 bg-yellow-50');
$label = new Label(
text: "Batch Window #$currentId",
style: 'text-lg text-gray-900'
);
$info = new Label(
text: "Teil einer Batch-Erstellung ($i/3)",
style: 'text-sm text-gray-600'
);
$closeButton = new Button(
text: 'X',
style: 'bg-red-500 hover:bg-red-700 text-white p-2 rounded'
);
$closeButton->setOnClick(function() use ($newWindow) {
$newWindow->close();
});
$container->addComponent($label);
$container->addComponent($info);
$container->addComponent($closeButton);
$newWindow->setRoot($container);
}
});
// Window count label
$windowCountLabel = new Label(
text: 'Offene Windows: 1',
style: 'text-sm text-blue-600 p-2 bg-white rounded'
);
// Update window count periodically
$mainContainer->addComponent($title);
$mainContainer->addComponent($subtitle);
$mainContainer->addComponent($windowCountLabel);
$mainContainer->addComponent($createAsyncWindowButton);
$mainContainer->addComponent($createMultipleButton);
// Info
$info = new Label(
text: 'Die UI bleibt responsive während des Ladens!',
style: 'text-xs text-gray-500 italic mt-4'
);
$mainContainer->addComponent($info);
// Quit button
$quitButton = new Button(
text: 'Beenden',
style: 'bg-red-600 hover:bg-red-800 text-white p-4 rounded-lg'
);
$quitButton->setOnClick(function() use ($app) {
$app->quit();
});
$mainContainer->addComponent($quitButton);
$mainWindow->setRoot($mainContainer);
// Run application
$app->run();

View File

@ -0,0 +1,161 @@
<?php
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;
// Create application
$app = new Application();
// Create main window
$mainWindow = $app->createWindow('Main Window - Multi-Window Demo', 600, 400, 100, 100);
// Counter for new windows
$windowCounter = 1;
// Create main window UI
$mainContainer = new Container('flex flex-col p-6 gap-4 bg-gray-100');
$title = new Label(
text: 'Multi-Window Demo',
style: 'text-2xl text-gray-900'
);
$description = new Label(
text: 'Klicken Sie auf die Buttons, um neue Windows zu erstellen!',
style: 'text-base text-gray-700'
);
$windowCountLabel = new Label(
text: 'Offene Windows: 1',
style: 'text-sm text-blue-600 p-2 bg-white rounded'
);
// Button to create a simple new window
$createSimpleButton = new Button(
text: 'Neues Fenster erstellen',
style: 'bg-blue-500 hover:bg-blue-700 text-white p-4 rounded-lg'
);
$createSimpleButton->setOnClick(function() use ($app, &$windowCounter, $windowCountLabel) {
// Create new window with offset position
$offset = $windowCounter * 30;
$newWindow = $app->createWindow(
"Window #$windowCounter",
500,
300,
150 + $offset,
150 + $offset
);
// Create UI for new window
$container = new Container('flex flex-col p-6 gap-3 bg-gradient-to-br from-purple-400 to-pink-400');
$label = new Label(
text: "Dies ist Window #$windowCounter",
style: 'text-xl text-white'
);
$closeButton = new Button(
text: 'Fenster schließen',
style: 'bg-red-500 hover:bg-red-700 text-white p-3 rounded'
);
$closeButton->setOnClick(function() use ($newWindow) {
$newWindow->close();
});
$container->addComponent($label);
$container->addComponent($closeButton);
$newWindow->setRoot($container);
$windowCounter++;
// Update counter
$windowCountLabel->setText('Offene Windows: ' . $app->getWindowCount());
});
// Button to create a window with interactive content
$createInteractiveButton = new Button(
text: 'Interaktives Fenster erstellen',
style: 'bg-green-500 hover:bg-green-700 text-white p-4 rounded-lg'
);
$createInteractiveButton->setOnClick(function() use ($app, &$windowCounter, $windowCountLabel) {
$offset = $windowCounter * 30;
$newWindow = $app->createWindow(
"Interactive Window #$windowCounter",
600,
400,
150 + $offset,
150 + $offset
);
// Create interactive UI
$container = new Container('flex flex-col p-6 gap-3 bg-blue-50');
$title = new Label(
text: "Interaktives Window #$windowCounter",
style: 'text-xl text-gray-900'
);
$counter = 0;
$counterLabel = new Label(
text: "Zähler: $counter",
style: 'text-lg text-blue-600 p-2 bg-white rounded'
);
$incrementButton = new Button(
text: 'Zähler erhöhen',
style: 'bg-blue-500 hover:bg-blue-700 text-white p-3 rounded'
);
$incrementButton->setOnClick(function() use (&$counter, $counterLabel) {
$counter++;
$counterLabel->setText("Zähler: $counter");
});
$closeButton = new Button(
text: 'Fenster schließen',
style: 'bg-red-500 hover:bg-red-700 text-white p-3 rounded'
);
$closeButton->setOnClick(function() use ($newWindow) {
$newWindow->close();
});
$container->addComponent($title);
$container->addComponent($counterLabel);
$container->addComponent($incrementButton);
$container->addComponent($closeButton);
$newWindow->setRoot($container);
$windowCounter++;
$windowCountLabel->setText('Offene Windows: ' . $app->getWindowCount());
});
// Button to quit application
$quitButton = new Button(
text: 'Alle Fenster schließen und beenden',
style: 'bg-red-600 hover:bg-red-800 text-white p-4 rounded-lg mt-4'
);
$quitButton->setOnClick(function() use ($app) {
$app->quit();
});
// Add all components to main container
$mainContainer->addComponent($title);
$mainContainer->addComponent($description);
$mainContainer->addComponent($windowCountLabel);
$mainContainer->addComponent($createSimpleButton);
$mainContainer->addComponent($createInteractiveButton);
$mainContainer->addComponent($quitButton);
$mainWindow->setRoot($mainContainer);
// Run application
$app->run();

View File

@ -0,0 +1,65 @@
<?php
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;
// Create application
$app = new Application();
// Create a single window (simple usage)
$window = $app->createWindow('Simple Window Example', 800, 600);
// Create UI
$container = new Container('flex flex-col p-6 gap-4 bg-gradient-to-br from-blue-400 to-purple-500');
$title = new Label(
text: 'Willkommen zu PHPNative!',
style: 'text-3xl text-white'
);
$description = new Label(
text: 'Dies ist ein einfaches Beispiel mit einem Window.',
style: 'text-lg text-white'
);
$button = new Button(
text: 'Klick mich!',
style: 'bg-white hover:bg-gray-100 text-blue-600 p-4 rounded-lg'
);
$statusLabel = new Label(
text: '',
style: 'text-base text-white'
);
$clickCount = 0;
$button->setOnClick(function() use ($statusLabel, &$clickCount) {
$clickCount++;
$statusLabel->setText("Button wurde $clickCount mal geklickt!");
});
$quitButton = new Button(
text: 'Beenden',
style: 'bg-red-500 hover:bg-red-700 text-white p-4 rounded-lg mt-4'
);
$quitButton->setOnClick(function() use ($app) {
$app->quit();
});
// Add components
$container->addComponent($title);
$container->addComponent($description);
$container->addComponent($button);
$container->addComponent($statusLabel);
$container->addComponent($quitButton);
// Set window root
$window->setRoot($container);
// Run application
$app->run();

View File

@ -4,229 +4,129 @@ namespace PHPNative\Framework;
use PHPNative\Async\TaskManager;
use PHPNative\Ui\Component;
use PHPNative\Ui\Viewport;
use PHPNative\Ui\Window;
class Application
{
private static ?Application $instance = null;
protected array $windows = [];
protected int $nextWindowId = 0;
protected bool $running = true;
protected $window;
protected $windowWidth;
protected $windowHeight;
protected $title;
protected $running = true;
protected $rootComponent = null;
private TextRenderer $textRenderer;
private $mouseX;
private $mouseY;
private Viewport $viewport;
private bool $shouldBeReLayouted = true;
private float $pixelRatio = 2;
public function __construct(string $title, int $width = 800, int $height = 600)
public function __construct()
{
$this->title = $title;
$this->windowWidth = $width;
$this->windowHeight = $height;
// Create window
$this->window = rgfw_createWindow($title, 100, 100, $width, $height, RGFW_CENTER);
if (!$this->window) {
throw new \Exception('Failed to create window');
}
// Initialize RSGL renderer
if (!rsgl_init($this->window)) {
throw new \Exception('Failed to initialize RSGL renderer');
}
// Initialize text renderer
$this->textRenderer = new TextRenderer($this->window);
if (!$this->textRenderer->init()) {
error_log('Warning: Failed to initialize text renderer. Text rendering will not be available.');
}
// Get actual window size
$size = rgfw_window_getSize($this->window);
$this->windowWidth = $size[0];
$this->windowHeight = $size[1];
$this->viewport = new Viewport(
windowWidth: $this->windowWidth,
windowHeight: $this->windowHeight,
width: $this->windowWidth,
height: $this->windowHeight,
);
self::$instance = $this;
}
public function createWindow(): void
/**
* Get singleton instance
*/
public static function getInstance(): ?Application
{
return self::$instance;
}
/**
* Create a new window
*
* @param string $title Window title
* @param int $width Window width
* @param int $height Window height
* @param int $x Window X position (default: 100)
* @param int $y Window Y position (default: 100)
* @return Window The created window instance
*/
public function createWindow(
string $title,
int $width = 800,
int $height = 600,
int $x = 100,
int $y = 100
): Window {
$window = new Window($title, $width, $height, $x, $y);
$windowId = $this->nextWindowId++;
$this->windows[$windowId] = $window;
return $window;
}
/**
* Get all windows
*/
public function getWindows(): array
{
return $this->windows;
}
/**
* Get window count
*/
public function getWindowCount(): int
{
return count($this->windows);
}
/**
* Main application loop
*/
public function run(): void
{
while ($this->running && !rgfw_window_shouldClose($this->window)) {
$this->handleEvents();
$this->update();
$this->layout();
$this->render();
while ($this->running && count($this->windows) > 0) {
// Handle events for all windows
foreach ($this->windows as $windowId => $window) {
$window->handleEvents();
}
// Update async tasks (global)
TaskManager::getInstance()->update();
// Update all windows
foreach ($this->windows as $windowId => $window) {
$window->update();
}
// Layout all windows
foreach ($this->windows as $windowId => $window) {
$window->layout();
}
// Render all windows
foreach ($this->windows as $windowId => $window) {
$window->render();
}
// Remove closed windows
foreach ($this->windows as $windowId => $window) {
if ($window->shouldClose()) {
$window->cleanup();
unset($this->windows[$windowId]);
}
}
// Limit frame rate to ~60 FPS
usleep(16666);
}
// Cleanup remaining windows
$this->cleanup();
}
/**
* Handle window and input events
*/
protected function handleEvents(): void
{
while ($event = rgfw_window_checkEvent($this->window)) {
switch ($event['type']) {
case RGFW_quit:
$this->running = false;
break;
case RGFW_keyPressed:
$keyCode = $event['keyCode'] ?? 0;
if ($keyCode == RGFW_Escape) {
$this->running = false;
}
break;
case RGFW_windowResized:
// Update window dimensions (from event data)
$newWidth = $event[0] ?? $this->windowWidth;
$newHeight = $event[1] ?? $this->windowHeight;
$this->windowWidth = $newWidth;
$this->windowHeight = $newHeight;
// Update RSGL renderer size and viewport
// This ensures the renderer is properly configured for the new window size
rsgl_updateRendererSize($this->window);
// Update text renderer framebuffer
if ($this->textRenderer && $this->textRenderer->isInitialized()) {
$this->textRenderer->updateFramebuffer($newWidth, $newHeight);
}
$this->viewport->x = 0;
$this->viewport->y = 0;
$this->viewport->windowWidth = $newWidth;
$this->viewport->width = $newWidth;
$this->viewport->height = $newHeight;
$this->viewport->windowHeight = $newHeight;
$this->shouldBeReLayouted = true;
break;
case RGFW_mousePosChanged:
$this->mouseX = $event[0] ?? 0;
$this->mouseY = $event[1] ?? 0;
// Propagate mouse move to root component
if ($this->rootComponent) {
$this->rootComponent->handleMouseMove($this->mouseX, $this->mouseY);
}
break;
case RGFW_mouseButtonPressed:
$button = $event['button'] ?? 0;
// Propagate click to root component
if ($this->rootComponent) {
$this->rootComponent->handleMouseClick($this->mouseX, $this->mouseY, $button);
}
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;
}
}
}
/**
* Update application state
*/
protected function update(): void
{
// Update async tasks
TaskManager::getInstance()->update();
if ($this->rootComponent) {
$this->rootComponent->update();
}
}
/**
* Render the application
*/
protected function render(): void
{
rsgl_clear($this->window, 255, 255, 255, 0);
// Render root component tree
if ($this->rootComponent) {
$this->rootComponent->render($this->textRenderer);
$this->rootComponent->renderContent($this->textRenderer);
}
// Render to screen
rsgl_render($this->window);
rgfw_window_swapBuffers($this->window);
}
protected function layout(): void
{
// Render root component tree
if ($this->rootComponent && $this->shouldBeReLayouted) {
$this->rootComponent->setViewport($this->viewport);
$this->rootComponent->setWindow($this->window);
$this->rootComponent->layout($this->textRenderer);
$this->shouldBeReLayouted = false;
}
}
/**
* Clean up resources
* Clean up all resources
*/
protected function cleanup(): void
{
if ($this->textRenderer) {
$this->textRenderer->free();
foreach ($this->windows as $window) {
$window->cleanup();
}
rsgl_close($this->window);
rgfw_window_close($this->window);
$this->windows = [];
}
/**
* Stop the application
* Stop the application (close all windows)
*/
public function quit(): void
{
$this->running = false;
}
public function setRoot(Component $component): self
{
$this->rootComponent = $component;
return $this;
}
}

View File

@ -22,6 +22,12 @@ class StyleParser
$styleStr = trim($styleStr);
$style = self::parseSimpleStyle($styleStr);
// Skip if style is not recognized
if($style === null) {
continue;
}
$s = new \PHPNative\Tailwind\Model\Style($style);
$mq = \PHPNative\Tailwind\Parser\MediaQuery::parse($styleStr);
if($mq) {

View File

@ -2,6 +2,230 @@
namespace PHPNative\Ui;
use PHPNative\Framework\TextRenderer;
class Window
{
private mixed $window = null;
private ?Component $rootComponent = null;
private TextRenderer $textRenderer;
private float $mouseX = 0;
private float $mouseY = 0;
private Viewport $viewport;
private bool $shouldBeReLayouted = true;
private float $pixelRatio = 2;
private bool $shouldClose = false;
public function __construct(
private string $title,
private int $width = 800,
private int $height = 600,
private int $x = 100,
private int $y = 100
) {
// Create window
$this->window = rgfw_createWindow($title, $x, $y, $width, $height, 0);
if (!$this->window) {
throw new \Exception('Failed to create window');
}
// Initialize RSGL renderer
if (!rsgl_init($this->window)) {
throw new \Exception('Failed to initialize RSGL renderer');
}
// Initialize text renderer
$this->textRenderer = new TextRenderer($this->window);
if (!$this->textRenderer->init()) {
error_log('Warning: Failed to initialize text renderer. Text rendering will not be available.');
}
// Get actual window size
$size = rgfw_window_getSize($this->window);
$this->width = $size[0];
$this->height = $size[1];
$this->viewport = new Viewport(
windowWidth: $this->width,
windowHeight: $this->height,
width: $this->width,
height: $this->height,
);
}
public function setRoot(Component $component): self
{
$this->rootComponent = $component;
$this->shouldBeReLayouted = true;
return $this;
}
public function getRoot(): ?Component
{
return $this->rootComponent;
}
public function getTitle(): string
{
return $this->title;
}
public function getWidth(): int
{
return $this->width;
}
public function getHeight(): int
{
return $this->height;
}
public function shouldClose(): bool
{
return $this->shouldClose || rgfw_window_shouldClose($this->window);
}
public function close(): void
{
$this->shouldClose = true;
}
public function getWindowResource(): mixed
{
return $this->window;
}
/**
* Handle window and input events
*/
public function handleEvents(): void
{
while ($event = rgfw_window_checkEvent($this->window)) {
switch ($event['type']) {
case RGFW_quit:
$this->shouldClose = true;
break;
case RGFW_keyPressed:
$keyCode = $event['keyCode'] ?? 0;
if ($keyCode == RGFW_Escape) {
$this->shouldClose = true;
}
break;
case RGFW_windowResized:
// Update window dimensions (from event data)
$newWidth = $event[0] ?? $this->width;
$newHeight = $event[1] ?? $this->height;
$this->width = $newWidth;
$this->height = $newHeight;
// Update RSGL renderer size and viewport
rsgl_updateRendererSize($this->window);
// Update text renderer framebuffer
if ($this->textRenderer && $this->textRenderer->isInitialized()) {
$this->textRenderer->updateFramebuffer($newWidth, $newHeight);
}
$this->viewport->x = 0;
$this->viewport->y = 0;
$this->viewport->windowWidth = $newWidth;
$this->viewport->width = $newWidth;
$this->viewport->height = $newHeight;
$this->viewport->windowHeight = $newHeight;
$this->shouldBeReLayouted = true;
break;
case RGFW_mousePosChanged:
$this->mouseX = $event[0] ?? 0;
$this->mouseY = $event[1] ?? 0;
// Propagate mouse move to root component
if ($this->rootComponent) {
$this->rootComponent->handleMouseMove($this->mouseX, $this->mouseY);
}
break;
case RGFW_mouseButtonPressed:
$button = $event['button'] ?? 0;
// Propagate click to root component
if ($this->rootComponent) {
$this->rootComponent->handleMouseClick($this->mouseX, $this->mouseY, $button);
}
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;
}
}
}
/**
* Update window state
*/
public function update(): void
{
if ($this->rootComponent) {
$this->rootComponent->update();
}
}
/**
* Layout components
*/
public function layout(): void
{
if ($this->rootComponent && $this->shouldBeReLayouted) {
$this->rootComponent->setViewport($this->viewport);
$this->rootComponent->setWindow($this->window);
$this->rootComponent->layout($this->textRenderer);
$this->shouldBeReLayouted = false;
}
}
/**
* Render the window
*/
public function render(): void
{
rsgl_clear($this->window, 255, 255, 255, 0);
// Render root component tree
if ($this->rootComponent) {
$this->rootComponent->render($this->textRenderer);
$this->rootComponent->renderContent($this->textRenderer);
}
// Render to screen
rsgl_render($this->window);
rgfw_window_swapBuffers($this->window);
}
/**
* Clean up resources
*/
public function cleanup(): void
{
if ($this->textRenderer) {
$this->textRenderer->free();
}
rsgl_close($this->window);
rgfw_window_close($this->window);
}
}