Skip to content
This repository was archived by the owner on Oct 15, 2024. It is now read-only.

Commit c89138e

Browse files
committed
feat: support hsl and hsla color widget
Fix #137
1 parent c0f88b2 commit c89138e

File tree

5 files changed

+192
-2
lines changed

5 files changed

+192
-2
lines changed

python/tests/assets/qss/QLabel.qss

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,8 @@ QLabel { border-color: rgba(255, 255, 0%, 100)}
1414

1515
QLabel { border-color: hsv(60, 255, 100%)}
1616

17-
QLabel { border-color: hsva(60, 255, 100%, 100)}
17+
QLabel { border-color: hsva(60, 255, 100%, 100)}
18+
19+
QLabel { border-color: hsl(60, 100%, 50%) }
20+
21+
QLabel { border-color: hsla(60, 100%, 50%, 100) }

snippets/qss.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,16 @@
1919
"body": ["hsva(${1:h}, ${2:s}, ${3:v}, ${4:alpha})$0"],
2020
"description": "HSVA Color"
2121
},
22+
"HSL": {
23+
"prefix": "hsl",
24+
"body": ["hsl(${1:h}, ${2:s}, ${3:l})$0"],
25+
"description": "HSL Color"
26+
},
27+
"HSLA": {
28+
"prefix": "hsla",
29+
"body": ["hsla(${1:h}, ${2:s}, ${3:l}, ${4:alpha})$0"],
30+
"description": "HSLA Color"
31+
},
2232
"Linear Gradient": {
2333
"prefix": "qlineargradient",
2434
"body": [

src/qss/color-provider.ts

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ function getDocumentColorProvider(): DocumentColorProvider {
3838
...extractRgbaToColors(text),
3939
...extractHsvToColors(text),
4040
...extractHsvaToColors(text),
41+
...extractHslToColors(text),
42+
...extractHslaToColors(text),
4143
]
4244

4345
return matchedColors.map(match => {
@@ -56,6 +58,7 @@ function getDocumentColorProvider(): DocumentColorProvider {
5658
new ColorPresentation(fromColorToHexAarrggbb(color)),
5759
new ColorPresentation(fromColorToRgba(color)),
5860
new ColorPresentation(fromColorToHsva(color)),
61+
new ColorPresentation(fromColorToHsla(color)),
5962
]
6063

6164
if (color.alpha === 1)
@@ -64,6 +67,7 @@ function getDocumentColorProvider(): DocumentColorProvider {
6467
new ColorPresentation(fromColorToHexRrggbb(color)),
6568
new ColorPresentation(fromColorToRgb(color)),
6669
new ColorPresentation(fromColorToHsv(color)),
70+
new ColorPresentation(fromColorToHsl(color)),
6771
)
6872

6973
return supportedPresentation
@@ -270,6 +274,66 @@ export function extractHsvaToColors(text: string): readonly MatchedColor[] {
270274
.filter(notNil)
271275
}
272276

277+
export function extractHslToColors(text: string): readonly MatchedColor[] {
278+
// parse 'hsl(360, 100%, 100%)'
279+
const matches = text.matchAll(
280+
/hsl\s*\(\s*(\d+%?)\s*,\s*(\d+%?)\s*,\s*(\d+%?)\s*\)/g,
281+
)
282+
283+
return [...matches]
284+
.map(match => {
285+
if (isNil(match.index)) return undefined
286+
287+
const [code, h, s, l] = match
288+
289+
if (isNil(h) || isNil(s) || isNil(l)) return undefined
290+
291+
const rgb = hslToRgb({
292+
h: Number(h),
293+
s: fromColorValueStringToNumber(s),
294+
l: fromColorValueStringToNumber(l),
295+
})
296+
return {
297+
color: new Color(rgb.r, rgb.g, rgb.b, 1),
298+
offsetRange: {
299+
start: match.index,
300+
end: match.index + code.length,
301+
},
302+
}
303+
})
304+
.filter(notNil)
305+
}
306+
307+
export function extractHslaToColors(text: string): readonly MatchedColor[] {
308+
// parse 'hsla(360, 100%, 100%, 100%)'
309+
const matches = text.matchAll(
310+
/hsla\s*\(\s*(\d+%?)\s*,\s*(\d+%?)\s*,\s*(\d+%?)\s*,\s*(\d+%?)\s*\)/g,
311+
)
312+
313+
return [...matches]
314+
.map(match => {
315+
if (isNil(match.index)) return undefined
316+
317+
const [code, h, s, l, a] = match
318+
319+
if (isNil(h) || isNil(s) || isNil(l) || isNil(a)) return undefined
320+
321+
const rgb = hslToRgb({
322+
h: Number(h),
323+
s: fromColorValueStringToNumber(s),
324+
l: fromColorValueStringToNumber(l),
325+
})
326+
return {
327+
color: new Color(rgb.r, rgb.g, rgb.b, fromColorValueStringToNumber(a)),
328+
offsetRange: {
329+
start: match.index,
330+
end: match.index + code.length,
331+
},
332+
}
333+
})
334+
.filter(notNil)
335+
}
336+
273337
export type MatchedColor = {
274338
readonly color: Color
275339
readonly offsetRange: {
@@ -356,6 +420,16 @@ export function fromColorToHsva(color: Color) {
356420
return `hsva(${h}, ${s * 100}%, ${v * 100}%, ${color.alpha * 100}%)`
357421
}
358422

423+
export function fromColorToHsl(color: Color) {
424+
const { h, s, l } = rgbToHsl({ r: color.red, g: color.green, b: color.blue })
425+
return `hsl(${h}, ${s * 100}%, ${l * 100}%)`
426+
}
427+
428+
export function fromColorToHsla(color: Color) {
429+
const { h, s, l } = rgbToHsl({ r: color.red, g: color.green, b: color.blue })
430+
return `hsla(${h}, ${s * 100}%, ${l * 100}%, ${color.alpha * 100}%)`
431+
}
432+
359433
function fromColorValueStringToNumber(str: string) {
360434
if (str.endsWith('%')) return parseInt(str.slice(0, -1), 10) / 100
361435
return parseInt(str, 10) / 255
@@ -423,3 +497,66 @@ export function rgbToHsv({ r, g, b }: { r: number; g: number; b: number }) {
423497

424498
return { h, s, v }
425499
}
500+
501+
export function hslToRgb({ h, s, l }: { h: number; s: number; l: number }) {
502+
const c = (1 - Math.abs(2 * l - 1)) * s
503+
const x = c * (1 - Math.abs(((h / 60) % 2) - 1))
504+
const m = l - c / 2
505+
506+
let r = 0
507+
let g = 0
508+
let b = 0
509+
510+
if (h < 60) {
511+
r = c
512+
g = x
513+
b = 0
514+
} else if (h < 120) {
515+
r = x
516+
g = c
517+
b = 0
518+
} else if (h < 180) {
519+
r = 0
520+
g = c
521+
b = x
522+
} else if (h < 240) {
523+
r = 0
524+
g = x
525+
b = c
526+
} else if (h < 300) {
527+
r = x
528+
g = 0
529+
b = c
530+
} else {
531+
r = c
532+
g = 0
533+
b = x
534+
}
535+
536+
return { r: r + m, g: g + m, b: b + m }
537+
}
538+
539+
export function rgbToHsl({ r, g, b }: { r: number; g: number; b: number }) {
540+
const max = Math.max(r, g, b)
541+
const min = Math.min(r, g, b)
542+
const c = max - min
543+
544+
let h = 0
545+
let s = 0
546+
const l = (max + min) / 2
547+
548+
if (c !== 0) {
549+
if (max === r) {
550+
h = ((g - b) / c) % 6
551+
} else if (max === g) {
552+
h = (b - r) / c + 2
553+
} else {
554+
h = (r - g) / c + 4
555+
}
556+
557+
h *= 60
558+
s = c / (1 - Math.abs(2 * l - 1))
559+
}
560+
561+
return { h, s, l }
562+
}

src/test/suite/qss/color-provider.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,24 @@ import {
55
extractHexAarrggbbToColor,
66
extractHexRgbToColor,
77
extractHexRrggbbToColor,
8+
extractHslaToColors,
9+
extractHslToColors,
810
extractHsvaToColors,
911
extractHsvToColors,
1012
extractRgbaToColors,
1113
extractRgbToColors,
1214
fromColorToHexAarrggbb,
1315
fromColorToHexRgb,
1416
fromColorToHexRrggbb,
17+
fromColorToHsl,
18+
fromColorToHsla,
1519
fromColorToHsv,
1620
fromColorToHsva,
1721
fromColorToRgb,
1822
fromColorToRgba,
23+
hslToRgb,
1924
hsvToRgb,
25+
rgbToHsl,
2026
rgbToHsv,
2127
} from '../../../qss/color-provider'
2228

@@ -104,4 +110,37 @@ suite('color-provider', () => {
104110
const expected = { h: 60, s: 1, v: 1 }
105111
assert.deepStrictEqual(rgbToHsv(hsvToRgb(expected)), expected)
106112
})
113+
114+
test('hsl', () => {
115+
const expected = 'hsl(0, 0, 255)'
116+
const color = extractHslToColors(expected).map(({ color }) => color)[0]
117+
assert.ok(color)
118+
assert.strictEqual(fromColorToHsl(color), 'hsl(0, 0%, 100%)')
119+
})
120+
121+
test('hsl with percentage', () => {
122+
const expected = 'hsl(0, 0%, 100%)'
123+
const color = extractHslToColors(expected).map(({ color }) => color)[0]
124+
assert.ok(color)
125+
assert.strictEqual(fromColorToHsl(color), expected)
126+
})
127+
128+
test('hsla', () => {
129+
const expected = 'hsla(0, 0, 255, 255)'
130+
const color = extractHslaToColors(expected).map(({ color }) => color)[0]
131+
assert.ok(color)
132+
assert.strictEqual(fromColorToHsla(color), 'hsla(0, 0%, 100%, 100%)')
133+
})
134+
135+
test('hsla with percentage', () => {
136+
const expected = 'hsla(0, 0%, 100%, 100%)'
137+
const color = extractHslaToColors(expected).map(({ color }) => color)[0]
138+
assert.ok(color)
139+
assert.strictEqual(fromColorToHsla(color), expected)
140+
})
141+
142+
test('hsl <-> rgb', () => {
143+
const expected = { h: 240, s: 1, l: 0.5 }
144+
assert.deepStrictEqual(rgbToHsl(hslToRgb(expected)), expected)
145+
})
107146
})

syntaxes/qss.tmLanguage.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@
225225
"patterns": [
226226
{
227227
"description": "Color Type",
228-
"begin": "\\b(rgb|rgba|hsv|hsva)\\s*\\(",
228+
"begin": "\\b(rgb|rgba|hsv|hsva|hsl|hsla)\\s*\\(",
229229
"beginCaptures": {
230230
"1": {
231231
"name": "entity.name.function.qss"

0 commit comments

Comments
 (0)