Backup
This commit is contained in:
parent
da0d560301
commit
2f34e4d2b2
@ -50,7 +50,7 @@ class ServerListTab
|
||||
// Refresh button
|
||||
$this->refreshButton = new Button(
|
||||
'Server aktualisieren',
|
||||
'flex flex-row gap-2 px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700',
|
||||
'flex shadow-lg/50 flex-row gap-2 px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700',
|
||||
);
|
||||
$refreshIcon = new Icon(IconName::sync, 16, 'text-white');
|
||||
$this->refreshButton->setIcon($refreshIcon);
|
||||
@ -104,7 +104,7 @@ 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 flex-row gap-2 bg-green-300 text-black mb-2',
|
||||
'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',
|
||||
);
|
||||
$sftpIcon = new Icon(IconName::folder, 16, 'text-white');
|
||||
$this->sftpButton->setIcon($sftpIcon);
|
||||
|
||||
49
examples/alpha_test.php
Normal file
49
examples/alpha_test.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use PHPNative\Framework\Application;
|
||||
use PHPNative\Ui\Widget\Container;
|
||||
use PHPNative\Ui\Window;
|
||||
|
||||
// Create application
|
||||
$app = new Application();
|
||||
$window = new Window('Alpha Blending Test', 400, 400);
|
||||
|
||||
// Custom container to test alpha blending
|
||||
class AlphaTestContainer extends Container
|
||||
{
|
||||
public function render(&$renderer, ?\PHPNative\Framework\TextRenderer $textRenderer = null): void
|
||||
{
|
||||
parent::render($renderer, $textRenderer);
|
||||
|
||||
// Enable alpha blending for renderer
|
||||
sdl_set_render_draw_blend_mode($renderer, SDL_BLENDMODE_BLEND);
|
||||
|
||||
// Draw 3 overlapping semi-transparent rectangles
|
||||
// Red rectangle - alpha 30
|
||||
sdl_set_render_draw_color($renderer, 255, 0, 0, 30);
|
||||
sdl_render_fill_rect($renderer, ['x' => 50, 'y' => 100, 'w' => 100, 'h' => 100]);
|
||||
|
||||
// Green rectangle - alpha 30
|
||||
sdl_set_render_draw_color($renderer, 0, 255, 0, 30);
|
||||
sdl_render_fill_rect($renderer, ['x' => 100, 'y' => 100, 'w' => 100, 'h' => 100]);
|
||||
|
||||
// Blue rectangle - alpha 30
|
||||
sdl_set_render_draw_color($renderer, 0, 0, 255, 30);
|
||||
sdl_render_fill_rect($renderer, ['x' => 150, 'y' => 100, 'w' => 100, 'h' => 100]);
|
||||
|
||||
// Test: 10 black layers with alpha 3
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
sdl_set_render_draw_color($renderer, 0, 0, 0, 3);
|
||||
sdl_render_fill_rect($renderer, ['x' => 50, 'y' => 250, 'w' => 200 + $i * 10, 'h' => 100]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$mainContainer = new AlphaTestContainer('flex items-center justify-center bg-gray-200');
|
||||
|
||||
// Set window content and run
|
||||
$window->setRoot($mainContainer);
|
||||
$app->addWindow($window);
|
||||
$app->run();
|
||||
@ -13,8 +13,8 @@ $window = new Window('Simple Shadow Test', 400, 400);
|
||||
// Main container with light background
|
||||
$mainContainer = new Container('flex items-center justify-center bg-gray-200');
|
||||
|
||||
// Simple box with shadow
|
||||
$box = new Container('w-48 h-48 bg-white rounded-lg shadow-lg');
|
||||
// Simple box with shadow - add bg-white so it's visible
|
||||
$box = new Container('w-48 h-48 bg-white rounded-lg shadow-lg shadow-rose-500');
|
||||
|
||||
$mainContainer->addComponent($box);
|
||||
|
||||
|
||||
@ -3,70 +3,42 @@
|
||||
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;
|
||||
use PHPNative\Ui\Window;
|
||||
|
||||
// Create application
|
||||
$app = new Application();
|
||||
$window = new Window('Shadow Test', 800, 600);
|
||||
$window = new Window('Shadow Color & Opacity Test', 800, 600);
|
||||
|
||||
// Main container
|
||||
$mainContainer = new Container('flex flex-col gap-4 p-8');
|
||||
$mainContainer = new Container('flex flex-col gap-6 items-center justify-center bg-gray-100 p-8');
|
||||
|
||||
// Title
|
||||
$title = new Label('Tailwind Shadow Test', 'text-2xl font-bold text-black mb-4');
|
||||
$mainContainer->addComponent($title);
|
||||
// Test 1: Default shadow (black)
|
||||
$box1 = new Container('w-48 h-32 bg-white rounded-lg shadow-lg flex items-center justify-center');
|
||||
|
||||
// Container for buttons
|
||||
$buttonContainer = new Container('flex flex-col gap-6 items-center');
|
||||
// Test 2: Shadow with opacity modifier (50% opacity)
|
||||
$box2 = new Container('w-48 h-32 bg-white rounded-lg shadow-lg/50 flex items-center justify-center');
|
||||
|
||||
// Test different shadow sizes
|
||||
$shadows = [
|
||||
'shadow-sm' => 'Small Shadow',
|
||||
'shadow' => 'Base Shadow',
|
||||
'shadow-md' => 'Medium Shadow',
|
||||
'shadow-lg' => 'Large Shadow',
|
||||
'shadow-xl' => 'Extra Large Shadow',
|
||||
'shadow-2xl' => '2X Large Shadow',
|
||||
'shadow-inner' => 'Inner Shadow',
|
||||
];
|
||||
// Test 3: Red shadow
|
||||
$box3 = new Container('w-48 h-32 bg-white rounded-lg shadow-red-500 flex items-center justify-center');
|
||||
|
||||
foreach ($shadows as $shadowClass => $label) {
|
||||
// Container for each example
|
||||
$exampleContainer = new Container('flex flex-row gap-4 items-center w-full');
|
||||
// Test 4: Blue shadow with opacity
|
||||
$box4 = new Container('w-48 h-32 bg-white rounded-lg shadow-blue-500/30 flex items-center justify-center');
|
||||
|
||||
// Label
|
||||
$labelWidget = new Label($label, 'w-48 text-black text-sm');
|
||||
$exampleContainer->addComponent($labelWidget);
|
||||
// Test 5: Green shadow with large size
|
||||
$box5 = new Container('w-48 h-32 bg-white rounded-lg shadow-xl shadow-green-500 flex items-center justify-center');
|
||||
|
||||
// Button with shadow
|
||||
$button = new Button('Button with ' . $shadowClass, 'px-6 py-3 text-white ' . $shadowClass);
|
||||
$exampleContainer->addComponent($button);
|
||||
// Create row containers for better layout
|
||||
$row1 = new Container('flex gap-6');
|
||||
$row1->addComponent($box1);
|
||||
$row1->addComponent($box2);
|
||||
$row1->addComponent($box3);
|
||||
|
||||
// Card with shadow
|
||||
$card = new Container('px-6 py-4 ' . $shadowClass);
|
||||
$cardLabel = new Label('Card', 'text-black text-sm');
|
||||
$card->addComponent($cardLabel);
|
||||
$exampleContainer->addComponent($card);
|
||||
$row2 = new Container('flex gap-6');
|
||||
$row2->addComponent($box4);
|
||||
$row2->addComponent($box5);
|
||||
|
||||
$buttonContainer->addComponent($exampleContainer);
|
||||
}
|
||||
|
||||
// Add no shadow example
|
||||
$noShadowContainer = new Container('flex flex-row gap-4 items-center w-full');
|
||||
$noShadowLabel = new Label('No Shadow', 'w-48 text-black text-sm');
|
||||
$noShadowContainer->addComponent($noShadowLabel);
|
||||
$noShadowButton = new Button('Button without shadow', 'px-6 py-3 bg-blue-500 text-white rounded-lg shadow-none');
|
||||
$noShadowContainer->addComponent($noShadowButton);
|
||||
$noShadowCard = new Container('px-6 py-4 bg-white rounded-lg shadow-none');
|
||||
$noShadowCardLabel = new Label('Card', 'text-black text-sm');
|
||||
$noShadowCard->addComponent($noShadowCardLabel);
|
||||
$noShadowContainer->addComponent($noShadowCard);
|
||||
$buttonContainer->addComponent($noShadowContainer);
|
||||
|
||||
$mainContainer->addComponent($buttonContainer);
|
||||
$mainContainer->addComponent($row1);
|
||||
$mainContainer->addComponent($row2);
|
||||
|
||||
// Set window content and run
|
||||
$window->setRoot($mainContainer);
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -425,6 +425,63 @@ PHP_FUNCTION(sdl_destroy_texture) {
|
||||
RETURN_TRUE;
|
||||
}
|
||||
|
||||
PHP_FUNCTION(sdl_update_texture) {
|
||||
zval *tex_res, *rect_arr, *pixels_arr;
|
||||
SDL_Texture *texture;
|
||||
SDL_Rect *rect = NULL, tmp_rect;
|
||||
zend_long pitch;
|
||||
|
||||
if (zend_parse_parameters(ZEND_NUM_ARGS(), "ra!al", &tex_res, &rect_arr, &pixels_arr, &pitch) == FAILURE) {
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
texture = (SDL_Texture *)zend_fetch_resource(Z_RES_P(tex_res), "SDL_Texture", le_sdl_texture);
|
||||
if (!texture) {
|
||||
RETURN_FALSE;
|
||||
}
|
||||
|
||||
// Parse rect if provided
|
||||
if (rect_arr != NULL && Z_TYPE_P(rect_arr) == IS_ARRAY) {
|
||||
HashTable *rect_ht = Z_ARRVAL_P(rect_arr);
|
||||
zval *val;
|
||||
|
||||
if ((val = zend_hash_str_find(rect_ht, "x", sizeof("x")-1)) != NULL) {
|
||||
tmp_rect.x = (int)zval_get_long(val);
|
||||
}
|
||||
if ((val = zend_hash_str_find(rect_ht, "y", sizeof("y")-1)) != NULL) {
|
||||
tmp_rect.y = (int)zval_get_long(val);
|
||||
}
|
||||
if ((val = zend_hash_str_find(rect_ht, "w", sizeof("w")-1)) != NULL) {
|
||||
tmp_rect.w = (int)zval_get_long(val);
|
||||
}
|
||||
if ((val = zend_hash_str_find(rect_ht, "h", sizeof("h")-1)) != NULL) {
|
||||
tmp_rect.h = (int)zval_get_long(val);
|
||||
}
|
||||
rect = &tmp_rect;
|
||||
}
|
||||
|
||||
// Convert PHP array to pixel data
|
||||
HashTable *pixels_ht = Z_ARRVAL_P(pixels_arr);
|
||||
int pixel_count = zend_hash_num_elements(pixels_ht);
|
||||
Uint32 *pixels = emalloc(pixel_count * sizeof(Uint32));
|
||||
|
||||
zval *pixel_val;
|
||||
int i = 0;
|
||||
ZEND_HASH_FOREACH_VAL(pixels_ht, pixel_val) {
|
||||
pixels[i++] = (Uint32)zval_get_long(pixel_val);
|
||||
} ZEND_HASH_FOREACH_END();
|
||||
|
||||
// Update texture
|
||||
if (SDL_UpdateTexture(texture, rect, pixels, (int)pitch) < 0) {
|
||||
efree(pixels);
|
||||
php_error_docref(NULL, E_WARNING, "Failed to update texture: %s", SDL_GetError());
|
||||
RETURN_FALSE;
|
||||
}
|
||||
|
||||
efree(pixels);
|
||||
RETURN_TRUE;
|
||||
}
|
||||
|
||||
PHP_FUNCTION(sdl_set_texture_blend_mode) {
|
||||
zval *tex_res;
|
||||
SDL_Texture *texture;
|
||||
@ -786,6 +843,13 @@ 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_update_texture, 0, 0, 4)
|
||||
ZEND_ARG_INFO(0, texture)
|
||||
ZEND_ARG_INFO(0, rect)
|
||||
ZEND_ARG_ARRAY_INFO(0, pixels, 0)
|
||||
ZEND_ARG_INFO(0, pitch)
|
||||
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)
|
||||
@ -871,6 +935,7 @@ const zend_function_entry sdl3_functions[] = {
|
||||
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_update_texture, arginfo_sdl_update_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)
|
||||
|
||||
@ -46,6 +46,8 @@ class StyleCollection extends TypedCollection
|
||||
\PHPNative\Tailwind\Parser\Text::merge($tmp[$style->style::class], $style->style);
|
||||
}elseif(isset($tmp[$style->style::class]) && $style->style::class === Flex::class) {
|
||||
\PHPNative\Tailwind\Parser\Flex::merge($tmp[$style->style::class], $style->style);
|
||||
}elseif(isset($tmp[$style->style::class]) && $style->style::class === \PHPNative\Tailwind\Style\Shadow::class) {
|
||||
\PHPNative\Tailwind\Parser\Shadow::merge($tmp[$style->style::class], $style->style);
|
||||
}else{
|
||||
$tmp[$style->style::class] = $style->style;
|
||||
}
|
||||
|
||||
@ -8,14 +8,49 @@ class Shadow implements Parser
|
||||
{
|
||||
public static function parse(string $style): ?\PHPNative\Tailwind\Style\Shadow
|
||||
{
|
||||
// shadow-sm, shadow, shadow-md, shadow-lg, shadow-xl, shadow-2xl, shadow-inner, shadow-none
|
||||
if (preg_match('/\bshadow-(sm|md|lg|xl|2xl|inner|none)\b/', $style, $matches)) {
|
||||
return new \PHPNative\Tailwind\Style\Shadow($matches[1]);
|
||||
$size = null;
|
||||
$color = new \PHPNative\Tailwind\Style\Color();
|
||||
$opacity = null;
|
||||
|
||||
// Parse shadow size with optional opacity (e.g., shadow-lg/50, shadow-xl/75)
|
||||
if (preg_match('/\bshadow-(sm|md|lg|xl|2xl|inner|none)(?:\/(\d+))?\b/', $style, $matches)) {
|
||||
$size = $matches[1];
|
||||
if (isset($matches[2])) {
|
||||
$opacity = (int)$matches[2];
|
||||
}
|
||||
}
|
||||
// shadow (base shadow) with optional opacity (e.g., shadow/50)
|
||||
elseif (preg_match('/\bshadow(?:\/(\d+))?\b/', $style, $matches)) {
|
||||
// Only match plain 'shadow' or 'shadow/XX', not 'shadow-color-XXX'
|
||||
if (!preg_match('/\bshadow-[a-z]+-\d+/', $style)) {
|
||||
$size = 'base';
|
||||
if (isset($matches[1])) {
|
||||
$opacity = (int)$matches[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// shadow (base shadow)
|
||||
if (preg_match('/\bshadow\b/', $style)) {
|
||||
return new \PHPNative\Tailwind\Style\Shadow('base');
|
||||
// Parse shadow color (e.g., shadow-red-500, shadow-blue-300)
|
||||
if (preg_match('/\bshadow-([a-z]+)-(\d+)(?:\/(\d+))?\b/', $style, $matches)) {
|
||||
$colorStr = $matches[1] . '-' . $matches[2];
|
||||
$color = Color::parse($colorStr);
|
||||
if (isset($matches[3])) {
|
||||
$opacity = (int)$matches[3];
|
||||
}
|
||||
// If color is specified but no size was found, use base shadow
|
||||
if ($size === null) {
|
||||
$size = 'base';
|
||||
}
|
||||
}
|
||||
|
||||
// If we still don't have a size, but have color or opacity, default to 'none'
|
||||
if ($size === null) {
|
||||
$size = 'none';
|
||||
}
|
||||
|
||||
// Return shadow style if we found a valid shadow class
|
||||
if ($size !== 'none' || $opacity !== null || $color->red >= 0) {
|
||||
return new \PHPNative\Tailwind\Style\Shadow($size, $color, $opacity);
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -23,8 +58,24 @@ class Shadow implements Parser
|
||||
|
||||
public static function merge(\PHPNative\Tailwind\Style\Shadow $class, \PHPNative\Tailwind\Style\Shadow $style)
|
||||
{
|
||||
if ($style->size !== 'none') {
|
||||
// Only merge size if it's not 'base' (which is the default for color-only shadows)
|
||||
// or if the existing size is 'none'
|
||||
if ($style->size !== 'none' && $style->size !== 'base') {
|
||||
$class->size = $style->size;
|
||||
} elseif ($style->size === 'base' && $class->size === 'none') {
|
||||
// Only set to 'base' if current size is 'none'
|
||||
$class->size = $style->size;
|
||||
}
|
||||
|
||||
if ($style->color->red >= 0 || $style->color->green >= 0 || $style->color->blue >= 0) {
|
||||
// Copy color values instead of reference
|
||||
$class->color->red = $style->color->red;
|
||||
$class->color->green = $style->color->green;
|
||||
$class->color->blue = $style->color->blue;
|
||||
$class->color->alpha = $style->color->alpha;
|
||||
}
|
||||
if ($style->opacity !== null) {
|
||||
$class->opacity = $style->opacity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,5 +9,6 @@ class Shadow implements Style
|
||||
public function __construct(
|
||||
public string $size = 'none', // none, sm, base, md, lg, xl, 2xl, inner
|
||||
public Color $color = new Color(),
|
||||
public ?int $opacity = null, // 0-100, null means use default
|
||||
) {}
|
||||
}
|
||||
|
||||
@ -63,6 +63,10 @@ class StyleParser
|
||||
|
||||
private static function parseSimpleStyle(string $style): ?Style
|
||||
{
|
||||
// Shadow must be parsed before Width to avoid "shadow-*" being matched as "w-*"
|
||||
if($s = \PHPNative\Tailwind\Parser\Shadow::parse($style)) {
|
||||
return $s;
|
||||
}
|
||||
if($pd = \PHPNative\Tailwind\Parser\Padding::parse($style)) {
|
||||
return $pd;
|
||||
}
|
||||
@ -96,9 +100,6 @@ class StyleParser
|
||||
if($b = \PHPNative\Tailwind\Parser\Border::parse($style)) {
|
||||
return $b;
|
||||
}
|
||||
if($s = \PHPNative\Tailwind\Parser\Shadow::parse($style)) {
|
||||
return $s;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -37,6 +37,9 @@ abstract class Component
|
||||
protected bool $useTextureCache = false;
|
||||
protected bool $textureCacheValid = false;
|
||||
|
||||
protected $cachedShadowTexture = null; // Cached shadow texture
|
||||
protected bool $shadowCacheValid = false;
|
||||
|
||||
protected Viewport $viewport;
|
||||
|
||||
protected array $computedStyles = [];
|
||||
@ -174,7 +177,12 @@ abstract class Component
|
||||
sdl_destroy_texture($this->cachedHoverTexture);
|
||||
$this->cachedHoverTexture = null;
|
||||
}
|
||||
if ($this->cachedShadowTexture !== null) {
|
||||
sdl_destroy_texture($this->cachedShadowTexture);
|
||||
$this->cachedShadowTexture = null;
|
||||
}
|
||||
$this->textureCacheValid = false;
|
||||
$this->shadowCacheValid = false;
|
||||
$this->renderDirty = true;
|
||||
}
|
||||
|
||||
@ -840,7 +848,105 @@ abstract class Component
|
||||
}
|
||||
|
||||
/**
|
||||
* Render shadow effect for the component
|
||||
* Render shadow if this component has one
|
||||
* Called by parent container before rendering the component itself
|
||||
*/
|
||||
public function renderShadowIfPresent(&$renderer): void
|
||||
{
|
||||
if (isset($this->computedStyles[\PHPNative\Tailwind\Style\Shadow::class])) {
|
||||
$shadow = $this->computedStyles[\PHPNative\Tailwind\Style\Shadow::class];
|
||||
if ($shadow instanceof \PHPNative\Tailwind\Style\Shadow) {
|
||||
$this->renderShadow($renderer, $shadow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Box blur algorithm for shadow texture
|
||||
*/
|
||||
private function boxBlur(array &$pixels, int $width, int $height, int $radius): void
|
||||
{
|
||||
$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;
|
||||
|
||||
for ($dy = -$radius; $dy <= $radius; $dy++) {
|
||||
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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
$pixels = $temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a blurred shadow texture
|
||||
*/
|
||||
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;
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply box blur - this will spread the alpha to the edges
|
||||
$this->boxBlur($pixels, $width, $height, $blurRadius);
|
||||
|
||||
// Create texture
|
||||
$texture = sdl_create_texture(
|
||||
$renderer,
|
||||
SDL_PIXELFORMAT_ARGB8888,
|
||||
SDL_TEXTUREACCESS_STATIC,
|
||||
$width,
|
||||
$height
|
||||
);
|
||||
|
||||
if (!$texture) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Update texture with pixel data
|
||||
sdl_update_texture($texture, null, $pixels, $width * 4);
|
||||
|
||||
// Set blend mode for alpha blending
|
||||
sdl_set_texture_blend_mode($texture, SDL_BLENDMODE_BLEND);
|
||||
|
||||
return $texture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render shadow effect for the component using texture-based approach
|
||||
*/
|
||||
private function renderShadow(&$renderer, \PHPNative\Tailwind\Style\Shadow $shadow): void
|
||||
{
|
||||
@ -849,114 +955,66 @@ abstract class Component
|
||||
}
|
||||
|
||||
// Define shadow properties based on size
|
||||
// Using fewer layers with proper alpha distribution
|
||||
// Alpha values much lower for transparent shadows (0-255 range)
|
||||
$shadowProps = match ($shadow->size) {
|
||||
'sm' => ['blur' => 3, 'spread' => 1, 'offsetX' => 0, 'offsetY' => 1, 'alpha' => 40],
|
||||
'base' => ['blur' => 4, 'spread' => 2, 'offsetX' => 0, 'offsetY' => 2, 'alpha' => 45],
|
||||
'md' => ['blur' => 6, 'spread' => 3, 'offsetX' => 0, 'offsetY' => 4, 'alpha' => 50],
|
||||
'lg' => ['blur' => 8, 'spread' => 5, 'offsetX' => 0, 'offsetY' => 6, 'alpha' => 55],
|
||||
'xl' => ['blur' => 10, 'spread' => 7, 'offsetX' => 0, 'offsetY' => 10, 'alpha' => 60],
|
||||
'2xl' => ['blur' => 15, 'spread' => 10, 'offsetX' => 0, 'offsetY' => 15, 'alpha' => 70],
|
||||
'inner' => ['blur' => 4, 'spread' => 2, 'offsetX' => 0, 'offsetY' => 0, 'alpha' => 45, 'inner' => true],
|
||||
default => ['blur' => 4, 'spread' => 2, 'offsetX' => 0, 'offsetY' => 2, 'alpha' => 45],
|
||||
'sm' => ['blur' => 2, 'offsetX' => 0, 'offsetY' => 1, 'alpha' => 25],
|
||||
'base' => ['blur' => 3, 'offsetX' => 0, 'offsetY' => 2, 'alpha' => 35],
|
||||
'md' => ['blur' => 4, 'offsetX' => 0, 'offsetY' => 4, 'alpha' => 45],
|
||||
'lg' => ['blur' => 6, 'offsetX' => 0, 'offsetY' => 10, 'alpha' => 60],
|
||||
'xl' => ['blur' => 10, 'offsetX' => 0, 'offsetY' => 20, 'alpha' => 75],
|
||||
'2xl' => ['blur' => 15, 'offsetX' => 0, 'offsetY' => 25, 'alpha' => 90],
|
||||
'inner' => ['blur' => 3, 'offsetX' => 0, 'offsetY' => 0, 'alpha' => 35, 'inner' => true],
|
||||
default => ['blur' => 3, 'offsetX' => 0, 'offsetY' => 2, 'alpha' => 35],
|
||||
};
|
||||
|
||||
// Get shadow color (default to black/gray if not specified)
|
||||
$shadowColor = $shadow->color->isNotSet()
|
||||
? $shadow->color
|
||||
: new \PHPNative\Tailwind\Style\Color(0, 0, 0, $shadowProps['alpha']);
|
||||
// Apply opacity modifier if specified (0-100 range -> 0-255 range)
|
||||
$alpha = $shadowProps['alpha'];
|
||||
if ($shadow->opacity !== null) {
|
||||
// Convert percentage to 0-255 range and apply to base alpha
|
||||
$alpha = (int)(($shadow->opacity / 100) * 255);
|
||||
}
|
||||
|
||||
// Override alpha with shadow-specific alpha if color alpha is default
|
||||
if ($shadowColor->alpha === 255) {
|
||||
$shadowColor = new \PHPNative\Tailwind\Style\Color(
|
||||
$shadowColor->red,
|
||||
$shadowColor->green,
|
||||
$shadowColor->blue,
|
||||
$shadowProps['alpha'],
|
||||
// Get shadow color (default to black if not specified)
|
||||
$r = $shadow->color->red >= 0 ? $shadow->color->red : 0;
|
||||
$g = $shadow->color->green >= 0 ? $shadow->color->green : 0;
|
||||
$b = $shadow->color->blue >= 0 ? $shadow->color->blue : 0;
|
||||
|
||||
// Use cached shadow texture if available and valid
|
||||
if ($this->shadowCacheValid && $this->cachedShadowTexture !== null) {
|
||||
$shadowTexture = $this->cachedShadowTexture;
|
||||
} else {
|
||||
// Create shadow texture with blur
|
||||
$shadowTexture = $this->createShadowTexture(
|
||||
$renderer,
|
||||
(int)$this->viewport->width,
|
||||
(int)$this->viewport->height,
|
||||
$shadowProps['blur'],
|
||||
$alpha,
|
||||
$r,
|
||||
$g,
|
||||
$b
|
||||
);
|
||||
|
||||
if (!$shadowTexture) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Cache the shadow texture
|
||||
$this->cachedShadowTexture = $shadowTexture;
|
||||
$this->shadowCacheValid = true;
|
||||
}
|
||||
|
||||
// Get border radius if present for rounded shadows
|
||||
$borderRadius = 0;
|
||||
$border = null;
|
||||
if (isset($this->computedStyles[\PHPNative\Tailwind\Style\Border::class])) {
|
||||
$border = $this->computedStyles[\PHPNative\Tailwind\Style\Border::class];
|
||||
$borderRadius = $border->roundTopLeft ?? 0;
|
||||
}
|
||||
// Render shadow texture with offset
|
||||
$shadowRect = [
|
||||
'x' => $this->viewport->x + $shadowProps['offsetX'],
|
||||
'y' => $this->viewport->y + $shadowProps['offsetY'],
|
||||
'w' => $this->viewport->width,
|
||||
'h' => $this->viewport->height,
|
||||
];
|
||||
|
||||
// Render shadow layers (simulate blur with expanding semi-transparent layers)
|
||||
// Note: Alpha blending is automatic when using alpha values < 255 in sdl_set_render_draw_color
|
||||
// Render from outermost to innermost for proper blending
|
||||
for ($i = $shadowProps['blur'] - 1; $i >= 0; $i--) {
|
||||
// Calculate alpha that decreases towards outer layers (blur effect)
|
||||
// Innermost layer (i=0) has full alpha, outermost has minimal alpha
|
||||
$progress = 1.0 - ($i / $shadowProps['blur']);
|
||||
$layerAlpha = (int) ($shadowProps['alpha'] * $progress);
|
||||
sdl_render_texture($renderer, $shadowTexture, $shadowRect);
|
||||
|
||||
// Ensure minimum visibility
|
||||
if ($layerAlpha < 5) {
|
||||
$layerAlpha = 5;
|
||||
}
|
||||
|
||||
// Calculate expansion for blur effect
|
||||
$expansion = $shadowProps['spread'] * ($i / $shadowProps['blur']);
|
||||
|
||||
if (isset($shadowProps['inner']) && $shadowProps['inner']) {
|
||||
// Inner shadow - render inside the element, shrinking inwards
|
||||
$shadowX = (int) ($this->viewport->x + $expansion);
|
||||
$shadowY = (int) ($this->viewport->y + $expansion);
|
||||
$shadowWidth = (int) max(0, $this->viewport->width - ($expansion * 2));
|
||||
$shadowHeight = (int) max(0, $this->viewport->height - ($expansion * 2));
|
||||
} else {
|
||||
// Outer shadow - expand outwards from element
|
||||
$shadowX = (int) ($this->viewport->x + $shadowProps['offsetX'] - $expansion);
|
||||
$shadowY = (int) ($this->viewport->y + $shadowProps['offsetY'] - $expansion);
|
||||
$shadowWidth = (int) ($this->viewport->width + ($expansion * 2));
|
||||
$shadowHeight = (int) ($this->viewport->height + ($expansion * 2));
|
||||
}
|
||||
|
||||
// Skip if shadow is too small or alpha is too low
|
||||
if ($shadowWidth <= 0 || $shadowHeight <= 0 || $layerAlpha < 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($borderRadius > 0 && $border !== null) {
|
||||
// Render rounded shadow with expanding border radius
|
||||
$x2 = $shadowX + $shadowWidth;
|
||||
$y2 = $shadowY + $shadowHeight;
|
||||
$radiusExpansion = $expansion / 2;
|
||||
|
||||
sdl_rounded_box_ex(
|
||||
$renderer,
|
||||
$shadowX,
|
||||
$shadowY,
|
||||
$x2,
|
||||
$y2,
|
||||
(int) (($border->roundTopLeft ?? 0) + $radiusExpansion),
|
||||
(int) (($border->roundTopRight ?? 0) + $radiusExpansion),
|
||||
(int) (($border->roundBottomRight ?? 0) + $radiusExpansion),
|
||||
(int) (($border->roundBottomLeft ?? 0) + $radiusExpansion),
|
||||
$shadowColor->red,
|
||||
$shadowColor->green,
|
||||
$shadowColor->blue,
|
||||
$layerAlpha,
|
||||
);
|
||||
} else {
|
||||
// Render rectangular shadow
|
||||
sdl_set_render_draw_color(
|
||||
$renderer,
|
||||
$shadowColor->red,
|
||||
$shadowColor->green,
|
||||
$shadowColor->blue,
|
||||
$layerAlpha,
|
||||
);
|
||||
sdl_render_fill_rect($renderer, [
|
||||
'x' => $shadowX,
|
||||
'y' => $shadowY,
|
||||
'w' => $shadowWidth,
|
||||
'h' => $shadowHeight,
|
||||
]);
|
||||
}
|
||||
}
|
||||
// Clean up texture only if it's not cached
|
||||
// If cached, it will be destroyed in invalidateTextureCache()
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,14 +9,68 @@ namespace PHPNative\Ui;
|
||||
class RenderStack
|
||||
{
|
||||
private array $layers = [];
|
||||
|
||||
private Viewport $savedViewport;
|
||||
private int $nextLayerId = 0;
|
||||
|
||||
public function __construct(
|
||||
public $renderer,
|
||||
public $textRenderer,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Add a render layer to the stack
|
||||
*
|
||||
* @param int $zIndex Lower values render first (behind), higher values render last (in front)
|
||||
* @param callable $renderCallback Function that performs the rendering
|
||||
* @param array $bounds ['x' => float, 'y' => float, 'w' => float, 'h' => float]
|
||||
* @return int Layer ID
|
||||
*/
|
||||
public function addLayer(int $zIndex, callable $renderCallback, array $bounds): int
|
||||
{
|
||||
$layerId = $this->nextLayerId++;
|
||||
|
||||
$this->layers[$layerId] = [
|
||||
'id' => $layerId,
|
||||
'zIndex' => $zIndex,
|
||||
'callback' => $renderCallback,
|
||||
'bounds' => $bounds,
|
||||
];
|
||||
|
||||
return $layerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a layer from the stack
|
||||
*/
|
||||
public function removeLayer(int $layerId): void
|
||||
{
|
||||
unset($this->layers[$layerId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all layers
|
||||
*/
|
||||
public function clear(): void
|
||||
{
|
||||
$this->layers = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Render all layers sorted by z-index
|
||||
*/
|
||||
public function renderAll(): void
|
||||
{
|
||||
// Sort layers by z-index (lower first)
|
||||
$sortedLayers = $this->layers;
|
||||
usort($sortedLayers, function ($a, $b) {
|
||||
return $a['zIndex'] <=> $b['zIndex'];
|
||||
});
|
||||
|
||||
// Render each layer
|
||||
foreach ($sortedLayers as $layer) {
|
||||
($layer['callback'])($this->renderer, $this->textRenderer);
|
||||
}
|
||||
}
|
||||
|
||||
public function beginRender(): void
|
||||
{
|
||||
$this->savedViewport = $component->getViewport();
|
||||
|
||||
@ -399,6 +399,13 @@ class Container extends Component
|
||||
return;
|
||||
}
|
||||
|
||||
// First pass: Render shadows of all children (behind them)
|
||||
foreach ($this->children as $child) {
|
||||
if (!$child->isOverlay() && method_exists($child, 'renderShadowIfPresent')) {
|
||||
$child->renderShadowIfPresent($renderer);
|
||||
}
|
||||
}
|
||||
|
||||
$overflow = $this->hasOverflow();
|
||||
|
||||
// Save original viewport
|
||||
|
||||
Loading…
Reference in New Issue
Block a user