diff --git a/Helpers/ColorVariants.js b/Helpers/ColorVariants.js new file mode 100644 index 000000000..b39f41a4a --- /dev/null +++ b/Helpers/ColorVariants.js @@ -0,0 +1,90 @@ +// Material 3 Color Generator Helpers + +/** + * Convert hex color to HSL + */ +function hexToHSL(hex) { + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + if (!result) return null; + + let r = parseInt(result[1], 16) / 255; + let g = parseInt(result[2], 16) / 255; + let b = parseInt(result[3], 16) / 255; + + const max = Math.max(r, g, b); + const min = Math.min(r, g, b); + let h, s, l = (max + min) / 2; + + if (max === min) { + h = s = 0; + } else { + const 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)) / 6; break; + case g: h = ((b - r) / d + 2) / 6; break; + case b: h = ((r - g) / d + 4) / 6; break; + } + } + + return { h: h * 360, s: s * 100, l: l * 100 }; +} + +/** + * Convert HSL to hex color + */ +function hslToHex(h, s, l) { + s /= 100; + l /= 100; + + 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, g = 0, b = 0; + + if (0 <= h && h < 60) { r = c; g = x; b = 0; } + else if (60 <= h && h < 120) { r = x; g = c; b = 0; } + else if (120 <= h && h < 180) { r = 0; g = c; b = x; } + else if (180 <= h && h < 240) { r = 0; g = x; b = c; } + else if (240 <= h && h < 300) { r = x; g = 0; b = c; } + else if (300 <= h && h < 360) { r = c; g = 0; b = x; } + + r = Math.round((r + m) * 255); + g = Math.round((g + m) * 255); + b = Math.round((b + m) * 255); + + return "#" + [r, g, b].map(x => { + const hex = x.toString(16); + return hex.length === 1 ? "0" + hex : hex; + }).join(''); +} + +/** + * Generate fixed_dim variant (darker, muted version) + * Material 3 fixed_dim is typically 20-30% darker + */ +function generateFixedDim(hexColor) { + const hsl = hexToHSL(hexColor); + if (!hsl) return hexColor; + + // Reduce lightness by 25-30% and slightly reduce saturation + const newL = Math.max(hsl.l * 0.7, 10); + const newS = Math.max(hsl.s * 0.85, 5); + + return hslToHex(hsl.h, newS, newL); +} + +/** + * Generate bright variant (lighter, more vibrant version) + * Material 3 bright is typically 15-25% lighter with boosted saturation + */ +function generateBright(hexColor) { + const hsl = hexToHSL(hexColor); + if (!hsl) return hexColor; + + // Increase lightness by 20-25% and boost saturation + const newL = Math.min(hsl.l * 1.25, 90); + const newS = Math.min(hsl.s * 1.1, 100); + + return hslToHex(hsl.h, newS, newL); +} diff --git a/Services/MatugenService.qml b/Services/MatugenService.qml index a420ba8ff..484d9eadb 100644 --- a/Services/MatugenService.qml +++ b/Services/MatugenService.qml @@ -6,6 +6,7 @@ import Quickshell.Io import qs.Commons import qs.Assets.Matugen import qs.Services +import "../Helpers/ColorVariants.js" as ColorVariants Singleton { id: root @@ -165,15 +166,26 @@ Singleton { "color": variant.mOnSurface } }, - "on_background": { + "surface_variant": { "light": { - "color": variant.mOnSurface + "color": variant.mSurfaceVariant }, "default": { - "color": variant.mOnSurface + "color": variant.mSurfaceVariant }, "dark": { - "color": variant.mOnSurface + "color": variant.mSurfaceVariant + } + }, + "on_surface_variant": { + "light": { + "color": variant.mOnSurfaceVariant + }, + "default": { + "color": variant.mOnSurfaceVariant + }, + "dark": { + "color": variant.mOnSurfaceVariant } }, "outline": { @@ -187,103 +199,59 @@ Singleton { "color": variant.mOutline } }, - "secondary_fixed_dim": { - "light": { - "color": variant.mSecondary - }, - "default": { - "color": variant.mSecondary - }, - "dark": { - "color": variant.mSecondary - } - }, - "tertiary_container": { - "light": { - "color": variant.mTertiary - }, - "default": { - "color": variant.mTertiary - }, - "dark": { - "color": variant.mTertiary - } - }, - "surface_container": { - "light": { - "color": variant.mSurfaceVariant - }, - "default": { - "color": variant.mSurfaceVariant - }, - "dark": { - "color": variant.mSurfaceVariant - } - }, - "primary_container": { - "light": { - "color": variant.mPrimary - }, - "default": { - "color": variant.mPrimary - }, - "dark": { - "color": variant.mPrimary - } - }, - "on_primary_container": { - "light": { - "color": variant.mOnPrimary - }, - "default": { - "color": variant.mOnPrimary - }, - "dark": { - "color": variant.mOnPrimary - } - }, - "surface_variant": { - "light": { - "color": variant.mSurfaceVariant - }, - "default": { - "color": variant.mSurfaceVariant - }, - "dark": { - "color": variant.mSurfaceVariant - } - }, "primary_fixed_dim": { "light": { - "color": variant.mError + "color": ColorVariants.generateFixedDim(variant.mPrimary) }, "default": { - "color": variant.mError + "color": ColorVariants.generateFixedDim(variant.mPrimary) }, "dark": { - "color": variant.mError + "color": ColorVariants.generateFixedDim(variant.mPrimary) + } + }, + "secondary_fixed_dim": { + "light": { + "color": ColorVariants.generateFixedDim(variant.mSecondary) + }, + "default": { + "color": ColorVariants.generateFixedDim(variant.mSecondary) + }, + "dark": { + "color": ColorVariants.generateFixedDim(variant.mSecondary) } }, "tertiary_fixed_dim": { "light": { - "color": variant.mOutline + "color": ColorVariants.generateFixedDim(variant.mTertiary) }, "default": { - "color": variant.mOutline + "color": ColorVariants.generateFixedDim(variant.mTertiary) }, "dark": { - "color": variant.mOutline + "color": ColorVariants.generateFixedDim(variant.mTertiary) } }, - "surface_dim": { + "surface_bright": { "light": { - "color": variant.mOnSurfaceVariant + "color": ColorVariants.generateBright(variant.mSurface) }, "default": { - "color": variant.mOnSurfaceVariant + "color": ColorVariants.generateBright(variant.mSurface) }, "dark": { - "color": variant.mOnSurfaceVariant + "color": ColorVariants.generateBright(variant.mSurface) + } + }, + "surface_variant_bright": { + "light": { + "color": ColorVariants.generateBright(variant.mSurfaceVariant) + }, + "default": { + "color": ColorVariants.generateBright(variant.mSurfaceVariant) + }, + "dark": { + "color": ColorVariants.generateBright(variant.mSurfaceVariant) } } }