Skip to content

Commit bc06e8e

Browse files
committed
feat: color picker for hsv settings
1 parent 24fc861 commit bc06e8e

File tree

2 files changed

+151
-38
lines changed

2 files changed

+151
-38
lines changed

src/lib/setting.ts

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,85 @@
11
import type { Action } from "svelte/action";
22
import { changes, ChangeType, settings } from "$lib/undo-redo";
33

4+
/**
5+
* https://gist.github.com/mjackson/5311256
6+
*/
7+
function rgbToHsv(r: number, g: number, b: number): [number, number, number] {
8+
r /= 255;
9+
g /= 255;
10+
b /= 255;
11+
12+
const max = Math.max(r, g, b);
13+
const min = Math.min(r, g, b);
14+
let h = 0;
15+
const v = max;
16+
17+
const d = max - min;
18+
const s = max == 0 ? 0 : d / max;
19+
20+
if (max == min) {
21+
h = 0; // achromatic
22+
} else {
23+
switch (max) {
24+
case r:
25+
h = (g - b) / d + (g < b ? 6 : 0);
26+
break;
27+
case g:
28+
h = (b - r) / d + 2;
29+
break;
30+
case b:
31+
h = (r - g) / d + 4;
32+
break;
33+
}
34+
35+
h /= 6;
36+
}
37+
38+
return [Math.floor(h * 0xffff), Math.floor(s * 0xff), Math.floor(v * 0xff)];
39+
}
40+
41+
/**
42+
* https://gist.github.com/mjackson/5311256
43+
*/
44+
function hsvToRgb(h: number, s: number, v: number): [number, number, number] {
45+
h /= 0xffff;
46+
s /= 0xff;
47+
v /= 0xff;
48+
49+
let r = 0;
50+
let g = 0;
51+
let b = 0;
52+
53+
const i = Math.floor(h * 6);
54+
const f = h * 6 - i;
55+
const p = v * (1 - s);
56+
const q = v * (1 - f * s);
57+
const t = v * (1 - (1 - f) * s);
58+
59+
switch (i % 6) {
60+
case 0:
61+
(r = v), (g = t), (b = p);
62+
break;
63+
case 1:
64+
(r = q), (g = v), (b = p);
65+
break;
66+
case 2:
67+
(r = p), (g = v), (b = t);
68+
break;
69+
case 3:
70+
(r = p), (g = q), (b = v);
71+
break;
72+
case 4:
73+
(r = t), (g = p), (b = v);
74+
break;
75+
case 5:
76+
(r = v), (g = p), (b = q);
77+
break;
78+
}
79+
80+
return [Math.floor(r * 0xff), Math.floor(g * 0xff), Math.floor(b * 0xff)];
81+
}
82+
483
export const setting: Action<
584
HTMLInputElement | HTMLSelectElement,
685
{ id: number; inverse?: number; scale?: number }
@@ -9,7 +88,12 @@ export const setting: Action<
988
{ id, inverse, scale },
1089
) {
1190
node.setAttribute("disabled", "");
12-
const type = node.getAttribute("type") as "number" | "checkbox" | "range";
91+
const type = node.getAttribute("type") as
92+
| "number"
93+
| "checkbox"
94+
| "range"
95+
| "color";
96+
const isColor = type === "color";
1397
const isNumeric =
1498
type === "number" || type === "range" || node instanceof HTMLSelectElement;
1599
const min = node.hasAttribute("min")
@@ -30,6 +114,13 @@ export const setting: Action<
30114
? scale * value
31115
: value
32116
).toString();
117+
} else if (isColor) {
118+
const rgb = hsvToRgb(
119+
settings[id]!.value,
120+
settings[id + 1]!.value,
121+
settings[id + 2]!.value,
122+
);
123+
node.value = `#${rgb.map((c) => c.toString(16).padStart(2, "0")).join("")}`;
33124
} else {
34125
node.checked = value !== 0;
35126
}
@@ -58,6 +149,22 @@ export const setting: Action<
58149
? value / scale
59150
: value,
60151
);
152+
} else if (isColor) {
153+
const r = parseInt(node.value.slice(1, 3), 16);
154+
const g = parseInt(node.value.slice(3, 5), 16);
155+
const b = parseInt(node.value.slice(5, 7), 16);
156+
const hsv = rgbToHsv(r, g, b);
157+
changes.update((changes) => {
158+
changes.push(
159+
hsv.map((value, i) => ({
160+
type: ChangeType.Setting,
161+
id: id + i,
162+
setting: value,
163+
})),
164+
);
165+
return changes;
166+
});
167+
return;
61168
} else {
62169
value = node.checked ? 1 : 0;
63170
}

src/routes/(app)/config/settings/+page.svelte

Lines changed: 43 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -117,43 +117,49 @@
117117
{/if}
118118
{#each category.items as item}
119119
{#if item.name !== "enable"}
120-
<label
121-
>{#if item.enum}
122-
<select use:setting={{ id: item.id }}>
123-
{#each item.enum as name, value}
124-
<option {value}>{titlecase(name)}</option>
125-
{/each}
126-
</select>
127-
{:else if item.range[0] === 0 && item.range[1] === 1}
128-
<input type="checkbox" use:setting={{ id: item.id }} />
129-
{:else}
130-
<span class="unit"
131-
><input
132-
type="number"
133-
min={settingValue(item.range[0], item)}
134-
max={settingValue(item.range[1], item)}
135-
step={item.inverse !== undefined ||
136-
item.scale !== undefined ||
137-
item.step === undefined
138-
? undefined
139-
: settingValue(item.step, item)}
140-
use:setting={{
141-
id: item.id,
142-
inverse: item.inverse,
143-
scale: item.scale,
144-
}}
145-
/>{item.unit}</span
146-
>
147-
{/if}
148-
{#if item.description}
149-
<span
150-
>{titlecase(item.name)}
151-
<p>{item.description}</p></span
152-
>
153-
{:else}
154-
{titlecase(item.name)}
155-
{/if}
156-
</label>
120+
{#if item.unit === "H"}
121+
<label
122+
><input type="color" use:setting={{ id: item.id }} /> Color</label
123+
>
124+
{:else if item.unit !== "S" && item.unit !== "B"}
125+
<label
126+
>{#if item.enum}
127+
<select use:setting={{ id: item.id }}>
128+
{#each item.enum as name, value}
129+
<option {value}>{titlecase(name)}</option>
130+
{/each}
131+
</select>
132+
{:else if item.range[0] === 0 && item.range[1] === 1}
133+
<input type="checkbox" use:setting={{ id: item.id }} />
134+
{:else}
135+
<span class="unit"
136+
><input
137+
type="number"
138+
min={settingValue(item.range[0], item)}
139+
max={settingValue(item.range[1], item)}
140+
step={item.inverse !== undefined ||
141+
item.scale !== undefined ||
142+
item.step === undefined
143+
? undefined
144+
: settingValue(item.step, item)}
145+
use:setting={{
146+
id: item.id,
147+
inverse: item.inverse,
148+
scale: item.scale,
149+
}}
150+
/>{item.unit}</span
151+
>
152+
{/if}
153+
{#if item.description}
154+
<span
155+
>{titlecase(item.name)}
156+
<p>{item.description}</p></span
157+
>
158+
{:else}
159+
{titlecase(item.name)}
160+
{/if}
161+
</label>
162+
{/if}
157163
{/if}
158164
{/each}
159165
</fieldset>

0 commit comments

Comments
 (0)