Created
January 17, 2022 01:38
-
-
Save Normal-Tangerine8609/33f3000a7ddb1960033c7b38276c75aa to your computer and use it in GitHub Desktop.
Scriptable HTML Gradient
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* HTMLGradient | |
* | |
* HTMLGradient(gradient: string): Promis<gradient> | |
* | |
* example | |
* ------------ | |
* const widget = new ListWidget() | |
* widget.backgroundGradient = await HTMLGradient("to left, red, green 25%, blue-yellow") | |
* widget.presentSmall() | |
* ------------ | |
* | |
* All parameters for the gradient are in a string and separated by commas. | |
* | |
* The first parameter is optional and is a degree or direction. If the first parameter is not a degree or direction, the gradient will go from the top to the bottom. | |
* | |
* Degrees are made with a number and the keyword `deg` directly beside it. `0deg` is a gradient going from the top to the bottom. The numbers continue around a clock, 3 O’clock would be `90deg` (from the left to the right) and so on. Invalid degrees result in `0deg`. | |
* | |
* example | |
* ------------ | |
* await HTMLGradient("0deg, red, green, blue") //valid | |
* await HTMLGradient("55deg, red, green, blue") //valid | |
* await HTMLGradient("720deg, red, green, blue") //valid | |
* await HTMLGradient("0 deg, red, green, blue") //invalid | |
* ------------ | |
* | |
* Directions are word directions like `to top left`, witch is from the bottom right to the top left. The valid directions are `to left`, `to right`, `to top`, `to bottom`, `to top left`, `to top right`, `to bottom left`, `to bottom right`, `to left top`, `to right top`, `to left bottom` and `to right bottom`. Invalid directions result in `0deg`. | |
* | |
* example | |
* ------------ | |
* await HTMLGradient("to left, red, green, blue") //valid | |
* await HTMLGradient("to top right, red, green, blue") //valid | |
* await HTMLGradient("to top right, red, green, blue") //invalid | |
* await HTMLGradient("left, red, green, blue") //invalid | |
* ------------ | |
* | |
* All other parameters are colours. Colours can be any supported HTML colour. If it is not a HTML colour, it will be black. Colours can also have a light or dark mode variation by separating the colours respectively by hyphens (`-`). | |
* | |
* example | |
* ------------ | |
* await HTMLGradient("red, green, blue") //valid | |
* await HTMLGradient("hsl(180, 50%,50%), rgb(100, 235, 22)") //valid | |
* await HTMLGradient("red, rgba(255, 0, 0, 0%)") //valid | |
* await HTMLGradient("lab(56.29% -10.93 16.58 / 50%), color(sRGB 0 0.5 1 / 50%), lch(56.29% 19.86 236.62 / 50%)") //valid but all are black | |
* ------------ | |
* | |
* Following the colour you can have a space and specify a location. Locations should go in ascending order and be or be between 0 and 1. Locations can be a percentage. Colours without a specified location will be placed a equal distance between adjacent colours. | |
* | |
* example | |
* ------------ | |
* await HTMLGradient("red 50%, green, blue") //valid | |
* await HTMLGradient("red, green 0.33, blue 1") //valid | |
* await HTMLGradient("red 80%, green 10%, blue") //invalid | |
* await HTMLGradient("red -1, green, blue 1.1") //invalid | |
* ------------ | |
*/ | |
async function HTMLGradient(gradient) { | |
const input = gradient | |
//split gradient in parts | |
gradient = gradient.split(/,(?![^(]*\))(?![^"']*["'](?:[^"']*["'][^"']*["'])*[^"']*$)/).map((e) => e.trim()) | |
let gradientDirection | |
const wordDirections = { | |
"to left": 90, | |
"to right": 270, | |
"to top": 180, | |
"to bottom": 0, | |
"to top left": 135, | |
"to top right": 225, | |
"to bottom left": 45, | |
"to bottom right": 315, | |
"to left top": 135, | |
"to right top": 225, | |
"to left bottom": 45, | |
"to right bottom": 315, | |
} | |
//set gradient direction | |
if(Object.keys(wordDirections).includes(gradient[0])) { | |
//set if direction is a word and remove first item from gradient array | |
gradientDirection = wordDirections[gradient.shift()] | |
} else if(/\d+\s*deg/.test(gradient[0])) { | |
//set if direction is a degree and remove first item from gradient array | |
gradientDirection = Number(gradient.shift().match(/(\d+)\s*deg/)[1]) | |
} else { | |
//set default direction | |
gradientDirection = 0 | |
} | |
//math stuff to figure out gradient points | |
let startPoint = new Point(1-(0.5 + 0.5 * Math.cos((Math.PI * (gradientDirection + 90)) / 180.0)), 1-(0.5 + 0.5 * Math.sin((Math.PI * (gradientDirection + 90)) / 180.0))) | |
let endPoint = new Point(0.5 + 0.5 * Math.cos((Math.PI * (gradientDirection + 90)) / 180.0), 0.5 + 0.5 * Math.sin((Math.PI * (gradientDirection + 90)) / 180.0)) | |
//get colours and replace the location | |
let colours = [] | |
for(let colour of gradient) { | |
colour = colour.replace(/\d*(\.\d+)?%?$/, "") | |
colour = colour.split("-") | |
if (colour.length == 2) { | |
colours.push(Color.dynamic(await colorFromValue(colour[0]), await colorFromValue(colour[1]))) | |
} else { | |
colours.push(await colorFromValue(colour[0])) | |
} | |
} | |
//get locations of colours and account for percentages | |
let locations = gradient.map((e) => | |
/\d*(\.\d+)?%?$/.test(e) ? e.match(/\d*(\.\d+)?%?$/)[0] : null | |
).map((e) => { | |
if(e) { | |
if(e.endsWith("%")) { | |
e = Number(e.replace("%", "")) / 100 | |
} | |
} | |
//validate numbers | |
return (!isNaN(e) && !isNaN(parseFloat(e))) || typeof e == "number" ? Number(e) : null | |
}) | |
//set first and last locations if not specified | |
if(!locations[0]) { | |
locations[0] = 0 | |
} | |
if(!locations[locations.length - 1]) { | |
locations[locations.length - 1] = 1 | |
} | |
//repeat with each location | |
let minLocation = 0 | |
for(let i = 0; i < locations.length; i++) { | |
let currentLocation = locations[i] | |
//if currentLocation is specified | |
if(currentLocation) { | |
//basic errors | |
if(minLocation > currentLocation) { | |
throw new Error("Gradient Locations must be in ascending order: " + input) | |
} | |
if(currentLocation < 0) { | |
throw new Error("Gradient Locations must be equal or greater than 0: " + input) | |
} | |
if(currentLocation > 1) { | |
throw new Error("Gradient Locations must be equal or less than 1: " + input) | |
} | |
//set new minLocation | |
minLocation = currentLocation | |
} else { | |
let counter = 0 | |
let index = i | |
//find all following locations without value | |
while(locations[index] === null) { | |
counter++ | |
index++ | |
} | |
//get difference between previous and future locations with value and divide between locations without value | |
let difference = (locations[index] - locations[i - 1]) / (counter + 1) | |
//set locations | |
for(let count = 0; count < counter; count++) { | |
locations[count + i] = difference * (count + 1) + locations[i - 1] | |
} | |
} | |
} | |
//create and return gradient | |
gradient = new LinearGradient() | |
gradient.colors = colours | |
gradient.locations = locations | |
gradient.startPoint = startPoint | |
gradient.endPoint = endPoint | |
return gradient | |
//use WebView() to get a html colour | |
async function colorFromValue(c) { | |
let w = new WebView() | |
await w.loadHTML(`<div id="div"style="color:${c}"></div>`) | |
let result = await w.evaluateJavaScript('window.getComputedStyle(document.getElementById("div")).color') | |
return rgbaToScriptable(...result.match(/\d+(\.\d+)?/g).map((e) => Number(e))) | |
function rgbaToScriptable(r, g, b, a) { | |
r = r.toString(16) | |
g = g.toString(16) | |
b = b.toString(16) | |
if(r.length == 1) { | |
r = "0" + r | |
} | |
if(g.length == 1) { | |
g = "0" + g | |
} | |
if(b.length == 1) { | |
b = "0" + b | |
} | |
if(a) { | |
a = Number(a) | |
} else { | |
a = 1 | |
} | |
return new Color("#" + r + g + b, a) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment