diff --git a/.config/ags/scss/_sidebars.scss b/.config/ags/scss/_sidebars.scss index 7e8dad8a..51a3b5dd 100644 --- a/.config/ags/scss/_sidebars.scss +++ b/.config/ags/scss/_sidebars.scss @@ -455,6 +455,7 @@ $onChatgpt: $onPrimary; background-color: $l_l_t_surfaceVariant; min-width: 1.705rem; min-height: 1.705rem; + &:hover { background-color: $hovercolor; } @@ -466,9 +467,74 @@ $onChatgpt: $onPrimary; background-color: $l_l_t_surfaceVariant; min-width: 1.705rem; min-height: 1.705rem; + &:hover { background-color: $hovercolor; } + + &:active { + background-color: $activecolor; + } +} + +$colorpicker_rounding: 0.341rem; + +.sidebar-module-colorpicker-wrapper { + padding: 0.341rem; +} + +.sidebar-module-colorpicker-cursorwrapper { + padding: 0.341rem 0.136rem; +} + +.sidebar-module-colorpicker-hue { + min-height: 13.636rem; + min-width: 1.091rem; + border-radius: $colorpicker_rounding; +} + +.sidebar-module-colorpicker-hue-cursor { + background-color: $onBackground; + border: 0.136rem solid $onBackground; + min-height: 0.136rem; + margin-top: -0.136rem; + border-radius: $colorpicker_rounding; +} + +.sidebar-module-colorpicker-saturationandlightness-wrapper { + padding: 0.341rem; +} + +.sidebar-module-colorpicker-saturationandlightness { + min-height: 13.636rem; + min-width: 13.636rem; + border-radius: $colorpicker_rounding; +} + +.sidebar-module-colorpicker-saturationandlightness-cursorwrapper { + padding: 0.341rem; + margin-top: -0.409rem; + margin-left: -0.409rem; +} + +.sidebar-module-colorpicker-saturationandlightness-cursor { + @include full-rounding; + border: 0.136rem solid white; + min-width: 0.682rem; + min-height: 0.682rem; + margin-top: -0.409rem; + margin-left: -0.409rem; +} + +.sidebar-module-colorpicker-result-area { + padding: 0.341rem; +} + +.sidebar-module-colorpicker-result-box { + border-radius: $colorpicker_rounding; + min-width: 2.045rem; + min-height: 0.682rem; + padding: 0.341rem; } .sidebar-chat-apiswitcher { @@ -778,9 +844,7 @@ $waifu_image_overlay_transparency: 0.7; @include full-rounding; min-width: 1.875rem; min-height: 1.875rem; - background-color: rgba(0, - 0, - 0, + background-color: rgba(0, 0, 0, $waifu_image_overlay_transparency ); // Fixed cuz on image color: rgba(255, 255, 255, $waifu_image_overlay_transparency); } @@ -792,4 +856,4 @@ $waifu_image_overlay_transparency: 0.7; .sidebar-waifu-image-action:active { background-color: rgba(60, 60, 60, $waifu_image_overlay_transparency); -} \ No newline at end of file +} diff --git a/.config/ags/widgets/sideleft/toolbox.js b/.config/ags/widgets/sideleft/toolbox.js index 1fbf7179..9ecb98b9 100644 --- a/.config/ags/widgets/sideleft/toolbox.js +++ b/.config/ags/widgets/sideleft/toolbox.js @@ -2,15 +2,18 @@ import Widget from 'resource:///com/github/Aylur/ags/widget.js'; import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'; const { Box, Button, EventBox, Label, Revealer, Scrollable, Stack } = Widget; const { execAsync, exec } = Utils; -import { QuickScripts } from './quickscripts.js'; +import QuickScripts from './tools/quickscripts.js'; +import ColorPicker from './tools/colorpicker.js'; export default Scrollable({ hscroll: "never", vscroll: "automatic", child: Box({ vertical: true, + className: 'spacing-v-10', children: [ QuickScripts(), + ColorPicker(), ] }) }); diff --git a/.config/ags/widgets/sideleft/tools/colorpicker.js b/.config/ags/widgets/sideleft/tools/colorpicker.js new file mode 100644 index 00000000..2e3e84f9 --- /dev/null +++ b/.config/ags/widgets/sideleft/tools/colorpicker.js @@ -0,0 +1,365 @@ +const { Gtk } = imports.gi; +import App from 'resource:///com/github/Aylur/ags/app.js'; +import Variable from 'resource:///com/github/Aylur/ags/variable.js'; +import Widget from 'resource:///com/github/Aylur/ags/widget.js'; +import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'; +const { execAsync, exec } = Utils; +const { Box, Button, Entry, EventBox, Icon, Label, Overlay, Scrollable } = Widget; +import SidebarModule from './module.js'; +import { MaterialIcon } from '../../../lib/materialicon.js'; +import { setupCursorHover } from '../../../lib/cursorhover.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); + function shouldUseBlackColor() { + return ((xAxis.value < 40 || (45 <= hue.value && hue.value <= 195)) && + yAxis.value > 60); + } + const colorBlack = 'rgba(0,0,0,0.9)'; + const colorWhite = 'rgba(255,255,255,0.9)'; + const hueRange = Box({ + homogeneous: true, + className: 'sidebar-module-colorpicker-wrapper', + children: [Box({ + className: 'sidebar-module-colorpicker-hue', + css: `background: linear-gradient(to bottom, #ff0000, #ffff00, #00ff00, #00ffff, #0000ff, #ff00ff, #ff0000);`, + })], + }); + const hueSlider = Box({ + vpack: 'start', + className: 'sidebar-module-colorpicker-cursorwrapper', + homogeneous: true, + children: [Box({ + className: 'sidebar-module-colorpicker-hue-cursor', + })], + setup: (self) => self.hook(hue, () => { + const widgetHeight = hueRange.children[0].get_allocated_height(); + self.setCss(`margin-top: ${widgetHeight * hue.value / 360}px;`) + }), + }); + const hueSelector = Box({ + children: [EventBox({ + child: Overlay({ + child: hueRange, + overlays: [hueSlider], + }), + attribute: { + clicked: false, + setHue: (self, event) => { + 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); + } + }, + setup: (self) => self + .on('motion-notify-event', (self, event) => { + if (!self.attribute.clicked) return; + self.attribute.setHue(self, event); + }) + .on('button-press-event', (self, event) => { + if (!(event.get_button()[1] === 1)) return; // We're only interested in left-click here + self.attribute.clicked = true; + self.attribute.setHue(self, event); + }) + .on('button-release-event', (self) => self.attribute.clicked = false) + , + })] + }); + const saturationAndLightnessRange = Box({ + 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: + linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,1)), + linear-gradient(to right, #ffffff, ${hslToHex(hue.value, 100, 50)}); + `); + }), + })], + }); + const saturationAndLightnessCursor = Box({ + className: 'sidebar-module-colorpicker-saturationandlightness-cursorwrapper', + children: [Box({ + vpack: 'start', + hpack: 'start', + homogeneous: true, + css: ` + 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 + children: [Box({ + className: 'sidebar-module-colorpicker-saturationandlightness-cursor', + css: ` + background-color: ${hslToHex(hue.value, xAxis.value, yAxis.value / (1 + xAxis.value / 100))}; + border-color: ${shouldUseBlackColor() ? colorBlack : colorWhite}; + `, + attribute: { + updateCursorColor: (self) => { + self.setCss(` + background-color: ${hslToHex(hue.value, xAxis.value, yAxis.value / (1 + xAxis.value / 100))}; + border-color: ${shouldUseBlackColor() ? colorBlack : colorWhite}; + `); + } + }, + setup: (self) => self + .hook(yAxis, (self) => self.attribute.updateCursorColor(self)) + .hook(hue, (self) => self.attribute.updateCursorColor(self)) + , + })], + 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({ + homogeneous: true, + className: 'sidebar-module-colorpicker-saturationandlightness-wrapper', + children: [EventBox({ + child: Overlay({ + child: saturationAndLightnessRange, + overlays: [saturationAndLightnessCursor], + }), + attribute: { + clicked: false, + setSaturationAndLightness: (self, event) => { + const allocation = saturationAndLightnessRange.children[0].get_allocation(); + 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); + } + }, + setup: (self) => self + .on('motion-notify-event', (self, event) => { + if (!self.attribute.clicked) return; + self.attribute.setSaturationAndLightness(self, event); + }) + .on('button-press-event', (self, event) => { + if (!(event.get_button()[1] === 1)) return; // We're only interested in left-click here + self.attribute.clicked = true; + self.attribute.setSaturationAndLightness(self, event); + }) + .on('button-release-event', (self) => self.attribute.clicked = false) + , + })] + }); + 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))};`, + 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))};`); + 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)) + , + }); + const resultHex = Box({ + children: [ + Box({ + vertical: true, + hexpand: true, + children: [ + Label({ + xalign: 0, + 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)) + , + }) + ] + }), + Button({ + child: MaterialIcon('content_copy', 'norm'), + onClicked: () => Utils + .execAsync(['wl-copy', `${hslToHex(hue.value, xAxis.value, yAxis.value / (1 + xAxis.value / 100))}`]) + }) + ] + }); + const resultRgb = Box({ + children: [ + Box({ + vertical: true, + hexpand: true, + children: [ + Label({ + xalign: 0, + className: 'txt-tiny', + label: 'RGB', + }), + 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)); + } + }, + setup: (self) => self + .hook(yAxis, (self) => self.attribute.updateColor(self)) + .hook(hue, (self) => self.attribute.updateColor(self)) + , + }) + ] + }), + Button({ + child: MaterialIcon('content_copy', 'norm'), + onClicked: () => Utils + .execAsync(['wl-copy', `rgb(${hslToRgbValues(hue.value, xAxis.value, yAxis.value / (1 + xAxis.value / 100))})`]) + }) + ] + }); + const resultHsl = Box({ + children: [ + Box({ + vertical: true, + hexpand: true, + children: [ + Label({ + xalign: 0, + className: 'txt-tiny', + label: 'HSL', + }), + 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))}%`; + } + }, + setup: (self) => self + .hook(yAxis, (self) => self.attribute.updateColor(self)) + .hook(hue, (self) => self.attribute.updateColor(self)) + , + }) + ] + }), + Button({ + child: MaterialIcon('content_copy', 'norm'), + onClicked: () => Utils + .execAsync(['wl-copy', `hsl(${hue.value},${xAxis.value}%,${Math.round(yAxis.value / (1 + xAxis.value / 100))}%)`]) + }) + ] + }); + const result = Box({ + className: 'sidebar-module-colorpicker-result-area spacing-v-5 txt', + hexpand: true, + vertical: true, + children: [ + resultColorBox, + resultHex, + resultRgb, + resultHsl, + ] + }) + return SidebarModule({ + icon: MaterialIcon('colorize', 'norm'), + name: 'Color picker', + // revealChild: false, + child: Box({ + className: 'spacing-h-5', + children: [ + hueSelector, + saturationAndLightnessSelector, + result, + ] + }) + }); +} \ No newline at end of file diff --git a/.config/ags/widgets/sideleft/module.js b/.config/ags/widgets/sideleft/tools/module.js similarity index 72% rename from .config/ags/widgets/sideleft/module.js rename to .config/ags/widgets/sideleft/tools/module.js index 166370f0..ad5365f6 100644 --- a/.config/ags/widgets/sideleft/module.js +++ b/.config/ags/widgets/sideleft/tools/module.js @@ -1,27 +1,28 @@ import Widget from 'resource:///com/github/Aylur/ags/widget.js'; -import { setupCursorHover } from '../../lib/cursorhover.js'; -import { MaterialIcon } from '../../lib/materialicon.js'; +import { setupCursorHover } from '../../../lib/cursorhover.js'; +import { MaterialIcon } from '../../../lib/materialicon.js'; const { Box, Button, Icon, Label, Revealer } = Widget; -export const SidebarModule = ({ +export default ({ icon, name, - child + child, + revealChild = true, }) => { - const headerButtonIcon = MaterialIcon('expand_more', 'norm'); + const headerButtonIcon = MaterialIcon(revealChild ? 'expand_less' : 'expand_more', 'norm'); const header = Box({ - className: 'spacing-h-10', + className: 'txt spacing-h-10', children: [ icon, Label({ - className: 'txt-norm txt', + className: 'txt-norm', label: `${name}`, }), Box({ hexpand: true, }), Button({ - className: 'sidebar-module-btn-arrow txt', + className: 'sidebar-module-btn-arrow', child: headerButtonIcon, onClicked: () => { console.log('clicked'); @@ -33,7 +34,7 @@ export const SidebarModule = ({ ] }); const content = Revealer({ - revealChild: true, + revealChild: revealChild, transition: 'slide_down', transitionDuration: 200, child: Box({ diff --git a/.config/ags/widgets/sideleft/quickscripts.js b/.config/ags/widgets/sideleft/tools/quickscripts.js similarity index 94% rename from .config/ags/widgets/sideleft/quickscripts.js rename to .config/ags/widgets/sideleft/tools/quickscripts.js index 483e2365..3ff907dc 100644 --- a/.config/ags/widgets/sideleft/quickscripts.js +++ b/.config/ags/widgets/sideleft/tools/quickscripts.js @@ -4,9 +4,9 @@ import Widget from 'resource:///com/github/Aylur/ags/widget.js'; import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'; const { execAsync, exec } = Utils; const { Box, Button, EventBox, Icon, Label, Scrollable } = Widget; -import { SidebarModule } from './module.js'; -import { MaterialIcon } from '../../lib/materialicon.js'; -import { setupCursorHover } from '../../lib/cursorhover.js'; +import SidebarModule from './module.js'; +import { MaterialIcon } from '../../../lib/materialicon.js'; +import { setupCursorHover } from '../../../lib/cursorhover.js'; Gtk.IconTheme.get_default().append_search_path(`${App.configDir}/assets`); const distroID = exec(`bash -c 'cat /etc/os-release | grep "^ID=" | cut -d "=" -f 2'`).trim(); @@ -53,7 +53,7 @@ const scripts = [ }, ]; -export const QuickScripts = () => SidebarModule({ +export default () => SidebarModule({ icon: MaterialIcon('code', 'norm'), name: 'Quick scripts', child: Box({