setUseTextureCache(true); // Create label inside button $this->label = new Label( text: $text, style: 'text-black', ); $this->addComponent($this->label); $this->onClick = $onClick; } public function setIcon(null|Icon $icon, string $position = 'left'): void { $position = strtolower($position); if (!in_array($position, ['left', 'right'], true)) { throw new \InvalidArgumentException('Button icon position must be "left" or "right".'); } if ($this->leadingIcon !== null) { $this->removeChild($this->leadingIcon); $this->leadingIcon = null; } if ($icon !== null) { $this->addComponent($icon); $this->leadingIcon = $icon; $this->iconPosition = $position; if ($position === 'left') { $this->moveChildBefore($icon, $this->label); } else { $this->moveChildAfter($icon, $this->label); } } $this->markDirty(true); } public function setText(string $text): void { $this->text = $text; $this->label->setText($text); } public function getText(): string { return $this->text; } public function setOnClick(callable $onClick): void { $this->onClick = $onClick; } public function setStyle(string $style): void { $this->style = $style; } /** * Set async click handler that runs in background thread * @param callable $onClickAsync Task to run asynchronously * @param callable|null $onComplete Optional callback when task completes * @param callable|null $onError Optional callback on error */ public function setOnClickAsync( callable $onClickAsync, null|callable $onComplete = null, null|callable $onError = null, ): void { $this->onClickAsync = [ 'task' => $onClickAsync, 'onComplete' => $onComplete, 'onError' => $onError, ]; } 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( "[Button '%s'] Click at (%.1f, %.1f), bounds: (%.1f, %.1f) to (%.1f, %.1f)", $this->text, $mouseX, $mouseY, $this->viewport->x, $this->viewport->y, $this->viewport->x + $this->viewport->width, $this->viewport->y + $this->viewport->height, )); } // Check if click is within button bounds if ( $mouseX >= $this->viewport->x && $mouseX <= ($this->viewport->x + $this->viewport->width) && $mouseY >= $this->viewport->y && $mouseY <= ($this->viewport->y + $this->viewport->height) ) { if (defined('DEBUG_EVENTS') && DEBUG_EVENTS) { error_log("[Button '{$this->text}'] Click INSIDE button - executing callback"); } // Call async onClick callback if set if ($this->onClickAsync !== null) { $task = TaskManager::getInstance()->runAsync($this->onClickAsync['task']); if ($this->onClickAsync['onComplete'] !== null) { $task->onComplete($this->onClickAsync['onComplete']); } if ($this->onClickAsync['onError'] !== null) { $task->onError($this->onClickAsync['onError']); } } // Call sync onClick callback if set elseif ($this->onClick !== null) { ($this->onClick)(); } return true; } if (defined('DEBUG_EVENTS') && DEBUG_EVENTS) { error_log("[Button '{$this->text}'] Click OUTSIDE button"); } // Click was outside button - don't handle it // Don't call parent::handleMouseClick as that would cause double event propagation return false; } private function removeChild(Component $component): void { foreach ($this->children as $index => $child) { if ($child === $component) { if (method_exists($child, 'detachFromWindow')) { $child->detachFromWindow(); } $child->setParent(null); unset($this->children[$index]); } } $this->children = array_values($this->children); } private function moveChildBefore(Component $child, Component $reference): void { $childIndex = array_search($child, $this->children, true); $referenceIndex = array_search($reference, $this->children, true); if ($childIndex === false || $referenceIndex === false || $childIndex === $referenceIndex) { return; } array_splice($this->children, $childIndex, 1); $referenceIndex = array_search($reference, $this->children, true); array_splice($this->children, $referenceIndex, 0, [$child]); } private function moveChildAfter(Component $child, Component $reference): void { $childIndex = array_search($child, $this->children, true); $referenceIndex = array_search($reference, $this->children, true); if ($childIndex === false || $referenceIndex === false || $childIndex === $referenceIndex + 1) { return; } array_splice($this->children, $childIndex, 1); $referenceIndex = array_search($reference, $this->children, true); array_splice($this->children, $referenceIndex + 1, 0, [$child]); } }