Sign in
EN
games/xencenter-xenadmin-xcp-jumphp / src / Gui / MenuBar.php · commit fb1b7c9
<?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);    }}