<?phpnamespace App\Gui;use JumPHP\Gui\Overlay;use JumPHP\Gui\Pointer;use JumPHP\Gui\Theme;use JumPHP\Gui\Widget;use JumPHP\Renderer\Renderer;/** * The menu bar (File, View, Pool, Server, VM, Storage, Templates, Tools, Help). Its items come from the form when a menu opens * ($itemsFor: fn (string $menu): list of PopupMenu items: what is enabled depends on the selection), a choice calls $onPick. * * @property list<string> $menus */final class MenuBar extends Widget implements Overlay{ public const DEFAULTS = ['menus' => [], 'width' => 'fill']; /** @var \Closure(string): list<array<string, mixed>|string>|null */ public ?\Closure $itemsFor = null; /** @var \Closure(array<string, mixed>): void|null */ public ?\Closure $onPick = null; public ?PopupMenu $popup = null; public int $open = -1; private int $hoverTitle = -1; protected function content(Theme $t, float $maxWidth, string $breakpoint): array { $lines = 1; foreach ($this->boxes($t, $maxWidth, 0.0) as [, , $line]) { $lines = max($lines, $line + 1); } return [$maxWidth, $t->px(28) * $lines]; } /** * The titles' boxes; a narrow window (a phone) puts the titles that do not fit on more lines. * * @return list<array{0: float, 1: float, 2: int, 3: float}> x, width, line, y */ private function boxes(Theme $t, ?float $width = null, ?float $left = null): array { $font = $t->font(); $left ??= $this->x; $width ??= $this->w; $x = $left + $t->px(6); $line = 0; $out = []; foreach ((array)$this->props['menus'] as $menu) { $w = $font->width(\JumPHP\Util\Lang::t((string)$menu)) + $t->px(20); if ($x + $w > $left + $width - $t->px(2) && $x > $left + $t->px(6)) { $x = $left + $t->px(6); $line++; } $out[] = [$x, $w, $line, $this->y + $line * $t->px(28)]; $x += $w; } return $out; } public function render(Renderer $r, Theme $t): void { $r->fillRect($this->x, $this->y, $this->w, $this->h, '#ffffff'); $font = $t->font(); $lh = $t->px(28); foreach ($this->boxes($t) as $i => [$x, $w, , $y]) { if ($i === $this->open || ($i === $this->hoverTitle && $this->hovered)) { $t->box($r, $x, $y + $t->px(3), $w, $lh - $t->px(6), $i === $this->open ? '#dce8fb' : '#eef2f8', $t->px(5)); } $font->draw($r, \JumPHP\Util\Lang::t((string)$this->props['menus'][$i]), $x + $w / 2, $y + ($lh - $font->height()) / 2, 1, '#1a1a1a', 'center'); } } public function update(float $dt): void { $engine = $this->form?->engineOrNull(); if ($engine === null || $this->form === null) { return; } $mouse = $engine->input->getMouse(); $this->hoverTitle = $this->titleAt($this->form->theme, $mouse->x, $mouse->y); // a menu open: moving over another title opens that one if ($this->open >= 0 && $this->hoverTitle >= 0 && $this->hoverTitle !== $this->open) { $this->openAt($this->hoverTitle); } } private function titleAt(Theme $t, float $px, float $py): int { if ($py < $this->y || $py >= $this->y + $this->h) { return -1; } foreach ($this->boxes($t) as $i => [$x, $w, , $y]) { if ($px >= $x && $px < $x + $w && $py >= $y && $py < $y + $t->px(28)) { return $i; } } return -1; } public function openMenu(string $menu): void { $index = array_search($menu, (array)$this->props['menus'], true); if ($index !== false) { $this->openAt((int)$index); } } private function openAt(int $index): void { if ($this->form === null || $this->itemsFor === null) { return; } $t = $this->form->theme; [$x, , , $y] = $this->boxes($t)[$index]; $this->open = $index; $this->popup = new PopupMenu(($this->itemsFor)((string)$this->props['menus'][$index]), $x, $y + $t->px(28), $t, $this->form->viewWidth(), $this->form->viewHeight()); $this->form->setOverlay($this); } public function renderOverlay(Renderer $r, Theme $t): void { $this->popup?->render($r, $t); } public function overlayContains(float $px, float $py): bool { return $this->popup !== null && ($this->popup->contains($px, $py) || $this->contains($px, $py)); } public function hoverAt(float $py): void { if ($this->popup !== null && $this->form !== null) { $this->popup->hover = $this->popup->indexAt($this->form->theme, $py); } } public function close(): void { $this->popup = null; $this->open = -1; if ($this->form?->overlay === $this) { $this->form->setOverlay(null); } } /** The item of the open menu (tests): choose('Start'). */ public function choose(string $text): bool { foreach ($this->popup !== null ? $this->popup->items : [] as $item) { if (is_array($item) && $item['text'] === $text && ($item['enabled'] ?? true)) { $this->close(); ($this->onPick)($item); return true; } } return false; } public function pointerUp(Pointer $p, bool $inside): void { if ($this->form === null) { return; } $t = $this->form->theme; if ($this->popup !== null && $this->popup->contains($p->x, $p->y)) { $item = $this->popup->pick($t, $p->y); if ($item !== null) { $this->close(); if ($this->onPick !== null) { ($this->onPick)($item); } } return; } $index = $this->titleAt($t, $p->x, $p->y); if ($index < 0) { $this->close(); return; } $index === $this->open ? $this->close() : $this->openAt($index); }}