ritual.sh/static/js/button-generator/effects/text-shadow.js
2026-01-13 12:34:41 +00:00

173 lines
5.6 KiB
JavaScript

import { ButtonEffect } from "../effect-base.js";
/**
* Text Drop Shadow Effect
* Renders text with a drop shadow underneath
* This draws the shadow first, then standard text rendering draws on top
*/
export class TextShadowEffect extends ButtonEffect {
constructor(textLineNumber = 1) {
const suffix = textLineNumber === 1 ? "" : "2";
super({
id: `text-shadow${suffix}`,
name: `Drop Shadow ${textLineNumber}`,
type: textLineNumber === 1 ? "text" : "text2",
category: textLineNumber === 1 ? "Text Line 1" : "Text Line 2",
renderOrder: 19, // Before standard text (20), so shadow draws first
textLineNumber: textLineNumber,
});
this.textLineNumber = textLineNumber;
}
defineControls() {
const textLineNumber =
this.textLineNumber || this.config?.textLineNumber || 1;
const suffix = textLineNumber === 1 ? "" : "2";
return [
{
id: `text${suffix}-shadow-enabled`,
type: "checkbox",
label: "Drop Shadow",
defaultValue: false,
description:
"Add drop shadow to text - Not compatible with other text effects!!",
},
{
id: `text${suffix}-shadow-color`,
type: "color",
label: "Shadow Color",
defaultValue: "#000000",
showWhen: `text${suffix}-shadow-enabled`,
description: "Color of the shadow",
},
{
id: `text${suffix}-shadow-blur`,
type: "range",
label: "Shadow Blur",
defaultValue: 4,
min: 0,
max: 10,
step: 1,
showWhen: `text${suffix}-shadow-enabled`,
description: "Blur radius of shadow",
},
{
id: `text${suffix}-shadow-offset-x`,
type: "range",
label: "Shadow X Offset",
defaultValue: 2,
min: -10,
max: 10,
step: 1,
showWhen: `text${suffix}-shadow-enabled`,
description: "Horizontal shadow offset",
},
{
id: `text${suffix}-shadow-offset-y`,
type: "range",
label: "Shadow Y Offset",
defaultValue: 2,
min: -10,
max: 10,
step: 1,
showWhen: `text${suffix}-shadow-enabled`,
description: "Vertical shadow offset",
},
{
id: `text${suffix}-shadow-opacity`,
type: "range",
label: "Shadow Opacity",
defaultValue: 0.8,
min: 0,
max: 1,
step: 0.1,
showWhen: `text${suffix}-shadow-enabled`,
description: "Opacity of the shadow",
},
];
}
isEnabled(controlValues) {
const suffix = this.textLineNumber === 1 ? "" : "2";
const text = controlValues[`button-text${suffix}`];
const shadowEnabled = controlValues[`text${suffix}-shadow-enabled`];
return text && text.trim() !== "" && shadowEnabled;
}
apply(context, controlValues, animState, renderData) {
const suffix = this.textLineNumber === 1 ? "" : "2";
// Check flash visibility - if flash is active and text is invisible, don't render
const flashActive = controlValues[`animate-text-flash${suffix}`];
if (flashActive && renderData[`textFlashVisible${suffix}`] === false) {
return;
}
const text = controlValues[`button-text${suffix}`] || "";
if (!text) return;
// Get shadow settings
const shadowColor =
controlValues[`text${suffix}-shadow-color`] || "#000000";
const shadowBlur = controlValues[`text${suffix}-shadow-blur`] || 4;
const shadowOffsetX = controlValues[`text${suffix}-shadow-offset-x`] || 2;
const shadowOffsetY = controlValues[`text${suffix}-shadow-offset-y`] || 2;
const shadowOpacity = controlValues[`text${suffix}-shadow-opacity`] || 0.8;
// Get text rendering settings
const fontSize = controlValues[`font-size${suffix}`] || 14;
const fontFamily = controlValues[`font-family${suffix}`] || "Arial";
const fontWeight = controlValues[`text${suffix}-bold`] ? "bold" : "normal";
const fontStyle = controlValues[`text${suffix}-italic`]
? "italic"
: "normal";
const textX = (controlValues[`text${suffix}-x`] || 50) / 100;
const textY = (controlValues[`text${suffix}-y`] || 50) / 100;
// Convert hex to rgba
const hexToRgba = (hex, alpha) => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
if (result) {
const r = parseInt(result[1], 16);
const g = parseInt(result[2], 16);
const b = parseInt(result[3], 16);
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}
return `rgba(0, 0, 0, ${alpha})`;
};
// Set up text rendering
context.font = `${fontStyle} ${fontWeight} ${fontSize}px "${fontFamily}"`;
context.textAlign = "center";
context.textBaseline = "middle";
// Calculate text position
const x = renderData.width * textX;
const y = renderData.height * textY;
// Draw the shadow using the shadow API
// This will create a shadow underneath whatever we draw
context.shadowColor = hexToRgba(shadowColor, shadowOpacity);
context.shadowBlur = shadowBlur;
context.shadowOffsetX = shadowOffsetX;
context.shadowOffsetY = shadowOffsetY;
// Draw a solid shadow by filling with the shadow color
// The shadow API will create the blur effect
context.fillStyle = hexToRgba(shadowColor, shadowOpacity);
context.fillText(text, x, y);
// Reset shadow for subsequent renders
context.shadowColor = "transparent";
context.shadowBlur = 0;
context.shadowOffsetX = 0;
context.shadowOffsetY = 0;
}
}
// Export two instances for text line 1 and text line 2
export function register(generator) {
generator.registerEffect(new TextShadowEffect(1));
generator.registerEffect(new TextShadowEffect(2));
}