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

Commit

Permalink
feat: support hsl and hsla color widget
Browse files Browse the repository at this point in the history
Fix #137
  • Loading branch information
seanwu1105 committed Feb 10, 2023
1 parent 13e6d79 commit 6411f26
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 2 deletions.
6 changes: 5 additions & 1 deletion python/tests/assets/qss/QLabel.qss
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,8 @@ QLabel { border-color: rgba(255, 255, 0%, 100)}

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

QLabel { border-color: hsva(60, 255, 100%, 100)}
QLabel { border-color: hsva(60, 255, 100%, 100)}

QLabel { border-color: hsl(60, 100%, 50%) }

QLabel { border-color: hsla(60, 100%, 50%, 100) }
10 changes: 10 additions & 0 deletions snippets/qss.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@
"body": ["hsva(${1:h}, ${2:s}, ${3:v}, ${4:alpha})$0"],
"description": "HSVA Color"
},
"HSL": {
"prefix": "hsl",
"body": ["hsl(${1:h}, ${2:s}, ${3:l})$0"],
"description": "HSL Color"
},
"HSLA": {
"prefix": "hsla",
"body": ["hsla(${1:h}, ${2:s}, ${3:l}, ${4:alpha})$0"],
"description": "HSLA Color"
},
"Linear Gradient": {
"prefix": "qlineargradient",
"body": [
Expand Down
137 changes: 137 additions & 0 deletions src/qss/color-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ function getDocumentColorProvider(): DocumentColorProvider {
...extractRgbaToColors(text),
...extractHsvToColors(text),
...extractHsvaToColors(text),
...extractHslToColors(text),
...extractHslaToColors(text),
]

return matchedColors.map(match => {
Expand All @@ -56,6 +58,7 @@ function getDocumentColorProvider(): DocumentColorProvider {
new ColorPresentation(fromColorToHexAarrggbb(color)),
new ColorPresentation(fromColorToRgba(color)),
new ColorPresentation(fromColorToHsva(color)),
new ColorPresentation(fromColorToHsla(color)),
]

if (color.alpha === 1)
Expand All @@ -64,6 +67,7 @@ function getDocumentColorProvider(): DocumentColorProvider {
new ColorPresentation(fromColorToHexRrggbb(color)),
new ColorPresentation(fromColorToRgb(color)),
new ColorPresentation(fromColorToHsv(color)),
new ColorPresentation(fromColorToHsl(color)),
)

return supportedPresentation
Expand Down Expand Up @@ -270,6 +274,66 @@ export function extractHsvaToColors(text: string): readonly MatchedColor[] {
.filter(notNil)
}

export function extractHslToColors(text: string): readonly MatchedColor[] {
// parse 'hsl(360, 100%, 100%)'
const matches = text.matchAll(
/hsl\s*\(\s*(\d+%?)\s*,\s*(\d+%?)\s*,\s*(\d+%?)\s*\)/g,
)

return [...matches]
.map(match => {
if (isNil(match.index)) return undefined

const [code, h, s, l] = match

if (isNil(h) || isNil(s) || isNil(l)) return undefined

const rgb = hslToRgb({
h: Number(h),
s: fromColorValueStringToNumber(s),
l: fromColorValueStringToNumber(l),
})
return {
color: new Color(rgb.r, rgb.g, rgb.b, 1),
offsetRange: {
start: match.index,
end: match.index + code.length,
},
}
})
.filter(notNil)
}

export function extractHslaToColors(text: string): readonly MatchedColor[] {
// parse 'hsla(360, 100%, 100%, 100%)'
const matches = text.matchAll(
/hsla\s*\(\s*(\d+%?)\s*,\s*(\d+%?)\s*,\s*(\d+%?)\s*,\s*(\d+%?)\s*\)/g,
)

return [...matches]
.map(match => {
if (isNil(match.index)) return undefined

const [code, h, s, l, a] = match

if (isNil(h) || isNil(s) || isNil(l) || isNil(a)) return undefined

const rgb = hslToRgb({
h: Number(h),
s: fromColorValueStringToNumber(s),
l: fromColorValueStringToNumber(l),
})
return {
color: new Color(rgb.r, rgb.g, rgb.b, fromColorValueStringToNumber(a)),
offsetRange: {
start: match.index,
end: match.index + code.length,
},
}
})
.filter(notNil)
}

export type MatchedColor = {
readonly color: Color
readonly offsetRange: {
Expand Down Expand Up @@ -356,6 +420,16 @@ export function fromColorToHsva(color: Color) {
return `hsva(${h}, ${s * 100}%, ${v * 100}%, ${color.alpha * 100}%)`
}

export function fromColorToHsl(color: Color) {
const { h, s, l } = rgbToHsl({ r: color.red, g: color.green, b: color.blue })
return `hsl(${h}, ${s * 100}%, ${l * 100}%)`
}

export function fromColorToHsla(color: Color) {
const { h, s, l } = rgbToHsl({ r: color.red, g: color.green, b: color.blue })
return `hsla(${h}, ${s * 100}%, ${l * 100}%, ${color.alpha * 100}%)`
}

function fromColorValueStringToNumber(str: string) {
if (str.endsWith('%')) return parseInt(str.slice(0, -1), 10) / 100
return parseInt(str, 10) / 255
Expand Down Expand Up @@ -423,3 +497,66 @@ export function rgbToHsv({ r, g, b }: { r: number; g: number; b: number }) {

return { h, s, v }
}

export function hslToRgb({ h, s, l }: { h: number; s: number; l: number }) {
const c = (1 - Math.abs(2 * l - 1)) * s
const x = c * (1 - Math.abs(((h / 60) % 2) - 1))
const m = l - c / 2

let r = 0
let g = 0
let b = 0

if (h < 60) {
r = c
g = x
b = 0
} else if (h < 120) {
r = x
g = c
b = 0
} else if (h < 180) {
r = 0
g = c
b = x
} else if (h < 240) {
r = 0
g = x
b = c
} else if (h < 300) {
r = x
g = 0
b = c
} else {
r = c
g = 0
b = x
}

return { r: r + m, g: g + m, b: b + m }
}

export function rgbToHsl({ r, g, b }: { r: number; g: number; b: number }) {
const max = Math.max(r, g, b)
const min = Math.min(r, g, b)
const c = max - min

let h = 0
let s = 0
const l = (max + min) / 2

if (c !== 0) {
if (max === r) {
h = ((g - b) / c) % 6
} else if (max === g) {
h = (b - r) / c + 2
} else {
h = (r - g) / c + 4
}

h *= 60
s = c / (1 - Math.abs(2 * l - 1))
}

return { h, s, l }
}
39 changes: 39 additions & 0 deletions src/test/suite/qss/color-provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,24 @@ import {
extractHexAarrggbbToColor,
extractHexRgbToColor,
extractHexRrggbbToColor,
extractHslaToColors,
extractHslToColors,
extractHsvaToColors,
extractHsvToColors,
extractRgbaToColors,
extractRgbToColors,
fromColorToHexAarrggbb,
fromColorToHexRgb,
fromColorToHexRrggbb,
fromColorToHsl,
fromColorToHsla,
fromColorToHsv,
fromColorToHsva,
fromColorToRgb,
fromColorToRgba,
hslToRgb,
hsvToRgb,
rgbToHsl,
rgbToHsv,
} from '../../../qss/color-provider'

Expand Down Expand Up @@ -104,4 +110,37 @@ suite('color-provider', () => {
const expected = { h: 60, s: 1, v: 1 }
assert.deepStrictEqual(rgbToHsv(hsvToRgb(expected)), expected)
})

test('hsl', () => {
const expected = 'hsl(0, 0, 255)'
const color = extractHslToColors(expected).map(({ color }) => color)[0]
assert.ok(color)
assert.strictEqual(fromColorToHsl(color), 'hsl(0, 0%, 100%)')
})

test('hsl with percentage', () => {
const expected = 'hsl(0, 0%, 100%)'
const color = extractHslToColors(expected).map(({ color }) => color)[0]
assert.ok(color)
assert.strictEqual(fromColorToHsl(color), expected)
})

test('hsla', () => {
const expected = 'hsla(0, 0, 255, 255)'
const color = extractHslaToColors(expected).map(({ color }) => color)[0]
assert.ok(color)
assert.strictEqual(fromColorToHsla(color), 'hsla(0, 0%, 100%, 100%)')
})

test('hsla with percentage', () => {
const expected = 'hsla(0, 0%, 100%, 100%)'
const color = extractHslaToColors(expected).map(({ color }) => color)[0]
assert.ok(color)
assert.strictEqual(fromColorToHsla(color), expected)
})

test('hsl <-> rgb', () => {
const expected = { h: 240, s: 1, l: 0.5 }
assert.deepStrictEqual(rgbToHsl(hslToRgb(expected)), expected)
})
})
2 changes: 1 addition & 1 deletion syntaxes/qss.tmLanguage.json
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@
"patterns": [
{
"description": "Color Type",
"begin": "\\b(rgb|rgba|hsv|hsva)\\s*\\(",
"begin": "\\b(rgb|rgba|hsv|hsva|hsl|hsla)\\s*\\(",
"beginCaptures": {
"1": {
"name": "entity.name.function.qss"
Expand Down

0 comments on commit 6411f26

Please sign in to comment.