<?phpnamespace App\Gui;use JumPHP\Gui\Theme;use JumPHP\Renderer\Renderer;/** * A drop-down list of commands (a menu of the menu bar, a right click's menu), drawn over the form. * Items: ['id' => 'vm.start', 'text' => 'Start', 'enabled' => true, 'icon' => 'power-on', 'shortcut' => 'Ctrl+B'] or '-' (a line). */final class PopupMenu{ /** @var list<array<string, mixed>|string> */ public array $items = []; public float $x = 0; public float $y = 0; public float $w = 0; public float $h = 0; public int $hover = -1; /** @param list<array<string, mixed>|string> $items */ public function __construct(array $items, float $x, float $y, Theme $t, float $viewW, float $viewH) { $this->items = array_values($items); $font = $t->font(); $w = $t->px(160); foreach ($this->items as $item) { if (is_array($item)) { $w = max($w, $font->width(\JumPHP\Util\Lang::t((string)$item['text'])) + $t->px(64) + (isset($item['shortcut']) ? $font->width((string)$item['shortcut']) + $t->px(24) : 0)); } } $h = $t->px(10); foreach ($this->items as $item) { $h += $this->rowHeight($t, $item); } $this->w = $w; $this->h = $h; $this->x = max(0, min($x, $viewW - $w - 2)); $this->y = $y + $h > $viewH ? max(0, $y - $h) : $y; } public function rowHeight(Theme $t, array|string $item): float { return is_string($item) ? $t->px(9) : $t->px(30); } public function contains(float $px, float $py): bool { return $px >= $this->x && $px < $this->x + $this->w && $py >= $this->y && $py < $this->y + $this->h; } public function indexAt(Theme $t, float $py): int { $y = $this->y + $t->px(5); foreach ($this->items as $i => $item) { $h = $this->rowHeight($t, $item); if ($py >= $y && $py < $y + $h) { return is_array($item) ? $i : -1; } $y += $h; } return -1; } /** @return array<string, mixed>|null the item there, when it can be chosen */ public function pick(Theme $t, float $py): ?array { $item = $this->items[$this->indexAt($t, $py)] ?? null; return is_array($item) && ($item['enabled'] ?? true) ? $item : null; } public function render(Renderer $r, Theme $t): void { $t->box($r, $this->x + $t->px(2), $this->y + $t->px(4), $this->w, $this->h, [0, 0, 0, 34], $t->px(8)); $t->frame($r, $this->x, $this->y, $this->w, $this->h, '#ffffff', '#cfd6e0', $t->px(7)); $font = $t->font(); $y = $this->y + $t->px(5); foreach ($this->items as $i => $item) { $h = $this->rowHeight($t, $item); if (is_string($item)) { $r->fillRect($this->x + $t->px(10), $y + $h / 2, $this->w - $t->px(20), 1, '#e3e7ed'); } else { $enabled = (bool)($item['enabled'] ?? true); if ($i === $this->hover && $enabled) { $t->box($r, $this->x + $t->px(4), $y + $t->px(1), $this->w - $t->px(8), $h - $t->px(2), '#e3edfc', $t->px(5)); } if (!empty($item['icon'])) { Icons::draw($r, (string)$item['icon'], $this->x + $t->px(10), $y + ($h - $t->px(16)) / 2, $t->px(16)); if (!$enabled) { $r->fillRect($this->x + $t->px(10), $y + ($h - $t->px(16)) / 2, $t->px(16), $t->px(16), [255, 255, 255, 170]); } } if (!empty($item['checked'])) { $r->fillRect($this->x + $t->px(10), $y + $h / 2 - $t->px(1), $t->px(8), $t->px(3), '#333333'); } $color = $enabled ? '#1f2329' : '#a6adb7'; $font->draw($r, \JumPHP\Util\Lang::t((string)$item['text']), $this->x + $t->px(38), $y + ($h - $font->height()) / 2, 1, $color); if (isset($item['shortcut'])) { $font->draw($r, (string)$item['shortcut'], $this->x + $this->w - $t->px(12), $y + ($h - $font->height()) / 2, 1, '#707070', 'right'); } } $y += $h; } }}