sdl3/src/Ui/Widget/Table.php
2025-10-26 22:47:12 +01:00

172 lines
4.9 KiB
PHP

<?php
namespace PHPNative\Ui\Widget;
use PHPNative\Framework\TextRenderer;
class Table extends Container
{
private array $columns = [];
private array $rows = [];
private Container $headerContainer;
private Container $bodyContainer;
private ?int $selectedRowIndex = null;
private $onRowSelect = null;
public function __construct(
string $style = '',
) {
parent::__construct('flex flex-col overflow-auto ' . $style);
// Create header container
$this->headerContainer = new Container('flex flex-row bg-gray-200 border-b-2 border-gray-400');
$this->addComponent($this->headerContainer);
// Create body container (scrollable)
$this->bodyContainer = new Container('flex flex-col overflow-auto flex-1');
$this->addComponent($this->bodyContainer);
}
/**
* Define columns
*
* @param array $columns Array of column definitions ['key' => string, 'title' => string, 'width' => int|null]
*/
public function setColumns(array $columns): void
{
$this->columns = $columns;
$this->headerContainer->clearChildren();
foreach ($columns as $column) {
$title = $column['title'] ?? $column['key'];
$width = $column['width'] ?? null;
$style = 'px-4 py-2 font-bold border-r border-gray-300';
if ($width) {
$style .= ' w-' . ((int)($width / 4));
} else {
$style .= ' flex-1';
}
$headerLabel = new Label($title, $style);
$this->headerContainer->addComponent($headerLabel);
}
}
/**
* Set table data
*
* @param array $data Array of row data (associative arrays)
*/
public function setData(array $data): void
{
$this->rows = $data;
$this->bodyContainer->clearChildren();
foreach ($data as $rowIndex => $row) {
$this->addRow($row, $rowIndex);
}
}
/**
* Add a single row
*/
private function addRow(array $rowData, int $rowIndex): void
{
$isSelected = $rowIndex === $this->selectedRowIndex;
$rowStyle = 'flex flex-row border-b border-gray-200 hover:bg-gray-100';
if ($isSelected) {
$rowStyle .= ' bg-blue-100';
}
$rowContainer = new Container($rowStyle);
foreach ($this->columns as $column) {
$key = $column['key'];
$value = $rowData[$key] ?? '';
$width = $column['width'] ?? null;
$cellStyle = 'px-4 py-2 border-r border-gray-300';
if ($width) {
$cellStyle .= ' w-' . ((int)($width / 4));
} else {
$cellStyle .= ' flex-1';
}
$cellLabel = new Label((string)$value, $cellStyle);
$rowContainer->addComponent($cellLabel);
}
// Make row clickable
$clickHandler = new class($rowContainer, $rowIndex, $this) extends Container {
private int $rowIndex;
private Table $table;
public function __construct(Container $rowContainer, int $rowIndex, Table $table)
{
$this->rowIndex = $rowIndex;
$this->table = $table;
parent::__construct('');
$this->addComponent($rowContainer);
}
public function handleMouseClick(float $mouseX, float $mouseY, int $button): bool
{
// Check if click is within row bounds
if (
$mouseX >= $this->viewport->x &&
$mouseX <= ($this->viewport->x + $this->viewport->width) &&
$mouseY >= $this->viewport->y &&
$mouseY <= ($this->viewport->y + $this->viewport->height)
) {
$this->table->selectRow($this->rowIndex);
return true;
}
return parent::handleMouseClick($mouseX, $mouseY, $button);
}
};
$this->bodyContainer->addComponent($clickHandler);
}
/**
* Select a row
*/
public function selectRow(int $rowIndex): void
{
$this->selectedRowIndex = $rowIndex;
// Trigger callback
if ($this->onRowSelect !== null) {
($this->onRowSelect)($rowIndex, $this->rows[$rowIndex] ?? null);
}
// Re-render rows to update selection
$this->setData($this->rows);
}
/**
* Set row select callback
*/
public function setOnRowSelect(callable $callback): void
{
$this->onRowSelect = $callback;
}
/**
* Get selected row index
*/
public function getSelectedRowIndex(): ?int
{
return $this->selectedRowIndex;
}
/**
* Get selected row data
*/
public function getSelectedRow(): ?array
{
return $this->selectedRowIndex !== null ? ($this->rows[$this->selectedRowIndex] ?? null) : null;
}
}