underlay customization, updated contrast calculations to account for alpha blending
This commit is contained in:
parent
1a78461443
commit
6e11924c27
5 changed files with 91 additions and 31 deletions
|
@ -32,6 +32,7 @@ h4 {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
max-width: 980px;
|
max-width: 980px;
|
||||||
background-color: rgba(0,0,0,0.15);
|
background-color: rgba(0,0,0,0.15);
|
||||||
|
background-color: var(--underlay, rgba(0,0,0,0.15));
|
||||||
align-content: flex-start;
|
align-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { rgb2hex, hex2rgb, getContrastRatio, alphaBlend } from '../../services/color_convert/color_convert.js'
|
import { rgb2hex, hex2rgb, getContrastRatio, getContrastRatioLayers, alphaBlend } from '../../services/color_convert/color_convert.js'
|
||||||
import { set, delete as del } from 'vue'
|
import { set, delete as del } from 'vue'
|
||||||
import { merge } from 'lodash'
|
import { merge } from 'lodash'
|
||||||
import { generateCompat, generateColors, generateShadows, generateRadii, generateFonts, composePreset, getThemes } from '../../services/style_setter/style_setter.js'
|
import { generateCompat, generateColors, generateShadows, generateRadii, generateFonts, composePreset, getThemes } from '../../services/style_setter/style_setter.js'
|
||||||
|
@ -53,6 +53,9 @@ export default {
|
||||||
bgColorLocal: '',
|
bgColorLocal: '',
|
||||||
bgOpacityLocal: undefined,
|
bgOpacityLocal: undefined,
|
||||||
|
|
||||||
|
underlayColorLocal: '',
|
||||||
|
underlayOpacityLocal: undefined,
|
||||||
|
|
||||||
fgColorLocal: '',
|
fgColorLocal: '',
|
||||||
fgTextColorLocal: undefined,
|
fgTextColorLocal: undefined,
|
||||||
fgLinkColorLocal: undefined,
|
fgLinkColorLocal: undefined,
|
||||||
|
@ -145,6 +148,8 @@ export default {
|
||||||
|
|
||||||
accent: this.accentColorLocal,
|
accent: this.accentColorLocal,
|
||||||
|
|
||||||
|
underlay: this.underlayColorLocal,
|
||||||
|
|
||||||
panel: this.panelColorLocal,
|
panel: this.panelColorLocal,
|
||||||
panelText: this.panelTextColorLocal,
|
panelText: this.panelTextColorLocal,
|
||||||
panelLink: this.panelLinkColorLocal,
|
panelLink: this.panelLinkColorLocal,
|
||||||
|
@ -182,7 +187,8 @@ export default {
|
||||||
panel: this.panelOpacityLocal,
|
panel: this.panelOpacityLocal,
|
||||||
topBar: this.topBarOpacityLocal,
|
topBar: this.topBarOpacityLocal,
|
||||||
border: this.borderOpacityLocal,
|
border: this.borderOpacityLocal,
|
||||||
faint: this.faintOpacityLocal
|
faint: this.faintOpacityLocal,
|
||||||
|
underlay: this.underlayOpacityLocal
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
currentRadii () {
|
currentRadii () {
|
||||||
|
@ -240,6 +246,7 @@ export default {
|
||||||
|
|
||||||
const bgs = {
|
const bgs = {
|
||||||
bg: hex2rgb(colors.bg),
|
bg: hex2rgb(colors.bg),
|
||||||
|
underlay: hex2rgb(colors.underlay),
|
||||||
btn: hex2rgb(colors.btn),
|
btn: hex2rgb(colors.btn),
|
||||||
panel: hex2rgb(colors.panel),
|
panel: hex2rgb(colors.panel),
|
||||||
topBar: hex2rgb(colors.topBar),
|
topBar: hex2rgb(colors.topBar),
|
||||||
|
@ -249,29 +256,31 @@ export default {
|
||||||
badgeNotification: hex2rgb(colors.badgeNotification)
|
badgeNotification: hex2rgb(colors.badgeNotification)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This is a bit confusing because "bottom layer" used is text color
|
const bg = [bgs.bg, opacity.bg]
|
||||||
* This is done to get worst case scenario when background below transparent
|
const underlay = [bgs.underlay, opacity.underlay]
|
||||||
* layer matches text color, making it harder to read the lower alpha is.
|
|
||||||
*/
|
|
||||||
const ratios = {
|
|
||||||
bgText: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.text), fgs.text),
|
|
||||||
bgLink: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.link), fgs.link),
|
|
||||||
bgRed: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.red), fgs.red),
|
|
||||||
bgGreen: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.green), fgs.green),
|
|
||||||
bgBlue: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.blue), fgs.blue),
|
|
||||||
bgOrange: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.orange), fgs.orange),
|
|
||||||
|
|
||||||
|
const panel = [underlay, bg]
|
||||||
|
|
||||||
|
const ratios = {
|
||||||
|
bgText: getContrastRatioLayers(fgs.text, panel, fgs.text),
|
||||||
|
bgLink: getContrastRatioLayers(fgs.link, panel, fgs.link),
|
||||||
|
bgRed: getContrastRatioLayers(fgs.red, panel, fgs.red),
|
||||||
|
bgGreen: getContrastRatioLayers(fgs.green, panel, fgs.green),
|
||||||
|
bgBlue: getContrastRatioLayers(fgs.blue, panel, fgs.blue),
|
||||||
|
bgOrange: getContrastRatioLayers(fgs.orange, panel, fgs.orange),
|
||||||
|
|
||||||
|
// TODO what's this?
|
||||||
tintText: getContrastRatio(alphaBlend(bgs.bg, 0.5, fgs.panelText), fgs.text),
|
tintText: getContrastRatio(alphaBlend(bgs.bg, 0.5, fgs.panelText), fgs.text),
|
||||||
|
|
||||||
panelText: getContrastRatio(alphaBlend(bgs.panel, opacity.panel, fgs.panelText), fgs.panelText),
|
panelText: getContrastRatioLayers(fgs.text, [...panel, [bgs.panel, opacity.panel]], fgs.panelText),
|
||||||
panelLink: getContrastRatio(alphaBlend(bgs.panel, opacity.panel, fgs.panelLink), fgs.panelLink),
|
panelLink: getContrastRatioLayers(fgs.link, [...panel, [bgs.panel, opacity.panel]], fgs.panelLink),
|
||||||
|
|
||||||
btnText: getContrastRatio(alphaBlend(bgs.btn, opacity.btn, fgs.btnText), fgs.btnText),
|
btnText: getContrastRatioLayers(fgs.text, [...panel, [bgs.btn, opacity.btn]], fgs.btnText),
|
||||||
|
|
||||||
inputText: getContrastRatio(alphaBlend(bgs.input, opacity.input, fgs.inputText), fgs.inputText),
|
inputText: getContrastRatioLayers(fgs.text, [...panel, [bgs.input, opacity.input]], fgs.inputText),
|
||||||
|
|
||||||
topBarText: getContrastRatio(alphaBlend(bgs.topBar, opacity.topBar, fgs.topBarText), fgs.topBarText),
|
topBarText: getContrastRatioLayers(fgs.text, [...panel, [bgs.topBar, opacity.topBar]], fgs.topBarText),
|
||||||
topBarLink: getContrastRatio(alphaBlend(bgs.topBar, opacity.topBar, fgs.topBarLink), fgs.topBarLink)
|
topBarLink: getContrastRatioLayers(fgs.link, [...panel, [bgs.topBar, opacity.topBar]], fgs.topBarLink)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.entries(ratios).reduce((acc, [k, v]) => { acc[k] = hints(v); return acc }, {})
|
return Object.entries(ratios).reduce((acc, [k, v]) => { acc[k] = hints(v); return acc }, {})
|
||||||
|
|
|
@ -366,6 +366,20 @@
|
||||||
:fallback="previewTheme.opacity.faint || 0.5"
|
:fallback="previewTheme.opacity.faint || 0.5"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="color-item">
|
||||||
|
<h4>{{ $t('settings.style.advanced_colors.underlay') }}</h4>
|
||||||
|
<ColorInput
|
||||||
|
v-model="underlayColorLocal"
|
||||||
|
name="underlay"
|
||||||
|
:label="$t('settings.style.advanced_colors.underlay')"
|
||||||
|
fallback='#000000'
|
||||||
|
/>
|
||||||
|
<OpacityInput
|
||||||
|
v-model="underlayOpacityLocal"
|
||||||
|
name="underlayOpacity"
|
||||||
|
:fallback="previewTheme.opacity.underlay || 0.15"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
import { map } from 'lodash'
|
import { map } from 'lodash'
|
||||||
|
|
||||||
|
// useful for visualizing color when debugging
|
||||||
|
export const consoleColor = (color) => console.log('%c##########', 'background: ' + color + '; color: ' + color)
|
||||||
|
|
||||||
const rgb2hex = (r, g, b) => {
|
const rgb2hex = (r, g, b) => {
|
||||||
if (r === null || typeof r === 'undefined') {
|
if (r === null || typeof r === 'undefined') {
|
||||||
return undefined
|
return undefined
|
||||||
|
@ -78,6 +81,16 @@ const getContrastRatio = (a, b) => {
|
||||||
|
|
||||||
return (l1 + 0.05) / (l2 + 0.05)
|
return (l1 + 0.05) / (l2 + 0.05)
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Same as `getContrastRatio` but for multiple layers in-between
|
||||||
|
*
|
||||||
|
* @param {Object} text - text color (topmost layer)
|
||||||
|
* @param {[Object, Number]} layers[] - layers between text and bedrock
|
||||||
|
* @param {Object} bedrock - layer at the very bottom
|
||||||
|
*/
|
||||||
|
export const getContrastRatioLayers = (text, layers, bedrock) => {
|
||||||
|
return getContrastRatio(alphaBlendLayers(bedrock, layers), text)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This performs alpha blending between solid background and semi-transparent foreground
|
* This performs alpha blending between solid background and semi-transparent foreground
|
||||||
|
@ -97,6 +110,16 @@ const alphaBlend = (fg, fga, bg) => {
|
||||||
}, {})
|
}, {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as `alphaBlend` but for multiple layers in-between
|
||||||
|
*
|
||||||
|
* @param {Object} bedrock - layer at the very bottom
|
||||||
|
* @param {[Object, Number]} layers[] - layers between text and bedrock
|
||||||
|
*/
|
||||||
|
export const alphaBlendLayers = (bedrock, layers) => layers.reduce((acc, [color, opacity]) => {
|
||||||
|
return alphaBlend(color, opacity, acc)
|
||||||
|
}, bedrock)
|
||||||
|
|
||||||
const invert = (rgb) => {
|
const invert = (rgb) => {
|
||||||
return 'rgb'.split('').reduce((acc, c) => {
|
return 'rgb'.split('').reduce((acc, c) => {
|
||||||
acc[c] = 255 - rgb[c]
|
acc[c] = 255 - rgb[c]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { times } from 'lodash'
|
import { times } from 'lodash'
|
||||||
import { brightness, invertLightness, convert, contrastRatio } from 'chromatism'
|
import { brightness, invertLightness, convert, contrastRatio } from 'chromatism'
|
||||||
import { rgb2hex, hex2rgb, mixrgb, getContrastRatio, alphaBlend } from '../color_convert/color_convert.js'
|
import { rgb2hex, hex2rgb, mixrgb, getContrastRatio, alphaBlend, alphaBlendLayers } from '../color_convert/color_convert.js'
|
||||||
|
|
||||||
// While this is not used anymore right now, I left it in if we want to do custom
|
// While this is not used anymore right now, I left it in if we want to do custom
|
||||||
// styles that aren't just colors, so user can pick from a few different distinct
|
// styles that aren't just colors, so user can pick from a few different distinct
|
||||||
|
@ -64,8 +64,10 @@ const getTextColor = function (bg, text, preserve) {
|
||||||
const base = typeof text.a !== 'undefined' ? { a: text.a } : {}
|
const base = typeof text.a !== 'undefined' ? { a: text.a } : {}
|
||||||
const result = Object.assign(base, invertLightness(text).rgb)
|
const result = Object.assign(base, invertLightness(text).rgb)
|
||||||
if (!preserve && getContrastRatio(bg, result) < 4.5) {
|
if (!preserve && getContrastRatio(bg, result) < 4.5) {
|
||||||
|
// B&W
|
||||||
return contrastRatio(bg, text).rgb
|
return contrastRatio(bg, text).rgb
|
||||||
}
|
}
|
||||||
|
// Inverted color
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
return text
|
return text
|
||||||
|
@ -173,7 +175,8 @@ const generateColors = (input) => {
|
||||||
const opacity = Object.assign({
|
const opacity = Object.assign({
|
||||||
alert: 0.5,
|
alert: 0.5,
|
||||||
input: 0.5,
|
input: 0.5,
|
||||||
faint: 0.5
|
faint: 0.5,
|
||||||
|
underlay: 0.15
|
||||||
}, Object.entries(input.opacity || {}).reduce((acc, [k, v]) => {
|
}, Object.entries(input.opacity || {}).reduce((acc, [k, v]) => {
|
||||||
if (typeof v !== 'undefined') {
|
if (typeof v !== 'undefined') {
|
||||||
acc[k] = v
|
acc[k] = v
|
||||||
|
@ -210,28 +213,37 @@ const generateColors = (input) => {
|
||||||
colors.faint = col.faint || Object.assign({}, col.text)
|
colors.faint = col.faint || Object.assign({}, col.text)
|
||||||
|
|
||||||
colors.bg = col.bg
|
colors.bg = col.bg
|
||||||
colors.lightBg = col.lightBg || brightness(5, colors.bg).rgb
|
colors.lightBg = col.lightBg || brightness(5 * mod, colors.bg).rgb
|
||||||
|
|
||||||
|
const underlay = [col.underlay, opacity.underlay]
|
||||||
|
const fg = [col.fg, opacity.fg]
|
||||||
|
const bg = [col.bg, opacity.bg]
|
||||||
|
|
||||||
colors.fg = col.fg
|
colors.fg = col.fg
|
||||||
colors.fgText = col.fgText || getTextColor(colors.fg, colors.text)
|
colors.fgText = col.fgText || getTextColor(alphaBlendLayers(colors.text, [underlay, bg, fg]), colors.text)
|
||||||
colors.fgLink = col.fgLink || getTextColor(colors.fg, colors.link, true)
|
colors.fgLink = col.fgLink || getTextColor(alphaBlendLayers(colors.link, [underlay, bg, fg]), colors.link, true)
|
||||||
|
colors.underlay = col.underlay || hex2rgb('#000000')
|
||||||
|
|
||||||
colors.border = col.border || brightness(2 * mod, colors.fg).rgb
|
colors.border = col.border || brightness(2 * mod, colors.fg).rgb
|
||||||
|
|
||||||
colors.btn = col.btn || Object.assign({}, col.fg)
|
colors.btn = col.btn || Object.assign({}, col.fg)
|
||||||
colors.btnText = col.btnText || getTextColor(colors.btn, colors.fgText)
|
const btn = [colors.btn, opacity.btn || 1]
|
||||||
|
colors.btnText = col.btnText || getTextColor(alphaBlendLayers(colors.fgText, [underlay, bg, fg, btn]), colors.fgText)
|
||||||
|
|
||||||
colors.input = col.input || Object.assign({}, col.fg)
|
colors.input = col.input || Object.assign({}, col.fg)
|
||||||
colors.inputText = col.inputText || getTextColor(colors.input, colors.lightText)
|
const inputCol = [colors.input, opacity.input]
|
||||||
|
colors.inputText = col.inputText || getTextColor(alphaBlendLayers(colors.lightText, [underlay, bg, fg, inputCol]), colors.lightText)
|
||||||
|
|
||||||
colors.panel = col.panel || Object.assign({}, col.fg)
|
colors.panel = col.panel || Object.assign({}, col.fg)
|
||||||
colors.panelText = col.panelText || getTextColor(colors.panel, colors.fgText)
|
const panel = [colors.panel, opacity.panel]
|
||||||
colors.panelLink = col.panelLink || getTextColor(colors.panel, colors.fgLink)
|
colors.panelText = col.panelText || getTextColor(alphaBlendLayers(colors.fgText, [underlay, bg, panel]), colors.fgText)
|
||||||
colors.panelFaint = col.panelFaint || getTextColor(colors.panel, colors.faint)
|
colors.panelLink = col.panelLink || getTextColor(alphaBlendLayers(colors.fgLink, [underlay, bg, panel]), colors.fgLink)
|
||||||
|
colors.panelFaint = col.panelFaint || getTextColor(alphaBlendLayers(colors.faint, [underlay, bg, panel]), colors.faint)
|
||||||
|
|
||||||
colors.topBar = col.topBar || Object.assign({}, col.fg)
|
colors.topBar = col.topBar || Object.assign({}, col.fg)
|
||||||
colors.topBarText = col.topBarText || getTextColor(colors.topBar, colors.fgText)
|
const topBar = [colors.topBar, opacity.topBar]
|
||||||
colors.topBarLink = col.topBarLink || getTextColor(colors.topBar, colors.fgLink)
|
colors.topBarText = col.topBarText || getTextColor(alphaBlendLayers(colors.fgText, [topBar]), colors.fgText)
|
||||||
|
colors.topBarLink = col.topBarLink || getTextColor(alphaBlendLayers(colors.fgLink, [topBar]), colors.fgLink)
|
||||||
|
|
||||||
colors.faintLink = col.faintLink || Object.assign({}, col.link || col.accent)
|
colors.faintLink = col.faintLink || Object.assign({}, col.link || col.accent)
|
||||||
colors.linkBg = alphaBlend(colors.link, 0.4, colors.bg)
|
colors.linkBg = alphaBlend(colors.link, 0.4, colors.bg)
|
||||||
|
@ -255,6 +267,7 @@ const generateColors = (input) => {
|
||||||
colors.badgeNotificationText = contrastRatio(colors.badgeNotification).rgb
|
colors.badgeNotificationText = contrastRatio(colors.badgeNotification).rgb
|
||||||
|
|
||||||
Object.entries(opacity).forEach(([ k, v ]) => {
|
Object.entries(opacity).forEach(([ k, v ]) => {
|
||||||
|
console.log(k)
|
||||||
if (typeof v === 'undefined') return
|
if (typeof v === 'undefined') return
|
||||||
if (k === 'alert') {
|
if (k === 'alert') {
|
||||||
colors.alertError.a = v
|
colors.alertError.a = v
|
||||||
|
|
Loading…
Reference in a new issue