Anmelden
DE
apps/caramail-chat / src / Gui / HsbPicker.php · commit 6eebcdb
<?phpnamespace App\Gui;use JumPHP\Gui\Pointer;use JumPHP\Gui\Theme;use JumPHP\Gui\Widget;use JumPHP\Renderer\Renderer;/** * The applet's colour picker (net.spray.chat.a.i, Options > "Configuration de l'interface"), as its bytecode drew it, * in dp (190 x 100 in the applet): * - a hue / saturation field of (W - 28) x 64 at (1, 1): hue from red at the left round to red at the right *   (hue = (d - x) / d), saturation 0 at the top to 1 at the bottom, at the brightness chosen; * - at the right, 8 bars 16 wide and 8 high: the brightness, white (1.0) at the top to 0.125 at the bottom; * - below (y >= 73), two boxes: at the left the colour under the pointer (crossed out in black when there is none), *   at the right the colour chosen (a click in the field). Each in a sunken frame (grey above / left, white below / right). * The applet posted no event: its "Valider" button reads selected(). Here a click also emits 'change'. */final class HsbPicker extends Widget{    public const DEFAULTS = ['width' => 190, 'height' => 100];    private const NONE = '#c0c0c0';    /** the colour chosen (the right box), '#rrggbb' */    public string $selected = self::NONE;    /** the colour under the pointer, null = none (the left box is crossed out) */    public ?string $hoverColor = null;    /** the brightness: 1.0, 0.875 ... 0.125 */    public float $brightness = 1.0;    private string $texture = '';    private string $textureKey = '';    public static function events(): array    {        return ['change', 'click'];    }    protected function content(Theme $t, float $maxWidth, string $breakpoint): array    {        return [$t->px(190), $t->px(100)];    }    /** java.awt.Color.HSBtoRGB, the same rounding. */    public static function hsb(float $h, float $s, float $b): string    {        if ($s == 0.0) {            $v = (int)($b * 255.0 + 0.5);            return sprintf('#%02x%02x%02x', $v, $v, $v);        }        $h6 = ($h - floor($h)) * 6.0;        $f = $h6 - floor($h6);        $p = $b * (1.0 - $s);        $q = $b * (1.0 - $s * $f);        $u = $b * (1.0 - $s * (1.0 - $f));        [$r, $g, $bl] = match ((int)$h6) {            0 => [$b, $u, $p],            1 => [$q, $b, $p],            2 => [$p, $b, $u],            3 => [$p, $q, $b],            4 => [$u, $p, $b],            default => [$b, $p, $q],        };        return sprintf('#%02x%02x%02x', (int)($r * 255.0 + 0.5), (int)($g * 255.0 + 0.5), (int)($bl * 255.0 + 0.5));    }    /** dp width of the hue / saturation field */    private function fieldWidth(Theme $t): int    {        return max(1, (int)floor($this->w / $this->unit($t)) - 28);    }    private function unit(Theme $t): int    {        return Awt::px($t);    }    /** The pointer in the widget's dp, [x, y], or null outside. */    private function local(float $px, float $py): ?array    {        $t = $this->form?->theme;        if ($t === null) {            return null;        }        $u = $this->unit($t);        return [(int)floor(($px - round($this->x)) / $u), (int)floor(($py - round($this->y)) / $u)];    }    /** The colour of a point of the field, or null when it is not in it (0 < x < W - 26, 0 < y < 65, as the applet). */    private function colourAt(int $x, int $y): ?string    {        $t = $this->form?->theme;        if ($t === null) {            return null;        }        $d = $this->fieldWidth($t);        $w = $d + 28;        if ($x > 0 && $x < $w - 26 && $y > 0 && $y < 65) {            $fx = min($d - 1, $x - 1);            $fy = min(63, $y - 1);            return self::hsb(($d - $fx) / $d, $fy / 64, $this->brightness);        }        return null;    }    /** The brightness of a bar, or null when the point is not on them (W - 18 < x < W - 1, 0 < y < 65). */    private function brightnessAt(int $x, int $y): ?float    {        $t = $this->form?->theme;        if ($t === null) {            return null;        }        $w = $this->fieldWidth($t) + 28;        if ($x > $w - 18 && $x < $w - 1 && $y > 0 && $y < 65) {            return (9 - (intdiv($y - 1, 8) + 1)) / 8;        }        return null;    }    public function update(float $dt): void    {        parent::update($dt);        $mouse = $this->hovered ? $this->form?->engineOrNull()?->input->getMouse() : null;        $at = $mouse !== null ? $this->local((float)$mouse->x, (float)$mouse->y) : null;        $this->hoverColor = $at !== null ? $this->colourAt($at[0], $at[1]) : null;    }    public function pointerDown(Pointer $p): void    {        parent::pointerDown($p);        $at = $this->local($p->x, $p->y);        if ($at === null) {            return;        }        if (($c = $this->colourAt($at[0], $at[1])) !== null) {            $this->selected = $c;            $this->emit('change', $c);        } elseif (($b = $this->brightnessAt($at[0], $at[1])) !== null) {            $this->brightness = $b;        }    }    public function render(Renderer $r, Theme $t): void    {        $u = $this->unit($t);        $ox = (int)round($this->x);        $oy = (int)round($this->y);        $d = $this->fieldWidth($t);        $w = $d + 28;        $h = max(80, (int)floor($this->h / $u));        $rect = static function (int $x, int $y, int $rw, int $rh, string $c) use ($r, $u, $ox, $oy): void {            $r->fillRect($ox + $x * $u, $oy + $y * $u, $rw * $u, $rh * $u, $c);        };        $hline = static fn (int $x1, int $x2, int $y, string $c) => $rect($x1, $y, $x2 - $x1 + 1, 1, $c);        $vline = static fn (int $x, int $y1, int $y2, string $c) => $rect($x, $y1, 1, $y2 - $y1 + 1, $c);        $r->fillRect($ox, $oy, $w * $u, $h * $u, Awt::$face);        // the hue / saturation field, regenerated when its width or the brightness changes        $sdl = $this->form?->sdlOrNull();        $key = $d . ':' . $this->brightness;        if ($sdl !== null && function_exists('imagecreatetruecolor')) {            if ($this->textureKey !== $key) {                $this->texture = 'hsb-picker-' . spl_object_id($this);                $img = imagecreatetruecolor($d, 64);                for ($y = 0; $y < 64; $y++) {                    for ($x = 0; $x < $d; $x++) {                        $hex = self::hsb(($d - $x) / $d, $y / 64, $this->brightness);                        imagesetpixel($img, $x, $y, (int)hexdec(substr($hex, 1)));                    }                }                $sdl->createTextureFromGd($this->texture, $img);                $this->textureKey = $key;            }            $r->copy($sdl->getTexture($this->texture)['texture'], null, $ox + $u, $oy + $u, $d * $u, 64 * $u);        }        $vline(0, 0, 65, Awt::SHADOW);        $hline(0, $w - 25, 0, Awt::SHADOW);        $hline(1, $w - 27, 65, Awt::LIGHT);        $vline($w - 27, 1, 65, Awt::LIGHT);        // the brightness bars        for ($n = 1; $n <= 8; $n++) {            $rect($w - 17, 8 * ($n - 1) + 1, 16, 8, self::hsb(0, 0, (9 - $n) / 8));        }        $hline($w - 18, $w - 1, 0, Awt::SHADOW);        $vline($w - 18, 0, 65, Awt::SHADOW);        $hline($w - 17, $w - 1, 65, Awt::LIGHT);        $vline($w - 1, 1, 65, Awt::LIGHT);        // the box of the colour under the pointer (crossed out when none) and the box of the colour chosen        $half = intdiv($w, 2);        $hline(0, $half - 4, 73, Awt::SHADOW);        $vline(0, 73, $h - 1, Awt::SHADOW);        $hline(1, $half - 4, $h - 1, Awt::LIGHT);        $vline($half - 4, 74, $h - 1, Awt::LIGHT);        $rect(1, 74, $half - 5, $h - 76, $this->hoverColor ?? self::NONE);        if ($this->hoverColor === null) {            $x1 = $ox + $u;            $y1 = $oy + 74 * $u;            $x2 = $ox + ($half - 4) * $u - 1;            $y2 = $oy + ($h - 1) * $u - 1;            $r->drawLine($x1, $y1, $x2, $y2, '#000000');            $r->drawLine($x1, $y2, $x2, $y1, '#000000');        }        $hline($half + 4, $w - 1, 73, Awt::SHADOW);        $vline($half + 4, 73, $h - 1, Awt::SHADOW);        $hline($half + 5, $w - 1, $h - 1, Awt::LIGHT);        $vline($w - 1, 74, $h - 1, Awt::LIGHT);        $rect($half + 5, 74, $w - $half - 6, $h - 75, $this->selected);    }}