diff --git a/.config/ags/widgets/sideleft/tools/color.js b/.config/ags/widgets/sideleft/tools/color.js new file mode 100644 index 000000000..e1556b1b3 --- /dev/null +++ b/.config/ags/widgets/sideleft/tools/color.js @@ -0,0 +1,198 @@ +const { Gio, GLib } = imports.gi; +import Service from 'resource:///com/github/Aylur/ags/service.js'; +import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'; +const { exec, execAsync } = Utils; + +const clamp = (num, min, max) => Math.min(Math.max(num, min), max); + +export class ColorPickerSelection extends Service { + static { + Service.register(this, { + 'picked': [], + 'assigned': ['int'], + 'hue': [], + 'sl': [], + }); + } + + _hue = 198; + _xAxis = 94; + _yAxis = 80; + + get hue() { return this._hue; } + set hue(value) { + this._hue = clamp(value, 0, 360); + this.emit('hue'); + this.emit('picked'); + this.emit('changed'); + } + get xAxis() { return this._xAxis; } + set xAxis(value) { + this._xAxis = clamp(value, 0, 100); + this.emit('sl'); + this.emit('picked'); + this.emit('changed'); + } + get yAxis() { return this._yAxis; } + set yAxis(value) { + this._yAxis = clamp(value, 0, 100); + this.emit('sl'); + this.emit('picked'); + this.emit('changed'); + } + setColorFromHex(hexString, id) { + const hsl = hexToHSL(hexString); + this._hue = hsl.hue; + this._xAxis = hsl.saturation; + // this._yAxis = hsl.lightness; + this._yAxis = (100 - hsl.saturation / 2) / 100 * hsl.lightness; + // console.log(this._hue, this._xAxis, this._yAxis) + this.emit('assigned', id); + this.emit('changed'); + } + + constructor() { + super(); + this.emit('changed'); + } +} + + +export function hslToRgbValues(h, s, l) { + h /= 360; + s /= 100; + l /= 100; + let r, g, b; + if (s === 0) { + r = g = b = l; // achromatic + } else { + const hue2rgb = (p, q, t) => { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1 / 6) return p + (q - p) * 6 * t; + if (t < 1 / 2) return q; + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; + return p; + }; + const q = l < 0.5 ? l * (1 + s) : l + s - l * s; + const p = 2 * l - q; + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1 / 3); + } + const to255 = x => Math.round(x * 255); + r = to255(r); + g = to255(g); + b = to255(b); + return `${Math.round(r)},${Math.round(g)},${Math.round(b)}`; + // return `rgb(${r},${g},${b})`; +} +export function hslToHex(h, s, l) { + h /= 360; + s /= 100; + l /= 100; + let r, g, b; + if (s === 0) { + r = g = b = l; // achromatic + } else { + const hue2rgb = (p, q, t) => { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1 / 6) return p + (q - p) * 6 * t; + if (t < 1 / 2) return q; + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; + return p; + }; + const q = l < 0.5 ? l * (1 + s) : l + s - l * s; + const p = 2 * l - q; + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1 / 3); + } + const toHex = x => { + const hex = Math.round(x * 255).toString(16); + return hex.length === 1 ? "0" + hex : hex; + }; + return `#${toHex(r)}${toHex(g)}${toHex(b)}`; +} + +// export function hexToHSL(hex) { +// // Remove the '#' if present +// hex = hex.replace(/^#/, ''); +// // Parse the hex value into RGB components +// const bigint = parseInt(hex, 16); +// const r = (bigint >> 16) & 255; +// const g = (bigint >> 8) & 255; +// const b = bigint & 255; +// // Normalize RGB values to range [0, 1] +// const normalizedR = r / 255; +// const normalizedG = g / 255; +// const normalizedB = b / 255; +// // Find the maximum and minimum values +// const max = Math.max(normalizedR, normalizedG, normalizedB); +// const min = Math.min(normalizedR, normalizedG, normalizedB); +// // Calculate the lightness +// const lightness = (max + min) / 2; +// // If the color is grayscale, set saturation to 0 +// if (max === min) { +// return { +// hue: 0, +// saturation: 0, +// lightness: lightness * 100 // Convert to percentage +// }; +// } +// // Calculate the saturation +// const d = max - min; +// const saturation = lightness > 0.5 ? d / (2 - max - min) : d / (max + min); +// // Calculate the hue +// let hue; +// if (max === normalizedR) { +// hue = ((normalizedG - normalizedB) / d + (normalizedG < normalizedB ? 6 : 0)) * 60; +// } else if (max === normalizedG) { +// hue = ((normalizedB - normalizedR) / d + 2) * 60; +// } else { +// hue = ((normalizedR - normalizedG) / d + 4) * 60; +// } +// return { +// hue: Math.round(hue), +// saturation: Math.round(saturation * 100), // Convert to percentage +// lightness: Math.round(lightness * 100) // Convert to percentage +// }; +// } + +export function hexToHSL(hex) { + var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + + var r = parseInt(result[1], 16); + var g = parseInt(result[2], 16); + var b = parseInt(result[3], 16); + + r /= 255, g /= 255, b /= 255; + var max = Math.max(r, g, b), min = Math.min(r, g, b); + var h, s, l = (max + min) / 2; + + if (max == min) { + h = s = 0; // achromatic + } else { + var d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + switch (max) { + case r: h = (g - b) / d + (g < b ? 6 : 0); break; + case g: h = (b - r) / d + 2; break; + case b: h = (r - g) / d + 4; break; + } + h /= 6; + } + + s = s * 100; + s = Math.round(s); + l = l * 100; + l = Math.round(l); + h = Math.round(360 * h); + + return { + hue: h, + saturation: s, + lightness: l + }; +} diff --git a/.config/ags/widgets/sideleft/tools/colorpicker.js b/.config/ags/widgets/sideleft/tools/colorpicker.js index 2e3e84f9c..99b1f48d9 100644 --- a/.config/ags/widgets/sideleft/tools/colorpicker.js +++ b/.config/ags/widgets/sideleft/tools/colorpicker.js @@ -1,3 +1,4 @@ +// TODO: Make selection update when entry changes const { Gtk } = imports.gi; import App from 'resource:///com/github/Aylur/ags/app.js'; import Variable from 'resource:///com/github/Aylur/ags/variable.js'; @@ -9,76 +10,21 @@ import SidebarModule from './module.js'; import { MaterialIcon } from '../../../lib/materialicon.js'; import { setupCursorHover } from '../../../lib/cursorhover.js'; +import { ColorPickerSelection, hslToHex, hslToRgbValues, hexToHSL } from './color.js'; + const clamp = (num, min, max) => Math.min(Math.max(num, min), max); -function hslToRgbValues(h, s, l) { - h /= 360; - s /= 100; - l /= 100; - let r, g, b; - if (s === 0) { - r = g = b = l; // achromatic - } else { - const hue2rgb = (p, q, t) => { - if (t < 0) t += 1; - if (t > 1) t -= 1; - if (t < 1 / 6) return p + (q - p) * 6 * t; - if (t < 1 / 2) return q; - if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; - return p; - }; - const q = l < 0.5 ? l * (1 + s) : l + s - l * s; - const p = 2 * l - q; - r = hue2rgb(p, q, h + 1 / 3); - g = hue2rgb(p, q, h); - b = hue2rgb(p, q, h - 1 / 3); - } - const to255 = x => Math.round(x * 255); - r = to255(r); - g = to255(g); - b = to255(b); - return `${Math.round(r)},${Math.round(g)},${Math.round(b)}`; - // return `rgb(${r},${g},${b})`; -} -function hslToHex(h, s, l) { - h /= 360; - s /= 100; - l /= 100; - let r, g, b; - if (s === 0) { - r = g = b = l; // achromatic - } else { - const hue2rgb = (p, q, t) => { - if (t < 0) t += 1; - if (t > 1) t -= 1; - if (t < 1 / 6) return p + (q - p) * 6 * t; - if (t < 1 / 2) return q; - if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; - return p; - }; - const q = l < 0.5 ? l * (1 + s) : l + s - l * s; - const p = 2 * l - q; - r = hue2rgb(p, q, h + 1 / 3); - g = hue2rgb(p, q, h); - b = hue2rgb(p, q, h - 1 / 3); - } - const toHex = x => { - const hex = Math.round(x * 255).toString(16); - return hex.length === 1 ? "0" + hex : hex; - }; - return `#${toHex(r)}${toHex(g)}${toHex(b)}`; -} export default () => { - const hue = Variable(198); - const xAxis = Variable(94); - const yAxis = Variable(80); - const alpha = Variable(1); + const selectedColor = new ColorPickerSelection(); function shouldUseBlackColor() { - return ((xAxis.value < 40 || (45 <= hue.value && hue.value <= 195)) && - yAxis.value > 60); + return ((selectedColor.xAxis < 40 || (45 <= selectedColor.hue && selectedColor.hue <= 195)) && + selectedColor.yAxis > 60); } const colorBlack = 'rgba(0,0,0,0.9)'; const colorWhite = 'rgba(255,255,255,0.9)'; + const colorRed = '#be2222'; + const colorGreen = '#51b932'; + const colorBlue = '#2f87c2'; const hueRange = Box({ homogeneous: true, className: 'sidebar-module-colorpicker-wrapper', @@ -90,13 +36,14 @@ export default () => { const hueSlider = Box({ vpack: 'start', className: 'sidebar-module-colorpicker-cursorwrapper', + css: `margin-top: ${13.636 * selectedColor.hue / 360}rem;`, homogeneous: true, children: [Box({ className: 'sidebar-module-colorpicker-hue-cursor', })], - setup: (self) => self.hook(hue, () => { + setup: (self) => self.hook(selectedColor, () => { const widgetHeight = hueRange.children[0].get_allocated_height(); - self.setCss(`margin-top: ${widgetHeight * hue.value / 360}px;`) + self.setCss(`margin-top: ${13.636 * selectedColor.hue / 360}rem;`) }), }); const hueSelector = Box({ @@ -111,7 +58,7 @@ export default () => { const widgetHeight = hueRange.children[0].get_allocated_height(); const [_, cursorX, cursorY] = event.get_coords(); const cursorYPercent = clamp(cursorY / widgetHeight, 0, 1); - hue.value = Math.round(cursorYPercent * 360); + selectedColor.hue = Math.round(cursorYPercent * 360); } }, setup: (self) => self @@ -132,13 +79,19 @@ export default () => { homogeneous: true, children: [Box({ className: 'sidebar-module-colorpicker-saturationandlightness', - setup: (self) => self.hook(hue, () => { - // css: `background: linear-gradient(to right, #ffffff, color);`, - self.setCss(`background: + attribute: { + update: (self) => { + // css: `background: linear-gradient(to right, #ffffff, color);`, + self.setCss(`background: linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,1)), - linear-gradient(to right, #ffffff, ${hslToHex(hue.value, 100, 50)}); - `); - }), + linear-gradient(to right, #ffffff, ${hslToHex(selectedColor.hue, 100, 50)}); + `); + }, + }, + setup: (self) => self + .hook(selectedColor, self.attribute.update, 'hue') + .hook(selectedColor, self.attribute.update, 'assigned') + , })], }); const saturationAndLightnessCursor = Box({ @@ -148,40 +101,42 @@ export default () => { hpack: 'start', homogeneous: true, css: ` - margin-left: ${13.636 * xAxis.value / 100}rem; - margin-top: ${13.636 * (100 - yAxis.value) / 100}rem; + margin-left: ${13.636 * selectedColor.xAxis / 100}rem; + margin-top: ${13.636 * (100 - selectedColor.yAxis) / 100}rem; `, // Why 13.636rem? see class name in stylesheet + attribute: { + update: (self) => { + const allocation = saturationAndLightnessRange.children[0].get_allocation(); + self.setCss(` + margin-left: ${13.636 * selectedColor.xAxis / 100}rem; + margin-top: ${13.636 * (100 - selectedColor.yAxis) / 100}rem; + `); // Why 13.636rem? see class name in stylesheet + } + }, + setup: (self) => self + .hook(selectedColor, self.attribute.update, 'sl') + .hook(selectedColor, self.attribute.update, 'assigned') + , children: [Box({ className: 'sidebar-module-colorpicker-saturationandlightness-cursor', css: ` - background-color: ${hslToHex(hue.value, xAxis.value, yAxis.value / (1 + xAxis.value / 100))}; + background-color: ${hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))}; border-color: ${shouldUseBlackColor() ? colorBlack : colorWhite}; `, attribute: { - updateCursorColor: (self) => { + update: (self) => { self.setCss(` - background-color: ${hslToHex(hue.value, xAxis.value, yAxis.value / (1 + xAxis.value / 100))}; + background-color: ${hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))}; border-color: ${shouldUseBlackColor() ? colorBlack : colorWhite}; `); } }, setup: (self) => self - .hook(yAxis, (self) => self.attribute.updateCursorColor(self)) - .hook(hue, (self) => self.attribute.updateCursorColor(self)) + .hook(selectedColor, self.attribute.update, 'sl') + .hook(selectedColor, self.attribute.update, 'hue') + .hook(selectedColor, self.attribute.update, 'assigned') , })], - attribute: { - update: (self) => { - const allocation = saturationAndLightnessRange.children[0].get_allocation(); - self.setCss(` - margin-left: ${13.636 * xAxis.value / 100}rem; - margin-top: ${13.636 * (100 - yAxis.value) / 100}rem; - `); // Why 13.636rem? see class name in stylesheet - } - }, - setup: (self) => self.hook(yAxis, (self) => { // And saturation, but both are updated at once so we only need to connect to one - self.attribute.update(self); - }), })] }); const saturationAndLightnessSelector = Box({ @@ -199,8 +154,8 @@ export default () => { const [_, cursorX, cursorY] = event.get_coords(); const cursorXPercent = clamp(cursorX / allocation.width, 0, 1); const cursorYPercent = clamp(cursorY / allocation.height, 0, 1); - xAxis.value = Math.round(cursorXPercent * 100); - yAxis.value = Math.round(100 - cursorYPercent * 100); + selectedColor.xAxis = Math.round(cursorXPercent * 100); + selectedColor.yAxis = Math.round(100 - cursorYPercent * 100); } }, setup: (self) => self @@ -220,20 +175,21 @@ export default () => { const resultColorBox = Box({ className: 'sidebar-module-colorpicker-result-box', homogeneous: true, - css: `background-color: ${hslToHex(hue.value, xAxis.value, yAxis.value / (1 + xAxis.value / 100))};`, + css: `background-color: ${hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))};`, children: [Label({ className: 'txt txt-small', label: 'Result', }),], attribute: { - updateColor: (self) => { - self.setCss(`background-color: ${hslToHex(hue.value, xAxis.value, yAxis.value / (1 + xAxis.value / 100))};`); + update: (self) => { + self.setCss(`background-color: ${hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))};`); self.children[0].setCss(`color: ${shouldUseBlackColor() ? colorBlack : colorWhite};`) } }, setup: (self) => self - .hook(yAxis, (self) => self.attribute.updateColor(self)) - .hook(hue, (self) => self.attribute.updateColor(self)) + .hook(selectedColor, self.attribute.update, 'sl') + .hook(selectedColor, self.attribute.update, 'hue') + .hook(selectedColor, self.attribute.update, 'assigned') , }); const resultHex = Box({ @@ -247,26 +203,40 @@ export default () => { className: 'txt-tiny', label: 'Hex', }), - Entry({ - widthChars: 10, - className: 'txt-small techfont', - css: 'min-width: 0rem;', - attribute: { - updateColor: (self) => { - self.text = hslToHex(hue.value, xAxis.value, yAxis.value / (1 + xAxis.value / 100)); - } - }, - setup: (self) => self - .hook(yAxis, (self) => self.attribute.updateColor(self)) - .hook(hue, (self) => self.attribute.updateColor(self)) - , + Overlay({ + child: Entry({ + widthChars: 10, + className: 'txt-small techfont', + // css: 'color: transparent;', + attribute: { + id: 0, + update: (self, id) => { + if (id && self.attribute.id === id) return; + self.text = hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100)); + } + }, + setup: (self) => self + .hook(selectedColor, self.attribute.update, 'sl') + .hook(selectedColor, self.attribute.update, 'hue') + .hook(selectedColor, self.attribute.update, 'assigned') + // .on('activate', (self) => { + // const newColor = self.text; + // if (newColor.length != 7) return; + // selectedColor.setColorFromHex(self.text, self.attribute.id); + // }) + , + }), }) ] }), Button({ child: MaterialIcon('content_copy', 'norm'), - onClicked: () => Utils - .execAsync(['wl-copy', `${hslToHex(hue.value, xAxis.value, yAxis.value / (1 + xAxis.value / 100))}`]) + onClicked: (self) => { + Utils.execAsync(['wl-copy', `${hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))}`]) + self.child.label = 'done'; + Utils.timeout(1000, () => self.child.label = 'content_copy'); + }, + setup: setupCursorHover, }) ] }); @@ -284,23 +254,29 @@ export default () => { Entry({ widthChars: 10, className: 'txt-small techfont', - css: 'min-width: 0rem;', attribute: { - updateColor: (self) => { - self.text = hslToRgbValues(hue.value, xAxis.value, yAxis.value / (1 + xAxis.value / 100)); + id: 1, + update: (self, id) => { + if (id && self.attribute.id === id) return; + self.text = hslToRgbValues(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100)); } }, setup: (self) => self - .hook(yAxis, (self) => self.attribute.updateColor(self)) - .hook(hue, (self) => self.attribute.updateColor(self)) + .hook(selectedColor, self.attribute.update, 'sl') + .hook(selectedColor, self.attribute.update, 'hue') + .hook(selectedColor, self.attribute.update, 'assigned') , }) ] }), Button({ child: MaterialIcon('content_copy', 'norm'), - onClicked: () => Utils - .execAsync(['wl-copy', `rgb(${hslToRgbValues(hue.value, xAxis.value, yAxis.value / (1 + xAxis.value / 100))})`]) + onClicked: (self) => { + Utils.execAsync(['wl-copy', `rgb(${hslToRgbValues(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))})`]) + self.child.label = 'done'; + Utils.timeout(1000, () => self.child.label = 'content_copy'); + }, + setup: setupCursorHover, }) ] }); @@ -318,23 +294,30 @@ export default () => { Entry({ widthChars: 10, className: 'txt-small techfont', - css: 'min-width: 0rem;', attribute: { - updateColor: (self) => { - self.text = `${hue.value},${xAxis.value}%,${Math.round(yAxis.value / (1 + xAxis.value / 100))}%`; + id: 2, + update: (self, id) => { + if (id && self.attribute.id === id) return; + self.text = `${selectedColor.hue},${selectedColor.xAxis}%,${Math.round(selectedColor.yAxis / (1 + selectedColor.xAxis / 100))}%`; } }, setup: (self) => self - .hook(yAxis, (self) => self.attribute.updateColor(self)) - .hook(hue, (self) => self.attribute.updateColor(self)) + .hook(selectedColor, self.attribute.update, 'sl') + .hook(selectedColor, self.attribute.update, 'hue') + .hook(selectedColor, self.attribute.update, 'assigned') , + }) ] }), Button({ child: MaterialIcon('content_copy', 'norm'), - onClicked: () => Utils - .execAsync(['wl-copy', `hsl(${hue.value},${xAxis.value}%,${Math.round(yAxis.value / (1 + xAxis.value / 100))}%)`]) + onClicked: (self) => { + Utils.execAsync(['wl-copy', `hsl(${selectedColor.hue},${selectedColor.xAxis}%,${Math.round(selectedColor.yAxis / (1 + selectedColor.xAxis / 100))}%)`]) + self.child.label = 'done'; + Utils.timeout(1000, () => self.child.label = 'content_copy'); + }, + setup: setupCursorHover, }) ] }); @@ -352,7 +335,7 @@ export default () => { return SidebarModule({ icon: MaterialIcon('colorize', 'norm'), name: 'Color picker', - // revealChild: false, + revealChild: false, child: Box({ className: 'spacing-h-5', children: [