*/ protected array $rows = []; public function __construct(protected Art $art, protected Fonts $fonts, protected Audio $audio) { } /** @return array */ abstract protected function items(): array; public function enter(array $params = []): void { $this->art->load($this->engine); $this->audio->load($this->sound()); $this->art->effects->apply($this->engine); $this->rows = $this->items(); $this->selected = 0; $this->time = 0; } public function resume(mixed $result = null): void { $this->rows = $this->items(); } protected function back(): void { } protected function top(): float { return 135 - count($this->rows) * self::STEP / 2 + ($this->title !== '' ? 12 : 0); } /** @return array{0:float,1:float} */ protected function rowXY(int $i): array { return [240 - self::BW / 2, $this->top() + $i * self::STEP]; } public function update(float $dt): void { $this->time += $dt; $a = $this->actions(); $n = count($this->rows); $m = $this->engine->input->getMouse(); $over = null; for ($i = 0; $i < $n; $i++) { [$x, $y] = $this->rowXY($i); if ($m->x >= $x && $m->x < $x + self::BW && $m->y >= $y - 1 && $m->y < $y + self::BH + 1) { $over = $i; } } if ($m->isPressed(1) && $over !== null) { $this->selected = $over; [$x] = $this->rowXY($over); $this->activate($over, isset($this->rows[$over][3]) ? ($m->x < $x + self::BW / 2 ? -1 : 1) : 0); return; } if ($a->isPressed('down')) { $this->selected = ($this->selected + 1) % $n; $this->sound()->playSfx('menu_move'); } elseif ($a->isPressed('up')) { $this->selected = ($this->selected + $n - 1) % $n; $this->sound()->playSfx('menu_move'); } elseif ($a->isPressed('left') || $a->isPressed('right')) { if (isset($this->rows[$this->selected][3])) { $this->activate($this->selected, $a->isPressed('left') ? -1 : 1); } } elseif ($a->isPressed('confirm')) { $this->activate($this->selected, isset($this->rows[$this->selected][3]) ? 1 : 0); } elseif ($a->isPressed('back')) { $this->back(); } } private function activate(int $i, int $dir): void { $row = $this->rows[$i]; if ($dir !== 0 && isset($row[3])) { ($row[3])($dir); $this->sound()->playSfx('menu_move'); $this->rows = $this->items(); } elseif (isset($row[2])) { $this->sound()->playSfx('menu_confirm'); ($row[2])(); } } protected function renderBackground(Renderer $r): void { $r->fillRect(0, 0, 480, 270, [8, 4, 20, 170]); } protected function renderTitle(Renderer $r): void { if ($this->title !== '') { $this->fonts->text($this->engine, $r, $this->title, 240, $this->top() - 30, 2, [255, 210, 60], 'center'); } } public function render(Renderer $r): void { $this->renderBackground($r); $this->renderTitle($r); $sdl = $this->sdl(); foreach ($this->rows as $i => $row) { [$x, $y] = $this->rowXY($i); $on = $i === $this->selected; $this->art->draw($r, $on ? 'btn-wide-on' : 'btn-wide', null, $x, $y + ($on ? sin($this->time * 6) * 0.6 : 0)); $label = \JumPHP\Util\Lang::t((string)$row[0]); if (isset($row[1])) { $value = is_callable($row[1]) ? ($row[1])() : $row[1]; $this->fonts->print($this->engine, $r, $label, $x + 10, $y + 4, 1.1, [255, 255, 255], 'left', [20, 12, 40]); $this->fonts->print($this->engine, $r, '‹ ' . \JumPHP\Util\Lang::t((string)$value) . ' ›', $x + self::BW - 10, $y + 4, 1.1, [255, 240, 170], 'right', [20, 12, 40]); } else { $this->fonts->print($this->engine, $r, $label, 240, $y + 4, 1.1, [255, 255, 255], 'center', [20, 12, 40]); } } } }