"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); } }