diff --git a/src/core/dom-renderer/domRenderer.ts b/src/core/dom-renderer/domRenderer.ts index a36a0c4..2c9002b 100644 --- a/src/core/dom-renderer/domRenderer.ts +++ b/src/core/dom-renderer/domRenderer.ts @@ -8,7 +8,9 @@ import * as lng from '@lightningjs/renderer'; import { EventEmitter } from '@lightningjs/renderer/utils'; import { Config } from '../config.js'; +import { FontLoadOptions } from '../intrinsicTypes.js'; import type { + DomRendererMainSettings, ExtractProps, IRendererMain, IRendererNode, @@ -17,22 +19,20 @@ import type { IRendererStage, IRendererTextNode, IRendererTextNodeProps, - DomRendererMainSettings, } from './domRendererTypes.js'; import { - colorToRgba, + applyEasing, + applySubTextureScaling, buildGradientStops, + colorToRgba, + compactString, computeLegacyObjectFit, - applySubTextureScaling, + computeRenderStateForNode, getNodeLineHeight, - applyEasing, interpolateProp, isRenderStateInBounds, nodeHasTextureSource, - computeRenderStateForNode, - compactString, } from './domRendererUtils.js'; -import { FontLoadOptions } from '../intrinsicTypes.js'; // Feature detection for legacy brousers const _styleRef: any = @@ -315,11 +315,15 @@ function updateNodeStyles(node: DOMNode | DOMText) { switch (textProps.contain) { case 'width': if (textProps.maxWidth && textProps.maxWidth > 0) { - style += `width: ${textProps.maxWidth}px;`; + if (node.textAlign === 'center') { + style += `width: ${textProps.maxWidth}px;`; + } else { + style += `max-width: ${textProps.maxWidth}px;`; + } + style += `overflow: hidden;`; } else { style += `width: 100%;`; } - style += `overflow: hidden;`; break; case 'both': { let lineHeight = getNodeLineHeight(textProps); @@ -514,27 +518,84 @@ function updateNodeStyles(node: DOMNode | DOMText) { let borderWidth = shaderProps['border-w']; let borderColor = shaderProps['border-color']; let borderGap = shaderProps['border-gap'] ?? 0; - let borderAlign = shaderProps['border-align'] ?? 'outside'; + let borderAlign = shaderProps['border-align'] ?? 'inside'; let radius = shaderProps['radius']; // Border + const borderWidthIsNumber = typeof borderWidth === 'number'; + const borderWidthIsArray = Array.isArray(borderWidth); + const borderWidthHasValue = + (borderWidthIsNumber && borderWidth !== 0) || + (borderWidthIsArray && + (borderWidth as number[]).some( + (w) => typeof w === 'number' && w !== 0, + )); + if ( - typeof borderWidth === 'number' && - borderWidth !== 0 && + borderWidthHasValue && typeof borderColor === 'number' && borderColor !== 0 ) { const rgbaColor = colorToRgba(borderColor); - let gap = borderGap; - if (borderAlign === 'inside') { - gap = -(borderWidth + borderGap); - } else if (borderAlign === 'center') { - gap = -(borderWidth / 2) + borderGap; - } + if (borderWidthIsNumber) { + let insideWidth = 0; + let outsideWidth = 0; - borderStyle += `outline: ${borderWidth}px solid ${rgbaColor};`; - borderStyle += `outline-offset: ${gap}px;`; + if (borderAlign === 'inside') { + insideWidth = borderWidth; + } else if (borderAlign === 'center') { + insideWidth = borderWidth / 2; + outsideWidth = borderWidth / 2; + } else { + outsideWidth = borderWidth; + } + + outsideWidth += borderGap; + insideWidth -= borderGap; + + if (insideWidth < 0) { + outsideWidth += insideWidth; + insideWidth = 0; + } + if (outsideWidth < 0) { + insideWidth += outsideWidth; + outsideWidth = 0; + } + + const shadows: string[] = []; + if (outsideWidth > 0) { + shadows.push(`0 0 0 ${outsideWidth}px ${rgbaColor}`); + } + if (insideWidth > 0) { + shadows.push(`inset 0 0 0 ${insideWidth}px ${rgbaColor}`); + } + + if (shadows.length > 0) { + borderStyle += `box-shadow: ${shadows.join(', ')};`; + } + } else if (borderWidthIsArray) { + // Individual borders per side [top, right, bottom, left] + // Allow individual properties to override array values + const topWidth = + shaderProps['border-top'] ?? (borderWidth as number[])[0]; + const rightWidth = + shaderProps['border-right'] ?? (borderWidth as number[])[1]; + const bottomWidth = + shaderProps['border-bottom'] ?? (borderWidth as number[])[2]; + const leftWidth = + shaderProps['border-left'] ?? (borderWidth as number[])[3]; + + const widths = [topWidth, rightWidth, bottomWidth, leftWidth]; + const sides = ['top', 'right', 'bottom', 'left'] as const; + + for (let i = 0; i < sides.length; i++) { + const width = widths[i]; + if (typeof width === 'number' && width !== 0) { + borderStyle += `border-${sides[i]}: ${width}px solid ${rgbaColor};`; + } + } + } } // Rounded if (typeof radius === 'number' && radius > 0) { @@ -826,7 +887,6 @@ type Size = { width: number; height: number }; function getElSize(node: DOMNode): Size { const rawRect = node.div.getBoundingClientRect(); - const dpr = Config.rendererOptions?.deviceLogicalPixelRatio ?? 1; let width = rawRect.width / dpr; let height = rawRect.height / dpr; @@ -857,11 +917,17 @@ function getElSize(node: DOMNode): Size { */ function updateDOMTextSize(node: DOMText): void { let size: Size; + let dimensionsChanged = false; switch (node.contain) { case 'width': size = getElSize(node); + if (node.props.w !== size.width) { + node.w = size.width; + dimensionsChanged = true; + } if (node.props.h !== size.height) { node.h = size.height; + dimensionsChanged = true; } break; case 'none': @@ -869,16 +935,17 @@ function updateDOMTextSize(node: DOMText): void { if (node.props.h !== size.height || node.props.w !== size.width) { node.w = size.width; node.h = size.height; + dimensionsChanged = true; } break; } - if (!node.loaded) { + if (!node.loaded || dimensionsChanged) { const payload: lng.NodeTextLoadedPayload = { type: 'text', dimensions: { - w: node.props.w, - h: node.props.h, + w: node.w, + h: node.h, }, }; node.emit('loaded', payload); @@ -1094,6 +1161,7 @@ export class DOMNode extends EventEmitter implements IRendererNode { if (child !== child.stage.root) { if (nodeHasTextureSource(child)) { const nextState = computeRenderStateForNode(child); + if (nextState != null) { child.updateRenderState(nextState); } diff --git a/src/core/dom-renderer/domRendererTypes.ts b/src/core/dom-renderer/domRendererTypes.ts index c772d97..09da81f 100644 --- a/src/core/dom-renderer/domRendererTypes.ts +++ b/src/core/dom-renderer/domRendererTypes.ts @@ -123,7 +123,6 @@ export interface IRendererMain extends IEventEmitter { createNode(props: Partial): IRendererNode; createShader: typeof lng.RendererMain.prototype.createShader; createTexture: typeof lng.RendererMain.prototype.createTexture; - //createEffect: typeof lng.RendererMain.prototype.createEffect; } export interface DomRendererMainSettings { diff --git a/src/core/elementNode.ts b/src/core/elementNode.ts index 0e694a3..249ef5d 100644 --- a/src/core/elementNode.ts +++ b/src/core/elementNode.ts @@ -726,7 +726,7 @@ export class ElementNode extends Object { if (this.rendered) { if (!this.lng.shader) { this.lng.shader = Config.convertToShader(this, target); - } else if (DOM_RENDERING) { + } else if (DOM_RENDERING && Config.domRendererEnabled) { this.lng.shader = this.lng.shader; // lng.shader is a setter, force style update } } else { @@ -1557,6 +1557,7 @@ export function shaderAccessor | number>( this._effects[key] = value; let animationSettings: AnimationSettings | undefined; + if (this.lng.shader?.props) { target = this.lng.shader.props; const transitionKey = key === 'rounded' ? 'borderRadius' : key; @@ -1583,6 +1584,8 @@ export function shaderAccessor | number>( if (this.rendered) { if (!this.lng.shader) { this.lng.shader = Config.convertToShader(this, target); + } else if (DOM_RENDERING && Config.domRendererEnabled) { + this.lng.shader = this.lng.shader; // lng.shader is a setter, force style update } } else { this.lng.shader = target; diff --git a/src/core/lightningInit.ts b/src/core/lightningInit.ts index da55130..2f2b834 100644 --- a/src/core/lightningInit.ts +++ b/src/core/lightningInit.ts @@ -1,8 +1,8 @@ import * as lng from '@lightningjs/renderer'; -import { DOMRendererMain, loadFontToDom } from './dom-renderer/domRenderer.js'; import { Config, DOM_RENDERING } from './config.js'; -import { FontLoadOptions } from './intrinsicTypes.js'; +import { DOMRendererMain, loadFontToDom } from './dom-renderer/domRenderer.js'; import { DomRendererMainSettings } from './dom-renderer/domRendererTypes.js'; +import { FontLoadOptions } from './intrinsicTypes.js'; export type SdfFontType = 'ssdf' | 'msdf'; // Global renderer instance: can be either the Lightning or DOM implementation diff --git a/src/core/shaders.ts b/src/core/shaders.ts index c0e6f87..8e2a361 100644 --- a/src/core/shaders.ts +++ b/src/core/shaders.ts @@ -18,7 +18,7 @@ export { }; export { WebGlShader }; -import { DOM_RENDERING, SHADERS_ENABLED } from './config.js'; +import { Config, DOM_RENDERING, SHADERS_ENABLED } from './config.js'; import type { CoreShaderManager } from './intrinsicTypes.js'; import { IRendererShaderManager } from './dom-renderer/domRendererTypes.js'; @@ -122,17 +122,17 @@ function toValidVec4(value: unknown): Vec4 { export function registerDefaultShaderRounded( shManager: IRendererShaderManager, ) { - if (SHADERS_ENABLED && !DOM_RENDERING) + if (SHADERS_ENABLED && !(DOM_RENDERING && Config.domRendererEnabled)) shManager.registerShaderType('rounded', defaultShaderRounded); } export function registerDefaultShaderShadow(shManager: CoreShaderManager) { - if (SHADERS_ENABLED && !DOM_RENDERING) + if (SHADERS_ENABLED && !(DOM_RENDERING && Config.domRendererEnabled)) shManager.registerShaderType('shadow', defaultShaderShadow); } export function registerDefaultShaderRoundedWithBorder( shManager: CoreShaderManager, ) { - if (SHADERS_ENABLED && !DOM_RENDERING) + if (SHADERS_ENABLED && !(DOM_RENDERING && Config.domRendererEnabled)) shManager.registerShaderType( 'roundedWithBorder', defaultShaderRoundedWithBorder, @@ -141,7 +141,7 @@ export function registerDefaultShaderRoundedWithBorder( export function registerDefaultShaderRoundedWithShadow( shManager: CoreShaderManager, ) { - if (SHADERS_ENABLED && !DOM_RENDERING) + if (SHADERS_ENABLED && !(DOM_RENDERING && Config.domRendererEnabled)) shManager.registerShaderType( 'roundedWithShadow', defaultShaderRoundedWithShadow, @@ -150,31 +150,31 @@ export function registerDefaultShaderRoundedWithShadow( export function registerDefaultShaderRoundedWithBorderAndShadow( shManager: CoreShaderManager, ) { - if (SHADERS_ENABLED && !DOM_RENDERING) + if (SHADERS_ENABLED && !(DOM_RENDERING && Config.domRendererEnabled)) shManager.registerShaderType( 'roundedWithBorderWithShadow', defaultShaderRoundedWithBorderAndShadow, ); } export function registerDefaultShaderHolePunch(shManager: CoreShaderManager) { - if (SHADERS_ENABLED && !DOM_RENDERING) + if (SHADERS_ENABLED && !(DOM_RENDERING && Config.domRendererEnabled)) shManager.registerShaderType('holePunch', defaultShaderHolePunch); } export function registerDefaultShaderRadialGradient( shManager: CoreShaderManager, ) { - if (SHADERS_ENABLED && !DOM_RENDERING) + if (SHADERS_ENABLED && !(DOM_RENDERING && Config.domRendererEnabled)) shManager.registerShaderType('radialGradient', defaultShaderRadialGradient); } export function registerDefaultShaderLinearGradient( shManager: CoreShaderManager, ) { - if (SHADERS_ENABLED && !DOM_RENDERING) + if (SHADERS_ENABLED && !(DOM_RENDERING && Config.domRendererEnabled)) shManager.registerShaderType('linearGradient', defaultShaderLinearGradient); } export function registerDefaultShaders(shManager: CoreShaderManager) { - if (SHADERS_ENABLED && !DOM_RENDERING) { + if (SHADERS_ENABLED && !(DOM_RENDERING && Config.domRendererEnabled)) { registerDefaultShaderRounded(shManager); registerDefaultShaderShadow(shManager); registerDefaultShaderRoundedWithBorder(shManager);