mirror of
https://github.com/rio-labs/rio.git
synced 2026-01-05 20:59:46 -06:00
Add FrostedGlassFill classes and update CSS
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { Color, ComponentId, Fill } from '../dataModels';
|
||||
import { colorToCssString, fillToCssString } from '../cssUtils';
|
||||
import { colorToCssString, fillToCss } from '../cssUtils';
|
||||
import { ComponentBase, ComponentState } from './componentBase';
|
||||
import { RippleEffect } from '../rippleEffect';
|
||||
import { SingleContainer } from './singleContainer';
|
||||
@@ -36,7 +36,7 @@ function numberToRem(num: number): string {
|
||||
}
|
||||
|
||||
const JS_TO_CSS_VALUE = {
|
||||
fill: fillToCssString,
|
||||
fill: fillToCss,
|
||||
stroke_color: colorToCssString,
|
||||
stroke_width: numberToRem,
|
||||
corner_radius: (radii: [number, number, number, number]) =>
|
||||
@@ -96,26 +96,50 @@ export class RectangleComponent extends SingleContainer {
|
||||
// Apply all the styling properties
|
||||
for (let [attrName, js_to_css] of Object.entries(JS_TO_CSS_VALUE)) {
|
||||
let value = deltaState[attrName];
|
||||
if (value !== undefined) {
|
||||
this.element.style.setProperty(
|
||||
`--rio-rectangle-${attrName}`,
|
||||
js_to_css(value)
|
||||
);
|
||||
if (value !== undefined && value !== null) {
|
||||
let cssValues = js_to_css(value);
|
||||
if (typeof cssValues === 'string') {
|
||||
cssValues = { [attrName]: cssValues };
|
||||
}
|
||||
for (let [prop, val] of Object.entries(cssValues)) {
|
||||
if (prop === 'backdropFilter') {
|
||||
this.element.style.backdropFilter = val;
|
||||
} else {
|
||||
this.element.style.setProperty(
|
||||
`--rio-rectangle-${prop}`,
|
||||
val
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let hoverValue = deltaState['hover_' + attrName];
|
||||
if (hoverValue !== undefined) {
|
||||
if (hoverValue === null) {
|
||||
// No hover value? Use the corresponding non-hover value
|
||||
this.element.style.setProperty(
|
||||
`--rio-rectangle-hover-${attrName}`,
|
||||
`var(--rio-rectangle-${attrName})`
|
||||
);
|
||||
if (value !== undefined && value !== null) {
|
||||
let cssValues = js_to_css(value);
|
||||
if (typeof cssValues === 'string') {
|
||||
cssValues = { [attrName]: cssValues };
|
||||
}
|
||||
for (let [prop, val] of Object.entries(cssValues)) {
|
||||
this.element.style.setProperty(
|
||||
`--rio-rectangle-hover-${prop}`,
|
||||
`var(--rio-rectangle-${prop})`
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.element.style.setProperty(
|
||||
`--rio-rectangle-hover-${attrName}`,
|
||||
js_to_css(hoverValue)
|
||||
);
|
||||
let cssValues = js_to_css(hoverValue);
|
||||
if (typeof cssValues === 'string') {
|
||||
cssValues = { [attrName]: cssValues };
|
||||
}
|
||||
for (let [prop, val] of Object.entries(cssValues)) {
|
||||
this.element.style.setProperty(
|
||||
`--rio-rectangle-hover-${prop}`,
|
||||
val
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,49 +22,61 @@ function gradientToCssString(
|
||||
)})`;
|
||||
}
|
||||
|
||||
export function fillToCssString(fill: Fill): string {
|
||||
// Solid Color
|
||||
if (fill.type === 'solid') {
|
||||
return colorToCssString(fill.color);
|
||||
}
|
||||
export function fillToCss(fill: Fill): { [key: string]: string } {
|
||||
const cssProps: { [key: string]: string } = {};
|
||||
|
||||
// Linear Gradient
|
||||
else if (fill.type === 'linearGradient') {
|
||||
if (fill.stops.length == 1) {
|
||||
return colorToCssString(fill.stops[0][0]);
|
||||
}
|
||||
switch (fill.type) {
|
||||
// Solid Color
|
||||
case 'solid':
|
||||
cssProps.fill = colorToCssString(fill.color);
|
||||
break;
|
||||
|
||||
return gradientToCssString(fill.angleDegrees, fill.stops);
|
||||
}
|
||||
// Linear Gradient
|
||||
case 'linearGradient':
|
||||
if (fill.stops.length === 1) {
|
||||
cssProps.fill = colorToCssString(fill.stops[0][0]);
|
||||
} else {
|
||||
cssProps.fill = gradientToCssString(
|
||||
fill.angleDegrees,
|
||||
fill.stops
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
// Image
|
||||
else if (fill.type === 'image') {
|
||||
let cssUrl = `url('${fill.imageUrl}')`;
|
||||
// Image
|
||||
case 'image':
|
||||
const cssUrl = `url('${fill.imageUrl}')`;
|
||||
switch (fill.fillMode) {
|
||||
case 'fit':
|
||||
cssProps.fill = `${cssUrl} center/contain no-repeat`;
|
||||
break;
|
||||
case 'stretch':
|
||||
cssProps.fill = `${cssUrl} top left / 100% 100%`;
|
||||
break;
|
||||
case 'tile':
|
||||
cssProps.fill = `${cssUrl} left top repeat`;
|
||||
break;
|
||||
case 'zoom':
|
||||
cssProps.fill = `${cssUrl} center/cover no-repeat`;
|
||||
break;
|
||||
default:
|
||||
// Invalid fill mode
|
||||
// @ts-ignore
|
||||
throw `Invalid fill mode for image fill: ${fill.type}`;
|
||||
}
|
||||
break;
|
||||
|
||||
if (fill.fillMode == 'fit') {
|
||||
return `${cssUrl} center/contain no-repeat`;
|
||||
} else if (fill.fillMode == 'stretch') {
|
||||
return `${cssUrl} top left / 100% 100%`;
|
||||
} else if (fill.fillMode == 'tile') {
|
||||
return `${cssUrl} left top repeat`;
|
||||
} else if (fill.fillMode == 'zoom') {
|
||||
return `${cssUrl} center/cover no-repeat`;
|
||||
} else {
|
||||
// Invalid fill mode
|
||||
// Frosted Glass
|
||||
case 'frostedGlass':
|
||||
cssProps.fill = colorToCssString(fill.color);
|
||||
cssProps.backdropFilter = `blur(${fill.blur / globalThis.pixelsPerRem}rem)`;
|
||||
break;
|
||||
default:
|
||||
// Invalid fill type
|
||||
// @ts-ignore
|
||||
throw `Invalid fill mode for image fill: ${fill.type}`;
|
||||
}
|
||||
throw `Invalid fill type: ${fill.type}`;
|
||||
}
|
||||
|
||||
// Invalid fill type
|
||||
// @ts-ignore
|
||||
throw `Invalid fill type: ${fill.type}`;
|
||||
}
|
||||
|
||||
export function fillToCss(fill: Fill): { background: string } {
|
||||
return {
|
||||
background: fillToCssString(fill),
|
||||
};
|
||||
return cssProps;
|
||||
}
|
||||
|
||||
export function textStyleToCss(
|
||||
@@ -78,6 +90,7 @@ export function textStyleToCss(
|
||||
'text-transform': string;
|
||||
color: string;
|
||||
background: string;
|
||||
'backdrop-filter'?: string;
|
||||
'-webkit-background-clip': string;
|
||||
'-webkit-text-fill-color': string;
|
||||
opacity: string;
|
||||
@@ -90,6 +103,7 @@ export function textStyleToCss(
|
||||
let textTransform: string;
|
||||
let color: string;
|
||||
let background: string;
|
||||
let backdropFilter: string | undefined;
|
||||
let backgroundClip: string;
|
||||
let textFillColor: string;
|
||||
let opacity: string;
|
||||
@@ -166,7 +180,10 @@ export function textStyleToCss(
|
||||
// Anything else
|
||||
else {
|
||||
color = 'unset';
|
||||
background = fillToCssString(style.fill);
|
||||
const cssProps = fillToCss(style.fill);
|
||||
background = cssProps.fill;
|
||||
backdropFilter = cssProps.backdropFilter;
|
||||
opacity = cssProps.opacity;
|
||||
backgroundClip = 'text';
|
||||
textFillColor = 'transparent';
|
||||
}
|
||||
@@ -181,6 +198,7 @@ export function textStyleToCss(
|
||||
'text-transform': textTransform,
|
||||
color: color,
|
||||
background: background,
|
||||
'backdrop-filter': backdropFilter || '',
|
||||
'-webkit-background-clip': backgroundClip,
|
||||
'-webkit-text-fill-color': textFillColor,
|
||||
opacity: opacity,
|
||||
|
||||
@@ -74,6 +74,10 @@ export function applyFillToSVG(svgRoot: SVGSVGElement, fill: Fill): void {
|
||||
applyImageFill(svgRoot, fill.imageUrl, fill.fillMode);
|
||||
break;
|
||||
|
||||
case 'frostedGlass':
|
||||
applyFrostedGlassFill(svgRoot, fill.color, fill.blur);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`Invalid fill type: ${fill}`);
|
||||
}
|
||||
@@ -148,6 +152,40 @@ function applyImageFill(
|
||||
svgRoot.setAttribute('fill', `url(#${patternId})`);
|
||||
}
|
||||
|
||||
function applyFrostedGlassFill(svgRoot, color, blur): void {
|
||||
const [r, g, b, a] = color;
|
||||
svgRoot.style.fill = `rgba(${r * 255}, ${g * 255}, ${b * 255}, ${a})`;
|
||||
|
||||
const filterId = 'frosted-glass-blur';
|
||||
let filter = svgRoot.querySelector(`#${filterId}`);
|
||||
|
||||
if (!filter) {
|
||||
filter = document.createElementNS(
|
||||
'http://www.w3.org/2000/svg',
|
||||
'filter'
|
||||
);
|
||||
filter.setAttribute('id', filterId);
|
||||
const feGaussianBlur = document.createElementNS(
|
||||
'http://www.w3.org/2000/svg',
|
||||
'feGaussianBlur'
|
||||
);
|
||||
feGaussianBlur.setAttribute('stdDeviation', blur.toString());
|
||||
filter.appendChild(feGaussianBlur);
|
||||
|
||||
let defs = svgRoot.querySelector('defs');
|
||||
if (!defs) {
|
||||
defs = document.createElementNS(
|
||||
'http://www.w3.org/2000/svg',
|
||||
'defs'
|
||||
);
|
||||
svgRoot.appendChild(defs);
|
||||
}
|
||||
|
||||
defs.appendChild(filter);
|
||||
}
|
||||
svgRoot.style.filter = `url(#${filterId})`;
|
||||
}
|
||||
|
||||
function generateUniqueId(): string {
|
||||
return Math.random().toString(36);
|
||||
}
|
||||
|
||||
25
rio/fills.py
25
rio/fills.py
@@ -228,4 +228,29 @@ class ImageFill(Fill):
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True, eq=True)
|
||||
class FrostedGlassFill(Fill):
|
||||
"""
|
||||
Fills a shape with a frosted glass effect.
|
||||
|
||||
`FrostedGlassFill` fills the shape with a color and applies a blur effect to
|
||||
create a frosted glass appearance.
|
||||
|
||||
## Attributes
|
||||
|
||||
`color`: The color to fill the shape with.
|
||||
`blur`: The amount of blur applied to the fill.
|
||||
"""
|
||||
|
||||
color: Color
|
||||
blur: float = 4
|
||||
|
||||
def _serialize(self, sess: rio.Session) -> Jsonable:
|
||||
return {
|
||||
"type": "frostedGlass",
|
||||
"color": self.color.rgba,
|
||||
"blur": self.blur,
|
||||
}
|
||||
|
||||
|
||||
FillLike: TypeAlias = Fill | Color
|
||||
|
||||
@@ -2068,6 +2068,15 @@ a.remove();
|
||||
"fill-color": "unset",
|
||||
}
|
||||
|
||||
if isinstance(fill, rio.FrostedGlassFill):
|
||||
return {
|
||||
"color": f"#{fill.color.hex}",
|
||||
"background": "none",
|
||||
"background-clip": "unset",
|
||||
"fill-color": "unset",
|
||||
"backdrop-filter": f"blur({fill.blur}rem)",
|
||||
}
|
||||
|
||||
assert isinstance(fill, (rio.LinearGradientFill, rio.ImageFill)), fill
|
||||
return {
|
||||
"color": "var(--rio-local-text-color)",
|
||||
|
||||
Reference in New Issue
Block a user