From 8a21594dbc5075b92d245f4c83530c7dae71c62a Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 21 Feb 2024 22:18:56 +0200 Subject: [PATCH] shadow slots work + minor fixes --- src/boot/after_store.js | 1 + src/components/button.style.js | 43 +++++------- src/components/input.style.js | 12 ++-- .../tabs/theme_tab/theme_tab.js | 20 +++--- .../shadow_control/shadow_control.js | 2 +- src/services/style_setter/style_setter.js | 26 +++++--- src/services/theme_data/css_utils.js | 20 ++++++ src/services/theme_data/theme2_to_theme3.js | 29 ++++++-- .../theme_data/theme3_slot_functions.js | 22 +++---- .../theme_data/theme_data_3.service.js | 66 ++++++++++++------- 10 files changed, 153 insertions(+), 88 deletions(-) diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 84fea954..49a8130c 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -359,6 +359,7 @@ const afterStoreSetup = async ({ store, i18n }) => { const { theme } = store.state.instance const customThemePresent = customThemeSource || customTheme + console.log({ ...customThemeSource }, { ...customTheme }) if (customThemePresent) { if (customThemeSource && customThemeSource.themeEngineVersion === CURRENT_VERSION) { applyTheme(customThemeSource) diff --git a/src/components/button.style.js b/src/components/button.style.js index 1404061c..4910a5ac 100644 --- a/src/components/button.style.js +++ b/src/components/button.style.js @@ -1,23 +1,3 @@ -const buttonInsetFakeBorders = ['$borderSide(#FFFFFF, top, 0.2)', '$borderSide(#000000, bottom, 0.2)'] -const inputInsetFakeBorders = ['$borderSide(#FFFFFF, bottom, 0.2)', '$borderSide(#000000, top, 0.2)'] -const buttonOuterShadow = { - x: 0, - y: 0, - blur: 2, - spread: 0, - color: '#000000', - alpha: 1 -} - -const hoverGlow = { - x: 0, - y: 0, - blur: 4, - spread: 0, - color: '--text', - alpha: 1 -} - export default { name: 'Button', // Name of the component selector: '.button-default', // CSS selector/prefix @@ -49,52 +29,61 @@ export default { ], // Default rules, used as "default theme", essentially. defaultRules: [ + { + component: 'Root', + directives: { + '--defaultButtonHoverGlow': 'shadow | 0 0 4 --text', + '--defaultButtonShadow': 'shadow | 0 0 2 #000000', + '--defaultButtonBevel': 'shadow | $borderSide(#FFFFFF, top, 0.2) | $borderSide(#000000, bottom, 0.2)', + '--pressedButtonBevel': 'shadow | $borderSide(#FFFFFF, bottom, 0.2)| $borderSide(#000000, top, 0.2)' + } + }, { // component: 'Button', // no need to specify components every time unless you're specifying how other component should look // like within it directives: { background: '--fg', - shadow: [buttonOuterShadow, ...buttonInsetFakeBorders], + shadow: ['--defaultButtonShadow', '--defaultButtonBevel'], roundness: 3 } }, { state: ['hover'], directives: { - shadow: [hoverGlow, ...buttonInsetFakeBorders] + shadow: ['--defaultButtonHoverGlow', '--defaultButtonBevel'] } }, { state: ['pressed'], directives: { - shadow: [buttonOuterShadow, ...inputInsetFakeBorders] + shadow: ['--defaultButtonShadow', '--pressedButtonBevel'] } }, { state: ['hover', 'pressed'], directives: { - shadow: [hoverGlow, ...inputInsetFakeBorders] + shadow: ['--defaultButtonHoverGlow', '--pressedButtonBevel'] } }, { state: ['toggled'], directives: { background: '--inheritedBackground,-24.2', - shadow: [buttonOuterShadow, ...inputInsetFakeBorders] + shadow: ['--defaultButtonShadow', '--pressedButtonBevel'] } }, { state: ['toggled', 'hover'], directives: { background: '--inheritedBackground,-24.2', - shadow: [hoverGlow, ...inputInsetFakeBorders] + shadow: ['--defaultButtonHoverGlow', '--pressedButtonBevel'] } }, { state: ['disabled'], directives: { background: '$blend(--inheritedBackground, 0.25, --parent)', - shadow: [...buttonInsetFakeBorders] + shadow: ['--defaultButtonBevel'] } }, { diff --git a/src/components/input.style.js b/src/components/input.style.js index b1c9f3db..70c775ad 100644 --- a/src/components/input.style.js +++ b/src/components/input.style.js @@ -1,5 +1,3 @@ -const inputInsetFakeBorders = ['$borderSide(#FFFFFF, bottom, 0.2)', '$borderSide(#000000, top, 0.2)'] - const hoverGlow = { x: 0, y: 0, @@ -25,6 +23,12 @@ export default { 'Text' ], defaultRules: [ + { + component: 'Root', + directives: { + '--defaultInputBevel': 'shadow | $borderSide(#FFFFFF, bottom, 0.2)| $borderSide(#000000, top, 0.2)' + } + }, { variant: 'checkbox', directives: { @@ -42,13 +46,13 @@ export default { spread: 0, color: '#000000', alpha: 1 - }, ...inputInsetFakeBorders] + }, '--defaultInputBevel'] } }, { state: ['hover'], directives: { - shadow: [hoverGlow, ...inputInsetFakeBorders] + shadow: [hoverGlow, '--defaultInputBevel'] } } ] diff --git a/src/components/settings_modal/tabs/theme_tab/theme_tab.js b/src/components/settings_modal/tabs/theme_tab/theme_tab.js index 58f8d44a..dd525920 100644 --- a/src/components/settings_modal/tabs/theme_tab/theme_tab.js +++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.js @@ -4,15 +4,7 @@ import { getContrastRatioLayers } from 'src/services/color_convert/color_convert.js' import { - DEFAULT_SHADOWS, - generateColors, - generateShadows, - generateRadii, - generateFonts, - composePreset, - getThemes, - shadows2to3, - colors2to3 + getThemes } from 'src/services/style_setter/style_setter.js' import { newImporter, @@ -25,7 +17,15 @@ import { CURRENT_VERSION, OPACITIES, getLayers, - getOpacitySlot + getOpacitySlot, + DEFAULT_SHADOWS, + generateColors, + generateShadows, + generateRadii, + generateFonts, + composePreset, + shadows2to3, + colors2to3 } from 'src/services/theme_data/theme_data.service.js' import ColorInput from 'src/components/color_input/color_input.vue' import RangeInput from 'src/components/range_input/range_input.vue' diff --git a/src/components/shadow_control/shadow_control.js b/src/components/shadow_control/shadow_control.js index a1d1012b..f8e12dbf 100644 --- a/src/components/shadow_control/shadow_control.js +++ b/src/components/shadow_control/shadow_control.js @@ -1,7 +1,7 @@ import ColorInput from '../color_input/color_input.vue' import OpacityInput from '../opacity_input/opacity_input.vue' import Select from '../select/select.vue' -import { getCssShadow } from '../../services/style_setter/style_setter.js' +import { getCssShadow } from '../../services/theme_data/theme_data.service.js' import { hex2rgb } from '../../services/color_convert/color_convert.js' import { library } from '@fortawesome/fontawesome-svg-core' import { diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js index 1fb65e1c..1bc92584 100644 --- a/src/services/style_setter/style_setter.js +++ b/src/services/style_setter/style_setter.js @@ -6,12 +6,22 @@ import { getCssRules } from '../theme_data/css_utils.js' import { defaultState } from '../../modules/config.js' export const applyTheme = (input) => { - const t0 = performance.now() - const { rules, theme } = generatePreset(input) - console.log(rules, theme) + const { version, theme: inputTheme } = input + let extraRules + let fonts + if (version === 2) { + const t0 = performance.now() + const { rules, theme } = generatePreset(inputTheme) + fonts = rules.fonts + const t1 = performance.now() + console.log('Themes 2 initialization took ' + (t1 - t0) + 'ms') + extraRules = convertTheme2To3(theme) + } else { + console.log(input) + extraRules = convertTheme2To3(input) + } + const t1 = performance.now() - console.log('Themes 2 initialization took ' + (t1 - t0) + 'ms') - const extraRules = convertTheme2To3(theme) const themes3 = init(extraRules) const t2 = performance.now() console.log('Themes 3 initialization took ' + (t2 - t1) + 'ms') @@ -24,7 +34,7 @@ export const applyTheme = (input) => { const styleSheet = styleEl.sheet styleSheet.toString() - styleSheet.insertRule(`:root { ${rules.fonts} }`, 'index-max') + styleSheet.insertRule(`:root { ${fonts} }`, 'index-max') getCssRules(themes3.eager, themes3.staticVars).forEach(rule => { // Hack to support multiple selectors on same component if (rule.match(/::-webkit-scrollbar-button/)) { @@ -133,8 +143,8 @@ export const getPreset = (val) => { data.colors = { bg, fg, text, link, cRed, cBlue, cGreen, cOrange } } - return { theme: data, source: theme.source } + return { theme: data, source: theme.source, version: isV1 ? 1 : 2 } }) } -export const setPreset = (val) => getPreset(val).then(data => applyTheme(data.theme)) +export const setPreset = (val) => getPreset(val).then(data => applyTheme(data)) diff --git a/src/services/theme_data/css_utils.js b/src/services/theme_data/css_utils.js index 8395f6a7..412accf9 100644 --- a/src/services/theme_data/css_utils.js +++ b/src/services/theme_data/css_utils.js @@ -2,6 +2,26 @@ import { convert } from 'chromatism' import { rgba2css } from '../color_convert/color_convert.js' +export const parseCssShadow = (text) => { + const dimensions = /(\d[a-z]*\s?){2,4}/.exec(text)?.[0] + const inset = /inset/.exec(text)?.[0] + const color = text.replace(dimensions, '').replace(inset, '') + + const [x, y, blur = 0, spread = 0] = dimensions.split(/ /).filter(x => x).map(x => x.trim()) + const isInset = inset?.trim() === 'inset' + console.log(color.trim()) + const colorString = color.split(/ /).filter(x => x).map(x => x.trim())[0] + + return { + x, + y, + blur, + spread, + inset: isInset, + color: colorString + } +} + export const getCssColorString = (color, alpha) => rgba2css({ ...convert(color).rgb, a: alpha }) export const getCssShadow = (input, usesDropShadow) => { diff --git a/src/services/theme_data/theme2_to_theme3.js b/src/services/theme_data/theme2_to_theme3.js index 743bc386..b367af36 100644 --- a/src/services/theme_data/theme2_to_theme3.js +++ b/src/services/theme_data/theme2_to_theme3.js @@ -100,6 +100,8 @@ export const temporary = new Set([ export const temporaryColors = {} export const convertTheme2To3 = (data) => { + data.colors.accent = data.colors.accent || data.colors.link + data.colors.link = data.colors.link || data.colors.accent const generateRoot = () => { const directives = {} basePaletteKeys.forEach(key => { directives['--' + key] = 'color | ' + data.colors[key] }) @@ -111,7 +113,8 @@ export const convertTheme2To3 = (data) => { const convertRadii = () => { const newRules = [] - radiiKeys.forEach(key => { + Object.keys(data.radii).forEach(key => { + if (!radiiKeys.has(key) || data.radii[key] === undefined) return null const originalRadius = data.radii[key] const rule = {} @@ -150,13 +153,17 @@ export const convertTheme2To3 = (data) => { roundness: originalRadius } newRules.push(rule) + if (rule.component === 'Button') { + newRules.push({ ...rule, component: 'ScrollbarElement' }) + } }) return newRules } const convertShadows = () => { const newRules = [] - shadowsKeys.forEach(key => { + Object.keys(data.shadows).forEach(key => { + if (!shadowsKeys.has(key)) return const originalShadow = data.shadows[key] const rule = {} @@ -205,6 +212,10 @@ export const convertTheme2To3 = (data) => { if (key === 'buttonPressed') { newRules.push({ ...rule, state: ['toggled'] }) } + + if (rule.component === 'Button') { + newRules.push({ ...rule, component: 'ScrollbarElement' }) + } }) return newRules } @@ -234,10 +245,13 @@ export const convertTheme2To3 = (data) => { rule.component = 'ChatMessage' } else if (prefix === 'poll') { rule.component = 'PollGraph' + } else if (prefix === 'btn') { + rule.component = 'Button' } else { rule.component = prefix[0].toUpperCase() + prefix.slice(1).toLowerCase() } return keys.map((key) => { + if (!data.colors[key]) return null const leftoverKey = key.replace(prefix, '') const parts = (leftoverKey || 'Bg').match(/[A-Z][a-z]*/g) const last = parts.slice(-1)[0] @@ -335,12 +349,17 @@ export const convertTheme2To3 = (data) => { newRule.variant = variantArray[0].toLowerCase() } } - console.log(key, newRule) - return newRule + + if (newRule.component === 'Button') { + console.log([newRule, { ...newRule, component: 'ScrollbarElement' }]) + return [newRule, { ...newRule, component: 'ScrollbarElement' }] + } else { + return [newRule] + } }) }) - const flatExtRules = extendedRules.filter(x => x).reduce((acc, x) => [...acc, ...x], []).filter(x => x) + const flatExtRules = extendedRules.filter(x => x).reduce((acc, x) => [...acc, ...x], []).filter(x => x).reduce((acc, x) => [...acc, ...x], []) return [generateRoot(), ...convertShadows(), ...convertRadii(), ...flatExtRules] } diff --git a/src/services/theme_data/theme3_slot_functions.js b/src/services/theme_data/theme3_slot_functions.js index 2324e121..2715c827 100644 --- a/src/services/theme_data/theme3_slot_functions.js +++ b/src/services/theme_data/theme3_slot_functions.js @@ -1,7 +1,7 @@ import { convert, brightness } from 'chromatism' import { alphaBlend, relativeLuminance } from '../color_convert/color_convert.js' -export const process = (text, functions, findColor, dynamicVars, staticVars) => { +export const process = (text, functions, { findColor, findShadow }, { dynamicVars, staticVars }) => { const { funcName, argsString } = /\$(?\w+)\((?[#a-zA-Z0-9-,.'"\s]*)\)/.exec(text).groups const args = argsString.split(/,/g).map(a => a.trim()) @@ -9,27 +9,27 @@ export const process = (text, functions, findColor, dynamicVars, staticVars) => if (args.length < func.argsNeeded) { throw new Error(`$${funcName} requires at least ${func.argsNeeded} arguments, but ${args.length} were provided`) } - return func.exec(args, findColor, dynamicVars, staticVars) + return func.exec(args, { findColor, findShadow }, { dynamicVars, staticVars }) } export const colorFunctions = { alpha: { argsNeeded: 2, - exec: (args, findColor, dynamicVars, staticVars) => { + exec: (args, { findColor }, { dynamicVars, staticVars }) => { const [color, amountArg] = args - const colorArg = convert(findColor(color, dynamicVars, staticVars)).rgb + const colorArg = convert(findColor(color, { dynamicVars, staticVars })).rgb const amount = Number(amountArg) return { ...colorArg, a: amount } } }, blend: { argsNeeded: 3, - exec: (args, findColor, dynamicVars, staticVars) => { + exec: (args, { findColor }, { dynamicVars, staticVars }) => { const [backgroundArg, amountArg, foregroundArg] = args - const background = convert(findColor(backgroundArg, dynamicVars, staticVars)).rgb - const foreground = convert(findColor(foregroundArg, dynamicVars, staticVars)).rgb + const background = convert(findColor(backgroundArg, { dynamicVars, staticVars })).rgb + const foreground = convert(findColor(foregroundArg, { dynamicVars, staticVars })).rgb const amount = Number(amountArg) return alphaBlend(background, amount, foreground) @@ -37,10 +37,10 @@ export const colorFunctions = { }, mod: { argsNeeded: 2, - exec: (args, findColor, dynamicVars, staticVars) => { + exec: (args, { findColor }, { dynamicVars, staticVars }) => { const [colorArg, amountArg] = args - const color = convert(findColor(colorArg, dynamicVars, staticVars)).rgb + const color = convert(findColor(colorArg, { dynamicVars, staticVars })).rgb const amount = Number(amountArg) const effectiveBackground = dynamicVars.lowerLevelBackground @@ -54,7 +54,7 @@ export const colorFunctions = { export const shadowFunctions = { borderSide: { argsNeeded: 3, - exec: (args, findColor, dynamicVars, staticVars) => { + exec: (args, { findColor }) => { const [color, side, alpha = '1', widthArg = '1', inset = 'inset'] = args const width = Number(widthArg) @@ -86,7 +86,7 @@ export const shadowFunctions = { break } }) - return targetShadow + return [targetShadow] } } } diff --git a/src/services/theme_data/theme_data_3.service.js b/src/services/theme_data/theme_data_3.service.js index 8196415b..91bda11e 100644 --- a/src/services/theme_data/theme_data_3.service.js +++ b/src/services/theme_data/theme_data_3.service.js @@ -1,4 +1,5 @@ import { convert, brightness } from 'chromatism' +import { flattenDeep } from 'lodash' import { alphaBlend, getTextColor, @@ -20,6 +21,7 @@ import { normalizeCombination, findRules } from './iss_utils.js' +import { parseCssShadow } from './css_utils.js' const DEBUG = false @@ -36,7 +38,32 @@ const components = { ChatMessage: null } -const findColor = (color, dynamicVars, staticVars) => { +const findShadow = (shadows, { dynamicVars, staticVars }) => { + return (shadows || []).map(shadow => { + let targetShadow + if (typeof shadow === 'string') { + if (shadow.startsWith('$')) { + targetShadow = process(shadow, shadowFunctions, { findColor, findShadow }, { dynamicVars, staticVars }) + } else if (shadow.startsWith('--')) { + const [variable] = shadow.split(/,/g).map(str => str.trim()) // discarding modifier since it's not supported + const variableSlot = variable.substring(2) + return findShadow(staticVars[variableSlot], { dynamicVars, staticVars }) + } else { + targetShadow = parseCssShadow(shadow) + } + } else { + targetShadow = shadow + } + + const shadowArray = Array.isArray(targetShadow) ? targetShadow : [targetShadow] + return shadowArray.map(s => ({ + ...s, + color: findColor(s.color, { dynamicVars, staticVars }) + })) + }) +} + +const findColor = (color, { dynamicVars, staticVars }) => { if (typeof color !== 'string' || (!color.startsWith('--') && !color.startsWith('$'))) return color let targetColor = null if (color.startsWith('--')) { @@ -76,7 +103,7 @@ const findColor = (color, dynamicVars, staticVars) => { if (color.startsWith('$')) { try { - targetColor = process(color, colorFunctions, findColor, dynamicVars, staticVars) + targetColor = process(color, colorFunctions, { findColor }, { dynamicVars, staticVars }) } catch (e) { console.error('Failure executing color function', e) targetColor = '#FF00FF' @@ -89,7 +116,7 @@ const findColor = (color, dynamicVars, staticVars) => { const getTextColorAlpha = (directives, intendedTextColor, dynamicVars, staticVars) => { const opacity = directives.textOpacity const backgroundColor = convert(dynamicVars.lowerLevelBackground).rgb - const textColor = convert(findColor(intendedTextColor, dynamicVars, staticVars)).rgb + const textColor = convert(findColor(intendedTextColor, { dynamicVars, staticVars })).rgb if (opacity === null || opacity === undefined || opacity >= 1) { return convert(textColor).hex } @@ -288,7 +315,7 @@ export const init = (extraRuleset) => { dynamicVars.inheritedBackground = lowerLevelBackground dynamicVars.stacked = convert(stacked[lowerLevelSelector]).rgb - const intendedTextColor = convert(findColor(inheritedTextColor, dynamicVars, staticVars)).rgb + const intendedTextColor = convert(findColor(inheritedTextColor, { dynamicVars, staticVars })).rgb const textColor = newTextRule.directives.textAuto === 'no-auto' ? intendedTextColor : getTextColor( @@ -355,7 +382,7 @@ export const init = (extraRuleset) => { dynamicVars.inheritedBackground = inheritedBackground - const rgb = convert(findColor(computedDirectives.background, dynamicVars, staticVars)).rgb + const rgb = convert(findColor(computedDirectives.background, { dynamicVars, staticVars })).rgb if (!stacked[selector]) { let blend @@ -373,21 +400,7 @@ export const init = (extraRuleset) => { } if (computedDirectives.shadow) { - dynamicVars.shadow = (computedDirectives.shadow || []).map(shadow => { - let targetShadow - if (typeof shadow === 'string') { - if (shadow.startsWith('$')) { - targetShadow = process(shadow, shadowFunctions, findColor, dynamicVars, staticVars) - } - } else { - targetShadow = shadow - } - - return { - ...targetShadow, - color: findColor(targetShadow.color, dynamicVars, staticVars) - } - }) + dynamicVars.shadow = flattenDeep(findShadow(flattenDeep(computedDirectives.shadow), { dynamicVars, staticVars })) } if (!stacked[selector]) { @@ -403,14 +416,23 @@ export const init = (extraRuleset) => { const dynamicSlots = Object.entries(computedDirectives).filter(([k, v]) => k.startsWith('--')) dynamicSlots.forEach(([k, v]) => { - const [type, value] = v.split('|').map(x => x.trim()) // woah, Extreme! + const [type, ...value] = v.split('|').map(x => x.trim()) // woah, Extreme! switch (type) { case 'color': { - const color = findColor(value, dynamicVars, staticVars) + const color = findColor(value[0], { dynamicVars, staticVars }) dynamicVars[k] = color if (component.name === 'Root') { staticVars[k.substring(2)] = color } + break + } + case 'shadow': { + const shadow = value + dynamicVars[k] = shadow + if (component.name === 'Root') { + staticVars[k.substring(2)] = shadow + } + break } } })