viewport = new Viewport( x: 0, y: 0, width: 0, height: 0, windowWidth: 800, windowHeight: 600 ); $this->contentViewport = clone $this->viewport; } public function setViewport(Viewport $viewport): void { $this->viewport = $viewport; } public function getViewport(): Viewport { return $this->viewport; } public function getContentViewport(): Viewport { return $this->contentViewport; } public function setContentViewport(Viewport $viewport): void { $this->contentViewport = $viewport; } /** * Apply offset to viewport and all child viewports recursively */ public function applyScrollOffset(int $offsetX, int $offsetY): void { // Offset this component's viewports $this->viewport->x = (int) ($this->viewport->x - $offsetX); $this->viewport->y = (int) ($this->viewport->y - $offsetY); $this->contentViewport->x = (int) ($this->contentViewport->x - $offsetX); $this->contentViewport->y = (int) ($this->contentViewport->y - $offsetY); // Recursively apply to all children foreach ($this->children as $child) { $child->applyScrollOffset($offsetX, $offsetY); } } 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; } public function getZIndex(): int { return $this->zIndex; } public function update(): void { foreach ($this->children as $child) { $child->update(); } } public function layout(null|TextRenderer $textRenderer = null): void { $this->computedStyles = StyleParser::parse($this->style)->getValidStyles( MediaQueryEnum::normal, $this->currentState, ); if (isset($this->computedStyles[Margin::class]) && ($m = $this->computedStyles[Margin::class])) { $this->viewport->x = (int) ($this->viewport->x + $m->left); $this->viewport->width = max(0, ($this->viewport->width - $m->right) - $m->left); $this->viewport->y = (int) ($this->viewport->y + $m->top); $this->viewport->height = max(0, ($this->viewport->height - $m->bottom) - $m->top); } $this->contentViewport = clone $this->viewport; if (isset($this->computedStyles[Padding::class]) && ($p = $this->computedStyles[Padding::class])) { $this->contentViewport->x = (int) ($this->contentViewport->x + $p->left); $this->contentViewport->width = max(0, ($this->contentViewport->width - $p->right) - $p->left); $this->contentViewport->y = (int) ($this->contentViewport->y + $p->top); $this->contentViewport->height = max(0, ($this->contentViewport->height - $p->bottom) - $p->top); } } public function render(&$renderer, null|TextRenderer $textRenderer = null): void { if (!$this->visible) { return; } if ( isset($this->computedStyles[\PHPNative\Tailwind\Style\Background::class]) && ($bg = $this->computedStyles[\PHPNative\Tailwind\Style\Background::class]) ) { 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); } if ( isset($this->computedStyles[\PHPNative\Tailwind\Style\Border::class]) && ($border = $this->computedStyles[\PHPNative\Tailwind\Style\Border::class]) ) { // SDL3: sdl_rounded_box_ex uses (x1, y1, x2, y2) instead of (x, y, w, h) $x2 = $this->viewport->x + $this->viewport->width; $y2 = $this->viewport->y + $this->viewport->height; sdl_rounded_box_ex( $renderer, $this->viewport->x, $this->viewport->y, $x2, $y2, $border->roundTopLeft ?? 0, $border->roundTopRight ?? 0, $border->roundBottomRight ?? 0, $border->roundBottomLeft ?? 0, $bg->color->red, $bg->color->green, $bg->color->blue, $bg->color->alpha, ); } else { sdl_render_fill_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 { if (!$this->visible) { return; } // Render children foreach ($this->children as $child) { $child->render($renderer, $textRenderer); $child->renderContent($renderer, $textRenderer); } } public function addComponent(Component $component): void { $this->children[] = $component; } /** * Collect all overlays recursively from this component and all children * @return array Array of overlay components */ public function collectOverlays(): array { $overlays = []; foreach ($this->children as $child) { if ($child->isOverlay()) { $overlays[] = $child; } // Recursively collect from children $overlays = array_merge($overlays, $child->collectOverlays()); } return $overlays; } /** * Handle mouse click event * @return bool True if event was handled */ public function handleMouseClick(float $mouseX, float $mouseY, int $button): bool { // Default implementation: propagate to children foreach ($this->children as $child) { if ($child->handleMouseClick($mouseX, $mouseY, $button)) { return true; } } return false; } /** * Handle mouse move event */ public function handleMouseMove(float $mouseX, float $mouseY): void { // Check if mouse is over this component $isMouseOver = $mouseX >= $this->viewport->x && $mouseX <= ($this->viewport->x + $this->viewport->width) && $mouseY >= $this->viewport->y && $mouseY <= ($this->viewport->y + $this->viewport->height); // Update state based on mouse position $previousState = $this->currentState; $this->currentState = $isMouseOver ? StateEnum::hover : StateEnum::normal; // Recompute styles if state changed if ($previousState !== $this->currentState) { $this->computedStyles = StyleParser::parse($this->style)->getValidStyles( MediaQueryEnum::normal, $this->currentState, ); } foreach ($this->children as $child) { $child->handleMouseMove($mouseX, $mouseY); } } /** * Handle mouse button release event */ public function handleMouseRelease(float $mouseX, float $mouseY, int $button): void { // Default implementation: propagate to children foreach ($this->children as $child) { $child->handleMouseRelease($mouseX, $mouseY, $button); } } /** * Handle mouse wheel event * @return bool True if event was handled */ public function handleMouseWheel(float $mouseX, float $mouseY, float $deltaY): bool { // Default implementation: propagate to children foreach ($this->children as $child) { if ($child->handleMouseWheel($mouseX, $mouseY, $deltaY)) { return true; } } return false; } /** * Handle text input event * @param string $text The input text */ public function handleTextInput(string $text): void { // Default implementation: propagate to children foreach ($this->children as $child) { $child->handleTextInput($text); } } /** * Handle key down event * @param int $keycode SDL keycode * @return bool True if event was handled */ public function handleKeyDown(int $keycode): bool { // Default implementation: propagate to children foreach ($this->children as $child) { if ($child->handleKeyDown($keycode)) { return true; } } return false; } }