From 521d308a6c6777a45c94183751f3305ce23bdad3 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 18 Jan 2024 14:35:25 +0200 Subject: themes 3 initial work --- src/components/button.style.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/components/button.style.js (limited to 'src/components/button.style.js') diff --git a/src/components/button.style.js b/src/components/button.style.js new file mode 100644 index 00000000..8f2e8f82 --- /dev/null +++ b/src/components/button.style.js @@ -0,0 +1,18 @@ +export default { + name: 'Button', + states: { + hover: ':hover', + disabled: ':disabled', + pressed: ':active', + toggled: '.toggled' + }, + variants: { + danger: '.danger', + unstyled: '.unstyled', + sublime: '.sublime' + }, + validInnerComponents: [ + 'Text', + 'Icon' + ] +} -- cgit v1.2.3-70-g09d2 From 0729b529d7da2002f25039e1dad2732302009cf3 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 23 Jan 2024 00:43:46 +0200 Subject: some more stuff, generating CSS selectors from rules --- src/components/button.style.js | 1 + src/components/icon.style.js | 3 +- src/components/panel.style.js | 1 + src/components/text.style.js | 4 +- src/components/underlay.style.js | 3 +- src/services/theme_data/pleromafe.t3.js | 26 +++++++++ src/services/theme_data/theme_data_3.service.js | 62 +++++++++++++++++----- .../specs/services/theme_data/theme_data3.spec.js | 26 +++++++++ 8 files changed, 109 insertions(+), 17 deletions(-) create mode 100644 src/services/theme_data/pleromafe.t3.js create mode 100644 test/unit/specs/services/theme_data/theme_data3.spec.js (limited to 'src/components/button.style.js') diff --git a/src/components/button.style.js b/src/components/button.style.js index 8f2e8f82..1c229f43 100644 --- a/src/components/button.style.js +++ b/src/components/button.style.js @@ -1,5 +1,6 @@ export default { name: 'Button', + selector: '.btn', states: { hover: ':hover', disabled: ':disabled', diff --git a/src/components/icon.style.js b/src/components/icon.style.js index 1e2781d6..732cf16f 100644 --- a/src/components/icon.style.js +++ b/src/components/icon.style.js @@ -1,3 +1,4 @@ export default { - name: 'Icon' + name: 'Icon', + selector: '.icon' } diff --git a/src/components/panel.style.js b/src/components/panel.style.js index 1666d923..d34d5434 100644 --- a/src/components/panel.style.js +++ b/src/components/panel.style.js @@ -1,5 +1,6 @@ export default { name: 'Panel', + selector: '.panel', validInnerComponents: [ 'Text', 'Icon', diff --git a/src/components/text.style.js b/src/components/text.style.js index f87268bb..2aa5e745 100644 --- a/src/components/text.style.js +++ b/src/components/text.style.js @@ -1,9 +1,7 @@ export default { name: 'Text', + selector: '', states: { faint: '.faint' - }, - variants: { - green: '/.greentext' } } diff --git a/src/components/underlay.style.js b/src/components/underlay.style.js index bae9fc0b..426df1d7 100644 --- a/src/components/underlay.style.js +++ b/src/components/underlay.style.js @@ -1,5 +1,6 @@ export default { - name: 'Panel', + name: 'Underlay', + selector: '.underlay', validInnerComponents: [ 'Panel' ] diff --git a/src/services/theme_data/pleromafe.t3.js b/src/services/theme_data/pleromafe.t3.js new file mode 100644 index 00000000..b44a395e --- /dev/null +++ b/src/services/theme_data/pleromafe.t3.js @@ -0,0 +1,26 @@ +export const sampleRules = [ + { + component: 'Underlay', + // variant: 'normal', + // state: 'normal' + directives: { + background: '#000', + opacity: 0.2 + } + }, + { + component: 'Panel', + directives: { + background: '#FFFFFF', + opacity: 0.9 + } + }, + { + component: 'Button', + directives: { + background: '#808080', + text: '#FFFFFF', + opacity: 0.5 + } + } +] diff --git a/src/services/theme_data/theme_data_3.service.js b/src/services/theme_data/theme_data_3.service.js index 3a6fd552..39a9998d 100644 --- a/src/services/theme_data/theme_data_3.service.js +++ b/src/services/theme_data/theme_data_3.service.js @@ -14,7 +14,7 @@ const components = { } // This gives you an array of arrays of all possible unique (i.e. order-insensitive) combinations -const getAllPossibleCombinations = (array) => { +export const getAllPossibleCombinations = (array) => { const combos = [array.map(x => [x])] for (let comboSize = 2; comboSize <= array.length; comboSize++) { const previous = combos[combos.length - 1] @@ -30,15 +30,52 @@ const getAllPossibleCombinations = (array) => { return combos.reduce((acc, x) => [...acc, ...x], []) } -export const init = () => { +export const ruleToSelector = (rule) => { + const component = components[rule.component] + const { states, variants, selector } = component + + const applicableStates = (rule.state.filter(x => x !== 'normal') || []).map(state => states[state]) + + const applicableVariantName = (rule.variant || 'normal') + let applicableVariant = '' + if (applicableVariantName !== 'normal') { + applicableVariant = variants[applicableVariantName] + } + + const selectors = [selector, applicableVariant, ...applicableStates] + .toSorted((a, b) => { + if (a.startsWith(':')) return 1 + else return -1 + }) + .join('') + + if (rule.parent) { + return ruleToSelector(rule.parent) + ' ' + selectors + } + return selectors +} + +export const init = (ruleset) => { const rootName = root.name const rules = [] + const rulesByComponent = {} + + const addRule = (rule) => { + rules.push(rule) + rulesByComponent[rule.component] = rulesByComponent[rule.component] || [] + rulesByComponent.push(rule) + } + + ruleset.forEach(rule => { + + }) const processInnerComponent = (component, parent) => { const { - validInnerComponents, + validInnerComponents = [], states: originalStates = {}, - variants: originalVariants = {} + variants: originalVariants = {}, + name } = component const states = { normal: '', ...originalStates } @@ -51,16 +88,17 @@ export const init = () => { }).reduce((acc, x) => [...acc, ...x], []) stateVariantCombination.forEach(combination => { - rules.push(({ - parent, - component: component.name, - state: combination.state, - variant: combination.variant - })) - - innerComponents.forEach(innerComponent => processInnerComponent(innerComponent, combination)) + // addRule(({ + // parent, + // component: component.name, + // state: combination.state, + // variant: combination.variant + // })) + + innerComponents.forEach(innerComponent => processInnerComponent(innerComponent, { parent, component: name, ...combination })) }) } processInnerComponent(components[rootName]) + return rules } diff --git a/test/unit/specs/services/theme_data/theme_data3.spec.js b/test/unit/specs/services/theme_data/theme_data3.spec.js new file mode 100644 index 00000000..e76200c0 --- /dev/null +++ b/test/unit/specs/services/theme_data/theme_data3.spec.js @@ -0,0 +1,26 @@ +// import { topoSort } from 'src/services/theme_data/theme_data.service.js' +import { + getAllPossibleCombinations, + init, + ruleToSelector +} from 'src/services/theme_data/theme_data_3.service.js' +import { + sampleRules +} from 'src/services/theme_data/pleromafe.t3.js' + +describe.only('Theme Data 3', () => { + describe('getAllPossibleCombinations', () => { + it('test simple case', () => { + const out = getAllPossibleCombinations([1, 2, 3]).map(x => x.sort((a, b) => a - b)) + expect(out).to.eql([[1], [2], [3], [1, 2], [1, 3], [2, 3], [1, 2, 3]]) + }) + }) + + describe('init', () => { + it('test simple case', () => { + const out = init(sampleRules) + console.log(JSON.stringify(out, null, 2)) + out.forEach(r => console.log(ruleToSelector(r))) + }) + }) +}) -- cgit v1.2.3-70-g09d2 From 22b32f149d7753592fddcfb1cd2679b3fbac33d6 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 23 Jan 2024 19:18:55 +0200 Subject: shit more or less works for the very basic stuff --- src/components/button.style.js | 4 +- src/components/panel.style.js | 3 +- src/components/panel_header.style.js | 9 ++ src/components/text.style.js | 2 +- src/components/underlay.style.js | 2 +- src/services/color_convert/color_convert.js | 1 + src/services/theme_data/pleromafe.t3.js | 10 +- src/services/theme_data/theme_data_3.service.js | 116 +++++++++++++++++++-- .../specs/services/theme_data/theme_data3.spec.js | 7 +- 9 files changed, 132 insertions(+), 22 deletions(-) create mode 100644 src/components/panel_header.style.js (limited to 'src/components/button.style.js') diff --git a/src/components/button.style.js b/src/components/button.style.js index 1c229f43..51f781e1 100644 --- a/src/components/button.style.js +++ b/src/components/button.style.js @@ -2,10 +2,10 @@ export default { name: 'Button', selector: '.btn', states: { - hover: ':hover', disabled: ':disabled', + toggled: '.toggled', pressed: ':active', - toggled: '.toggled' + hover: ':hover' }, variants: { danger: '.danger', diff --git a/src/components/panel.style.js b/src/components/panel.style.js index d34d5434..e3da4d1a 100644 --- a/src/components/panel.style.js +++ b/src/components/panel.style.js @@ -4,6 +4,7 @@ export default { validInnerComponents: [ 'Text', 'Icon', - 'Button' + 'Button', + 'PanelHeader' ] } diff --git a/src/components/panel_header.style.js b/src/components/panel_header.style.js new file mode 100644 index 00000000..aaa8bea9 --- /dev/null +++ b/src/components/panel_header.style.js @@ -0,0 +1,9 @@ +export default { + name: 'PanelHeader', + selector: '.panel-heading', + validInnerComponents: [ + 'Text', + 'Icon', + 'Button' + ] +} diff --git a/src/components/text.style.js b/src/components/text.style.js index 2aa5e745..050194cb 100644 --- a/src/components/text.style.js +++ b/src/components/text.style.js @@ -1,6 +1,6 @@ export default { name: 'Text', - selector: '', + selector: '/*text*/', states: { faint: '.faint' } diff --git a/src/components/underlay.style.js b/src/components/underlay.style.js index 426df1d7..be1ecc56 100644 --- a/src/components/underlay.style.js +++ b/src/components/underlay.style.js @@ -1,6 +1,6 @@ export default { name: 'Underlay', - selector: '.underlay', + selector: '#app', validInnerComponents: [ 'Panel' ] diff --git a/src/services/color_convert/color_convert.js b/src/services/color_convert/color_convert.js index 47d6344e..23e4ca61 100644 --- a/src/services/color_convert/color_convert.js +++ b/src/services/color_convert/color_convert.js @@ -188,6 +188,7 @@ export const rgba2css = function (rgba) { */ export const getTextColor = function (bg, text, preserve) { const contrast = getContrastRatio(bg, text) + console.log(contrast) if (contrast < 4.5) { const base = typeof text.a !== 'undefined' ? { a: text.a } : {} diff --git a/src/services/theme_data/pleromafe.t3.js b/src/services/theme_data/pleromafe.t3.js index b44a395e..b7b12573 100644 --- a/src/services/theme_data/pleromafe.t3.js +++ b/src/services/theme_data/pleromafe.t3.js @@ -4,7 +4,7 @@ export const sampleRules = [ // variant: 'normal', // state: 'normal' directives: { - background: '#000', + background: '#000000', opacity: 0.2 } }, @@ -15,11 +15,17 @@ export const sampleRules = [ opacity: 0.9 } }, + { + component: 'PanelHeader', + directives: { + background: '#000000', + opacity: 0.9 + } + }, { component: 'Button', directives: { background: '#808080', - text: '#FFFFFF', opacity: 0.5 } } diff --git a/src/services/theme_data/theme_data_3.service.js b/src/services/theme_data/theme_data_3.service.js index 39a9998d..d65d5352 100644 --- a/src/services/theme_data/theme_data_3.service.js +++ b/src/services/theme_data/theme_data_3.service.js @@ -1,5 +1,9 @@ +import { convert } from 'chromatism' +import { alphaBlend, getTextColor, rgba2css } from '../color_convert/color_convert.js' + import Underlay from 'src/components/underlay.style.js' import Panel from 'src/components/panel.style.js' +import PanelHeader from 'src/components/panel_header.style.js' import Button from 'src/components/button.style.js' import Text from 'src/components/text.style.js' import Icon from 'src/components/icon.style.js' @@ -8,6 +12,7 @@ const root = Underlay const components = { Underlay, Panel, + PanelHeader, Button, Text, Icon @@ -34,7 +39,7 @@ export const ruleToSelector = (rule) => { const component = components[rule.component] const { states, variants, selector } = component - const applicableStates = (rule.state.filter(x => x !== 'normal') || []).map(state => states[state]) + const applicableStates = ((rule.state || []).filter(x => x !== 'normal')).map(state => states[state]) const applicableVariantName = (rule.variant || 'normal') let applicableVariant = '' @@ -63,12 +68,37 @@ export const init = (ruleset) => { const addRule = (rule) => { rules.push(rule) rulesByComponent[rule.component] = rulesByComponent[rule.component] || [] - rulesByComponent.push(rule) + rulesByComponent[rule.component].push(rule) } - ruleset.forEach(rule => { + const findRules = (combination) => rule => { + if (combination.component !== rule.component) return false + if (Object.prototype.hasOwnProperty.call(rule, 'variant')) { + if (combination.variant !== rule.variant) return false + } else { + if (combination.variant !== 'normal') return false + } + + if (Object.prototype.hasOwnProperty.call(rule, 'state')) { + const ruleStatesSet = new Set(['normal', ...(rule.state || [])]) + return combination.state.every(state => ruleStatesSet.has(state)) + } else { + if (combination.state.length !== 1 || combination.state[0] !== 'normal') return false + return true + } + } - }) + const findLowerLevelRule = (parent, filter = () => true) => { + let lowerLevelComponent = null + let currentParent = parent + while (currentParent) { + const rulesParent = ruleset.filter(findRules(currentParent, true)) + lowerLevelComponent = rulesParent[rulesParent.length - 1] + currentParent = currentParent.parent + if (lowerLevelComponent && filter(lowerLevelComponent)) currentParent = null + } + return filter(lowerLevelComponent) ? lowerLevelComponent : null + } const processInnerComponent = (component, parent) => { const { @@ -87,18 +117,82 @@ export const init = (ruleset) => { return stateCombinations.map(state => ({ variant, state })) }).reduce((acc, x) => [...acc, ...x], []) + const VIRTUAL_COMPONENTS = new Set(['Text', 'Link', 'Icon']) + stateVariantCombination.forEach(combination => { - // addRule(({ - // parent, - // component: component.name, - // state: combination.state, - // variant: combination.variant - // })) + const existingRules = ruleset.filter(findRules({ component: component.name, ...combination })) + const lastRule = existingRules[existingRules.length - 1] + + if (existingRules.length !== 0) { + const { directives } = lastRule + const rgb = convert(directives.background).rgb + + // TODO: DEFAULT TEXT COLOR + const bg = findLowerLevelRule(parent)?.cache.background || convert('#FFFFFF').rgb + + if (!lastRule.cache?.background) { + const blend = directives.opacity < 1 ? alphaBlend(rgb, directives.opacity, bg) : rgb + lastRule.cache = lastRule.cache || {} + lastRule.cache.background = blend + + addRule(lastRule) + } + } else { + if (VIRTUAL_COMPONENTS.has(component.name)) { + const selector = component.name + ruleToSelector({ component: component.name, ...combination }) + + const lowerLevel = findLowerLevelRule(parent, (r) => { + if (components[r.component].validInnerComponents.indexOf(component.name) < 0) return false + if (r.cache?.background === undefined) return false + if (r.cache.textDefined) { + return !r.cache.textDefined[selector] + } + return true + }) + if (!lowerLevel) return + lowerLevel.cache.textDefined = lowerLevel.cache.textDefined || {} + lowerLevel.cache.textDefined[selector] = true + addRule({ + parent, + component: component.name, + ...combination, + directives: { + // TODO: DEFAULT TEXT COLOR + textColor: getTextColor(convert(lowerLevel.cache.background).rgb, convert('#FFFFFF').rgb, component.name === 'Link'), + // Debug: lets you see what it think background color should be + background: convert(lowerLevel.cache.background).hex + } + }) + } + } innerComponents.forEach(innerComponent => processInnerComponent(innerComponent, { parent, component: name, ...combination })) }) } processInnerComponent(components[rootName]) - return rules + + // console.info(rules.map(x => [ + // (parent?.component || 'root') + ' -> ' + x.component, + // // 'Cached background:' + convert(bg).hex, + // // 'Color: ' + convert(x.directives.background).hex + ' A:' + x.directives.opacity, + // JSON.stringify(x.directives) + // // '=> Blend: ' + convert(x.cache.background).hex + // ].join(' '))) + + return { + raw: rules, + css: rules.map(rule => { + const header = ruleToSelector(rule) + ' {' + const footer = '}' + const directives = Object.entries(rule.directives).map(([k, v]) => { + switch (k) { + case 'background': return 'background-color: ' + rgba2css({ ...convert(v).rgb, a: rule.directives.opacity ?? 1 }) + case 'textColor': return 'color: ' + rgba2css({ ...convert(v).rgb, a: rule.directives.opacity ?? 1 }) + default: return '' + } + }).filter(x => x).map(x => ' ' + x).join(';\n') + return [header, directives, footer].join('\n') + }) + } } diff --git a/test/unit/specs/services/theme_data/theme_data3.spec.js b/test/unit/specs/services/theme_data/theme_data3.spec.js index e76200c0..915bb5ce 100644 --- a/test/unit/specs/services/theme_data/theme_data3.spec.js +++ b/test/unit/specs/services/theme_data/theme_data3.spec.js @@ -1,8 +1,7 @@ // import { topoSort } from 'src/services/theme_data/theme_data.service.js' import { getAllPossibleCombinations, - init, - ruleToSelector + init } from 'src/services/theme_data/theme_data_3.service.js' import { sampleRules @@ -19,8 +18,8 @@ describe.only('Theme Data 3', () => { describe('init', () => { it('test simple case', () => { const out = init(sampleRules) - console.log(JSON.stringify(out, null, 2)) - out.forEach(r => console.log(ruleToSelector(r))) + // console.log(JSON.stringify(out, null, 2)) + console.log('\n' + out.css.join('\n') + '\n') }) }) }) -- cgit v1.2.3-70-g09d2 From d4795d2e3c363065319a978fd2d9237fb62f2fe6 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 1 Feb 2024 01:27:30 +0200 Subject: moved default rules to component style.js files, added some basic text inheritance --- src/components/button.style.js | 15 ++++++ src/components/link.style.js | 1 - src/components/panel.style.js | 8 +++ src/components/panel_header.style.js | 9 ++++ src/components/text.style.js | 10 ---- src/components/underlay.style.js | 11 ++++ src/services/theme_data/pleromafe.t3.js | 38 -------------- src/services/theme_data/theme_data_3.service.js | 69 ++++++++++++++++--------- 8 files changed, 89 insertions(+), 72 deletions(-) (limited to 'src/components/button.style.js') diff --git a/src/components/button.style.js b/src/components/button.style.js index 51f781e1..49147c8d 100644 --- a/src/components/button.style.js +++ b/src/components/button.style.js @@ -15,5 +15,20 @@ export default { validInnerComponents: [ 'Text', 'Icon' + ], + defaultRules: [ + { + component: 'Button', + directives: { + background: '--fg' + } + }, + { + component: 'Button', + state: ['hover'], + directives: { + background: '#FFFFFF' + } + } ] } diff --git a/src/components/link.style.js b/src/components/link.style.js index 0128fd91..d13cef33 100644 --- a/src/components/link.style.js +++ b/src/components/link.style.js @@ -16,7 +16,6 @@ export default { component: 'Link', state: ['faint'], directives: { - textColor: '--link', textOpacity: 0.5, textOpacityMode: 'fake' } diff --git a/src/components/panel.style.js b/src/components/panel.style.js index 870f5099..a9de7e56 100644 --- a/src/components/panel.style.js +++ b/src/components/panel.style.js @@ -7,5 +7,13 @@ export default { 'Icon', 'Button', 'PanelHeader' + ], + defaultRules: [ + { + component: 'Panel', + directives: { + background: '--fg' + } + } ] } diff --git a/src/components/panel_header.style.js b/src/components/panel_header.style.js index e83e1f15..01f8a67f 100644 --- a/src/components/panel_header.style.js +++ b/src/components/panel_header.style.js @@ -6,5 +6,14 @@ export default { 'Link', 'Icon', 'Button' + ], + defaultRules: [ + { + component: 'PanelHeader', + directives: { + background: '--fg' + // opacity: 0.9 + } + } ] } diff --git a/src/components/text.style.js b/src/components/text.style.js index e52b6f68..18472032 100644 --- a/src/components/text.style.js +++ b/src/components/text.style.js @@ -19,7 +19,6 @@ export default { component: 'Text', state: ['faint'], directives: { - textColor: '--text', textOpacity: 0.5 } }, @@ -29,15 +28,6 @@ export default { directives: { textColor: '--cGreen' } - }, - { - component: 'Text', - variant: 'greentext', - state: ['faint'], - directives: { - textColor: '--cGreen', - textOpacity: 0.5 - } } ] } diff --git a/src/components/underlay.style.js b/src/components/underlay.style.js index c35fbada..380ea26e 100644 --- a/src/components/underlay.style.js +++ b/src/components/underlay.style.js @@ -4,5 +4,16 @@ export default { outOfTreeSelector: '.underlay', validInnerComponents: [ 'Panel' + ], + defaultRules: [ + { + component: 'Underlay', + // variant: 'normal', + // state: 'normal' + directives: { + background: '#000000', + opacity: 0.2 + } + } ] } diff --git a/src/services/theme_data/pleromafe.t3.js b/src/services/theme_data/pleromafe.t3.js index 9fadf0ee..db612a5b 100644 --- a/src/services/theme_data/pleromafe.t3.js +++ b/src/services/theme_data/pleromafe.t3.js @@ -1,40 +1,2 @@ export const sampleRules = [ - { - component: 'Underlay', - // variant: 'normal', - // state: 'normal' - directives: { - background: '#000000', - opacity: 0.2 - } - }, - { - component: 'Panel', - directives: { - background: '--fg' - // opacity: 0.9 - } - }, - { - component: 'PanelHeader', - directives: { - background: '--fg' - // opacity: 0.9 - } - }, - { - component: 'Button', - directives: { - background: '--fg' - // opacity: 0.8 - } - }, - { - component: 'Button', - state: ['hover'], - directives: { - background: '#FFFFFF' - // opacity: 0.9 - } - } ] diff --git a/src/services/theme_data/theme_data_3.service.js b/src/services/theme_data/theme_data_3.service.js index c9468d07..b7679021 100644 --- a/src/services/theme_data/theme_data_3.service.js +++ b/src/services/theme_data/theme_data_3.service.js @@ -1,5 +1,6 @@ -import { convert } from 'chromatism' -import { alphaBlend, getTextColor, rgba2css, mixrgb } from '../color_convert/color_convert.js' +import { convert, brightness } from 'chromatism' +import merge from 'lodash.merge' +import { alphaBlend, getTextColor, rgba2css, mixrgb, relativeLuminance } from '../color_convert/color_convert.js' import Underlay from 'src/components/underlay.style.js' import Panel from 'src/components/panel.style.js' @@ -87,27 +88,35 @@ export const init = (extraRuleset, palette) => { rulesByComponent[rule.component].push(rule) } - const findRules = (combination, parent) => rule => { + const findRules = (searchCombination, parent) => rule => { // inexact search const doesCombinationMatch = () => { - if (combination.component !== rule.component) return false - if (Object.prototype.hasOwnProperty.call(rule, 'variant')) { - if (combination.variant !== rule.variant) return false + if (searchCombination.component !== rule.component) return false + const ruleVariant = Object.prototype.hasOwnProperty.call(rule, 'variant') ? rule.variant : 'normal' + + if (ruleVariant !== 'normal') { + if (searchCombination.variant !== rule.variant) return false + } + + const ruleHasStateDefined = Object.prototype.hasOwnProperty.call(rule, 'state') + let ruleStateSet + if (ruleHasStateDefined) { + ruleStateSet = new Set(['normal', ...rule.state]) } else { - if (combination.variant !== 'normal') return false + ruleStateSet = new Set(['normal']) } - if (Object.prototype.hasOwnProperty.call(rule, 'state')) { - const ruleStatesSet = new Set(['normal', ...(rule.state || [])]) - const combinationSet = new Set(['normal', ...combination.state]) - const setsAreEqual = combination.state.every(state => ruleStatesSet.has(state)) && + if (ruleStateSet.size > 1) { + const ruleStatesSet = ruleStateSet + const combinationSet = new Set(['normal', ...searchCombination.state]) + const setsAreEqual = searchCombination.state.every(state => ruleStatesSet.has(state)) && [...ruleStatesSet].every(state => combinationSet.has(state)) return setsAreEqual } else { - if (combination.state.length !== 1 || combination.state[0] !== 'normal') return false return true } } + const combinationMatches = doesCombinationMatch() if (!parent || !combinationMatches) return combinationMatches @@ -154,21 +163,31 @@ export const init = (extraRuleset, palette) => { return filter(lowerLevelComponent) ? lowerLevelComponent : null } - const findColor = (color) => { - if (typeof color === 'string' && color.startsWith('--')) { - const name = color.substring(2) - return palette[name] + const findColor = (color, background) => { + if (typeof color !== 'string' || !color.startsWith('--')) return color + let targetColor = null + // Color references other color + const [variable, modifier] = color.split(/,/g).map(str => str.trim()) + const variableSlot = variable.substring(2) + targetColor = palette[variableSlot] + + if (modifier) { + const effectiveBackground = background ?? targetColor + const isLightOnDark = relativeLuminance(convert(effectiveBackground).rgb) < 0.5 + const mod = isLightOnDark ? 1 : -1 + targetColor = brightness(Number.parseFloat(modifier) * mod, targetColor).rgb } - return color + + return targetColor } const getTextColorAlpha = (rule, lowerRule, value) => { const opacity = rule.directives.textOpacity - const textColor = convert(findColor(value)).rgb + const backgroundColor = convert(lowerRule.cache.background).rgb + const textColor = convert(findColor(value, backgroundColor)).rgb if (opacity === null || opacity === undefined || opacity >= 1) { return convert(textColor).hex } - const backgroundColor = convert(lowerRule.cache.background).rgb if (opacity === 0) { return convert(backgroundColor).hex } @@ -217,6 +236,7 @@ export const init = (extraRuleset, palette) => { ].join('') const lowerLevel = findLowerLevelRule(parent, (r) => { + if (!r) return false if (components[r.component].validInnerComponents.indexOf(component.name) < 0) return false if (r.cache.background === undefined) return false if (r.cache.textDefined) { @@ -234,15 +254,15 @@ export const init = (extraRuleset, palette) => { if (!inheritedTextColorRule) { const generalTextColorRules = ruleset.filter(findRules({ component: component.name, ...combination }, null, true)) - inheritedTextColorRule = generalTextColorRules[generalTextColorRules.length - 1] + inheritedTextColorRule = generalTextColorRules.reduce((acc, rule) => merge(acc, rule), {}) } else { - inheritedTextColorRule = inheritedTextColorRules[inheritedTextColorRules.length - 1] + inheritedTextColorRule = inheritedTextColorRules.reduce((acc, rule) => merge(acc, rule), {}) } let inheritedTextColor let inheritedTextOpacity = {} if (inheritedTextColorRule) { - inheritedTextColor = findColor(inheritedTextColorRule.directives.textColor) + inheritedTextColor = findColor(inheritedTextColorRule.directives.textColor, convert(lowerLevel.cache.background).rgb) // also inherit opacity settings const { textOpacity, textOpacityMode } = inheritedTextColorRule.directives inheritedTextOpacity = { textOpacity, textOpacityMode } @@ -284,8 +304,11 @@ export const init = (extraRuleset, palette) => { // Global (general) rules if (existingGlobalRules.length !== 0) { + const totalRule = existingGlobalRules.reduce((acc, rule) => merge(acc, rule), {}) + const { directives } = totalRule + + // last rule is used as a cache const lastRule = existingGlobalRules[existingGlobalRules.length - 1] - const { directives } = lastRule lastRule.cache = lastRule.cache || {} if (directives.background) { -- cgit v1.2.3-70-g09d2 From c34590c43983711152a843a2e36b991a5fe3bcdf Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 7 Feb 2024 15:53:34 +0200 Subject: update, should inherit stuff properly now. --- src/App.scss | 44 +- src/components/button.style.js | 66 ++- src/components/dropdown_menu.style.js | 19 + src/components/input.style.js | 60 +++ src/components/panel.style.js | 7 +- src/components/panel_header.style.js | 1 - src/components/popover.style.js | 20 + src/components/root.style.js | 17 + src/components/top_bar.style.js | 18 + src/components/underlay.style.js | 2 +- src/panel.scss | 9 +- src/services/style_setter/style_setter.js | 4 +- src/services/theme_data/theme2_keys.js | 176 ++++++++ src/services/theme_data/theme2_to_theme3.js | 58 +++ src/services/theme_data/theme_data_3.service.js | 510 +++++++++++++++--------- top_bar.style.js | 9 + 16 files changed, 794 insertions(+), 226 deletions(-) create mode 100644 src/components/dropdown_menu.style.js create mode 100644 src/components/input.style.js create mode 100644 src/components/popover.style.js create mode 100644 src/components/root.style.js create mode 100644 src/components/top_bar.style.js create mode 100644 src/services/theme_data/theme2_keys.js create mode 100644 src/services/theme_data/theme2_to_theme3.js create mode 100644 top_bar.style.js (limited to 'src/components/button.style.js') diff --git a/src/App.scss b/src/App.scss index 8e9f3171..a8eecaf5 100644 --- a/src/App.scss +++ b/src/App.scss @@ -371,8 +371,7 @@ nav { border-radius: $fallback--btnRadius; border-radius: var(--btnRadius, $fallback--btnRadius); cursor: pointer; - box-shadow: $fallback--buttonShadow; - box-shadow: var(--buttonShadow); + box-shadow: var(--shadow); font-size: 1em; font-family: sans-serif; font-family: var(--interfaceFont, sans-serif); @@ -383,25 +382,14 @@ nav { i[class*="icon-"], .svg-inline--fa { - color: $fallback--text; - color: var(--btnText, $fallback--text); + color: var(--icon); } &::-moz-focus-inner { border: none; } - &:hover { - box-shadow: 0 0 4px rgb(255 255 255 / 30%); - box-shadow: var(--buttonHoverShadow); - } - &:active { - box-shadow: - 0 0 4px 0 rgb(255 255 255 / 30%), - 0 1px 0 0 rgb(0 0 0 / 20%) inset, - 0 -1px 0 0 rgb(255 255 255 / 20%) inset; - box-shadow: var(--buttonPressedShadow); color: $fallback--text; color: var(--btnPressedText, $fallback--text); background-color: $fallback--fg; @@ -487,7 +475,12 @@ nav { } input, -textarea, +textarea { + border: none; + display: inline-block; + outline: none; +} + .input { &.unstyled { border-radius: 0; @@ -501,15 +494,7 @@ textarea, border: none; border-radius: $fallback--inputRadius; border-radius: var(--inputRadius, $fallback--inputRadius); - box-shadow: - 0 1px 0 0 rgb(0 0 0 / 20%) inset, - 0 -1px 0 0 rgb(255 255 255 / 20%) inset, - 0 0 2px 0 rgb(0 0 0 / 100%) inset; - box-shadow: var(--inputShadow); - background-color: $fallback--fg; - background-color: var(--input, $fallback--fg); - color: $fallback--lightText; - color: var(--inputText, $fallback--lightText); + box-shadow: var(--shadow); font-family: sans-serif; font-family: var(--inputFont, sans-serif); font-size: 1em; @@ -561,11 +546,9 @@ textarea, width: 1.1em; height: 1.1em; border-radius: 100%; // Radio buttons should always be circle - box-shadow: 0 0 2px black inset; - box-shadow: var(--inputShadow); + background-color: var(--background); + box-shadow: var(--shadow); margin-right: 0.5em; - background-color: $fallback--fg; - background-color: var(--input, $fallback--fg); vertical-align: top; text-align: center; line-height: 1.1; @@ -578,8 +561,9 @@ textarea, &[type="checkbox"] { &:checked + label::before { - color: $fallback--text; - color: var(--inputText, $fallback--text); + color: var(--text); + background-color: var(--background); + box-shadow: var(--shadow); } &:disabled { diff --git a/src/components/button.style.js b/src/components/button.style.js index 49147c8d..2f74d127 100644 --- a/src/components/button.style.js +++ b/src/components/button.style.js @@ -1,11 +1,34 @@ +const border = (top, shadow) => ({ + x: 0, + y: top ? 1 : -1, + blur: 0, + spread: 0, + color: shadow ? '#000000' : '#FFFFFF', + alpha: 0.2, + inset: true +}) + +const buttonInsetFakeBorders = [border(true, false), border(false, true)] +const inputInsetFakeBorders = [border(true, true), border(false, false)] + +const hoverGlow = { + x: 0, + y: 0, + blur: 4, + spread: 0, + color: '--text', + alpha: 1 +} + export default { name: 'Button', - selector: '.btn', + selector: '.button-default', states: { disabled: ':disabled', toggled: '.toggled', pressed: ':active', - hover: ':hover' + hover: ':hover', + focused: ':focus-within' }, variants: { danger: '.danger', @@ -20,14 +43,49 @@ export default { { component: 'Button', directives: { - background: '--fg' + background: '--fg', + shadow: [{ + x: 0, + y: 0, + blur: 2, + spread: 0, + color: '#000000', + alpha: 1 + }, ...buttonInsetFakeBorders] } }, { component: 'Button', state: ['hover'], directives: { - background: '#FFFFFF' + shadow: [hoverGlow, ...buttonInsetFakeBorders] + } + }, + { + component: 'Button', + state: ['hover', 'pressed'], + directives: { + background: '--accent,-24.2', + shadow: [hoverGlow, ...inputInsetFakeBorders] + } + }, + { + component: 'Button', + state: ['disabled'], + directives: { + background: '$blend(--background, 0.25, --parent)', + shadow: [...buttonInsetFakeBorders] + } + }, + { + component: 'Text', + parent: { + component: 'Button', + state: ['disabled'] + }, + directives: { + textOpacity: 0.25, + textOpacityMode: 'blend' } } ] diff --git a/src/components/dropdown_menu.style.js b/src/components/dropdown_menu.style.js new file mode 100644 index 00000000..905984e9 --- /dev/null +++ b/src/components/dropdown_menu.style.js @@ -0,0 +1,19 @@ +export default { + name: 'DropdownMenu', + selector: '.dropdown', + validInnerComponents: [ + 'Text', + 'Icon', + 'Input' + ], + states: { + hover: ':hover' + }, + defaultRules: [ + { + directives: { + background: '--fg' + } + } + ] +} diff --git a/src/components/input.style.js b/src/components/input.style.js new file mode 100644 index 00000000..48d74bfa --- /dev/null +++ b/src/components/input.style.js @@ -0,0 +1,60 @@ +const border = (top, shadow) => ({ + x: 0, + y: top ? 1 : -1, + blur: 0, + spread: 0, + color: shadow ? '#000000' : '#FFFFFF', + alpha: 0.2, + inset: true +}) + +const inputInsetFakeBorders = [border(true, true), border(false, false)] + +const hoverGlow = { + x: 0, + y: 0, + blur: 4, + spread: 0, + color: '--text', + alpha: 1 +} + +export default { + name: 'Input', + selector: '.input', + states: { + disabled: ':disabled', + pressed: ':active', + hover: ':hover', + focused: ':focus-within' + }, + variants: { + danger: '.danger', + unstyled: '.unstyled', + sublime: '.sublime' + }, + validInnerComponents: [ + 'Text' + ], + defaultRules: [ + { + directives: { + background: '--fg', + shadow: [{ + x: 0, + y: 0, + blur: 2, + spread: 0, + color: '#000000', + alpha: 1 + }, ...inputInsetFakeBorders] + } + }, + { + state: ['hover'], + directives: { + shadow: [hoverGlow, ...inputInsetFakeBorders] + } + } + ] +} diff --git a/src/components/panel.style.js b/src/components/panel.style.js index a9de7e56..d93792e9 100644 --- a/src/components/panel.style.js +++ b/src/components/panel.style.js @@ -6,13 +6,14 @@ export default { 'Link', 'Icon', 'Button', - 'PanelHeader' + 'Input', + 'PanelHeader', + 'DropdownMenu' ], defaultRules: [ { - component: 'Panel', directives: { - background: '--fg' + background: '--bg' } } ] diff --git a/src/components/panel_header.style.js b/src/components/panel_header.style.js index 01f8a67f..e18fa799 100644 --- a/src/components/panel_header.style.js +++ b/src/components/panel_header.style.js @@ -12,7 +12,6 @@ export default { component: 'PanelHeader', directives: { background: '--fg' - // opacity: 0.9 } } ] diff --git a/src/components/popover.style.js b/src/components/popover.style.js new file mode 100644 index 00000000..415795a8 --- /dev/null +++ b/src/components/popover.style.js @@ -0,0 +1,20 @@ +export default { + name: 'Popover', + selector: '.popover', + validInnerComponents: [ + 'Text', + 'Link', + 'Icon', + 'Button', + 'Input', + 'PanelHeader', + 'DropdownMenu' + ], + defaultRules: [ + { + directives: { + background: '--fg' + } + } + ] +} diff --git a/src/components/root.style.js b/src/components/root.style.js new file mode 100644 index 00000000..b762b2ba --- /dev/null +++ b/src/components/root.style.js @@ -0,0 +1,17 @@ +export default { + name: 'Root', + selector: ':root', + validInnerComponents: [ + 'Underlay', + 'TopBar', + 'Popover' + ], + defaultRules: [ + { + directives: { + background: '--bg', + opacity: 0 + } + } + ] +} diff --git a/src/components/top_bar.style.js b/src/components/top_bar.style.js new file mode 100644 index 00000000..99e8efdc --- /dev/null +++ b/src/components/top_bar.style.js @@ -0,0 +1,18 @@ +export default { + name: 'TopBar', + selector: 'nav', + validInnerComponents: [ + 'Link', + 'Text', + 'Icon', + 'Button', + 'Input' + ], + defaultRules: [ + { + directives: { + background: '--fg' + } + } + ] +} diff --git a/src/components/underlay.style.js b/src/components/underlay.style.js index 380ea26e..48965f20 100644 --- a/src/components/underlay.style.js +++ b/src/components/underlay.style.js @@ -1,6 +1,6 @@ export default { name: 'Underlay', - selector: '#content', + selector: 'body', // Should be '#content' but for now this is better for testing until I have proper popovers and such... outOfTreeSelector: '.underlay', validInnerComponents: [ 'Panel' diff --git a/src/panel.scss b/src/panel.scss index d6816937..7d572133 100644 --- a/src/panel.scss +++ b/src/panel.scss @@ -6,6 +6,10 @@ background-color: $fallback--bg; background-color: var(--bg, $fallback--bg); + .panel-heading { + background-color: inherit; + } + &::after, & { border-radius: $fallback--panelRadius; @@ -131,12 +135,9 @@ align-items: start; // panel theme color: var(--panelText); - background-color: $fallback--bg; - background-color: var(--bg, $fallback--bg); &::after { - background-color: $fallback--fg; - background-color: var(--panel, $fallback--fg); + background-color: var(--background); z-index: -2; border-radius: $fallback--panelRadius $fallback--panelRadius 0 0; border-radius: var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius) 0 0; diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js index ba98c4d9..c09b3790 100644 --- a/src/services/style_setter/style_setter.js +++ b/src/services/style_setter/style_setter.js @@ -20,8 +20,8 @@ export const applyTheme = (input) => { styleSheet.toString() styleSheet.insertRule(`:root { ${rules.radii} }`, 'index-max') - styleSheet.insertRule(`:root { ${rules.colors} }`, 'index-max') - styleSheet.insertRule(`:root { ${rules.shadows} }`, 'index-max') + // styleSheet.insertRule(`:root { ${rules.colors} }`, 'index-max') + // styleSheet.insertRule(`:root { ${rules.shadows} }`, 'index-max') styleSheet.insertRule(`:root { ${rules.fonts} }`, 'index-max') themes3.css.forEach(rule => { console.log(rule) diff --git a/src/services/theme_data/theme2_keys.js b/src/services/theme_data/theme2_keys.js new file mode 100644 index 00000000..09092e0c --- /dev/null +++ b/src/services/theme_data/theme2_keys.js @@ -0,0 +1,176 @@ +export default [ + 'bg', + 'wallpaper', + 'fg', + 'text', + 'underlay', + 'link', + 'accent', + 'faint', + 'faintLink', + 'postFaintLink', + + 'cBlue', + 'cRed', + 'cGreen', + 'cOrange', + + 'profileBg', + 'profileTint', + + 'highlight', + 'highlightLightText', + 'highlightPostLink', + 'highlightFaintText', + 'highlightFaintLink', + 'highlightPostFaintLink', + 'highlightText', + 'highlightLink', + 'highlightIcon', + + 'popover', + 'popoverLightText', + 'popoverPostLink', + 'popoverFaintText', + 'popoverFaintLink', + 'popoverPostFaintLink', + 'popoverText', + 'popoverLink', + 'popoverIcon', + + 'selectedPost', + 'selectedPostFaintText', + 'selectedPostLightText', + 'selectedPostPostLink', + 'selectedPostFaintLink', + 'selectedPostText', + 'selectedPostLink', + 'selectedPostIcon', + + 'selectedMenu', + 'selectedMenuLightText', + 'selectedMenuFaintText', + 'selectedMenuFaintLink', + 'selectedMenuText', + 'selectedMenuLink', + 'selectedMenuIcon', + + 'selectedMenuPopover', + 'selectedMenuPopoverLightText', + 'selectedMenuPopoverFaintText', + 'selectedMenuPopoverFaintLink', + 'selectedMenuPopoverText', + 'selectedMenuPopoverLink', + 'selectedMenuPopoverIcon', + + 'lightText', + + 'postLink', + + 'postGreentext', + + 'postCyantext', + + 'border', + + 'poll', + 'pollText', + + 'icon', + + // Foreground, + 'fgText', + 'fgLink', + + // Panel header, + 'panel', + 'panelText', + 'panelFaint', + 'panelLink', + + // Top bar, + 'topBar', + 'topBarLink', + + // Tabs, + 'tab', + 'tabText', + 'tabActiveText', + + // Buttons, + 'btn', + 'btnText', + 'btnPanelText', + 'btnTopBarText', + + // Buttons: pressed, + 'btnPressed', + 'btnPressedText', + 'btnPressedPanel', + 'btnPressedPanelText', + 'btnPressedTopBar', + 'btnPressedTopBarText', + + // Buttons: toggled, + 'btnToggled', + 'btnToggledText', + 'btnToggledPanelText', + 'btnToggledTopBarText', + + // Buttons: disabled, + 'btnDisabled', + 'btnDisabledText', + 'btnDisabledPanelText', + 'btnDisabledTopBarText', + + // Input fields, + 'input', + 'inputText', + 'inputPanelText', + 'inputTopbarText', + + 'alertError', + 'alertErrorText', + 'alertErrorPanelText', + + 'alertWarning', + 'alertWarningText', + 'alertWarningPanelText', + + 'alertSuccess', + 'alertSuccessText', + 'alertSuccessPanelText', + + 'alertNeutral', + 'alertNeutralText', + 'alertNeutralPanelText', + + 'alertPopupError', + 'alertPopupErrorText', + + 'alertPopupWarning', + 'alertPopupWarningText', + + 'alertPopupSuccess', + 'alertPopupSuccessText', + + 'alertPopupNeutral', + 'alertPopupNeutralText', + + 'badgeNotification', + 'badgeNotificationText', + + 'badgeNeutral', + 'badgeNeutralText', + + 'chatBg', + + 'chatMessageIncomingBg', + 'chatMessageIncomingText', + 'chatMessageIncomingLink', + 'chatMessageIncomingBorder', + 'chatMessageOutgoingBg', + 'chatMessageOutgoingText', + 'chatMessageOutgoingLink', + 'chatMessageOutgoingBorder' +] diff --git a/src/services/theme_data/theme2_to_theme3.js b/src/services/theme_data/theme2_to_theme3.js new file mode 100644 index 00000000..5ac87beb --- /dev/null +++ b/src/services/theme_data/theme2_to_theme3.js @@ -0,0 +1,58 @@ +import allKeys from './theme2_keys' + +// keys that are meant to be used globally, i.e. what's the rest of the theme is based upon. +const basePaletteKeys = new Set([ + 'bg', + 'fg', + 'text', + 'link', + 'accent', + + 'cBlue', + 'cRed', + 'cGreen', + 'cOrange' +]) + +// Keys that are not available in editor and never meant to be edited +const hiddenKeys = new Set([ + 'profileBg', + 'profileTint' +]) + +const extendedBasePrefixes = [ + 'border', + 'icon', + 'highlight', + 'lightText', + + 'popover', + + 'panel', + 'topBar', + 'tab', + 'btn', + 'input', + 'selectedMenu', + + 'alert', + 'badge', + + 'post', + 'selectedPost', // wrong nomenclature + 'poll', + + 'chatBg', + 'chatMessageIncoming', + 'chatMessageOutgoing' +] + +const extendedBaseKeys = Object.fromEntries(extendedBasePrefixes.map(prefix => [prefix, allKeys.filter(k => k.startsWith(prefix))])) + +// Keysets that are only really used intermideately, i.e. to generate other colors +const temporary = new Set([ + 'border', + 'highlight' +]) + +const temporaryColors = {} diff --git a/src/services/theme_data/theme_data_3.service.js b/src/services/theme_data/theme_data_3.service.js index b7679021..1228efc2 100644 --- a/src/services/theme_data/theme_data_3.service.js +++ b/src/services/theme_data/theme_data_3.service.js @@ -1,24 +1,89 @@ import { convert, brightness } from 'chromatism' import merge from 'lodash.merge' -import { alphaBlend, getTextColor, rgba2css, mixrgb, relativeLuminance } from '../color_convert/color_convert.js' - +import { + alphaBlend, + getTextColor, + rgba2css, + mixrgb, + relativeLuminance +} from '../color_convert/color_convert.js' + +import Root from 'src/components/root.style.js' +import TopBar from 'src/components/top_bar.style.js' import Underlay from 'src/components/underlay.style.js' +import Popover from 'src/components/popover.style.js' +import DropdownMenu from 'src/components/dropdown_menu.style.js' import Panel from 'src/components/panel.style.js' import PanelHeader from 'src/components/panel_header.style.js' import Button from 'src/components/button.style.js' +import Input from 'src/components/input.style.js' import Text from 'src/components/text.style.js' import Link from 'src/components/link.style.js' import Icon from 'src/components/icon.style.js' -const root = Underlay +export const DEFAULT_SHADOWS = { + panel: [{ + x: 1, + y: 1, + blur: 4, + spread: 0, + color: '#000000', + alpha: 0.6 + }], + topBar: [{ + x: 0, + y: 0, + blur: 4, + spread: 0, + color: '#000000', + alpha: 0.6 + }], + popup: [{ + x: 2, + y: 2, + blur: 3, + spread: 0, + color: '#000000', + alpha: 0.5 + }], + avatar: [{ + x: 0, + y: 1, + blur: 8, + spread: 0, + color: '#000000', + alpha: 0.7 + }], + avatarStatus: [], + panelHeader: [] +} + const components = { + Root, + Text, + Link, + Icon, Underlay, + Popover, + DropdownMenu, Panel, PanelHeader, + TopBar, Button, - Text, - Link, - Icon + Input +} + +// "Unrolls" a tree structure of item: { parent: { ...item2, parent: { ...item3, parent: {...} } }} +// into an array [item2, item3] for iterating +const unroll = (item) => { + const out = [] + let currentParent = item.parent + while (currentParent) { + const { parent: newParent, ...rest } = currentParent + out.push(rest) + currentParent = newParent + } + return out } // This gives you an array of arrays of all possible unique (i.e. order-insensitive) combinations @@ -38,7 +103,9 @@ export const getAllPossibleCombinations = (array) => { return combos.reduce((acc, x) => [...acc, ...x], []) } -export const ruleToSelector = (rule, isParent) => { +// Converts rule, parents and their criteria into a CSS (or path if ignoreOutOfTreeSelector == true) selector +export const ruleToSelector = (rule, ignoreOutOfTreeSelector, isParent) => { + if (!rule && !isParent) return null const component = components[rule.component] const { states, variants, selector, outOfTreeSelector } = component @@ -51,10 +118,12 @@ export const ruleToSelector = (rule, isParent) => { } let realSelector - if (isParent) { + if (selector === ':root') { + realSelector = '' + } else if (isParent) { realSelector = selector } else { - if (outOfTreeSelector) realSelector = outOfTreeSelector + if (outOfTreeSelector && !ignoreOutOfTreeSelector) realSelector = outOfTreeSelector else realSelector = selector } @@ -67,123 +136,133 @@ export const ruleToSelector = (rule, isParent) => { .join('') if (rule.parent) { - return ruleToSelector(rule.parent, true) + ' ' + selectors + return (ruleToSelector(rule.parent, ignoreOutOfTreeSelector, true) + ' ' + selectors).trim() } - return selectors + return selectors.trim() +} + +const combinationsMatch = (criteria, subject) => { + if (criteria.component !== subject.component) return false + + // All variants inherit from normal + const subjectVariant = Object.prototype.hasOwnProperty.call(subject, 'variant') ? subject.variant : 'normal' + if (subjectVariant !== 'normal') { + if (criteria.variant !== subject.variant) return false + } + + const subjectStatesSet = new Set(['normal', ...(subject.state || [])]) + const criteriaStatesSet = new Set(['normal', ...(criteria.state || [])]) + + // Subject states > 1 essentially means state is "normal" and therefore matches + if (subjectStatesSet.size > 1) { + const setsAreEqual = + [...criteriaStatesSet].every(state => subjectStatesSet.has(state)) && + [...subjectStatesSet].every(state => criteriaStatesSet.has(state)) + + if (!setsAreEqual) return false + } + return true +} + +const findRules = criteria => subject => { + // If we searching for "general" rules - ignore "specific" ones + if (criteria.parent === null && !!subject.parent) return false + if (!combinationsMatch(criteria, subject)) return false + + if (criteria.parent !== undefined && criteria.parent !== null) { + if (!subject.parent) return true + const pathCriteria = unroll(criteria) + const pathSubject = unroll(subject) + if (pathCriteria.length < pathSubject.length) return false + + // Search: .a .b .c + // Matches: .a .b .c; .b .c; .c; .z .a .b .c + // Does not match .a .b .c .d, .a .b .e + for (let i = 0; i < pathCriteria.length; i++) { + const criteriaParent = pathCriteria[i] + const subjectParent = pathSubject[i] + if (!subjectParent) return true + if (!combinationsMatch(criteriaParent, subjectParent)) return false + } + } + return true } export const init = (extraRuleset, palette) => { - const rootName = root.name + const cache = {} + const computed = {} + const rules = [] - const rulesByComponent = {} const ruleset = [ - ...Object.values(components).map(c => c.defaultRules || []).reduce((acc, arr) => [...acc, ...arr], []), + ...Object.values(components).map(c => c.defaultRules.map(r => ({ component: c.name, ...r })) || []).reduce((acc, arr) => [...acc, ...arr], []), ...extraRuleset ] + const virtualComponents = new Set(Object.values(components).filter(c => c.virtual).map(c => c.name)) + const addRule = (rule) => { rules.push(rule) - rulesByComponent[rule.component] = rulesByComponent[rule.component] || [] - rulesByComponent[rule.component].push(rule) } - const findRules = (searchCombination, parent) => rule => { - // inexact search - const doesCombinationMatch = () => { - if (searchCombination.component !== rule.component) return false - const ruleVariant = Object.prototype.hasOwnProperty.call(rule, 'variant') ? rule.variant : 'normal' - - if (ruleVariant !== 'normal') { - if (searchCombination.variant !== rule.variant) return false - } - - const ruleHasStateDefined = Object.prototype.hasOwnProperty.call(rule, 'state') - let ruleStateSet - if (ruleHasStateDefined) { - ruleStateSet = new Set(['normal', ...rule.state]) + const findColor = (color, inheritedBackground, lowerLevelBackground) => { + if (typeof color !== 'string' || (!color.startsWith('--') && !color.startsWith('$'))) return color + let targetColor = null + if (color.startsWith('--')) { + const [variable, modifier] = color.split(/,/g).map(str => str.trim()) + const variableSlot = variable.substring(2) + if (variableSlot.startsWith('parent')) { + // TODO support more than just background? + if (variableSlot === 'parent') { + targetColor = lowerLevelBackground + } } else { - ruleStateSet = new Set(['normal']) + switch (variableSlot) { + case 'background': + targetColor = inheritedBackground + break + default: + targetColor = palette[variableSlot] + } } - if (ruleStateSet.size > 1) { - const ruleStatesSet = ruleStateSet - const combinationSet = new Set(['normal', ...searchCombination.state]) - const setsAreEqual = searchCombination.state.every(state => ruleStatesSet.has(state)) && - [...ruleStatesSet].every(state => combinationSet.has(state)) - return setsAreEqual - } else { - return true + if (modifier) { + const effectiveBackground = lowerLevelBackground ?? targetColor + const isLightOnDark = relativeLuminance(convert(effectiveBackground).rgb) < 0.5 + const mod = isLightOnDark ? 1 : -1 + targetColor = brightness(Number.parseFloat(modifier) * mod, targetColor).rgb } } - - const combinationMatches = doesCombinationMatch() - if (!parent || !combinationMatches) return combinationMatches - - // exact search - - // unroll parents into array - const unroll = (item) => { - const out = [] - let currentParent = item.parent - while (currentParent) { - const { parent: newParent, ...rest } = currentParent - out.push(rest) - currentParent = newParent + if (color.startsWith('$')) { + try { + const { funcName, argsString } = /\$(?\w+)\((?[a-zA-Z0-9-,.'"\s]*)\)/.exec(color).groups + const args = argsString.split(/,/g).map(a => a.trim()) + switch (funcName) { + case 'blend': { + if (args.length !== 3) { + throw new Error(`$blend requires 3 arguments, ${args.length} were provided`) + } + const backgroundArg = findColor(args[2], inheritedBackground, lowerLevelBackground) + const foregroundArg = findColor(args[0], inheritedBackground, lowerLevelBackground) + const amount = Number(args[1]) + targetColor = alphaBlend(backgroundArg, amount, foregroundArg) + break + } + } + } catch (e) { + console.error('Failure executing color function', e) + targetColor = '#FF00FF' } - return out - } - const { parent: _, ...rest } = parent - const pathSearch = [rest, ...unroll(parent)] - const pathRule = unroll(rule) - if (pathSearch.length !== pathRule.length) return false - const pathsMatch = pathSearch.every((searchRule, i) => { - const existingRule = pathRule[i] - if (existingRule.component !== searchRule.component) return false - if (existingRule.variant !== searchRule.variant) return false - const existingRuleStatesSet = new Set(['normal', ...(existingRule.state || [])]) - const searchStatesSet = new Set(['normal', ...(searchRule.state || [])]) - const setsAreEqual = existingRule.state.every(state => searchStatesSet.has(state)) && - [...searchStatesSet].every(state => existingRuleStatesSet.has(state)) - return setsAreEqual - }) - return pathsMatch - } - - const findLowerLevelRule = (parent, filter = () => true) => { - let lowerLevelComponent = null - let currentParent = parent - while (currentParent) { - const rulesParent = ruleset.filter(findRules(currentParent)) - rulesParent > 1 && console.warn('OOPS') - lowerLevelComponent = rulesParent[rulesParent.length - 1] - currentParent = currentParent.parent - if (lowerLevelComponent && filter(lowerLevelComponent)) currentParent = null } - return filter(lowerLevelComponent) ? lowerLevelComponent : null - } - - const findColor = (color, background) => { - if (typeof color !== 'string' || !color.startsWith('--')) return color - let targetColor = null // Color references other color - const [variable, modifier] = color.split(/,/g).map(str => str.trim()) - const variableSlot = variable.substring(2) - targetColor = palette[variableSlot] - - if (modifier) { - const effectiveBackground = background ?? targetColor - const isLightOnDark = relativeLuminance(convert(effectiveBackground).rgb) < 0.5 - const mod = isLightOnDark ? 1 : -1 - targetColor = brightness(Number.parseFloat(modifier) * mod, targetColor).rgb - } - return targetColor } - const getTextColorAlpha = (rule, lowerRule, value) => { + const cssColorString = (color, alpha) => rgba2css({ ...convert(color).rgb, a: alpha }) + + const getTextColorAlpha = (rule, lowerColor, value) => { const opacity = rule.directives.textOpacity - const backgroundColor = convert(lowerRule.cache.background).rgb + const backgroundColor = convert(lowerColor).rgb const textColor = convert(findColor(value, backgroundColor)).rgb if (opacity === null || opacity === undefined || opacity >= 1) { return convert(textColor).hex @@ -202,6 +281,44 @@ export const init = (extraRuleset, palette) => { } } + const getCssShadow = (input, usesDropShadow) => { + if (input.length === 0) { + return 'none' + } + + return input + .filter(_ => usesDropShadow ? _.inset : _) + .map((shad) => [ + shad.x, + shad.y, + shad.blur, + shad.spread + ].map(_ => _ + 'px ').concat([ + cssColorString(findColor(shad.color), shad.alpha), + shad.inset ? 'inset' : '' + ]).join(' ')).join(', ') + } + + const getCssShadowFilter = (input) => { + if (input.length === 0) { + return 'none' + } + + return input + // drop-shadow doesn't support inset or spread + .filter((shad) => !shad.inset && Number(shad.spread) === 0) + .map((shad) => [ + shad.x, + shad.y, + // drop-shadow's blur is twice as strong compared to box-shadow + shad.blur / 2 + ].map(_ => _ + 'px').concat([ + cssColorString(findColor(shad.color), shad.alpha) + ]).join(' ')) + .map(_ => `drop-shadow(${_})`) + .join(' ') + } + const processInnerComponent = (component, parent) => { const { validInnerComponents = [], @@ -210,6 +327,7 @@ export const init = (extraRuleset, palette) => { name } = component + // Normalizing states and variants to always include "normal" const states = { normal: '', ...originalStates } const variants = { normal: '', ...originalVariants } const innerComponents = validInnerComponents.map(name => components[name]) @@ -219,13 +337,24 @@ export const init = (extraRuleset, palette) => { return stateCombinations.map(state => ({ variant, state })) }).reduce((acc, x) => [...acc, ...x], []) - const VIRTUAL_COMPONENTS = new Set(['Text', 'Link', 'Icon']) - stateVariantCombination.forEach(combination => { - let needRuleAdd = false + const soloSelector = ruleToSelector({ component: component.name, ...combination }, true) + const selector = ruleToSelector({ component: component.name, ...combination, parent }, true) + + // Inheriting all of the applicable rules + const existingRules = ruleset.filter(findRules({ component: component.name, ...combination, parent })) + const { directives: computedDirectives } = existingRules.reduce((acc, rule) => merge(acc, rule), {}) + const computedRule = { + component: component.name, + ...combination, + parent, + directives: computedDirectives + } - if (VIRTUAL_COMPONENTS.has(component.name)) { - const selector = component.name + ruleToSelector({ component: component.name, ...combination }) + computed[selector] = computed[selector] || {} + computed[selector].computedRule = computedRule + + if (virtualComponents.has(component.name)) { const virtualName = [ '--', component.name.toLowerCase(), @@ -235,52 +364,46 @@ export const init = (extraRuleset, palette) => { ...combination.state.filter(x => x !== 'normal').toSorted().map(state => state[0].toUpperCase() + state.slice(1).toLowerCase()) ].join('') - const lowerLevel = findLowerLevelRule(parent, (r) => { - if (!r) return false - if (components[r.component].validInnerComponents.indexOf(component.name) < 0) return false - if (r.cache.background === undefined) return false - if (r.cache.textDefined) { - return !r.cache.textDefined[selector] - } - return true - }) - - if (!lowerLevel) return + let inheritedTextColor = computedDirectives.textColor + let inheritedTextOpacity = computedDirectives.textOpacity + let inheritedTextOpacityMode = computedDirectives.textOpacityMode + const lowerLevelTextSelector = [...selector.split(/ /g).slice(0, -1), soloSelector].join(' ') + const lowerLevelTextRule = computed[lowerLevelTextSelector] - let inheritedTextColorRule - const inheritedTextColorRules = findLowerLevelRule(parent, (r) => { - return r.cache?.textDefined?.[selector] - }) - - if (!inheritedTextColorRule) { - const generalTextColorRules = ruleset.filter(findRules({ component: component.name, ...combination }, null, true)) - inheritedTextColorRule = generalTextColorRules.reduce((acc, rule) => merge(acc, rule), {}) - } else { - inheritedTextColorRule = inheritedTextColorRules.reduce((acc, rule) => merge(acc, rule), {}) + if (inheritedTextColor == null || inheritedTextOpacity == null || inheritedTextOpacityMode == null) { + inheritedTextColor = computedDirectives.textColor ?? lowerLevelTextRule.textColor + inheritedTextOpacity = computedDirectives.textOpacity ?? lowerLevelTextRule.textOpacity + inheritedTextOpacityMode = computedDirectives.textOpacityMode ?? lowerLevelTextRule.textOpacityMode } - let inheritedTextColor - let inheritedTextOpacity = {} - if (inheritedTextColorRule) { - inheritedTextColor = findColor(inheritedTextColorRule.directives.textColor, convert(lowerLevel.cache.background).rgb) - // also inherit opacity settings - const { textOpacity, textOpacityMode } = inheritedTextColorRule.directives - inheritedTextOpacity = { textOpacity, textOpacityMode } - } else { - // Emergency fallback - inheritedTextColor = '#000000' + const newTextRule = { + ...computedRule, + directives: { + ...computedRule.directives, + textColor: inheritedTextColor, + textOpacity: inheritedTextOpacity, + textOpacityMode: inheritedTextOpacityMode + } } + const lowerLevelSelector = selector.split(/ /g).slice(0, -1).join(' ') + const lowerLevelBackground = cache[lowerLevelSelector].background + const textColor = getTextColor( - convert(lowerLevel.cache.background).rgb, - convert(inheritedTextColor).rgb, - component.name === 'Link' // make it configurable? + convert(lowerLevelBackground).rgb, + // TODO properly provide "parent" text color? + convert(findColor(inheritedTextColor, null, lowerLevelBackground)).rgb, + true // component.name === 'Link' || combination.variant === 'greentext' // make it configurable? ) - lowerLevel.cache.textDefined = lowerLevel.cache.textDefined || {} - lowerLevel.cache.textDefined[selector] = textColor - lowerLevel.virtualDirectives = lowerLevel.virtualDirectives || {} - lowerLevel.virtualDirectives[virtualName] = getTextColorAlpha(inheritedTextColorRule, lowerLevel, textColor) + // Storing color data in lower layer to use as custom css properties + cache[lowerLevelSelector].textDefined = cache[lowerLevelSelector].textDefined || {} + cache[lowerLevelSelector].textDefined[selector] = textColor + + const virtualDirectives = {} + virtualDirectives[virtualName] = getTextColorAlpha(newTextRule, lowerLevelBackground, textColor) + + // lastRule.computed = lastRule.computed || {} const directives = { textColor, @@ -288,45 +411,54 @@ export const init = (extraRuleset, palette) => { } // Debug: lets you see what it think background color should be - directives.background = convert(lowerLevel.cache.background).hex + // directives.background = convert(cache[lowerLevelSelector].background).hex addRule({ parent, virtual: true, component: component.name, ...combination, - cache: { background: lowerLevel.cache.background }, - directives + directives, + virtualDirectives }) } else { - const existingGlobalRules = ruleset.filter(findRules({ component: component.name, ...combination }, null)) - const existingRules = ruleset.filter(findRules({ component: component.name, ...combination }, parent)) + cache[selector] = cache[selector] || {} + computed[selector] = computed[selector] || {} + + if (computedDirectives.background) { + let inheritRule = null + const variantRules = ruleset.filter(findRules({ component: component.name, variant: combination.variant, parent })) + const lastVariantRule = variantRules[variantRules.length - 1] + if (lastVariantRule) { + inheritRule = lastVariantRule + } else { + const normalRules = ruleset.filter(findRules({ component: component.name, parent })) + const lastNormalRule = normalRules[normalRules.length - 1] + inheritRule = lastNormalRule + } - // Global (general) rules - if (existingGlobalRules.length !== 0) { - const totalRule = existingGlobalRules.reduce((acc, rule) => merge(acc, rule), {}) - const { directives } = totalRule + const inheritSelector = ruleToSelector({ ...inheritRule, parent }, true) + const inheritedBackground = cache[inheritSelector].background + const lowerLevelSelector = selector.split(/ /g).slice(0, -1).join(' ') - // last rule is used as a cache - const lastRule = existingGlobalRules[existingGlobalRules.length - 1] - lastRule.cache = lastRule.cache || {} + // TODO: DEFAULT TEXT COLOR + const bg = cache[lowerLevelSelector]?.background || convert('#FFFFFF').rgb - if (directives.background) { - const rgb = convert(findColor(directives.background)).rgb + console.log('SELECTOR', lowerLevelSelector) - // TODO: DEFAULT TEXT COLOR - const bg = findLowerLevelRule(parent)?.cache.background || convert('#FFFFFF').rgb + const rgb = convert(findColor(computedDirectives.background, inheritedBackground, cache[lowerLevelSelector].background)).rgb - if (!lastRule.cache.background) { - const blend = directives.opacity < 1 ? alphaBlend(rgb, directives.opacity, bg) : rgb - lastRule.cache.background = blend + if (!cache[selector].background) { + const blend = computedDirectives.opacity < 1 ? alphaBlend(rgb, computedDirectives.opacity, bg) : rgb + cache[selector].background = blend + computed[selector].background = rgb - needRuleAdd = true - } - } - - if (needRuleAdd) { - addRule(lastRule) + addRule({ + component: component.name, + ...combination, + parent, + directives: computedDirectives + }) } } @@ -338,12 +470,12 @@ export const init = (extraRuleset, palette) => { }) } - processInnerComponent(components[rootName]) + processInnerComponent(components.Root, { component: 'Root' }) return { raw: rules, css: rules.map(rule => { - if (rule.virtual) return '' + // if (rule.virtual) return '' let selector = ruleToSelector(rule).replace(/\/\*.*\*\//g, '') if (!selector) { @@ -356,22 +488,38 @@ export const init = (extraRuleset, palette) => { return ' ' + k + ': ' + v }).join(';\n') - const directives = Object.entries(rule.directives).map(([k, v]) => { - switch (k) { - case 'background': { - return 'background-color: ' + rgba2css({ ...convert(findColor(v)).rgb, a: rule.directives.opacity ?? 1 }) - } - case 'textColor': { - return 'color: ' + v + let directives + if (rule.component !== 'Root') { + directives = Object.entries(rule.directives).map(([k, v]) => { + switch (k) { + case 'shadow': { + return ' ' + [ + '--shadow: ' + getCssShadow(v), + '--shadowFilter: ' + getCssShadowFilter(v), + '--shadowInset: ' + getCssShadow(v, true) + ].join(';\n ') + } + case 'background': { + const color = cssColorString(computed[ruleToSelector(rule, true)].background, rule.directives.opacity) + return [ + 'background-color: ' + color, + ' --background: ' + color + ].join(';\n') + } + case 'textColor': { + return 'color: ' + v + } + default: return '' } - default: return '' - } - }).filter(x => x).map(x => ' ' + x).join(';\n') + }).filter(x => x).map(x => ' ' + x).join(';\n') + } else { + directives = {} + } return [ header, directives + ';', - ' color: var(--text);', + !rule.virtual ? ' color: var(--text);' : '', '', virtualDirectives, footer diff --git a/top_bar.style.js b/top_bar.style.js new file mode 100644 index 00000000..91b2e72b --- /dev/null +++ b/top_bar.style.js @@ -0,0 +1,9 @@ +export default { + name: 'TopBar', + selector: 'nav', + validInnerComponents: [ + 'Link', + 'Text', + 'Icon' + ] +} -- cgit v1.2.3-70-g09d2 From 6df28cde9d8ded05417845f507a353a87269afa9 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 8 Feb 2024 18:18:01 +0200 Subject: improvements & performance testing --- src/components/button.style.js | 14 ++++- src/components/input.style.js | 7 +-- src/components/underlay.style.js | 2 - src/services/style_setter/style_setter.js | 6 +- src/services/theme_data/theme_data_3.service.js | 81 ++++++++++++++++--------- 5 files changed, 71 insertions(+), 39 deletions(-) (limited to 'src/components/button.style.js') diff --git a/src/components/button.style.js b/src/components/button.style.js index 2f74d127..f24e8ccc 100644 --- a/src/components/button.style.js +++ b/src/components/button.style.js @@ -22,7 +22,7 @@ const hoverGlow = { export default { name: 'Button', - selector: '.button-default', + selector: '.button', states: { disabled: ':disabled', toggled: '.toggled', @@ -31,9 +31,9 @@ export default { focused: ':focus-within' }, variants: { + normal: '-default', danger: '.danger', - unstyled: '.unstyled', - sublime: '.sublime' + unstyled: '-unstyled' }, validInnerComponents: [ 'Text', @@ -54,6 +54,14 @@ export default { }, ...buttonInsetFakeBorders] } }, + { + component: 'Button', + variant: 'unstyled', + directives: { + background: '--fg', + opacity: 0 + } + }, { component: 'Button', state: ['hover'], diff --git a/src/components/input.style.js b/src/components/input.style.js index 48d74bfa..4d0690cf 100644 --- a/src/components/input.style.js +++ b/src/components/input.style.js @@ -28,11 +28,8 @@ export default { hover: ':hover', focused: ':focus-within' }, - variants: { - danger: '.danger', - unstyled: '.unstyled', - sublime: '.sublime' - }, + // variants: { + // }, validInnerComponents: [ 'Text' ], diff --git a/src/components/underlay.style.js b/src/components/underlay.style.js index 48965f20..f879c530 100644 --- a/src/components/underlay.style.js +++ b/src/components/underlay.style.js @@ -8,8 +8,6 @@ export default { defaultRules: [ { component: 'Underlay', - // variant: 'normal', - // state: 'normal' directives: { background: '#000000', opacity: 0.2 diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js index c09b3790..7429f323 100644 --- a/src/services/style_setter/style_setter.js +++ b/src/services/style_setter/style_setter.js @@ -8,8 +8,13 @@ import { import { defaultState } from '../../modules/config.js' export const applyTheme = (input) => { + const t0 = performance.now() const { rules, t3b } = generatePreset(input) + const t1 = performance.now() const themes3 = init(sampleRules, t3b) + const t2 = performance.now() + console.log('Themes 2 initialization took ' + (t1 - t0) + 'ms') + console.log('Themes 3 initialization took ' + (t2 - t1) + 'ms') const head = document.head const body = document.body body.classList.add('hidden') @@ -24,7 +29,6 @@ export const applyTheme = (input) => { // styleSheet.insertRule(`:root { ${rules.shadows} }`, 'index-max') styleSheet.insertRule(`:root { ${rules.fonts} }`, 'index-max') themes3.css.forEach(rule => { - console.log(rule) styleSheet.insertRule(rule, 'index-max') }) body.classList.remove('hidden') diff --git a/src/services/theme_data/theme_data_3.service.js b/src/services/theme_data/theme_data_3.service.js index c5695d67..84e5cf82 100644 --- a/src/services/theme_data/theme_data_3.service.js +++ b/src/services/theme_data/theme_data_3.service.js @@ -77,7 +77,7 @@ const components = { // into an array [item2, item3] for iterating const unroll = (item) => { const out = [] - let currentParent = item.parent + let currentParent = item while (currentParent) { const { parent: newParent, ...rest } = currentParent out.push(rest) @@ -115,6 +115,8 @@ export const ruleToSelector = (rule, ignoreOutOfTreeSelector, isParent) => { let applicableVariant = '' if (applicableVariantName !== 'normal') { applicableVariant = variants[applicableVariantName] + } else { + applicableVariant = variants?.normal ?? '' } let realSelector @@ -130,7 +132,7 @@ export const ruleToSelector = (rule, ignoreOutOfTreeSelector, isParent) => { const selectors = [realSelector, applicableVariant, ...applicableStates] .toSorted((a, b) => { if (a.startsWith(':')) return 1 - if (!a.startsWith('.')) return -1 + if (/^[a-z]/.exec(a)) return -1 else return 0 }) .join('') @@ -189,7 +191,7 @@ const findRules = (criteria, strict) => subject => { } export const init = (extraRuleset, palette) => { - const cache = {} + const stacked = {} const computed = {} const rules = [] @@ -213,19 +215,20 @@ export const init = (extraRuleset, palette) => { const variableSlot = variable.substring(2) if (variableSlot.startsWith('parent')) { if (variableSlot === 'parent') { - targetColor = dynamicVars.lowerLevelBackground + const { r, g, b } = dynamicVars.lowerLevelBackground + targetColor = { r, g, b } } else { const virtualSlot = variableSlot.replace(/^parent/, '') - targetColor = dynamicVars.lowerLevelVirtualDirectives[virtualSlot] + targetColor = convert(dynamicVars.lowerLevelVirtualDirectivesRaw[virtualSlot]).rgb } } else { - // TODO add support for --current prefix + // TODO add support for --current prefix switch (variableSlot) { case 'background': - targetColor = dynamicVars.inheritedBackground + targetColor = convert(dynamicVars.inheritedBackground).rgb break default: - targetColor = palette[variableSlot] + targetColor = convert(palette[variableSlot]).rgb } } @@ -249,7 +252,6 @@ export const init = (extraRuleset, palette) => { const backgroundArg = convert(findColor(args[2], dynamicVars)).rgb const foregroundArg = convert(findColor(args[0], dynamicVars)).rgb const amount = Number(args[1]) - console.log('ASS', backgroundArg, foregroundArg, amount) targetColor = alphaBlend(backgroundArg, amount, foregroundArg) break } @@ -325,6 +327,12 @@ export const init = (extraRuleset, palette) => { } const processInnerComponent = (component, parent) => { + const parentList = parent ? unroll(parent).reverse().map(c => c.component) : [] + if (!component.virtual) { + const path = [...parentList, component.name].join(' > ') + console.log('Component ' + path + ' process starting') + } + const t0 = performance.now() const { validInnerComponents = [], states: originalStates = {}, @@ -338,6 +346,7 @@ export const init = (extraRuleset, palette) => { const innerComponents = validInnerComponents.map(name => components[name]) const stateCombinations = getAllPossibleCombinations(Object.keys(states)) + const stateVariantCombination = Object.keys(variants).map(variant => { return stateCombinations.map(state => ({ variant, state })) }).reduce((acc, x) => [...acc, ...x], []) @@ -347,13 +356,14 @@ export const init = (extraRuleset, palette) => { const selector = ruleToSelector({ component: component.name, ...combination, parent }, true) const lowerLevelSelector = selector.split(/ /g).slice(0, -1).join(' ') - const lowerLevelBackground = cache[lowerLevelSelector]?.background - const lowerLevelVirtualDirectives = cache[lowerLevelSelector]?.virtualDirectives - // console.log('ASS', lowerLevelVirtualDirectives) + const lowerLevelBackground = computed[lowerLevelSelector]?.background + const lowerLevelVirtualDirectives = computed[lowerLevelSelector]?.virtualDirectives + const lowerLevelVirtualDirectivesRaw = computed[lowerLevelSelector]?.virtualDirectivesRaw const dynamicVars = { lowerLevelBackground, - lowerLevelVirtualDirectives + lowerLevelVirtualDirectives, + lowerLevelVirtualDirectivesRaw } // Inheriting all of the applicable rules @@ -411,7 +421,7 @@ export const init = (extraRuleset, palette) => { const textColor = newTextRule.directives.textAuto === 'no-auto' ? intendedTextColor : getTextColor( - convert(lowerLevelBackground).rgb, + convert(stacked[lowerLevelSelector]).rgb, intendedTextColor, newTextRule.directives.textAuto === 'preserve' ) @@ -421,17 +431,21 @@ export const init = (extraRuleset, palette) => { const earlyLowerLevelRule = earlyLowerLevelRules.slice(-1)[0] const virtualDirectives = earlyLowerLevelRule.virtualDirectives || {} + const virtualDirectivesRaw = earlyLowerLevelRule.virtualDirectivesRaw || {} // Storing color data in lower layer to use as custom css properties virtualDirectives[virtualName] = getTextColorAlpha(newTextRule.directives, textColor, dynamicVars) + virtualDirectivesRaw[virtualName] = textColor earlyLowerLevelRule.virtualDirectives = virtualDirectives - cache[lowerLevelSelector].virtualDirectives = virtualDirectives + earlyLowerLevelRule.virtualDirectivesRaw = virtualDirectivesRaw + computed[lowerLevelSelector].virtualDirectives = virtualDirectives + computed[lowerLevelSelector].virtualDirectivesRaw = virtualDirectivesRaw // Debug: lets you see what it think background color should be const directives = { textColor, - background: convert(cache[lowerLevelSelector].background).hex, + background: convert(computed[lowerLevelSelector].background).hex, ...inheritedTextOpacity } @@ -441,10 +455,10 @@ export const init = (extraRuleset, palette) => { component: component.name, ...combination, directives, - virtualDirectives + virtualDirectives, + virtualDirectivesRaw }) } else { - cache[selector] = cache[selector] || {} computed[selector] = computed[selector] || {} if (computedDirectives.background) { @@ -460,7 +474,7 @@ export const init = (extraRuleset, palette) => { } const inheritSelector = ruleToSelector({ ...inheritRule, parent }, true) - const inheritedBackground = cache[inheritSelector].background + const inheritedBackground = computed[inheritSelector].background // TODO: DEFAULT TEXT COLOR const lowerLevelComputedBackground = computed[lowerLevelSelector]?.background || convert('#FFFFFF').rgb @@ -469,10 +483,18 @@ export const init = (extraRuleset, palette) => { const rgb = convert(findColor(computedDirectives.background, dynamicVars)).rgb - if (!cache[selector].background) { - const blend = computedDirectives.opacity < 1 ? alphaBlend(rgb, computedDirectives.opacity, lowerLevelComputedBackground) : rgb - cache[selector].background = blend - computed[selector].background = rgb + if (!stacked[selector]) { + let blend + const alpha = computedDirectives.opacity + if (alpha >= 1) { + blend = rgb + } else if (alpha <= 0) { + blend = lowerLevelComputedBackground + } else { + blend = alphaBlend(rgb, computedDirectives.opacity, lowerLevelComputedBackground) + } + stacked[selector] = blend + computed[selector].background = { ...rgb, a: computedDirectives.opacity ?? 1 } addRule({ component: component.name, @@ -482,16 +504,19 @@ export const init = (extraRuleset, palette) => { }) } } - - if (existingRules.length !== 0) { - console.warn('MORE EXISTING RULES', existingRules) - } } + innerComponents.forEach(innerComponent => processInnerComponent(innerComponent, { parent, component: name, ...combination })) }) + + const t1 = performance.now() + if (!component.virtual) { + const path = [...parentList, component.name].join(' > ') + console.log('Component ' + path + ' procession time: ' + (t1 - t0) + 'ms') + } } - processInnerComponent(components.Root, { component: 'Root' }) + processInnerComponent(components.Root) return { raw: rules, -- cgit v1.2.3-70-g09d2 From 9ec61d0f0a26369196d9e9b4ac62f4a5b3c38ce1 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Fri, 9 Feb 2024 19:37:22 +0200 Subject: comments and cleanup --- src/components/button.style.js | 9 ++++++--- src/services/theme_data/theme_data_3.service.js | 19 +++++++++++-------- 2 files changed, 17 insertions(+), 11 deletions(-) (limited to 'src/components/button.style.js') diff --git a/src/components/button.style.js b/src/components/button.style.js index f24e8ccc..c86fee64 100644 --- a/src/components/button.style.js +++ b/src/components/button.style.js @@ -21,17 +21,20 @@ const hoverGlow = { } export default { - name: 'Button', - selector: '.button', + name: 'Button', // Name of the component + selector: '.button', // CSS selector/prefix + // States, system witll calculate ALL possible combinations of those and append a "normal" to them + standalone "normal" state states: { + // normal: state is implicitly added disabled: ':disabled', toggled: '.toggled', pressed: ':active', hover: ':hover', focused: ':focus-within' }, + // Variants are mutually exclusive, which saves on computation time variants: { - normal: '-default', + normal: '-default', // you can override normal variant danger: '.danger', unstyled: '-unstyled' }, diff --git a/src/services/theme_data/theme_data_3.service.js b/src/services/theme_data/theme_data_3.service.js index 9bb1db94..7cbd95a4 100644 --- a/src/services/theme_data/theme_data_3.service.js +++ b/src/services/theme_data/theme_data_3.service.js @@ -20,6 +20,8 @@ import Text from 'src/components/text.style.js' import Link from 'src/components/link.style.js' import Icon from 'src/components/icon.style.js' +const DEBUG = false + export const DEFAULT_SHADOWS = { panel: [{ x: 1, @@ -150,11 +152,11 @@ const combinationsMatch = (criteria, subject, strict) => { if (criteria.variant !== subject.variant) return false } - const subjectStatesSet = new Set(subject.state) - const criteriaStatesSet = new Set(criteria.state) - // Subject states > 1 essentially means state is "normal" and therefore matches - if (subjectStatesSet.size > 1 || strict) { + if (subject.state.length > 1 || strict) { + const subjectStatesSet = new Set(subject.state) + const criteriaStatesSet = new Set(criteria.state) + const setsAreEqual = [...criteriaStatesSet].every(state => subjectStatesSet.has(state)) && [...subjectStatesSet].every(state => criteriaStatesSet.has(state)) @@ -448,6 +450,7 @@ export const init = (extraRuleset, palette) => { computed[lowerLevelSelector].virtualDirectivesRaw = virtualDirectivesRaw // Debug: lets you see what it think background color should be + if (!DEBUG) return const directives = { textColor, @@ -456,9 +459,10 @@ export const init = (extraRuleset, palette) => { } addRule({ - parent, + selector, virtual: true, component: component.name, + parent, ...combination, directives, virtualDirectives, @@ -503,6 +507,7 @@ export const init = (extraRuleset, palette) => { computed[selector].background = { ...rgb, a: computedDirectives.opacity ?? 1 } addRule({ + selector, component: component.name, ...combination, parent, @@ -531,9 +536,7 @@ export const init = (extraRuleset, palette) => { return { raw: rules, css: rules.map(rule => { - if (rule.virtual) return '' - - let selector = ruleToSelector(rule).replace(/\/\*.*\*\//g, '') + let selector = rule.selector if (!selector) { selector = 'body' } -- cgit v1.2.3-70-g09d2 From 98f972e557047a626880692189fdae68269a732e Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sun, 11 Feb 2024 23:11:28 +0200 Subject: menu-item improvements --- src/App.scss | 84 +---------------- src/components/border.style.js | 13 +++ src/components/button.style.js | 27 +++--- src/components/dropdown_menu.style.js | 19 ---- src/components/menu_item.style.js | 45 +++++++++ src/components/modals.style.js | 8 ++ src/components/nav_panel/nav_panel.vue | 6 +- src/components/navigation/navigation_entry.vue | 36 -------- src/components/panel.style.js | 13 ++- src/components/panel_header.style.js | 3 +- src/components/popover.style.js | 16 +++- src/components/root.style.js | 4 +- src/components/underlay.style.js | 5 +- src/panel.scss | 49 +--------- src/services/theme_data/theme_data_3.service.js | 118 ++++++++++++++---------- top_bar.style.js | 14 +++ 16 files changed, 209 insertions(+), 251 deletions(-) create mode 100644 src/components/border.style.js delete mode 100644 src/components/dropdown_menu.style.js create mode 100644 src/components/menu_item.style.js create mode 100644 src/components/modals.style.js (limited to 'src/components/button.style.js') diff --git a/src/App.scss b/src/App.scss index a8eecaf5..1f9bab40 100644 --- a/src/App.scss +++ b/src/App.scss @@ -129,15 +129,6 @@ i[class*="icon-"], color: var(--icon); } -.button-unstyled:hover, -a:hover { - > i[class*="icon-"], - > .svg-inline--fa, - > .iconLetter { - color: var(--text); - } -} - nav { z-index: var(--ZI_navbar); background-color: $fallback--fg; @@ -192,8 +183,7 @@ nav { grid-column: 1 / span 3; grid-row: 1 / 1; pointer-events: none; - background-color: rgb(0 0 0 / 15%); - background-color: var(--underlay, rgb(0 0 0 / 15%)); + background-color: var(--underlay); z-index: -1000; } @@ -363,10 +353,7 @@ nav { .button-default { user-select: none; - color: $fallback--text; - color: var(--btnText, $fallback--text); - background-color: $fallback--fg; - background-color: var(--btn, $fallback--fg); + color: var(--text); border: none; border-radius: $fallback--btnRadius; border-radius: var(--btnRadius, $fallback--btnRadius); @@ -376,70 +363,12 @@ nav { font-family: sans-serif; font-family: var(--interfaceFont, sans-serif); - &.-sublime { - background: transparent; - } - - i[class*="icon-"], - .svg-inline--fa { - color: var(--icon); - } - &::-moz-focus-inner { border: none; } - &:active { - color: $fallback--text; - color: var(--btnPressedText, $fallback--text); - background-color: $fallback--fg; - background-color: var(--btnPressed, $fallback--fg); - - svg, - i { - color: $fallback--text; - color: var(--btnPressedText, $fallback--text); - } - } - &:disabled { cursor: not-allowed; - color: $fallback--text; - color: var(--btnDisabledText, $fallback--text); - background-color: $fallback--fg; - background-color: var(--btnDisabled, $fallback--fg); - - svg, - i { - color: $fallback--text; - color: var(--btnDisabledText, $fallback--text); - } - } - - &.toggled { - color: $fallback--text; - color: var(--btnToggledText, $fallback--text); - background-color: $fallback--fg; - background-color: var(--btnToggled, $fallback--fg); - box-shadow: - 0 0 4px 0 rgb(255 255 255 / 30%), - 0 1px 0 0 rgb(0 0 0 / 20%) inset, - 0 -1px 0 0 rgb(255 255 255 / 20%) inset; - box-shadow: var(--buttonPressedShadow); - - svg, - i { - color: $fallback--text; - color: var(--btnToggledText, $fallback--text); - } - } - - &.danger { - // TODO: add better color variable - color: $fallback--text; - color: var(--alertErrorPanelText, $fallback--text); - background-color: $fallback--alertError; - background-color: var(--alertError, $fallback--alertError); } } @@ -458,8 +387,7 @@ nav { color: inherit; &.-link { - color: $fallback--link; - color: var(--link, $fallback--link); + color: var(--link); } &.-fullwidth { @@ -510,7 +438,6 @@ textarea { &[disabled="disabled"], &.disabled { cursor: not-allowed; - opacity: 0.5; } &[type="range"] { @@ -610,9 +537,7 @@ textarea { } option { - color: $fallback--text; - color: var(--text, $fallback--text); - background-color: $fallback--bg; + color: var(--text); background-color: var(--bg, $fallback--bg); } @@ -746,6 +671,7 @@ option { .faint { --text: var(--textFaint); --textGreentext: var(--textGreentextFaint); + --textCyantext: var(--textCyantextFaint); --link: var(--linkFaint); color: var(--text); diff --git a/src/components/border.style.js b/src/components/border.style.js new file mode 100644 index 00000000..a87ee9c8 --- /dev/null +++ b/src/components/border.style.js @@ -0,0 +1,13 @@ +export default { + name: 'Border', + selector: '/*border*/', + virtual: true, + defaultRules: [ + { + directives: { + textColor: '$mod(--parent, 10)', + textAuto: 'no-auto' + } + } + ] +} diff --git a/src/components/button.style.js b/src/components/button.style.js index c86fee64..84db88fb 100644 --- a/src/components/button.style.js +++ b/src/components/button.style.js @@ -23,28 +23,37 @@ const hoverGlow = { export default { name: 'Button', // Name of the component selector: '.button', // CSS selector/prefix - // States, system witll calculate ALL possible combinations of those and append a "normal" to them + standalone "normal" state + // outOfTreeSelector: '' // out-of-tree selector is used when other components are laid over it but it's not part of the tree, see Underlay component + // States, system witll calculate ALL possible combinations of those and prepend "normal" to them + standalone "normal" state states: { - // normal: state is implicitly added + // States are a bit expensive - the amount of combinations generated is about (1/6)n^3+n, so adding more state increased number of combination by an order of magnitude! + // All states inherit from "normal" state, there is no other inheirtance, i.e. hover+disabled only inherits from "normal", not from hover nor disabled. + // However, cascading still works, so resulting state will be result of merging of all relevant states/variants + // normal: '' // normal state is implicitly added, it is always included disabled: ':disabled', toggled: '.toggled', pressed: ':active', hover: ':hover', focused: ':focus-within' }, - // Variants are mutually exclusive, which saves on computation time + // Variants are mutually exclusive, each component implicitly has "normal" variant, and all other variants inherit from it. variants: { - normal: '-default', // you can override normal variant - danger: '.danger', - unstyled: '-unstyled' + // Variants save on computation time since adding new variant just adds one more "set". + normal: '-default', // you can override normal variant, it will be appenended to the main class + danger: '-default.danger' + // Overall the compuation difficulty is N*((1/6)M^3+M) where M is number of distinct states and N is number of variants. + // This (currently) is further multipled by number of places where component can exist. }, + // This lists all other components that can possibly exist within one. Recursion is currently not supported (and probably won't be supported ever). validInnerComponents: [ 'Text', 'Icon' ], + // Default rules, used as "default theme", essentially. defaultRules: [ { - component: 'Button', + // 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: [{ @@ -58,7 +67,6 @@ export default { } }, { - component: 'Button', variant: 'unstyled', directives: { background: '--fg', @@ -66,14 +74,12 @@ export default { } }, { - component: 'Button', state: ['hover'], directives: { shadow: [hoverGlow, ...buttonInsetFakeBorders] } }, { - component: 'Button', state: ['hover', 'pressed'], directives: { background: '--accent,-24.2', @@ -81,7 +87,6 @@ export default { } }, { - component: 'Button', state: ['disabled'], directives: { background: '$blend(--background, 0.25, --parent)', diff --git a/src/components/dropdown_menu.style.js b/src/components/dropdown_menu.style.js deleted file mode 100644 index 905984e9..00000000 --- a/src/components/dropdown_menu.style.js +++ /dev/null @@ -1,19 +0,0 @@ -export default { - name: 'DropdownMenu', - selector: '.dropdown', - validInnerComponents: [ - 'Text', - 'Icon', - 'Input' - ], - states: { - hover: ':hover' - }, - defaultRules: [ - { - directives: { - background: '--fg' - } - } - ] -} diff --git a/src/components/menu_item.style.js b/src/components/menu_item.style.js new file mode 100644 index 00000000..43b5c35a --- /dev/null +++ b/src/components/menu_item.style.js @@ -0,0 +1,45 @@ +export default { + name: 'MenuItem', + selector: '.menu-item', + validInnerComponents: [ + 'Text', + 'Icon', + 'Input', + 'Border' + ], + states: { + hover: ':hover', + active: 'active' + }, + defaultRules: [ + { + directives: { + background: '--fg' + } + }, + { + component: 'Text', + variant: 'normal', + parent: { + component: 'MenuItem', + state: ['normal', 'hover'], + variant: 'normal' + }, + directives: { + textColor: '--link', + textAuto: 'no-preserve' + } + }, + { + component: 'Icon', + parent: { + component: 'MenuItem', + state: ['hover'] + }, + directives: { + textColor: '--link', + textAuto: 'no-preserve' + } + } + ] +} diff --git a/src/components/modals.style.js b/src/components/modals.style.js new file mode 100644 index 00000000..3cfddf2f --- /dev/null +++ b/src/components/modals.style.js @@ -0,0 +1,8 @@ +export default { + name: 'Modals', + selector: '.modal-view', + validInnerComponents: [ + 'Panel' + ], + defaultRules: [] +} diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue index 1a826cc4..33547a41 100644 --- a/src/components/nav_panel/nav_panel.vue +++ b/src/components/nav_panel/nav_panel.vue @@ -107,7 +107,7 @@ .NavPanel { .panel { overflow: hidden; - box-shadow: var(--panelShadow); + box-shadow: var(--shadow); } ul { @@ -124,14 +124,14 @@ } > li { - &:first-child .menu-item { + &:first-child.menu-item { border-top-right-radius: $fallback--panelRadius; border-top-right-radius: var(--panelRadius, $fallback--panelRadius); border-top-left-radius: $fallback--panelRadius; border-top-left-radius: var(--panelRadius, $fallback--panelRadius); } - &:last-child .menu-item { + &:last-child.menu-item { border-bottom-right-radius: $fallback--panelRadius; border-bottom-right-radius: var(--panelRadius, $fallback--panelRadius); border-bottom-left-radius: $fallback--panelRadius; diff --git a/src/components/navigation/navigation_entry.vue b/src/components/navigation/navigation_entry.vue index 411ca536..59722f6c 100644 --- a/src/components/navigation/navigation_entry.vue +++ b/src/components/navigation/navigation_entry.vue @@ -1,7 +1,6 @@