Backup
This commit is contained in:
parent
6ed95d47e5
commit
91ac766f4c
33
examples/simple_container.php
Normal file
33
examples/simple_container.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use PHPNative\Framework\Application;
|
||||
use PHPNative\Ui\Widget\Container;
|
||||
use PHPNative\Ui\Widget\Label;
|
||||
use PHPNative\Ui\Window;
|
||||
|
||||
define('DEBUG_RENDERING', true);
|
||||
|
||||
$app = new Application();
|
||||
$window = new Window('Simple Container with Label', 600, 400);
|
||||
|
||||
// Main container
|
||||
$mainContainer = new Container('p-4 bg-gray-100');
|
||||
|
||||
$label = new Label(
|
||||
text: 'Test',
|
||||
style: 'text-xl m-4 bg-lime-400 p-4',
|
||||
);
|
||||
|
||||
$mainContainer->addComponent($label);
|
||||
|
||||
$window->setRoot($mainContainer);
|
||||
$app->addWindow($window);
|
||||
|
||||
echo "TextInput Test started!\n";
|
||||
echo "- Red container (80px)\n";
|
||||
echo "- Blue container with TextInput and Button (40px)\n";
|
||||
echo "- Green container (80px)\n\n";
|
||||
|
||||
$app->run();
|
||||
@ -5,6 +5,7 @@ require_once __DIR__ . '/../vendor/autoload.php';
|
||||
use PHPNative\Framework\Application;
|
||||
use PHPNative\Ui\Widget\Container;
|
||||
use PHPNative\Ui\Widget\Label;
|
||||
use PHPNative\Ui\Widget\Menu;
|
||||
use PHPNative\Ui\Widget\MenuBar;
|
||||
use PHPNative\Ui\Widget\StatusBar;
|
||||
use PHPNative\Ui\Widget\TabContainer;
|
||||
@ -21,7 +22,7 @@ $mainContainer = new Container('flex flex-col bg-gray-100');
|
||||
$menuBar = new MenuBar();
|
||||
|
||||
// File Menu
|
||||
$fileMenu = $menuBar->addMenu('Datei');
|
||||
$fileMenu = new Menu(title: 'Datei');
|
||||
$fileMenu->addItem('Neu', function () {
|
||||
echo "Neu clicked\n";
|
||||
});
|
||||
@ -35,14 +36,15 @@ $fileMenu->addItem('Beenden', function () use ($app) {
|
||||
});
|
||||
|
||||
// Settings Menu
|
||||
$settingsMenu = $menuBar->addMenu('Einstellungen');
|
||||
$settingsMenu = new Menu(title: 'Einstellungen');
|
||||
$settingsMenu->addItem('Optionen', function () {
|
||||
echo "Optionen clicked\n";
|
||||
});
|
||||
$settingsMenu->addItem('Sprache', function () {
|
||||
echo "Sprache clicked\n";
|
||||
});
|
||||
|
||||
$menuBar->addMenu($fileMenu);
|
||||
$menuBar->addMenu($settingsMenu);
|
||||
$mainContainer->addComponent($menuBar);
|
||||
|
||||
// === 2. Tab Container (flex-1) ===
|
||||
@ -50,7 +52,7 @@ $tabContainer = new TabContainer('flex-1');
|
||||
|
||||
// Tab 1: Table with data
|
||||
$tab1 = new Container('flex flex-col p-4');
|
||||
$table = new Table();
|
||||
$table = new Table(style: 'bg-lime-200');
|
||||
|
||||
$table->setColumns([
|
||||
['key' => 'id', 'title' => 'ID', 'width' => 80],
|
||||
@ -73,10 +75,13 @@ $table->setData([
|
||||
]);
|
||||
|
||||
// Row selection handler
|
||||
$statusBar = null; // Will be set later
|
||||
$table->setOnRowSelect(function ($index, $row) use (&$statusBar) {
|
||||
if ($statusBar && $row) {
|
||||
$statusBar->updateSegment(0, "Selected: {$row['name']} ({$row['email']})");
|
||||
$statusLabel = new Label(
|
||||
text: 'Fenster: ' . $window->getViewport()->windowWidth . 'x' . $window->getViewport()->windowHeight,
|
||||
style: 'basis-4/8 text-black',
|
||||
);
|
||||
$table->setOnRowSelect(function ($index, $row) use (&$statusLabel) {
|
||||
if ($row) {
|
||||
$statusLabel->setText("Selected: {$row['name']} ({$row['email']})");
|
||||
}
|
||||
});
|
||||
|
||||
@ -99,12 +104,21 @@ $mainContainer->addComponent($tabContainer);
|
||||
|
||||
// === 3. StatusBar ===
|
||||
$statusBar = new StatusBar();
|
||||
$statusBar->addSegment('Bereit', '', 0); // Flexible segment
|
||||
$statusBar->addSegment('Zeilen: 10', 'border-l', 200); // Fixed width
|
||||
$statusBar->addSegment('Version 1.0', 'border-l', 150); // Fixed width
|
||||
|
||||
$statusBar->addSegment($statusLabel);
|
||||
$statusBar->addSegment(new Label(
|
||||
text: 'Zeilen: 10',
|
||||
style: 'basis-2/8 text-black border-l',
|
||||
)); // Fixed width
|
||||
$statusBar->addSegment(new Label(
|
||||
text: 'Version 1.0',
|
||||
style: 'border-l text-black basis-2/8',
|
||||
));
|
||||
$mainContainer->addComponent($statusBar);
|
||||
|
||||
$window->setOnResize(function (Window $window) use (&$statusLabel) {
|
||||
$statusLabel->setText(
|
||||
'Fenster: ' . $window->getViewport()->windowWidth . 'x' . $window->getViewport()->windowHeight,
|
||||
);
|
||||
});
|
||||
// Set root and run
|
||||
$window->setRoot($mainContainer);
|
||||
$app->addWindow($window);
|
||||
|
||||
@ -5,6 +5,7 @@ require_once __DIR__ . '/../vendor/autoload.php';
|
||||
use PHPNative\Framework\Application;
|
||||
use PHPNative\Ui\Widget\Container;
|
||||
use PHPNative\Ui\Widget\Label;
|
||||
use PHPNative\Ui\Widget\Menu;
|
||||
use PHPNative\Ui\Widget\MenuBar;
|
||||
use PHPNative\Ui\Widget\StatusBar;
|
||||
use PHPNative\Ui\Widget\TabContainer;
|
||||
@ -20,7 +21,7 @@ $mainContainer = new Container('bg-gray-100');
|
||||
$menuBar = new MenuBar();
|
||||
|
||||
// File Menu
|
||||
$fileMenu = $menuBar->addMenu('Datei');
|
||||
$fileMenu = new Menu(title: 'Datei');
|
||||
$fileMenu->addItem('Neu', function () {
|
||||
echo "Neu clicked\n";
|
||||
});
|
||||
@ -34,13 +35,15 @@ $fileMenu->addItem('Beenden', function () use ($app) {
|
||||
});
|
||||
|
||||
// Settings Menu
|
||||
$settingsMenu = $menuBar->addMenu('Einstellungen');
|
||||
$settingsMenu = new Menu(title: 'Einstellungen');
|
||||
$settingsMenu->addItem('Optionen', function () {
|
||||
echo "Optionen clicked\n";
|
||||
});
|
||||
$settingsMenu->addItem('Sprache', function () {
|
||||
echo "Sprache clicked\n";
|
||||
});
|
||||
$menuBar->addMenu($fileMenu);
|
||||
$menuBar->addMenu($settingsMenu);
|
||||
|
||||
$mainContainer->addComponent($menuBar);
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
231
php-sdl3/sdl3.c
231
php-sdl3/sdl3.c
@ -69,6 +69,24 @@ PHP_MINIT_FUNCTION(sdl3) {
|
||||
REGISTER_LONG_CONSTANT("SDL_WINDOW_MAXIMIZED", SDL_WINDOW_MAXIMIZED, CONST_CS | CONST_PERSISTENT);
|
||||
REGISTER_LONG_CONSTANT("SDL_WINDOW_HIGH_PIXEL_DENSITY", SDL_WINDOW_HIGH_PIXEL_DENSITY, CONST_CS | CONST_PERSISTENT);
|
||||
|
||||
// SDL Pixel Formats
|
||||
REGISTER_LONG_CONSTANT("SDL_PIXELFORMAT_RGBA8888", SDL_PIXELFORMAT_RGBA8888, CONST_CS | CONST_PERSISTENT);
|
||||
REGISTER_LONG_CONSTANT("SDL_PIXELFORMAT_ARGB8888", SDL_PIXELFORMAT_ARGB8888, CONST_CS | CONST_PERSISTENT);
|
||||
REGISTER_LONG_CONSTANT("SDL_PIXELFORMAT_BGRA8888", SDL_PIXELFORMAT_BGRA8888, CONST_CS | CONST_PERSISTENT);
|
||||
REGISTER_LONG_CONSTANT("SDL_PIXELFORMAT_ABGR8888", SDL_PIXELFORMAT_ABGR8888, CONST_CS | CONST_PERSISTENT);
|
||||
|
||||
// SDL Texture Access
|
||||
REGISTER_LONG_CONSTANT("SDL_TEXTUREACCESS_STATIC", SDL_TEXTUREACCESS_STATIC, CONST_CS | CONST_PERSISTENT);
|
||||
REGISTER_LONG_CONSTANT("SDL_TEXTUREACCESS_STREAMING", SDL_TEXTUREACCESS_STREAMING, CONST_CS | CONST_PERSISTENT);
|
||||
REGISTER_LONG_CONSTANT("SDL_TEXTUREACCESS_TARGET", SDL_TEXTUREACCESS_TARGET, CONST_CS | CONST_PERSISTENT);
|
||||
|
||||
// SDL Blend Modes
|
||||
REGISTER_LONG_CONSTANT("SDL_BLENDMODE_NONE", SDL_BLENDMODE_NONE, CONST_CS | CONST_PERSISTENT);
|
||||
REGISTER_LONG_CONSTANT("SDL_BLENDMODE_BLEND", SDL_BLENDMODE_BLEND, CONST_CS | CONST_PERSISTENT);
|
||||
REGISTER_LONG_CONSTANT("SDL_BLENDMODE_ADD", SDL_BLENDMODE_ADD, CONST_CS | CONST_PERSISTENT);
|
||||
REGISTER_LONG_CONSTANT("SDL_BLENDMODE_MOD", SDL_BLENDMODE_MOD, CONST_CS | CONST_PERSISTENT);
|
||||
REGISTER_LONG_CONSTANT("SDL_BLENDMODE_MUL", SDL_BLENDMODE_MUL, CONST_CS | CONST_PERSISTENT);
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
@ -231,6 +249,37 @@ PHP_FUNCTION(sdl_render_fill_rect) {
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
PHP_FUNCTION(sdl_render_rect) {
|
||||
zval *ren_res;
|
||||
SDL_Renderer *ren;
|
||||
zval *rect_arr;
|
||||
HashTable *rect_ht;
|
||||
zval *data;
|
||||
zend_long x, y, w, h;
|
||||
|
||||
if (zend_parse_parameters(ZEND_NUM_ARGS(), "ra", &ren_res, &rect_arr) == FAILURE) {
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
ren = (SDL_Renderer *)zend_fetch_resource(Z_RES_P(ren_res), "SDL_Renderer", le_sdl_renderer);
|
||||
if (!ren) {
|
||||
RETURN_FALSE;
|
||||
}
|
||||
|
||||
rect_ht = Z_ARRVAL_P(rect_arr);
|
||||
if (((data = zend_hash_str_find(rect_ht, "x", 1)) != NULL && (x = zval_get_long(data), true)) &&
|
||||
((data = zend_hash_str_find(rect_ht, "y", 1)) != NULL && (y = zval_get_long(data), true)) &&
|
||||
((data = zend_hash_str_find(rect_ht, "w", 1)) != NULL && (w = zval_get_long(data), true)) &&
|
||||
((data = zend_hash_str_find(rect_ht, "h", 1)) != NULL && (h = zval_get_long(data), true))) {
|
||||
|
||||
SDL_FRect rect = {(float)x, (float)y, (float)w, (float)h};
|
||||
SDL_RenderRect(ren, &rect);
|
||||
RETURN_TRUE;
|
||||
}
|
||||
|
||||
zend_throw_error(NULL, "Invalid rectangle array passed to sdl_render_rect. Expected ['x'=>int, 'y'=>int, 'w'=>int, 'h'=>int]");
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
PHP_FUNCTION(sdl_render_present) {
|
||||
zval *ren_res;
|
||||
@ -335,6 +384,145 @@ PHP_FUNCTION(sdl_render_texture) {
|
||||
}
|
||||
}
|
||||
|
||||
PHP_FUNCTION(sdl_create_texture) {
|
||||
zval *ren_res;
|
||||
SDL_Renderer *renderer;
|
||||
zend_long format, access, width, height;
|
||||
|
||||
if (zend_parse_parameters(ZEND_NUM_ARGS(), "rllll", &ren_res, &format, &access, &width, &height) == FAILURE) {
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
renderer = (SDL_Renderer *)zend_fetch_resource(Z_RES_P(ren_res), "SDL_Renderer", le_sdl_renderer);
|
||||
if (!renderer) {
|
||||
RETURN_FALSE;
|
||||
}
|
||||
|
||||
SDL_Texture *texture = SDL_CreateTexture(renderer, (Uint32)format, (int)access, (int)width, (int)height);
|
||||
if (!texture) {
|
||||
php_error_docref(NULL, E_WARNING, "Failed to create texture: %s", SDL_GetError());
|
||||
RETURN_FALSE;
|
||||
}
|
||||
|
||||
RETURN_RES(zend_register_resource(texture, le_sdl_texture));
|
||||
}
|
||||
|
||||
PHP_FUNCTION(sdl_destroy_texture) {
|
||||
zval *tex_res;
|
||||
SDL_Texture *texture;
|
||||
|
||||
if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &tex_res) == FAILURE) {
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
texture = (SDL_Texture *)zend_fetch_resource(Z_RES_P(tex_res), "SDL_Texture", le_sdl_texture);
|
||||
if (!texture) {
|
||||
RETURN_FALSE;
|
||||
}
|
||||
|
||||
// Delete the resource to trigger the destructor
|
||||
zend_list_close(Z_RES_P(tex_res));
|
||||
RETURN_TRUE;
|
||||
}
|
||||
|
||||
PHP_FUNCTION(sdl_set_texture_blend_mode) {
|
||||
zval *tex_res;
|
||||
SDL_Texture *texture;
|
||||
zend_long blend_mode;
|
||||
|
||||
if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl", &tex_res, &blend_mode) == FAILURE) {
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
texture = (SDL_Texture *)zend_fetch_resource(Z_RES_P(tex_res), "SDL_Texture", le_sdl_texture);
|
||||
if (!texture) {
|
||||
RETURN_FALSE;
|
||||
}
|
||||
|
||||
if (SDL_SetTextureBlendMode(texture, (SDL_BlendMode)blend_mode) < 0) {
|
||||
php_error_docref(NULL, E_WARNING, "Failed to set texture blend mode: %s", SDL_GetError());
|
||||
RETURN_FALSE;
|
||||
}
|
||||
|
||||
RETURN_TRUE;
|
||||
}
|
||||
|
||||
PHP_FUNCTION(sdl_set_texture_alpha_mod) {
|
||||
zval *tex_res;
|
||||
SDL_Texture *texture;
|
||||
zend_long alpha;
|
||||
|
||||
if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl", &tex_res, &alpha) == FAILURE) {
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
texture = (SDL_Texture *)zend_fetch_resource(Z_RES_P(tex_res), "SDL_Texture", le_sdl_texture);
|
||||
if (!texture) {
|
||||
RETURN_FALSE;
|
||||
}
|
||||
|
||||
if (SDL_SetTextureAlphaMod(texture, (Uint8)alpha) < 0) {
|
||||
php_error_docref(NULL, E_WARNING, "Failed to set texture alpha mod: %s", SDL_GetError());
|
||||
RETURN_FALSE;
|
||||
}
|
||||
|
||||
RETURN_TRUE;
|
||||
}
|
||||
|
||||
PHP_FUNCTION(sdl_get_render_target) {
|
||||
zval *ren_res;
|
||||
SDL_Renderer *renderer;
|
||||
|
||||
if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &ren_res) == FAILURE) {
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
renderer = (SDL_Renderer *)zend_fetch_resource(Z_RES_P(ren_res), "SDL_Renderer", le_sdl_renderer);
|
||||
if (!renderer) {
|
||||
RETURN_FALSE;
|
||||
}
|
||||
|
||||
SDL_Texture *texture = SDL_GetRenderTarget(renderer);
|
||||
if (!texture) {
|
||||
// NULL is valid - means rendering to the screen
|
||||
RETURN_NULL();
|
||||
}
|
||||
|
||||
// Return the existing texture resource (don't register a new one)
|
||||
// We just return the texture pointer as a resource
|
||||
RETURN_RES(zend_register_resource(texture, le_sdl_texture));
|
||||
}
|
||||
|
||||
PHP_FUNCTION(sdl_set_render_target) {
|
||||
zval *ren_res;
|
||||
zval *tex_res = NULL;
|
||||
SDL_Renderer *renderer;
|
||||
SDL_Texture *texture = NULL;
|
||||
|
||||
if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|r!", &ren_res, &tex_res) == FAILURE) {
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
renderer = (SDL_Renderer *)zend_fetch_resource(Z_RES_P(ren_res), "SDL_Renderer", le_sdl_renderer);
|
||||
if (!renderer) {
|
||||
RETURN_FALSE;
|
||||
}
|
||||
|
||||
if (tex_res != NULL && Z_TYPE_P(tex_res) == IS_RESOURCE) {
|
||||
texture = (SDL_Texture *)zend_fetch_resource(Z_RES_P(tex_res), "SDL_Texture", le_sdl_texture);
|
||||
if (!texture) {
|
||||
RETURN_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
if (SDL_SetRenderTarget(renderer, texture) < 0) {
|
||||
php_error_docref(NULL, E_WARNING, "Failed to set render target: %s", SDL_GetError());
|
||||
RETURN_FALSE;
|
||||
}
|
||||
|
||||
RETURN_TRUE;
|
||||
}
|
||||
|
||||
PHP_FUNCTION(sdl_rounded_box)
|
||||
{
|
||||
zval *ren_res;
|
||||
@ -569,6 +757,11 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_render_fill_rect, 0, 0, 2)
|
||||
ZEND_ARG_INFO(0, rect)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_render_rect, 0, 0, 2)
|
||||
ZEND_ARG_INFO(0, renderer)
|
||||
ZEND_ARG_INFO(0, rect)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_render_present, 0, 0, 1)
|
||||
ZEND_ARG_INFO(0, renderer)
|
||||
ZEND_END_ARG_INFO()
|
||||
@ -591,6 +784,37 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_render_texture, 0, 0, 2)
|
||||
ZEND_ARG_INFO(0, dstrect)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_create_texture, 0, 0, 5)
|
||||
ZEND_ARG_INFO(0, renderer)
|
||||
ZEND_ARG_INFO(0, format)
|
||||
ZEND_ARG_INFO(0, access)
|
||||
ZEND_ARG_INFO(0, width)
|
||||
ZEND_ARG_INFO(0, height)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_destroy_texture, 0, 0, 1)
|
||||
ZEND_ARG_INFO(0, texture)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_set_texture_blend_mode, 0, 0, 2)
|
||||
ZEND_ARG_INFO(0, texture)
|
||||
ZEND_ARG_INFO(0, blend_mode)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_set_texture_alpha_mod, 0, 0, 2)
|
||||
ZEND_ARG_INFO(0, texture)
|
||||
ZEND_ARG_INFO(0, alpha)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_get_render_target, 0, 0, 1)
|
||||
ZEND_ARG_INFO(0, renderer)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_set_render_target, 0, 0, 1)
|
||||
ZEND_ARG_INFO(0, renderer)
|
||||
ZEND_ARG_INFO(0, texture)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_rounded_box, 0, 0, 10)
|
||||
ZEND_ARG_INFO(0, renderer)
|
||||
ZEND_ARG_INFO(0, x1)
|
||||
@ -649,11 +873,18 @@ const zend_function_entry sdl3_functions[] = {
|
||||
PHP_FE(sdl_set_render_draw_color, arginfo_sdl_set_render_draw_color)
|
||||
PHP_FE(sdl_render_clear, arginfo_sdl_render_clear)
|
||||
PHP_FE(sdl_render_fill_rect, arginfo_sdl_render_fill_rect)
|
||||
PHP_FE(sdl_render_rect, arginfo_sdl_render_rect)
|
||||
PHP_FE(sdl_render_present, arginfo_sdl_render_present)
|
||||
PHP_FE(sdl_delay, arginfo_sdl_delay)
|
||||
PHP_FE(sdl_get_error, arginfo_sdl_get_error)
|
||||
PHP_FE(sdl_create_texture_from_surface, arginfo_sdl_create_texture_from_surface)
|
||||
PHP_FE(sdl_render_texture, arginfo_sdl_render_texture)
|
||||
PHP_FE(sdl_create_texture, arginfo_sdl_create_texture)
|
||||
PHP_FE(sdl_destroy_texture, arginfo_sdl_destroy_texture)
|
||||
PHP_FE(sdl_set_texture_blend_mode, arginfo_sdl_set_texture_blend_mode)
|
||||
PHP_FE(sdl_set_texture_alpha_mod, arginfo_sdl_set_texture_alpha_mod)
|
||||
PHP_FE(sdl_get_render_target, arginfo_sdl_get_render_target)
|
||||
PHP_FE(sdl_set_render_target, arginfo_sdl_set_render_target)
|
||||
PHP_FE(sdl_rounded_box, arginfo_sdl_rounded_box)
|
||||
PHP_FE(sdl_rounded_box_ex, arginfo_sdl_rounded_box_ex)
|
||||
PHP_FE(sdl_set_render_clip_rect, arginfo_sdl_set_render_clip_rect)
|
||||
|
||||
55
php-sdl3/test_border.php
Normal file
55
php-sdl3/test_border.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
// Teste die neue sdl_render_rect Funktion
|
||||
|
||||
if (!sdl_init(SDL_INIT_VIDEO)) {
|
||||
die("SDL Init failed: " . sdl_get_error());
|
||||
}
|
||||
|
||||
$window = sdl_create_window("Test Border", 640, 480, SDL_WINDOW_RESIZABLE);
|
||||
if (!$window) {
|
||||
die("Window creation failed: " . sdl_get_error());
|
||||
}
|
||||
|
||||
$renderer = sdl_create_renderer($window);
|
||||
if (!$renderer) {
|
||||
die("Renderer creation failed: " . sdl_get_error());
|
||||
}
|
||||
|
||||
$running = true;
|
||||
while ($running) {
|
||||
// Events verarbeiten
|
||||
while (sdl_poll_event($event)) {
|
||||
if ($event['type'] === SDL_EVENT_QUIT ||
|
||||
$event['type'] === SDL_EVENT_WINDOW_CLOSE_REQUESTED) {
|
||||
$running = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Hintergrund weiß
|
||||
sdl_set_render_draw_color($renderer, 255, 255, 255, 255);
|
||||
sdl_render_clear($renderer);
|
||||
|
||||
// Rahmen zeichnen - Blau
|
||||
sdl_set_render_draw_color($renderer, 0, 0, 255, 255);
|
||||
sdl_render_rect($renderer, ['x' => 50, 'y' => 50, 'w' => 200, 'h' => 150]);
|
||||
|
||||
// Zweiter Rahmen - Rot
|
||||
sdl_set_render_draw_color($renderer, 255, 0, 0, 255);
|
||||
sdl_render_rect($renderer, ['x' => 100, 'y' => 100, 'w' => 300, 'h' => 200]);
|
||||
|
||||
// Gefülltes Rechteck zum Vergleich - Grün
|
||||
sdl_set_render_draw_color($renderer, 0, 255, 0, 255);
|
||||
sdl_render_fill_rect($renderer, ['x' => 300, 'y' => 250, 'w' => 150, 'h' => 100]);
|
||||
|
||||
// Rahmen um das gefüllte Rechteck - Schwarz
|
||||
sdl_set_render_draw_color($renderer, 0, 0, 0, 255);
|
||||
sdl_render_rect($renderer, ['x' => 300, 'y' => 250, 'w' => 150, 'h' => 100]);
|
||||
|
||||
sdl_render_present($renderer);
|
||||
sdl_delay(16); // ~60 FPS
|
||||
}
|
||||
|
||||
sdl_destroy_renderer($renderer);
|
||||
sdl_destroy_window($window);
|
||||
sdl_quit();
|
||||
@ -150,6 +150,46 @@ class TextRenderer
|
||||
// Note: Texture and surface are automatically cleaned up by PHP resource destructors
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an SDL texture for the given text using the current color settings.
|
||||
*
|
||||
* @param string $text Text to render into a texture
|
||||
* @return array|null Returns ['texture' => resource, 'width' => int, 'height' => int] or null on failure
|
||||
*/
|
||||
public function createTextTexture(string $text): null|array
|
||||
{
|
||||
if (!$this->initialized || !$this->font) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$r = (int) ($this->colorR * 255);
|
||||
$g = (int) ($this->colorG * 255);
|
||||
$b = (int) ($this->colorB * 255);
|
||||
|
||||
$surface = ttf_render_text_blended($this->font, $text, $r, $g, $b);
|
||||
if (!$surface) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$texture = sdl_create_texture_from_surface($this->renderer, $surface);
|
||||
if (!$texture) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$dimensions = ttf_size_text($this->font, $text);
|
||||
|
||||
sdl_set_texture_blend_mode($texture, SDL_BLENDMODE_BLEND);
|
||||
if (\function_exists('sdl_set_texture_alpha_mod')) {
|
||||
sdl_set_texture_alpha_mod($texture, (int) ($this->colorA * 255));
|
||||
}
|
||||
|
||||
return [
|
||||
'texture' => $texture,
|
||||
'width' => (int) $dimensions['w'],
|
||||
'height' => (int) $dimensions['h'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set text color
|
||||
*
|
||||
|
||||
@ -6,45 +6,82 @@ use PHPNative\Framework\TextRenderer;
|
||||
use PHPNative\Tailwind\Style\Margin;
|
||||
use PHPNative\Tailwind\Style\MediaQueryEnum;
|
||||
use PHPNative\Tailwind\Style\Padding;
|
||||
use PHPNative\Tailwind\Style\State;
|
||||
use PHPNative\Tailwind\Style\StateEnum;
|
||||
use PHPNative\Tailwind\StyleParser;
|
||||
|
||||
abstract class Component
|
||||
{
|
||||
protected $children = [];
|
||||
protected int $id;
|
||||
|
||||
protected $pixelRatio;
|
||||
protected $children = [];
|
||||
|
||||
protected bool $visible = true;
|
||||
|
||||
protected bool $isOverlay = false;
|
||||
protected bool $overlay = false;
|
||||
|
||||
protected bool $layoutDirty = true;
|
||||
|
||||
protected bool $renderDirty = true;
|
||||
|
||||
protected int $zIndex = 0;
|
||||
|
||||
protected StateEnum $currentState = StateEnum::normal;
|
||||
|
||||
protected null|Component $parent = null; // Reference to parent component
|
||||
|
||||
protected $cachedTexture = null; // SDL texture cache for this component
|
||||
|
||||
protected bool $useTextureCache = false; // Disabled by default, enable per component if needed
|
||||
|
||||
protected Viewport $viewport;
|
||||
|
||||
protected array $computedStyles = [];
|
||||
protected Viewport $contentViewport;
|
||||
|
||||
protected null|Window $attachedWindow = null;
|
||||
|
||||
public function __construct(
|
||||
protected string $style = '',
|
||||
) {
|
||||
// Initialize viewports with default values
|
||||
// These will be properly set during layout()
|
||||
$this->id = rand();
|
||||
|
||||
$this->viewport = new Viewport(
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
windowWidth: 800,
|
||||
windowHeight: 600
|
||||
windowHeight: 600,
|
||||
);
|
||||
$this->contentViewport = clone $this->viewport;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor - clean up resources
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
$this->invalidateTextureCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up component resources
|
||||
*/
|
||||
public function cleanup(): void
|
||||
{
|
||||
// Free texture cache
|
||||
$this->invalidateTextureCache();
|
||||
|
||||
// Recursively cleanup children
|
||||
foreach ($this->children as $child) {
|
||||
if (method_exists($child, 'cleanup')) {
|
||||
$child->cleanup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function setViewport(Viewport $viewport): void
|
||||
{
|
||||
$this->viewport = $viewport;
|
||||
@ -82,31 +119,6 @@ abstract class Component
|
||||
}
|
||||
}
|
||||
|
||||
public function setPixelRatio($pixelRatio): void
|
||||
{
|
||||
$this->pixelRatio = $pixelRatio;
|
||||
}
|
||||
|
||||
public function setVisible(bool $visible): void
|
||||
{
|
||||
$this->visible = $visible;
|
||||
}
|
||||
|
||||
public function isVisible(): bool
|
||||
{
|
||||
return $this->visible;
|
||||
}
|
||||
|
||||
public function setOverlay(bool $isOverlay): void
|
||||
{
|
||||
$this->isOverlay = $isOverlay;
|
||||
}
|
||||
|
||||
public function isOverlay(): bool
|
||||
{
|
||||
return $this->isOverlay;
|
||||
}
|
||||
|
||||
public function setZIndex(int $zIndex): void
|
||||
{
|
||||
$this->zIndex = $zIndex;
|
||||
@ -117,6 +129,88 @@ abstract class Component
|
||||
return $this->zIndex;
|
||||
}
|
||||
|
||||
public function isVisible(): bool
|
||||
{
|
||||
return $this->visible;
|
||||
}
|
||||
|
||||
public function setVisible(bool $visible): void
|
||||
{
|
||||
if ($this->visible !== $visible) {
|
||||
$this->visible = $visible;
|
||||
$this->markDirty(true);
|
||||
}
|
||||
}
|
||||
|
||||
public function setOverlay(bool $overlay): void
|
||||
{
|
||||
if ($this->overlay !== $overlay) {
|
||||
$this->overlay = $overlay;
|
||||
$this->markDirty(false);
|
||||
}
|
||||
}
|
||||
|
||||
public function isOverlay(): bool
|
||||
{
|
||||
return $this->overlay;
|
||||
}
|
||||
|
||||
public function invalidateTextureCache(): void
|
||||
{
|
||||
if ($this->cachedTexture !== null) {
|
||||
sdl_destroy_texture($this->cachedTexture);
|
||||
$this->cachedTexture = null;
|
||||
}
|
||||
$this->renderDirty = true;
|
||||
}
|
||||
|
||||
public function getCachedTexture()
|
||||
{
|
||||
return $this->cachedTexture;
|
||||
}
|
||||
|
||||
public function setCachedTexture($texture): void
|
||||
{
|
||||
// Free old texture if exists
|
||||
if ($this->cachedTexture !== null) {
|
||||
sdl_destroy_texture($this->cachedTexture);
|
||||
}
|
||||
$this->cachedTexture = $texture;
|
||||
}
|
||||
|
||||
public function useTextureCache(): bool
|
||||
{
|
||||
return $this->useTextureCache;
|
||||
}
|
||||
|
||||
public function setUseTextureCache(bool $use): void
|
||||
{
|
||||
$this->useTextureCache = $use;
|
||||
if (!$use) {
|
||||
$this->invalidateTextureCache();
|
||||
}
|
||||
}
|
||||
|
||||
public function markClean(): void
|
||||
{
|
||||
$this->layoutDirty = false;
|
||||
$this->renderDirty = false;
|
||||
|
||||
foreach ($this->children as $child) {
|
||||
$child->markClean();
|
||||
}
|
||||
}
|
||||
|
||||
public function setParent(null|Component $parent): void
|
||||
{
|
||||
$this->parent = $parent;
|
||||
}
|
||||
|
||||
public function getParent(): null|Component
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
public function update(): void
|
||||
{
|
||||
foreach ($this->children as $child) {
|
||||
@ -145,6 +239,13 @@ abstract class Component
|
||||
$this->contentViewport->y = (int) ($this->contentViewport->y + $p->top);
|
||||
$this->contentViewport->height = max(0, ($this->contentViewport->height - $p->bottom) - $p->top);
|
||||
}
|
||||
|
||||
if ($this->useTextureCache) {
|
||||
$this->invalidateTextureCache();
|
||||
}
|
||||
|
||||
$this->layoutDirty = false;
|
||||
$this->renderDirty = true;
|
||||
}
|
||||
|
||||
public function render(&$renderer, null|TextRenderer $textRenderer = null): void
|
||||
@ -160,7 +261,13 @@ abstract class Component
|
||||
if ($this->currentState == StateEnum::hover) {
|
||||
sdl_set_render_draw_color($renderer, $bg->color->red, $bg->color->green, $bg->color->blue, 10);
|
||||
} else {
|
||||
sdl_set_render_draw_color($renderer, $bg->color->red, $bg->color->green, $bg->color->blue, $bg->color->alpha);
|
||||
sdl_set_render_draw_color(
|
||||
$renderer,
|
||||
$bg->color->red,
|
||||
$bg->color->green,
|
||||
$bg->color->blue,
|
||||
$bg->color->alpha,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
@ -187,17 +294,23 @@ abstract class Component
|
||||
$bg->color->alpha,
|
||||
);
|
||||
} else {
|
||||
sdl_render_fill_rect(
|
||||
$renderer,
|
||||
[
|
||||
sdl_render_fill_rect($renderer, [
|
||||
'x' => $this->viewport->x,
|
||||
'y' => $this->viewport->y,
|
||||
'w' => $this->viewport->width,
|
||||
'h' => $this->viewport->height,
|
||||
]
|
||||
);
|
||||
]);
|
||||
}
|
||||
}
|
||||
if (defined('DEBUG_RENDERING') && DEBUG_RENDERING) {
|
||||
sdl_set_render_draw_color($renderer, rand(0, 255), rand(0, 255), rand(0, 255), 10);
|
||||
sdl_render_rect($renderer, [
|
||||
'x' => $this->viewport->x,
|
||||
'y' => $this->viewport->y,
|
||||
'w' => $this->viewport->width,
|
||||
'h' => $this->viewport->height,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function renderContent(&$renderer, null|TextRenderer $textRenderer = null): void
|
||||
@ -216,6 +329,11 @@ abstract class Component
|
||||
public function addComponent(Component $component): void
|
||||
{
|
||||
$this->children[] = $component;
|
||||
$component->setParent($this);
|
||||
if ($this->attachedWindow !== null) {
|
||||
$component->attachToWindow($this->attachedWindow);
|
||||
}
|
||||
$this->markDirty(true); // Adding a child means we need to re-layout and re-render
|
||||
}
|
||||
|
||||
/**
|
||||
@ -244,6 +362,11 @@ abstract class Component
|
||||
*/
|
||||
public function handleMouseClick(float $mouseX, float $mouseY, int $button): bool
|
||||
{
|
||||
// Don't handle events if component is not visible
|
||||
if (!$this->visible) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Default implementation: propagate to children
|
||||
foreach ($this->children as $child) {
|
||||
if ($child->handleMouseClick($mouseX, $mouseY, $button)) {
|
||||
@ -275,6 +398,8 @@ abstract class Component
|
||||
MediaQueryEnum::normal,
|
||||
$this->currentState,
|
||||
);
|
||||
// Mark as dirty since visual state changed
|
||||
$this->markDirty(false, false);
|
||||
}
|
||||
foreach ($this->children as $child) {
|
||||
$child->handleMouseMove($mouseX, $mouseY);
|
||||
@ -298,6 +423,11 @@ abstract class Component
|
||||
*/
|
||||
public function handleMouseWheel(float $mouseX, float $mouseY, float $deltaY): bool
|
||||
{
|
||||
// Don't handle events if component is not visible
|
||||
if (!$this->visible) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Default implementation: propagate to children
|
||||
foreach ($this->children as $child) {
|
||||
if ($child->handleMouseWheel($mouseX, $mouseY, $deltaY)) {
|
||||
@ -334,4 +464,69 @@ abstract class Component
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function needsLayout(): bool
|
||||
{
|
||||
return $this->layoutDirty;
|
||||
}
|
||||
|
||||
public function isDirty(): bool
|
||||
{
|
||||
return $this->renderDirty;
|
||||
}
|
||||
|
||||
public function markDirty(bool $requiresLayout = false, bool $bubble = true): void
|
||||
{
|
||||
if ($requiresLayout) {
|
||||
$this->layoutDirty = true;
|
||||
$this->renderDirty = true;
|
||||
$this->invalidateTextureCache();
|
||||
|
||||
if ($this->attachedWindow !== null) {
|
||||
$this->attachedWindow->setShouldBeReLayouted(true);
|
||||
}
|
||||
} else {
|
||||
$this->renderDirty = true;
|
||||
}
|
||||
|
||||
if ($bubble && $this->parent !== null) {
|
||||
$this->parent->markDirty($requiresLayout);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$bubble && !$requiresLayout) {
|
||||
$ancestor = $this->parent;
|
||||
while ($ancestor !== null && $ancestor->useTextureCache()) {
|
||||
$ancestor->renderDirty = true;
|
||||
$ancestor->invalidateTextureCache();
|
||||
$ancestor = $ancestor->getParent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function attachToWindow(Window $window): void
|
||||
{
|
||||
$this->attachedWindow = $window;
|
||||
|
||||
foreach ($this->children as $child) {
|
||||
$child->attachToWindow($window);
|
||||
}
|
||||
}
|
||||
|
||||
public function detachFromWindow(): void
|
||||
{
|
||||
$this->attachedWindow = null;
|
||||
|
||||
foreach ($this->children as $child) {
|
||||
$child->detachFromWindow();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<Component>
|
||||
*/
|
||||
public function getChildren(): array
|
||||
{
|
||||
return $this->children;
|
||||
}
|
||||
}
|
||||
|
||||
161
src/Ui/RenderStack.php
Normal file
161
src/Ui/RenderStack.php
Normal file
@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Ui;
|
||||
|
||||
/**
|
||||
* RenderStack manages layered rendering based on z-index
|
||||
* Optimizes rendering by only re-rendering dirty layers
|
||||
*/
|
||||
class RenderStack
|
||||
{
|
||||
private array $layers = [];
|
||||
|
||||
private Viewport $savedViewport;
|
||||
|
||||
public function __construct(
|
||||
public $renderer,
|
||||
public $textRenderer,
|
||||
) {}
|
||||
|
||||
public function beginRender(): void
|
||||
{
|
||||
$this->savedViewport = $component->getViewport();
|
||||
|
||||
// Create texture for this component
|
||||
$texture = sdl_create_texture(
|
||||
$renderer,
|
||||
SDL_PIXELFORMAT_RGBA8888,
|
||||
SDL_TEXTUREACCESS_TARGET,
|
||||
$viewport->width,
|
||||
$viewport->height,
|
||||
);
|
||||
|
||||
// Enable alpha blending for the texture
|
||||
sdl_set_texture_blend_mode($texture, SDL_BLENDMODE_BLEND);
|
||||
|
||||
// Save current render target
|
||||
$previousTarget = sdl_get_render_target($renderer);
|
||||
|
||||
// Set texture as render target
|
||||
sdl_set_render_target($renderer, $texture);
|
||||
|
||||
// Clear texture with transparent background
|
||||
sdl_set_render_draw_color($renderer, 0, 0, 0, 0);
|
||||
sdl_render_clear($renderer);
|
||||
|
||||
// Temporarily adjust viewport for texture-relative rendering
|
||||
$viewport = clone $this->savedViewport;
|
||||
$originalX = $viewport->x;
|
||||
$originalY = $viewport->y;
|
||||
$viewport->x = 0;
|
||||
$viewport->y = 0;
|
||||
}
|
||||
|
||||
public function endRender(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function render($renderer, $textRenderer, bool $onlyDirty = true): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a single component with texture caching
|
||||
*/
|
||||
private function renderComponentWithCache($renderer, $textRenderer, Component $component): void
|
||||
{
|
||||
$viewport = $component->getViewport();
|
||||
|
||||
// Skip if component has no size
|
||||
if ($viewport->width <= 0 || $viewport->height <= 0) {
|
||||
return;
|
||||
}
|
||||
// Check if we should use texture caching
|
||||
if ($component->useTextureCache() && !$component->isDirty() && $component->getCachedTexture() !== null) {
|
||||
// Use cached texture - just copy it to the screen
|
||||
$cachedTexture = $component->getCachedTexture();
|
||||
|
||||
sdl_render_texture($renderer, $cachedTexture, [
|
||||
'x' => $viewport->x,
|
||||
'y' => $viewport->y,
|
||||
'w' => $viewport->width,
|
||||
'h' => $viewport->height,
|
||||
]);
|
||||
} elseif ($component->useTextureCache() && $component->isDirty()) {
|
||||
// Component is dirty - render to texture and cache it
|
||||
$this->renderToTextureAndCache($renderer, $textRenderer, $component);
|
||||
} else {
|
||||
// Texture caching disabled - render directly
|
||||
$component->render($renderer, $textRenderer);
|
||||
$component->renderContent($renderer, $textRenderer);
|
||||
}
|
||||
|
||||
// Mark as clean after rendering
|
||||
$component->markClean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render component to a texture and cache it
|
||||
*/
|
||||
private function renderToTextureAndCache($renderer, $textRenderer, Component $component): void
|
||||
{
|
||||
$viewport = $component->getViewport();
|
||||
|
||||
// Create texture for this component
|
||||
$texture = sdl_create_texture(
|
||||
$renderer,
|
||||
SDL_PIXELFORMAT_RGBA8888,
|
||||
SDL_TEXTUREACCESS_TARGET,
|
||||
$viewport->width,
|
||||
$viewport->height,
|
||||
);
|
||||
|
||||
if (!$texture) {
|
||||
// Fallback: render directly if texture creation failed
|
||||
$component->render($renderer, $textRenderer);
|
||||
$component->renderContent($renderer, $textRenderer);
|
||||
return;
|
||||
}
|
||||
|
||||
// Enable alpha blending for the texture
|
||||
sdl_set_texture_blend_mode($texture, SDL_BLENDMODE_BLEND);
|
||||
|
||||
// Save current render target
|
||||
$previousTarget = sdl_get_render_target($renderer);
|
||||
|
||||
// Set texture as render target
|
||||
sdl_set_render_target($renderer, $texture);
|
||||
|
||||
// Clear texture with transparent background
|
||||
sdl_set_render_draw_color($renderer, 0, 0, 0, 0);
|
||||
sdl_render_clear($renderer);
|
||||
|
||||
// Temporarily adjust viewport for texture-relative rendering
|
||||
$originalX = $viewport->x;
|
||||
$originalY = $viewport->y;
|
||||
$viewport->x = 0;
|
||||
$viewport->y = 0;
|
||||
|
||||
// Render component into texture
|
||||
$component->render($renderer, $textRenderer);
|
||||
$component->renderContent($renderer, $textRenderer);
|
||||
|
||||
// Restore original viewport position
|
||||
$viewport->x = $originalX;
|
||||
$viewport->y = $originalY;
|
||||
|
||||
// Restore previous render target
|
||||
sdl_set_render_target($renderer, $previousTarget);
|
||||
|
||||
// Cache the texture
|
||||
$component->setCachedTexture($texture);
|
||||
|
||||
// Now render the cached texture to screen
|
||||
sdl_render_texture($renderer, $texture, [
|
||||
'x' => $viewport->x,
|
||||
'y' => $viewport->y,
|
||||
'w' => $viewport->width,
|
||||
'h' => $viewport->height,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@ -70,6 +70,11 @@ class Button extends Container
|
||||
|
||||
public function handleMouseClick(float $mouseX, float $mouseY, int $button): bool
|
||||
{
|
||||
// Don't handle events if button is not visible
|
||||
if (!$this->visible) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Debug output
|
||||
if (defined('DEBUG_EVENTS') && DEBUG_EVENTS) {
|
||||
error_log(sprintf(
|
||||
|
||||
@ -363,6 +363,11 @@ class Container extends Component
|
||||
];
|
||||
}
|
||||
|
||||
public function render(&$renderer, null|TextRenderer $textRenderer = null): void
|
||||
{
|
||||
parent::render($renderer, $textRenderer);
|
||||
}
|
||||
|
||||
public function renderContent(&$renderer, null|TextRenderer $textRenderer = null): void
|
||||
{
|
||||
if (!$this->visible) {
|
||||
@ -519,6 +524,11 @@ class Container extends Component
|
||||
|
||||
public function handleMouseClick(float $mouseX, float $mouseY, int $button): bool
|
||||
{
|
||||
// Don't handle events if container is not visible
|
||||
if (!$this->visible) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if click is on scrollbar
|
||||
$overflow = $this->hasOverflow();
|
||||
|
||||
@ -628,6 +638,11 @@ class Container extends Component
|
||||
|
||||
public function handleMouseWheel(float $mouseX, float $mouseY, float $deltaY): bool
|
||||
{
|
||||
// Don't handle events if container is not visible
|
||||
if (!$this->visible) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$overflow = $this->hasOverflow();
|
||||
|
||||
// Check if mouse is over this container
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
namespace PHPNative\Ui\Widget;
|
||||
|
||||
use PHPNative\Framework\TextRenderer;
|
||||
use PHPNative\Tailwind\Style\Padding;
|
||||
use PHPNative\Tailwind\Style\Text;
|
||||
use PHPNative\Ui\Component;
|
||||
|
||||
@ -10,6 +11,9 @@ class Label extends Component
|
||||
{
|
||||
private int $intrinsicWidth = 0;
|
||||
private int $intrinsicHeight = 0;
|
||||
private mixed $textTexture = null;
|
||||
private int $textWidth = 0;
|
||||
private int $textHeight = 0;
|
||||
|
||||
public function __construct(
|
||||
public string $text = '',
|
||||
@ -20,7 +24,13 @@ class Label extends Component
|
||||
|
||||
public function setText(string $text): void
|
||||
{
|
||||
if ($this->text === $text) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->text = $text;
|
||||
$this->clearTextTexture();
|
||||
$this->markDirty(true);
|
||||
}
|
||||
|
||||
public function layout(null|TextRenderer $textRenderer = null): void
|
||||
@ -36,11 +46,20 @@ class Label extends Component
|
||||
if (!isset($this->computedStyles[\PHPNative\Tailwind\Style\Width::class])) {
|
||||
$this->viewport->width = $this->intrinsicWidth;
|
||||
$this->contentViewport->width = $this->intrinsicWidth;
|
||||
if (isset($this->computedStyles[Padding::class]) && ($pd = $this->computedStyles[Padding::class])) {
|
||||
$this->viewport->width = $this->intrinsicWidth + $pd->left + $pd->right;
|
||||
$this->contentViewport->width = $this->intrinsicWidth + $pd->left + $pd->right;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($this->computedStyles[\PHPNative\Tailwind\Style\Height::class])) {
|
||||
$this->viewport->height = $this->intrinsicHeight;
|
||||
$this->contentViewport->height = $this->intrinsicHeight;
|
||||
|
||||
if (isset($this->computedStyles[Padding::class]) && ($pd = $this->computedStyles[Padding::class])) {
|
||||
$this->viewport->height = $this->intrinsicHeight + $pd->top + $pd->bottom;
|
||||
$this->contentViewport->height = $this->intrinsicHeight + $pd->top + $pd->bottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -56,16 +75,62 @@ class Label extends Component
|
||||
|
||||
// Set text color
|
||||
$color = $textStyle->color;
|
||||
$textRenderer->setColor($color->red / 255, $color->green / 255, $color->blue / 255, $color->alpha / 255);
|
||||
$red = $color->red >= 0 ? $color->red : 0;
|
||||
$green = $color->green >= 0 ? $color->green : 0;
|
||||
$blue = $color->blue >= 0 ? $color->blue : 0;
|
||||
$alpha = max(0, min(255, $color->alpha));
|
||||
|
||||
// Calculate text position based on alignment
|
||||
$x = $this->contentViewport->x;
|
||||
$y = $this->contentViewport->y;
|
||||
$textRenderer->setColor($red / 255, $green / 255, $blue / 255, $alpha / 255);
|
||||
|
||||
// Draw the text
|
||||
$textRenderer->drawText($this->text, (int) $x, (int) $y, $textStyle->size);
|
||||
if ($this->renderDirty || $this->textTexture === null) {
|
||||
$this->clearTextTexture();
|
||||
$textureData = $textRenderer->createTextTexture($this->text);
|
||||
if ($textureData !== null) {
|
||||
$this->textTexture = $textureData['texture'];
|
||||
$this->textWidth = $textureData['width'];
|
||||
$this->textHeight = $textureData['height'];
|
||||
if (\function_exists('sdl_set_texture_alpha_mod')) {
|
||||
sdl_set_texture_alpha_mod($this->textTexture, $alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->textTexture !== null) {
|
||||
sdl_render_texture($window, $this->textTexture, [
|
||||
'x' => (int) $this->contentViewport->x,
|
||||
'y' => (int) $this->contentViewport->y,
|
||||
'w' => $this->textWidth,
|
||||
'h' => $this->textHeight,
|
||||
]);
|
||||
} else {
|
||||
// Fallback: render text directly if texture creation failed
|
||||
$textRenderer->drawText(
|
||||
$this->text,
|
||||
(int) $this->contentViewport->x,
|
||||
(int) $this->contentViewport->y,
|
||||
$textStyle->size
|
||||
);
|
||||
}
|
||||
|
||||
$this->renderDirty = false;
|
||||
|
||||
// Call parent to render children if any
|
||||
parent::renderContent($window, $textRenderer);
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->clearTextTexture();
|
||||
parent::__destruct();
|
||||
}
|
||||
|
||||
private function clearTextTexture(): void
|
||||
{
|
||||
if ($this->textTexture !== null) {
|
||||
sdl_destroy_texture($this->textTexture);
|
||||
$this->textTexture = null;
|
||||
$this->textWidth = 0;
|
||||
$this->textHeight = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,16 +7,16 @@ use PHPNative\Framework\TextRenderer;
|
||||
class Menu extends Container
|
||||
{
|
||||
private Button $menuButton;
|
||||
private Container $dropdown;
|
||||
private bool $isOpen = false;
|
||||
private array $items = [];
|
||||
private Container $dropdown;
|
||||
private null|MenuBar $menuBar = null;
|
||||
|
||||
public function __construct(string $title, callable $onToggle)
|
||||
public function __construct(string $title)
|
||||
{
|
||||
parent::__construct('relative');
|
||||
|
||||
// Create menu button
|
||||
$this->menuButton = new Button($title, 'px-4 py-2 hover:bg-gray-200', $onToggle);
|
||||
$this->menuButton = new Button($title, 'px-4 py-2 hover:bg-gray-200', fn() => $this->toggle());
|
||||
|
||||
$this->addComponent($this->menuButton);
|
||||
|
||||
@ -42,7 +42,6 @@ class Menu extends Container
|
||||
$onClick();
|
||||
});
|
||||
|
||||
$this->items[] = $menuItem;
|
||||
$this->dropdown->addComponent($menuItem);
|
||||
|
||||
return $menuItem;
|
||||
@ -54,34 +53,40 @@ class Menu extends Container
|
||||
public function addSeparator(): void
|
||||
{
|
||||
$separator = new Separator(true, 'my-1');
|
||||
$this->items[] = $separator;
|
||||
$this->dropdown->addComponent($separator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the menu
|
||||
*/
|
||||
public function toggle(): void
|
||||
{
|
||||
if ($this->isOpen) {
|
||||
$this->close();
|
||||
} else {
|
||||
$this->open();
|
||||
}
|
||||
}
|
||||
|
||||
public function open(): void
|
||||
{
|
||||
if ($this->isOpen) {
|
||||
return;
|
||||
}
|
||||
$this->isOpen = true;
|
||||
$this->dropdown->setVisible(true);
|
||||
if ($this->menuBar !== null) {
|
||||
$this->menuBar->notifyMenuOpened($this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the menu
|
||||
*/
|
||||
public function close(): void
|
||||
public function close(bool $notify = true): void
|
||||
{
|
||||
if (!$this->isOpen) {
|
||||
return;
|
||||
}
|
||||
$this->isOpen = false;
|
||||
$this->dropdown->setVisible(false);
|
||||
if ($notify && $this->menuBar !== null) {
|
||||
$this->menuBar->notifyMenuClosed($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if menu is open
|
||||
*/
|
||||
public function isOpen(): bool
|
||||
{
|
||||
return $this->isOpen;
|
||||
}
|
||||
|
||||
public function layout(null|TextRenderer $textRenderer = null): void
|
||||
@ -97,4 +102,32 @@ class Menu extends Container
|
||||
$this->dropdown->getViewport()->windowHeight = $buttonViewport->windowHeight;
|
||||
$this->dropdown->layout($textRenderer);
|
||||
}
|
||||
|
||||
public function handleMouseMove(float $mouseX, float $mouseY): void
|
||||
{
|
||||
parent::handleMouseMove($mouseX, $mouseY);
|
||||
if ($this->isOpen) {
|
||||
$this->dropdown->handleMouseMove($mouseX, $mouseY);
|
||||
}
|
||||
}
|
||||
|
||||
public function render(&$renderer, null|TextRenderer $textRenderer = null): void
|
||||
{
|
||||
parent::render($renderer, $textRenderer);
|
||||
}
|
||||
|
||||
public function renderContent(&$renderer, null|TextRenderer $textRenderer = null): void
|
||||
{
|
||||
parent::renderContent($renderer, $textRenderer);
|
||||
}
|
||||
|
||||
public function setMenuBar(MenuBar $menuBar): void
|
||||
{
|
||||
$this->menuBar = $menuBar;
|
||||
}
|
||||
|
||||
public function isOpen(): bool
|
||||
{
|
||||
return $this->isOpen;
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,77 +6,44 @@ use PHPNative\Framework\TextRenderer;
|
||||
|
||||
class MenuBar extends Container
|
||||
{
|
||||
/** @var Menu[] */
|
||||
private array $menus = [];
|
||||
private null|int $openMenuIndex = null;
|
||||
private null|Menu $openMenu = null;
|
||||
|
||||
public function __construct(string $style = 'w-full bg-gray-100 border-b border-gray-300')
|
||||
{
|
||||
parent::__construct('flex flex-row ' . $style);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a menu to the menu bar
|
||||
*
|
||||
* @param string $title Menu title
|
||||
* @return Menu The created menu
|
||||
*/
|
||||
public function addMenu(string $title): Menu
|
||||
public function addMenu(Menu $menu): Menu
|
||||
{
|
||||
$menuIndex = count($this->menus);
|
||||
|
||||
$menu = new Menu($title, function () use ($menuIndex) {
|
||||
$this->toggleMenu($menuIndex);
|
||||
});
|
||||
|
||||
$this->menus[] = $menu;
|
||||
$menu->setMenuBar($this);
|
||||
$this->addComponent($menu);
|
||||
|
||||
return $menu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle a menu open/closed
|
||||
*
|
||||
* @param int $index Menu index
|
||||
*/
|
||||
private function toggleMenu(int $index): void
|
||||
{
|
||||
if ($this->openMenuIndex === $index) {
|
||||
// Close the currently open menu
|
||||
$this->menus[$index]->close();
|
||||
$this->openMenuIndex = null;
|
||||
} else {
|
||||
// Close previously open menu
|
||||
if ($this->openMenuIndex !== null) {
|
||||
$this->menus[$this->openMenuIndex]->close();
|
||||
}
|
||||
|
||||
// Open the clicked menu
|
||||
$this->menus[$index]->open();
|
||||
$this->openMenuIndex = $index;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close all menus
|
||||
*/
|
||||
public function closeAllMenus(): void
|
||||
{
|
||||
if ($this->openMenuIndex !== null) {
|
||||
$this->menus[$this->openMenuIndex]->close();
|
||||
$this->openMenuIndex = null;
|
||||
foreach ($this->menus as $menu) {
|
||||
$menu->close(false);
|
||||
}
|
||||
$this->openMenu = null;
|
||||
}
|
||||
|
||||
public function handleMouseClick(float $mouseX, float $mouseY, int $button): bool
|
||||
public function notifyMenuOpened(Menu $menu): void
|
||||
{
|
||||
$handled = parent::handleMouseClick($mouseX, $mouseY, $button);
|
||||
|
||||
// If click was not handled by any menu, close all menus
|
||||
if (!$handled && $this->openMenuIndex !== null) {
|
||||
$this->closeAllMenus();
|
||||
if ($this->openMenu !== null && $this->openMenu !== $menu) {
|
||||
$this->openMenu->close(false);
|
||||
}
|
||||
$this->openMenu = $menu;
|
||||
}
|
||||
|
||||
return $handled;
|
||||
public function notifyMenuClosed(Menu $menu): void
|
||||
{
|
||||
if ($this->openMenu === $menu) {
|
||||
$this->openMenu = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,4 +8,9 @@ class MenuItem extends Button
|
||||
{
|
||||
parent::__construct($text, 'px-4 text py-2 hover:bg-gray-100 text-left w-full ' . $style, $onClick);
|
||||
}
|
||||
|
||||
public function handleMouseMove(float $mouseX, float $mouseY): void
|
||||
{
|
||||
parent::handleMouseMove($mouseX, $mouseY);
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,12 +10,11 @@ class Table extends Container
|
||||
private array $rows = [];
|
||||
private Container $headerContainer;
|
||||
private Container $bodyContainer;
|
||||
private ?int $selectedRowIndex = null;
|
||||
private null|int $selectedRowIndex = null;
|
||||
private $onRowSelect = null;
|
||||
|
||||
public function __construct(
|
||||
string $style = '',
|
||||
) {
|
||||
public function __construct(string $style = '')
|
||||
{
|
||||
parent::__construct('flex flex-col overflow-auto ' . $style);
|
||||
|
||||
// Create header container
|
||||
@ -27,6 +26,14 @@ class Table extends Container
|
||||
$this->addComponent($this->bodyContainer);
|
||||
}
|
||||
|
||||
public function layout(null|TextRenderer $textRenderer = null): void
|
||||
{
|
||||
$this->headerContainer->layout($textRenderer);
|
||||
$this->bodyContainer->layout($textRenderer);
|
||||
|
||||
parent::layout($textRenderer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define columns
|
||||
*
|
||||
@ -41,7 +48,7 @@ class Table extends Container
|
||||
$title = $column['title'] ?? $column['key'];
|
||||
$width = $column['width'] ?? null;
|
||||
|
||||
$style = 'px-4 py-2 font-bold border-r border-gray-300';
|
||||
$style = 'px-4 py-2 text-black font-bold border-r border-gray-300';
|
||||
if ($width) {
|
||||
$style .= ' w-' . ((int) ($width / 4));
|
||||
} else {
|
||||
@ -86,7 +93,7 @@ class Table extends Container
|
||||
$value = $rowData[$key] ?? '';
|
||||
$width = $column['width'] ?? null;
|
||||
|
||||
$cellStyle = 'px-4 py-2 border-r border-gray-300';
|
||||
$cellStyle = 'px-4 py-2 text-black border-r border-gray-300';
|
||||
if ($width) {
|
||||
$cellStyle .= ' w-' . ((int) ($width / 4));
|
||||
} else {
|
||||
@ -142,7 +149,7 @@ class Table extends Container
|
||||
}
|
||||
|
||||
// Re-render rows to update selection
|
||||
$this->setData($this->rows);
|
||||
// $this->setData($this->rows);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -156,7 +163,7 @@ class Table extends Container
|
||||
/**
|
||||
* Get selected row index
|
||||
*/
|
||||
public function getSelectedRowIndex(): ?int
|
||||
public function getSelectedRowIndex(): null|int
|
||||
{
|
||||
return $this->selectedRowIndex;
|
||||
}
|
||||
@ -164,7 +171,7 @@ class Table extends Container
|
||||
/**
|
||||
* Get selected row data
|
||||
*/
|
||||
public function getSelectedRow(): ?array
|
||||
public function getSelectedRow(): null|array
|
||||
{
|
||||
return $this->selectedRowIndex !== null ? ($this->rows[$this->selectedRowIndex] ?? null) : null;
|
||||
}
|
||||
|
||||
@ -17,9 +17,7 @@ class Window
|
||||
private bool $shouldBeReLayouted = true;
|
||||
private float $pixelRatio = 2;
|
||||
private bool $shouldClose = false;
|
||||
private bool $hasBeenLaidOut = false;
|
||||
private $onResize = null;
|
||||
|
||||
public function __construct(
|
||||
private string $title,
|
||||
private int $width = 800,
|
||||
@ -79,13 +77,18 @@ class Window
|
||||
width: $this->width,
|
||||
height: $this->height,
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
public function setRoot(Component $component): self
|
||||
{
|
||||
if ($this->rootComponent !== null) {
|
||||
$this->rootComponent->detachFromWindow();
|
||||
}
|
||||
|
||||
$this->rootComponent = $component;
|
||||
$this->rootComponent->attachToWindow($this);
|
||||
$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
|
||||
@ -220,10 +223,28 @@ class Window
|
||||
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||
$button = $event['button'] ?? 0;
|
||||
// Propagate click to root component
|
||||
|
||||
// Check overlays first (in reverse z-index order - highest first)
|
||||
if ($this->rootComponent) {
|
||||
$overlays = $this->rootComponent->collectOverlays();
|
||||
usort($overlays, fn($a, $b) => $b->getZIndex() <=> $a->getZIndex());
|
||||
|
||||
$handled = false;
|
||||
foreach ($overlays as $overlay) {
|
||||
if (
|
||||
$overlay->isVisible() &&
|
||||
$overlay->handleMouseClick($this->mouseX, $this->mouseY, $button)
|
||||
) {
|
||||
$handled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If no overlay handled it, propagate to normal components
|
||||
if (!$handled) {
|
||||
$this->rootComponent->handleMouseClick($this->mouseX, $this->mouseY, $button);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_EVENT_MOUSE_BUTTON_UP:
|
||||
@ -238,10 +259,27 @@ class Window
|
||||
case SDL_EVENT_MOUSE_WHEEL:
|
||||
$deltaY = $event['y'] ?? 0;
|
||||
|
||||
// Propagate wheel to root component
|
||||
// Check overlays first (in reverse z-index order - highest first)
|
||||
if ($this->rootComponent) {
|
||||
$overlays = $this->rootComponent->collectOverlays();
|
||||
usort($overlays, fn($a, $b) => $b->getZIndex() <=> $a->getZIndex());
|
||||
|
||||
$handled = false;
|
||||
foreach ($overlays as $overlay) {
|
||||
if (
|
||||
$overlay->isVisible() &&
|
||||
$overlay->handleMouseWheel($this->mouseX, $this->mouseY, $deltaY)
|
||||
) {
|
||||
$handled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If no overlay handled it, propagate to normal components
|
||||
if (!$handled) {
|
||||
$this->rootComponent->handleMouseWheel($this->mouseX, $this->mouseY, $deltaY);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -278,7 +316,7 @@ class Window
|
||||
$this->rootComponent->setViewport($this->viewport);
|
||||
$this->rootComponent->layout($this->textRenderer);
|
||||
$this->shouldBeReLayouted = false;
|
||||
$this->hasBeenLaidOut = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -286,32 +324,44 @@ class Window
|
||||
|
||||
/**
|
||||
* Render the window
|
||||
*
|
||||
* Note: With SDL3, we must clear and redraw everything each frame.
|
||||
* The optimization comes from dirty tracking which prevents unnecessary
|
||||
* state updates and layout recalculations, not from skipping rendering.
|
||||
*/
|
||||
public function render(): void
|
||||
{
|
||||
// Clear the window with white background
|
||||
if ($this->rootComponent === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->shouldBeReLayouted) {
|
||||
$this->layout();
|
||||
}
|
||||
|
||||
sdl_set_render_draw_color($this->renderer, 255, 255, 255, 255);
|
||||
sdl_render_clear($this->renderer);
|
||||
|
||||
// Only render content if window has been laid out
|
||||
// This can happen when windows are created during async callbacks
|
||||
if ($this->hasBeenLaidOut && $this->rootComponent) {
|
||||
$this->rootComponent->render($this->renderer, $this->textRenderer);
|
||||
$this->rootComponent->renderContent($this->renderer, $this->textRenderer);
|
||||
|
||||
// Render all overlays last (they appear on top of everything)
|
||||
$overlays = $this->rootComponent->collectOverlays();
|
||||
usort($overlays, fn($a, $b) => $a->getZIndex() <=> $b->getZIndex());
|
||||
|
||||
if (!empty($overlays)) {
|
||||
usort($overlays, static fn($a, $b) => $a->getZIndex() <=> $b->getZIndex());
|
||||
|
||||
foreach ($overlays as $overlay) {
|
||||
if ($overlay->isVisible()) {
|
||||
if (!$overlay->isVisible()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$overlay->render($this->renderer, $this->textRenderer);
|
||||
$overlay->renderContent($this->renderer, $this->textRenderer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Present the rendered content
|
||||
$this->rootComponent->markClean();
|
||||
|
||||
sdl_render_present($this->renderer);
|
||||
}
|
||||
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
use PHPNative\Framework\Application;
|
||||
use PHPNative\Ui\Window;
|
||||
use PHPNative\Ui\Widget\Button;
|
||||
use PHPNative\Ui\Widget\Container;
|
||||
|
||||
define('DEBUG_EVENTS', true); // Enable event debugging
|
||||
|
||||
$app = new Application();
|
||||
|
||||
// Main window
|
||||
$mainWindow = new Window('Main Window', 400, 300);
|
||||
$mainContainer = new Container('flex flex-col p-4 bg-gray-100');
|
||||
|
||||
$openButton = new Button('Open Second Window', 'bg-blue-500 text-white p-4 m-2 rounded hover:bg-blue-600');
|
||||
$openButton->setOnClick(function() use ($app) {
|
||||
echo "Creating second window...\n";
|
||||
$secondWindow = new Window('Second Window', 400, 300);
|
||||
$secondContainer = new Container('flex flex-col p-4 bg-green-100');
|
||||
|
||||
$closeButton = new Button('Close This Window', 'bg-red-500 text-white p-4 m-2 rounded hover:bg-red-600');
|
||||
$closeButton->setOnClick(function() use ($secondWindow) {
|
||||
echo "Close button clicked in second window (ID: {$secondWindow->getWindowId()})\n";
|
||||
$secondWindow->close();
|
||||
});
|
||||
|
||||
$secondContainer->addComponent($closeButton);
|
||||
$secondWindow->setRoot($secondContainer);
|
||||
$app->addWindow($secondWindow);
|
||||
|
||||
echo "Second window created with ID: {$secondWindow->getWindowId()}\n";
|
||||
});
|
||||
|
||||
$mainContainer->addComponent($openButton);
|
||||
$mainWindow->setRoot($mainContainer);
|
||||
$app->addWindow($mainWindow);
|
||||
|
||||
echo "Main window ID: {$mainWindow->getWindowId()}\n";
|
||||
|
||||
$app->run();
|
||||
Loading…
Reference in New Issue
Block a user