RU
games/open-lemmings / src / Scene / MenuBase.php · commit 04e2e2a
<?phpnamespace App\Scene;use App\Service\Art;use App\Service\Audio;use App\Service\Fonts;use JumPHP\Renderer\Renderer;use JumPHP\Scene\AbstractScene;/** * A menu of big buttons that works with the keyboard, a gamepad, the mouse and a finger. * items(): rows [label, value (callable or null), onPress (or null), onSide (fn(int $dir), or null)]. */abstract class MenuBase extends AbstractScene{    protected const BW = 200;    protected const BH = 22;    protected const STEP = 25;    protected string $title = '';    protected int $selected = 0;    protected float $time = 0;    /** @var array<int, array> */    protected array $rows = [];    public function __construct(protected Art $art, protected Fonts $fonts, protected Audio $audio)    {    }    /** @return array<int, 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]);            }        }    }}