Backup
This commit is contained in:
parent
2f34e4d2b2
commit
e07c06c7cc
@ -104,7 +104,9 @@ class ServerListTab
|
||||
// SFTP Manager Button (handler will be set by SftpManagerTab)
|
||||
$this->sftpButton = new Button(
|
||||
'SFTP Manager öffnen',
|
||||
'w-full border border-gray-300 rounded px-3 py-2 flex shadow-lg/50 shadow-cyan-500 flex-row gap-2 bg-green-300 text-black mb-2',
|
||||
'rounded-md bg-blue-500 px-3 py-2 text-sm font-semibold text-white shadow-lg shadow-blue-500/50 focus:outline-none flex flex-row gap-2',
|
||||
null,
|
||||
'text-white',
|
||||
);
|
||||
$sftpIcon = new Icon(IconName::folder, 16, 'text-white');
|
||||
$this->sftpButton->setIcon($sftpIcon);
|
||||
@ -114,7 +116,7 @@ class ServerListTab
|
||||
// SSH Terminal Button
|
||||
$this->sshTerminalButton = new Button(
|
||||
'SSH Terminal öffnen',
|
||||
'w-full border border-gray-300 rounded px-3 py-2 flex flex-row gap-2 bg-lime-300 text-black mb-2',
|
||||
'w-full border border-gray-300 rounded px-3 py-2 flex shadow-lg/100 shadow-lime-300 flex-row gap-2 bg-lime-300 text-black mb-2',
|
||||
);
|
||||
$sshTerminalIcon = new Icon(IconName::terminal, 16, 'text-white');
|
||||
$this->sshTerminalButton->setIcon($sshTerminalIcon);
|
||||
|
||||
@ -1,129 +0,0 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use PHPNative\Framework\Application;
|
||||
use PHPNative\Ui\Widget\Button;
|
||||
use PHPNative\Ui\Widget\Checkbox;
|
||||
use PHPNative\Ui\Widget\Container;
|
||||
use PHPNative\Ui\Widget\Label;
|
||||
use PHPNative\Ui\Widget\TextInput;
|
||||
use PHPNative\Ui\Window;
|
||||
|
||||
$app = new Application();
|
||||
$window = new Window('Todo App', 600, 500);
|
||||
|
||||
// Main container
|
||||
$mainContainer = new Container('flex flex-col p-4 bg-gray-50');
|
||||
|
||||
// Title
|
||||
$title = new Label('My Todo List', 'text-2xl font-bold mb-4 text-gray-800');
|
||||
$mainContainer->addComponent($title);
|
||||
|
||||
// Input section
|
||||
$inputContainer = new Container('flex flex-row mb-4');
|
||||
|
||||
$input = new TextInput(
|
||||
placeholder: 'What needs to be done?',
|
||||
style: 'flex-1 p-2 border border-gray-300 rounded mr-2',
|
||||
);
|
||||
|
||||
$addButton = new Button('Add', 'bg-blue-500 text-white p-2 rounded-lg hover:bg-blue-600');
|
||||
|
||||
$inputContainer->addComponent($input);
|
||||
$inputContainer->addComponent($addButton);
|
||||
$mainContainer->addComponent($inputContainer);
|
||||
|
||||
// Todo list container
|
||||
$todoListContainer = new Container('flex flex-col');
|
||||
$mainContainer->addComponent($todoListContainer);
|
||||
|
||||
// Stats
|
||||
$statsLabel = new Label('0 items', 'mt-4 text-gray-600 text-sm h-10');
|
||||
$mainContainer->addComponent($statsLabel);
|
||||
|
||||
// Todos array
|
||||
$todos = [];
|
||||
|
||||
// Function to update stats
|
||||
$updateStats = function () use (&$todos, $statsLabel) {
|
||||
$total = count($todos);
|
||||
$completed = count(array_filter($todos, fn($t) => $t['completed']));
|
||||
$statsLabel->setText("{$total} items ({$completed} completed)");
|
||||
};
|
||||
|
||||
// Function to render todos - use a reference so it can be used in closures
|
||||
$renderTodos = null;
|
||||
$renderTodos = function () use (&$todos, $todoListContainer, $updateStats, &$renderTodos, $window) {
|
||||
// Clear existing todos
|
||||
$todoListContainer->clearChildren();
|
||||
|
||||
foreach ($todos as $index => $todo) {
|
||||
$todoItem = new Container('flex flex-row items-center p-2 mb-2 bg-white border border-gray-200 rounded h-12');
|
||||
|
||||
// Checkbox
|
||||
$checkbox = new Checkbox(
|
||||
label: '',
|
||||
checked: $todo['completed'],
|
||||
style: 'mr-2 w-6 h-6',
|
||||
);
|
||||
|
||||
$checkbox->setOnChange(function ($checked) use (&$todos, $index, &$renderTodos, $updateStats) {
|
||||
$todos[$index]['completed'] = $checked;
|
||||
$updateStats();
|
||||
});
|
||||
|
||||
// Text
|
||||
$textStyle = $todo['completed'] ? 'flex-1 text-gray-400 line-through' : 'flex-1 text-gray-800';
|
||||
$todoText = new Label($todo['text'], $textStyle);
|
||||
|
||||
// Delete button
|
||||
$deleteButton = new Button('Delete', 'bg-red-500 text-white px-2 py-1 rounded text-sm hover:bg-red-600');
|
||||
$deleteButton->setOnClick(function () use (&$todos, $index, &$renderTodos, $updateStats) {
|
||||
array_splice($todos, $index, 1);
|
||||
$renderTodos();
|
||||
$updateStats();
|
||||
});
|
||||
|
||||
$todoItem->addComponent($checkbox);
|
||||
$todoItem->addComponent($todoText);
|
||||
$todoItem->addComponent($deleteButton);
|
||||
|
||||
$todoListContainer->addComponent($todoItem);
|
||||
}
|
||||
|
||||
$updateStats();
|
||||
|
||||
// Trigger re-layout after adding new components
|
||||
$window->setShouldBeReLayouted(true);
|
||||
};
|
||||
|
||||
// Add button click handler
|
||||
$addButton->setOnClick(function () use ($input, &$todos, &$renderTodos) {
|
||||
$text = trim($input->getValue());
|
||||
|
||||
if (!empty($text)) {
|
||||
$todos[] = [
|
||||
'text' => $text,
|
||||
'completed' => false,
|
||||
];
|
||||
|
||||
$input->setValue(''); // Clear input
|
||||
$renderTodos();
|
||||
}
|
||||
});
|
||||
|
||||
// Handle Enter key in input
|
||||
$input->setOnChange(function ($value) {
|
||||
// Could add live validation here
|
||||
});
|
||||
|
||||
$window->setRoot($mainContainer);
|
||||
$app->addWindow($window);
|
||||
|
||||
echo "Todo App started!\n";
|
||||
echo "- Type in the input field and click 'Add' or press Enter\n";
|
||||
echo "- Click checkboxes to mark todos as complete\n";
|
||||
echo "- Click 'Delete' to remove a todo\n\n";
|
||||
|
||||
$app->run();
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -702,6 +702,28 @@ PHP_FUNCTION(sdl_set_render_clip_rect) {
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
PHP_FUNCTION(sdl_set_render_scale) {
|
||||
zval *ren_res;
|
||||
SDL_Renderer *ren;
|
||||
double scale_x, scale_y;
|
||||
|
||||
if (zend_parse_parameters(ZEND_NUM_ARGS(), "rdd", &ren_res, &scale_x, &scale_y) == FAILURE) {
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
ren = (SDL_Renderer *)zend_fetch_resource(Z_RES_P(ren_res), "SDL_Renderer", le_sdl_renderer);
|
||||
if (!ren) {
|
||||
RETURN_FALSE;
|
||||
}
|
||||
|
||||
if (SDL_SetRenderScale(ren, (float)scale_x, (float)scale_y) < 0) {
|
||||
php_error_docref(NULL, E_WARNING, "Failed to set render scale: %s", SDL_GetError());
|
||||
RETURN_FALSE;
|
||||
}
|
||||
|
||||
RETURN_TRUE;
|
||||
}
|
||||
|
||||
PHP_FUNCTION(sdl_get_window_size) {
|
||||
zval *win_res;
|
||||
SDL_Window *win;
|
||||
@ -723,6 +745,54 @@ PHP_FUNCTION(sdl_get_window_size) {
|
||||
add_index_long(return_value, 1, h);
|
||||
}
|
||||
|
||||
PHP_FUNCTION(sdl_get_window_size_in_pixels) {
|
||||
zval *win_res;
|
||||
SDL_Window *win;
|
||||
int w, h;
|
||||
|
||||
if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &win_res) == FAILURE) {
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
win = (SDL_Window *)zend_fetch_resource(Z_RES_P(win_res), "SDL_Window", le_sdl_window);
|
||||
if (!win) {
|
||||
RETURN_FALSE;
|
||||
}
|
||||
|
||||
if (SDL_GetWindowSizeInPixels(win, &w, &h) < 0) {
|
||||
php_error_docref(NULL, E_WARNING, "Failed to get window size in pixels: %s", SDL_GetError());
|
||||
RETURN_FALSE;
|
||||
}
|
||||
|
||||
array_init(return_value);
|
||||
add_index_long(return_value, 0, w);
|
||||
add_index_long(return_value, 1, h);
|
||||
}
|
||||
|
||||
PHP_FUNCTION(sdl_get_renderer_output_size) {
|
||||
zval *ren_res;
|
||||
SDL_Renderer *ren;
|
||||
int w, h;
|
||||
|
||||
if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &ren_res) == FAILURE) {
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
ren = (SDL_Renderer *)zend_fetch_resource(Z_RES_P(ren_res), "SDL_Renderer", le_sdl_renderer);
|
||||
if (!ren) {
|
||||
RETURN_FALSE;
|
||||
}
|
||||
|
||||
if (SDL_GetRenderOutputSize(ren, &w, &h) < 0) {
|
||||
php_error_docref(NULL, E_WARNING, "Failed to get renderer output size: %s", SDL_GetError());
|
||||
RETURN_FALSE;
|
||||
}
|
||||
|
||||
array_init(return_value);
|
||||
add_index_long(return_value, 0, w);
|
||||
add_index_long(return_value, 1, h);
|
||||
}
|
||||
|
||||
PHP_FUNCTION(sdl_start_text_input) {
|
||||
zval *win_res;
|
||||
SDL_Window *win;
|
||||
@ -903,10 +973,24 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_set_render_clip_rect, 0, 0, 1)
|
||||
ZEND_ARG_INFO(0, rect)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_set_render_scale, 0, 0, 3)
|
||||
ZEND_ARG_INFO(0, renderer)
|
||||
ZEND_ARG_INFO(0, scaleX)
|
||||
ZEND_ARG_INFO(0, scaleY)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_get_window_size, 0, 0, 1)
|
||||
ZEND_ARG_INFO(0, window)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_get_window_size_in_pixels, 0, 0, 1)
|
||||
ZEND_ARG_INFO(0, window)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_get_renderer_output_size, 0, 0, 1)
|
||||
ZEND_ARG_INFO(0, renderer)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_sdl_start_text_input, 0, 0, 1)
|
||||
ZEND_ARG_INFO(0, window)
|
||||
ZEND_END_ARG_INFO()
|
||||
@ -929,6 +1013,7 @@ const zend_function_entry sdl3_functions[] = {
|
||||
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_set_render_scale, arginfo_sdl_set_render_scale)
|
||||
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)
|
||||
@ -944,6 +1029,8 @@ const zend_function_entry sdl3_functions[] = {
|
||||
PHP_FE(sdl_rounded_box_ex, arginfo_sdl_rounded_box_ex)
|
||||
PHP_FE(sdl_set_render_clip_rect, arginfo_sdl_set_render_clip_rect)
|
||||
PHP_FE(sdl_get_window_size, arginfo_sdl_get_window_size)
|
||||
PHP_FE(sdl_get_window_size_in_pixels, arginfo_sdl_get_window_size_in_pixels)
|
||||
PHP_FE(sdl_get_renderer_output_size, arginfo_sdl_get_renderer_output_size)
|
||||
PHP_FE(sdl_start_text_input, arginfo_sdl_start_text_input)
|
||||
PHP_FE(sdl_stop_text_input, arginfo_sdl_stop_text_input)
|
||||
|
||||
|
||||
@ -9,14 +9,16 @@ namespace PHPNative\Framework;
|
||||
class TextRenderer
|
||||
{
|
||||
private $renderer;
|
||||
private $font = null;
|
||||
private bool $initialized = false;
|
||||
private string $fontPath;
|
||||
private int $fontSize;
|
||||
private string $fontPath = '';
|
||||
private int $defaultFontSize = 16;
|
||||
/** @var array<int, resource> */
|
||||
private array $fonts = [];
|
||||
private float $colorR = 1.0;
|
||||
private float $colorG = 1.0;
|
||||
private float $colorB = 1.0;
|
||||
private float $colorA = 1.0;
|
||||
private float $pixelRatio = 1.0;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@ -54,11 +56,10 @@ class TextRenderer
|
||||
}
|
||||
|
||||
$this->fontPath = $fontPath;
|
||||
$this->fontSize = $fontSize;
|
||||
$this->defaultFontSize = $fontSize;
|
||||
|
||||
$this->font = ttf_open_font($fontPath, $fontSize);
|
||||
|
||||
if (!$this->font) {
|
||||
$font = $this->loadFont($fontSize);
|
||||
if ($font === null) {
|
||||
error_log("Failed to open font: {$fontPath}");
|
||||
$this->initialized = false;
|
||||
return false;
|
||||
@ -72,6 +73,50 @@ class TextRenderer
|
||||
return true;
|
||||
}
|
||||
|
||||
private function loadFont(int $size): mixed
|
||||
{
|
||||
$size = max(1, (int) round($size));
|
||||
|
||||
if (isset($this->fonts[$size])) {
|
||||
return $this->fonts[$size];
|
||||
}
|
||||
|
||||
if ($this->fontPath === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$font = ttf_open_font($this->fontPath, $size);
|
||||
if (!$font) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->fonts[$size] = $font;
|
||||
return $font;
|
||||
}
|
||||
|
||||
private function getFont(int $size): mixed
|
||||
{
|
||||
if (!$this->initialized) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->loadFont($size);
|
||||
}
|
||||
|
||||
private function getScaledFontSize(int $size): int
|
||||
{
|
||||
if ($this->pixelRatio <= 1.0) {
|
||||
return $size;
|
||||
}
|
||||
|
||||
return max(1, (int) round($size * $this->pixelRatio));
|
||||
}
|
||||
|
||||
public function setPixelRatio(float $ratio): void
|
||||
{
|
||||
$this->pixelRatio = max(1.0, $ratio);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a system font
|
||||
*
|
||||
@ -111,13 +156,18 @@ class TextRenderer
|
||||
*/
|
||||
public function drawText(string $text, int $x, int $y, null|int $size = null): void
|
||||
{
|
||||
if (!$this->initialized || !$this->font) {
|
||||
if (!$this->initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// SDL_ttf doesn't support variable font sizes easily
|
||||
// We use the font size that was set when the font was opened
|
||||
// For different sizes, you'd need to open multiple font instances
|
||||
$fontSize = $size ?? $this->defaultFontSize;
|
||||
$baseFont = $this->getFont($fontSize);
|
||||
if ($baseFont === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$renderFontSize = $this->getScaledFontSize($fontSize);
|
||||
$renderFont = $this->getFont($renderFontSize) ?? $baseFont;
|
||||
|
||||
// Convert float color (0.0-1.0) to int (0-255)
|
||||
$r = (int) ($this->colorR * 255);
|
||||
@ -128,7 +178,7 @@ class TextRenderer
|
||||
if (strlen($text) < 1) {
|
||||
return;
|
||||
}
|
||||
$surface = ttf_render_text_blended($this->font, $text, $r, $g, $b);
|
||||
$surface = ttf_render_text_blended($renderFont, $text, $r, $g, $b);
|
||||
if (!$surface) {
|
||||
return;
|
||||
}
|
||||
@ -140,7 +190,7 @@ class TextRenderer
|
||||
}
|
||||
|
||||
// Get text size
|
||||
$textSize = ttf_size_text($this->font, $text);
|
||||
$textSize = ttf_size_text($baseFont, $text);
|
||||
|
||||
// Render texture
|
||||
sdl_render_texture($this->renderer, $texture, [
|
||||
@ -158,19 +208,28 @@ class TextRenderer
|
||||
* @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
|
||||
public function createTextTexture(string $text, null|int $size = null): null|array
|
||||
{
|
||||
if (!$this->initialized || !$this->font) {
|
||||
if (!$this->initialized) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$fontSize = $size ?? $this->defaultFontSize;
|
||||
$baseFont = $this->getFont($fontSize);
|
||||
if ($baseFont === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$renderFontSize = $this->getScaledFontSize($fontSize);
|
||||
$renderFont = $this->getFont($renderFontSize) ?? $baseFont;
|
||||
|
||||
$r = (int) ($this->colorR * 255);
|
||||
$g = (int) ($this->colorG * 255);
|
||||
$b = (int) ($this->colorB * 255);
|
||||
if (strlen($text) < 1) {
|
||||
return null;
|
||||
}
|
||||
$surface = ttf_render_text_blended($this->font, $text, $r, $g, $b);
|
||||
$surface = ttf_render_text_blended($renderFont, $text, $r, $g, $b);
|
||||
if (!$surface) {
|
||||
return null;
|
||||
}
|
||||
@ -180,7 +239,7 @@ class TextRenderer
|
||||
return null;
|
||||
}
|
||||
|
||||
$dimensions = ttf_size_text($this->font, $text);
|
||||
$dimensions = ttf_size_text($baseFont, $text);
|
||||
|
||||
sdl_set_texture_blend_mode($texture, SDL_BLENDMODE_BLEND);
|
||||
if (\function_exists('sdl_set_texture_alpha_mod')) {
|
||||
@ -219,12 +278,17 @@ class TextRenderer
|
||||
*/
|
||||
public function measureText(string $text, null|int $size = null): array
|
||||
{
|
||||
if (!$this->initialized || !$this->font) {
|
||||
if (!$this->initialized) {
|
||||
return [0, 0];
|
||||
}
|
||||
|
||||
// Get text size using TTF
|
||||
$dimensions = ttf_size_text($this->font, $text);
|
||||
$fontSize = $size ?? $this->defaultFontSize;
|
||||
$font = $this->getFont($fontSize);
|
||||
if ($font === null) {
|
||||
return [0, 0];
|
||||
}
|
||||
|
||||
$dimensions = ttf_size_text($font, $text);
|
||||
return [(int) $dimensions['w'], (int) $dimensions['h']];
|
||||
}
|
||||
|
||||
@ -245,11 +309,16 @@ class TextRenderer
|
||||
*/
|
||||
public function free(): void
|
||||
{
|
||||
if ($this->initialized && $this->font) {
|
||||
ttf_close_font($this->font);
|
||||
$this->font = null;
|
||||
$this->initialized = false;
|
||||
if (!$this->initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->fonts as $font) {
|
||||
ttf_close_font($font);
|
||||
}
|
||||
|
||||
$this->fonts = [];
|
||||
$this->initialized = false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -862,42 +862,45 @@ abstract class Component
|
||||
}
|
||||
|
||||
/**
|
||||
* Box blur algorithm for shadow texture
|
||||
* Box blur algorithm for the alpha channel of the shadow texture
|
||||
* Keeps RGB constant so tinted shadows don't desaturate while fading out.
|
||||
*/
|
||||
private function boxBlur(array &$pixels, int $width, int $height, int $radius): void
|
||||
private function boxBlurAlpha(array &$alphaMap, int $width, int $height, int $radius): void
|
||||
{
|
||||
if ($radius <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$temp = array_fill(0, $width * $height, 0);
|
||||
|
||||
for ($y = 0; $y < $height; $y++) {
|
||||
for ($x = 0; $x < $width; $x++) {
|
||||
$r = 0; $g = 0; $b = 0; $a = 0; $count = 0;
|
||||
$alphaSum = 0;
|
||||
$count = 0;
|
||||
|
||||
for ($dy = -$radius; $dy <= $radius; $dy++) {
|
||||
$ny = $y + $dy;
|
||||
if ($ny < 0 || $ny >= $height) {
|
||||
continue;
|
||||
}
|
||||
$rowOffset = $ny * $width;
|
||||
|
||||
for ($dx = -$radius; $dx <= $radius; $dx++) {
|
||||
$nx = $x + $dx;
|
||||
$ny = $y + $dy;
|
||||
|
||||
if ($nx >= 0 && $nx < $width && $ny >= 0 && $ny < $height) {
|
||||
$pixel = $pixels[$ny * $width + $nx];
|
||||
$r += ($pixel >> 16) & 0xFF;
|
||||
$g += ($pixel >> 8) & 0xFF;
|
||||
$b += $pixel & 0xFF;
|
||||
$a += ($pixel >> 24) & 0xFF;
|
||||
$count++;
|
||||
if ($nx < 0 || $nx >= $width) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$alphaSum += $alphaMap[$rowOffset + $nx];
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
$r = (int)($r / $count);
|
||||
$g = (int)($g / $count);
|
||||
$b = (int)($b / $count);
|
||||
$a = (int)($a / $count);
|
||||
|
||||
$temp[$y * $width + $x] = ($a << 24) | ($r << 16) | ($g << 8) | $b;
|
||||
$temp[$y * $width + $x] = $count > 0 ? (int) ($alphaSum / $count) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
$pixels = $temp;
|
||||
$alphaMap = $temp;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -905,23 +908,28 @@ abstract class Component
|
||||
*/
|
||||
private function createShadowTexture(&$renderer, int $width, int $height, int $blurRadius, int $alpha, int $r = 0, int $g = 0, int $b = 0): mixed
|
||||
{
|
||||
// Create pixel array (ARGB8888 format) - start with transparent
|
||||
$pixels = array_fill(0, $width * $height, 0x00000000);
|
||||
|
||||
// Create color value in ARGB format
|
||||
$color = ($alpha << 24) | ($r << 16) | ($g << 8) | $b;
|
||||
// Create alpha map (single channel) - start with transparent
|
||||
$alphaMap = array_fill(0, $width * $height, 0);
|
||||
|
||||
// Fill only the inner rectangle with semi-transparent color
|
||||
// Leave a border for the blur to spread into
|
||||
$margin = $blurRadius + 2;
|
||||
for ($y = $margin; $y < $height - $margin; $y++) {
|
||||
for ($x = $margin; $x < $width - $margin; $x++) {
|
||||
$pixels[$y * $width + $x] = $color;
|
||||
$alphaMap[$y * $width + $x] = $alpha;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply box blur - this will spread the alpha to the edges
|
||||
$this->boxBlur($pixels, $width, $height, $blurRadius);
|
||||
// Apply box blur on alpha to spread transparency without washing out color
|
||||
$this->boxBlurAlpha($alphaMap, $width, $height, $blurRadius);
|
||||
|
||||
// Build final pixel buffer with constant RGB and varying alpha
|
||||
$pixels = [];
|
||||
$totalPixels = $width * $height;
|
||||
for ($i = 0; $i < $totalPixels; $i++) {
|
||||
$pixelAlpha = $alphaMap[$i];
|
||||
$pixels[$i] = ($pixelAlpha << 24) | ($r << 16) | ($g << 8) | $b;
|
||||
}
|
||||
|
||||
// Create texture
|
||||
$texture = sdl_create_texture(
|
||||
|
||||
@ -19,6 +19,7 @@ class Button extends Container
|
||||
public string $text = '',
|
||||
string $style = '',
|
||||
null|callable $onClick = null,
|
||||
string $buttonStyle = '',
|
||||
) {
|
||||
parent::__construct($style);
|
||||
|
||||
@ -29,7 +30,7 @@ class Button extends Container
|
||||
// Create label inside button
|
||||
$this->label = new Label(
|
||||
text: $text,
|
||||
style: 'text-black',
|
||||
style: 'text-black ' . $buttonStyle,
|
||||
);
|
||||
|
||||
$this->addComponent($this->label);
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
namespace PHPNative\Ui\Widget;
|
||||
|
||||
use PHPNative\Framework\TextRenderer;
|
||||
use PHPNative\Tailwind\Style\Text as TextStyle;
|
||||
use PHPNative\Ui\Component;
|
||||
|
||||
class Checkbox extends Component
|
||||
@ -102,9 +103,10 @@ class Checkbox extends Component
|
||||
if (!empty($this->labelText) && $textRenderer !== null && $textRenderer->isInitialized()) {
|
||||
$textX = $this->viewport->x + $checkboxSize + 8; // Checkbox + margin
|
||||
$textY = $this->viewport->y + 2;
|
||||
$textStyle = $this->computedStyles[\PHPNative\Tailwind\Style\Text::class] ?? new TextStyle();
|
||||
|
||||
$textRenderer->setColor(0, 0, 0, 1.0); // Black
|
||||
$textRenderer->drawText($this->labelText, (int) $textX, (int) $textY);
|
||||
$textRenderer->drawText($this->labelText, (int) $textX, (int) $textY, $textStyle->size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -224,13 +224,30 @@ class Container extends Component
|
||||
$width = $childStyles[Width::class] ?? null;
|
||||
$height = $childStyles[Height::class] ?? null;
|
||||
|
||||
// Track explicit cross-axis sizing so we don't stretch components unintentionally.
|
||||
$crossSize = null;
|
||||
$hasExplicitCrossSize = false;
|
||||
if ($isRow && $height) {
|
||||
$crossSize = $this->calculateSize($height, $this->contentViewport->height);
|
||||
$hasExplicitCrossSize = true;
|
||||
} elseif (!$isRow && $width) {
|
||||
$crossSize = $this->calculateSize($width, $this->contentViewport->width);
|
||||
$hasExplicitCrossSize = true;
|
||||
}
|
||||
|
||||
$size = 0;
|
||||
$hasExplicitSize = false;
|
||||
|
||||
// Check if child has flex-grow
|
||||
if ($childFlex && $childFlex->type !== FlexTypeEnum::none) {
|
||||
$flexGrowCount++;
|
||||
$childSizes[$index] = ['size' => 0, 'flexGrow' => true, 'natural' => false];
|
||||
$childSizes[$index] = [
|
||||
'size' => 0,
|
||||
'flexGrow' => true,
|
||||
'natural' => false,
|
||||
'crossSize' => $crossSize,
|
||||
'crossExplicit' => $hasExplicitCrossSize,
|
||||
];
|
||||
} else {
|
||||
// Calculate fixed size from basis, width, or height
|
||||
if ($basis) {
|
||||
@ -264,7 +281,13 @@ class Container extends Component
|
||||
}
|
||||
|
||||
$usedSpace += $size;
|
||||
$childSizes[$index] = ['size' => $size, 'flexGrow' => false, 'natural' => !$hasExplicitSize];
|
||||
$childSizes[$index] = [
|
||||
'size' => $size,
|
||||
'flexGrow' => false,
|
||||
'natural' => !$hasExplicitSize,
|
||||
'crossSize' => $crossSize,
|
||||
'crossExplicit' => $hasExplicitCrossSize,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -284,6 +307,8 @@ class Container extends Component
|
||||
|
||||
$childSize = $childSizes[$index];
|
||||
$size = $childSize['flexGrow'] ? $flexGrowSize : $childSize['size'];
|
||||
$crossSize = $childSize['crossSize'] ?? null;
|
||||
$hasExplicitCrossSize = $childSize['crossExplicit'] ?? false;
|
||||
|
||||
// Add gap before this child (except for first child)
|
||||
if ($childIndex > 0 && $gapSize > 0) {
|
||||
@ -293,21 +318,29 @@ class Container extends Component
|
||||
// Create viewport for child
|
||||
if ($isRow) {
|
||||
// Flex row
|
||||
$childHeight = $hasExplicitCrossSize && $crossSize !== null
|
||||
? min($crossSize, $this->contentViewport->height)
|
||||
: $this->contentViewport->height;
|
||||
|
||||
$childViewport = new Viewport(
|
||||
x: (int) $currentPosition,
|
||||
y: $this->contentViewport->y,
|
||||
width: $size,
|
||||
height: $this->contentViewport->height,
|
||||
height: $childHeight,
|
||||
windowWidth: $this->contentViewport->windowWidth,
|
||||
windowHeight: $this->contentViewport->windowHeight,
|
||||
);
|
||||
$currentPosition += $size;
|
||||
} else {
|
||||
$childWidth = $hasExplicitCrossSize && $crossSize !== null
|
||||
? min($crossSize, $this->contentViewport->width)
|
||||
: $this->contentViewport->width;
|
||||
|
||||
// Flex column
|
||||
$childViewport = new Viewport(
|
||||
x: $this->contentViewport->x,
|
||||
y: (int) $currentPosition,
|
||||
width: $this->contentViewport->width,
|
||||
width: $childWidth,
|
||||
height: $size,
|
||||
windowWidth: $this->contentViewport->windowWidth,
|
||||
windowHeight: $this->contentViewport->windowHeight,
|
||||
|
||||
@ -83,7 +83,7 @@ class Label extends Component
|
||||
|
||||
if ($this->renderDirty || $this->textTexture === null) {
|
||||
$this->clearTextTexture();
|
||||
$textureData = $textRenderer->createTextTexture($this->text);
|
||||
$textureData = $textRenderer->createTextTexture($this->text, $textStyle->size);
|
||||
if ($textureData !== null) {
|
||||
$this->textTexture = $textureData['texture'];
|
||||
$this->textWidth = $textureData['width'];
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
namespace PHPNative\Ui\Widget;
|
||||
|
||||
use PHPNative\Framework\TextRenderer;
|
||||
use PHPNative\Tailwind\Style\Text as TextStyle;
|
||||
|
||||
class TextInput extends Container
|
||||
{
|
||||
@ -42,12 +43,14 @@ class TextInput extends Container
|
||||
if (!isset($this->computedStyles[\PHPNative\Tailwind\Style\Height::class])) {
|
||||
$padding = $this->computedStyles[\PHPNative\Tailwind\Style\Padding::class] ?? null;
|
||||
$paddingY = $padding ? ($padding->top + $padding->bottom) : 0;
|
||||
$textStyle = $this->computedStyles[\PHPNative\Tailwind\Style\Text::class] ?? new TextStyle();
|
||||
$fontSize = $textStyle->size;
|
||||
|
||||
// Calculate text height
|
||||
$textHeight = 16; // Default font size
|
||||
if ($textRenderer !== null && $textRenderer->isInitialized()) {
|
||||
$displayText = empty($this->value) ? ($this->placeholder ?: 'A') : 'A';
|
||||
$size = $textRenderer->measureText($displayText);
|
||||
$size = $textRenderer->measureText($displayText, $fontSize);
|
||||
$textHeight = $size[1] ?? 16;
|
||||
}
|
||||
|
||||
@ -374,6 +377,9 @@ class TextInput extends Container
|
||||
'h' => $this->viewport->height - 4,
|
||||
]);
|
||||
|
||||
$textStyle = $this->computedStyles[\PHPNative\Tailwind\Style\Text::class] ?? new TextStyle();
|
||||
$fontSize = $textStyle->size;
|
||||
|
||||
// Render selection highlight
|
||||
if ($this->hasSelection() && $textRenderer !== null && $textRenderer->isInitialized()) {
|
||||
$start = min($this->selectionStart, $this->selectionEnd);
|
||||
@ -384,13 +390,13 @@ class TextInput extends Container
|
||||
|
||||
$selectionX = $this->viewport->x + 6;
|
||||
if (!empty($textBeforeSelection)) {
|
||||
$size = $textRenderer->measureText($textBeforeSelection);
|
||||
$size = $textRenderer->measureText($textBeforeSelection, $fontSize);
|
||||
$selectionX += $size[0];
|
||||
}
|
||||
|
||||
$selectionWidth = 0;
|
||||
if (!empty($selectedText)) {
|
||||
$size = $textRenderer->measureText($selectedText);
|
||||
$size = $textRenderer->measureText($selectedText, $fontSize);
|
||||
$selectionWidth = $size[0];
|
||||
}
|
||||
|
||||
@ -417,7 +423,7 @@ class TextInput extends Container
|
||||
$textRenderer->setColor(0, 0, 0, 1.0); // Black
|
||||
}
|
||||
|
||||
$textRenderer->drawText($displayText, (int) $textX, (int) $textY);
|
||||
$textRenderer->drawText($displayText, (int) $textX, (int) $textY, $fontSize);
|
||||
}
|
||||
|
||||
// Render cursor if focused
|
||||
@ -427,7 +433,7 @@ class TextInput extends Container
|
||||
$cursorX = $this->viewport->x + 6; // Left padding
|
||||
|
||||
if (!empty($textBeforeCursor)) {
|
||||
$size = $textRenderer->measureText($textBeforeCursor);
|
||||
$size = $textRenderer->measureText($textBeforeCursor, $fontSize);
|
||||
$cursorX += $size[0];
|
||||
}
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@ class Window
|
||||
private float $mouseY = 0;
|
||||
private Viewport $viewport;
|
||||
private bool $shouldBeReLayouted = true;
|
||||
private float $pixelRatio = 2;
|
||||
private float $pixelRatio = 1.0;
|
||||
private bool $shouldClose = false;
|
||||
private $onResize = null;
|
||||
private $onFpsChange = null;
|
||||
@ -46,9 +46,8 @@ class Window
|
||||
$sdlInitialized = true;
|
||||
}
|
||||
|
||||
// Create window with resizable flag for normal window decorations
|
||||
// SDL_WINDOW_RESIZABLE gives you the standard window controls (close, minimize, maximize)
|
||||
$flags = SDL_WINDOW_RESIZABLE;
|
||||
// Create window with resizable + high pixel density flags to get crisp rendering on HiDPI displays
|
||||
$flags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY;
|
||||
|
||||
$this->window = sdl_create_window($title, $width, $height, $flags);
|
||||
if (!$this->window) {
|
||||
@ -72,6 +71,7 @@ class Window
|
||||
if (!$this->textRenderer->init()) {
|
||||
error_log('Warning: Failed to initialize text renderer. Text rendering will not be available.');
|
||||
}
|
||||
$this->updatePixelRatio();
|
||||
|
||||
// Get actual window size
|
||||
$size = sdl_get_window_size($this->window);
|
||||
@ -87,6 +87,52 @@ class Window
|
||||
$this->lastFpsUpdate = microtime(true);
|
||||
}
|
||||
|
||||
private function updatePixelRatio(): void
|
||||
{
|
||||
$this->pixelRatio = 1.0;
|
||||
$windowSize = sdl_get_window_size($this->window);
|
||||
$pixelSize = null;
|
||||
|
||||
if (function_exists('sdl_get_window_size_in_pixels')) {
|
||||
$pixelSize = sdl_get_window_size_in_pixels($this->window);
|
||||
}
|
||||
|
||||
if (
|
||||
(!is_array($pixelSize) || count($pixelSize) < 2) &&
|
||||
function_exists('sdl_get_renderer_output_size')
|
||||
) {
|
||||
$pixelSize = sdl_get_renderer_output_size($this->renderer);
|
||||
}
|
||||
|
||||
if (is_array($windowSize) && is_array($pixelSize)) {
|
||||
$logicalWidth = max(1, (int) ($windowSize[0] ?? 1));
|
||||
$logicalHeight = max(1, (int) ($windowSize[1] ?? 1));
|
||||
$pixelWidth = max(1, (int) ($pixelSize[0] ?? 1));
|
||||
$pixelHeight = max(1, (int) ($pixelSize[1] ?? 1));
|
||||
|
||||
$ratioX = $pixelWidth / $logicalWidth;
|
||||
$ratioY = $pixelHeight / $logicalHeight;
|
||||
|
||||
$computed = max($ratioX, $ratioY);
|
||||
if ($computed > 0) {
|
||||
$this->pixelRatio = max(1.0, $computed);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->textRenderer) {
|
||||
$this->textRenderer->setPixelRatio($this->pixelRatio);
|
||||
}
|
||||
|
||||
if (function_exists('sdl_set_render_scale')) {
|
||||
sdl_set_render_scale($this->renderer, $this->pixelRatio, $this->pixelRatio);
|
||||
}
|
||||
}
|
||||
|
||||
public function getPixelRatio(): float
|
||||
{
|
||||
return $this->pixelRatio;
|
||||
}
|
||||
|
||||
public function setRoot(Component $component): self
|
||||
{
|
||||
if ($this->rootComponent !== null) {
|
||||
@ -207,6 +253,8 @@ class Window
|
||||
$this->textRenderer->updateFramebuffer($newWidth, $newHeight);
|
||||
}
|
||||
|
||||
$this->updatePixelRatio();
|
||||
|
||||
$this->viewport->x = 0;
|
||||
$this->viewport->y = 0;
|
||||
$this->viewport->windowWidth = $newWidth;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user