import { MatugenColors } from '../lib/options/types'; import { initializeTrackers } from './optionsTrackers'; import { readFile, writeFile } from 'astal/file'; import { App } from 'astal/gtk3'; import { initializeHotReload } from './utils/hotReload'; import { Opt } from 'src/lib/options'; import { SystemUtilities } from 'src/core/system/SystemUtilities'; import options from 'src/configuration'; import { MatugenService } from 'src/services/matugen'; import { isHexColor } from 'src/lib/validation/colors'; const matugenService = MatugenService.getInstance(); /** * Central manager for theme styling throughout the application * Handles the transformation of theme options into compiled CSS */ class ThemeStyleManager { /** * Orchestrates the full theme regeneration process * Falls back to standard theme if Matugen is unavailable */ public async applyCss(): Promise { if (!SystemUtilities.checkDependencies('sass')) return; try { const variables = await this._generateThemeVariables(); await this._compileSass(variables); this._applyCss(); } catch (error) { console.error(error); } } /** * Decides whether to use Matugen-generated colors or standard theme variables * Controls the main theming strategy based on user configuration * * @returns An array of SCSS variable declarations */ private async _generateThemeVariables(): Promise { const useMatugen = options.theme.matugen.get(); if (!useMatugen) { return this._extractStandardVariables(); } const matugenColors = await matugenService.generateMatugenColors(); if (!matugenColors) { return this._extractStandardVariables(); } return this._extractMatugenizedVariables(matugenColors); } /** * Recursively processes theme objects to generate SCSS variables * Handles nested properties by creating properly namespaced variable names * * @returns An array of SCSS variable declarations using standard theme values */ private _extractStandardVariables(): string[] { const cssVariables: string[] = []; const optArray = options.toArray(); for (const opt of optArray) { const currentPath = opt.id; if (!currentPath.startsWith('theme.')) { continue; } const variableName = this._buildCssVariableName(currentPath); const variable = this._buildCssVariable(variableName, opt); cssVariables.push(variable); } return cssVariables; } /** * Alternative variable extraction when using Matugen's color generation * Processes all theme options and applies Matugen's palette where appropriate * * @param matugenColors - Color palette generated by Matugen service * @returns An array of SCSS variable declarations with Matugen colors applied */ private async _extractMatugenizedVariables(matugenColors: MatugenColors): Promise { try { const result: string[] = []; const optArray = options.toArray(); for (const opt of optArray) { const currentPath = opt.id; if (!currentPath.startsWith('theme.')) { continue; } const optionValue = opt.get(); const variableName = this._buildCssVariableName(currentPath); if (!isHexColor(optionValue)) { result.push(`$${variableName}: ${optionValue};`); continue; } const defaultThemeValue = opt.initial; if (!isHexColor(defaultThemeValue)) { continue; } const matugenColor = matugenService.getMatugenHex(defaultThemeValue, matugenColors); result.push(`$${variableName}: ${matugenColor};`); } return result; } catch (error) { console.error(error); return []; } } /** * Handles object properties that have values needing transformation * Creates properly formatted SCSS variable declarations * * @param variableName - CSS-friendly variable name * @param property - Option object containing the property value * @returns Formatted SCSS variable declaration */ private _buildCssVariable(variableName: string, property: Opt): string { const propertyValue = property.get(); return `$${variableName}: ${propertyValue};`; } /** * Transforms dotted paths into hyphenated CSS variable names * Strips the "theme." prefix for cleaner variable naming * * @param path - Dot-notation path of an option (e.g., "theme.background.primary") * @returns CSS-friendly variable name (e.g., "background-primary") */ private _buildCssVariableName(path: string): string { return path.replace('theme.', '').split('.').join('-'); } /** * Executes the SCSS compilation process with generated variables * Combines main SCSS with custom variables and module styles * * @param themeVariables - Array of SCSS variable declarations for user customization options * * File paths used in compilation: * - themeVariablesPath: Contains all user-configurable variables (theme colors, margins, borders, etc.) * - appScssPath: The application's main SCSS entry point file * - entryScssPath: A temporary file that combines all SCSS sources in the correct order * - modulesScssPath: User-defined custom module styles * - compiledCssPath: The final compiled CSS that gets used by the application */ private async _compileSass(themeVariables: string[]): Promise { const themeVariablesPath = `${TMP}/variables.scss`; const appScssPath = `${SRC_DIR}/src/style/main.scss`; const entryScssPath = `${TMP}/entry.scss`; const modulesScssPath = `${CONFIG_DIR}/modules.scss`; const compiledCssPath = `${TMP}/main.css`; const scssImports = [`@import '${themeVariablesPath}';`]; writeFile(themeVariablesPath, themeVariables.join('\n')); let combinedScss = readFile(appScssPath); combinedScss = `${scssImports.join('\n')}\n${combinedScss}`; const moduleCustomizations = readFile(modulesScssPath); combinedScss = `${combinedScss}\n${moduleCustomizations}`; writeFile(entryScssPath, combinedScss); await SystemUtilities.bash( `sass --load-path=${SRC_DIR}/src/style ${entryScssPath} ${compiledCssPath}`, ); } /** * Loads the compiled CSS into the application * * @remarks * Uses the compiled CSS file generated in _compileSass to apply styles to the application */ private _applyCss(): void { const compiledCssPath = `${TMP}/main.css`; App.apply_css(compiledCssPath, true); } } const themeManager = new ThemeStyleManager(); const optionsToWatch = [ 'font', 'theme', 'bar.flatButtons', 'bar.position', 'bar.battery.charging', 'bar.battery.blocks', ]; initializeTrackers(themeManager.applyCss.bind(themeManager)); initializeHotReload(); options.handler(optionsToWatch, themeManager.applyCss.bind(themeManager)); await themeManager.applyCss(); export { themeManager };