UNPKG

@cognima/banners

Version:

Biblioteca avançada para geração de banners dinâmicos para diversas plataformas e aplicações

1,209 lines (1,062 loc) 41.5 kB
"use strict"; /** * Módulo de elementos interativos para banners * * Este módulo fornece funções para criar elementos que simulam * interatividade nos banners estáticos. * * @module interactive-elements * @author Cognima Team (melhorado) * @version 2.0.0 */ const utils = require("../utils"); const animationEffects = require("./animation-effects"); /** * Cria um botão com efeito de hover * * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {number} x - Posição X do botão * @param {number} y - Posição Y do botão * @param {number} width - Largura do botão * @param {number} height - Altura do botão * @param {string} text - Texto do botão * @param {Object} [options={}] - Opções de configuração * @param {string} [options.backgroundColor="#007BFF"] - Cor de fundo do botão * @param {string} [options.textColor="#FFFFFF"] - Cor do texto * @param {string} [options.font="bold 24px Poppins"] - Fonte do texto * @param {number} [options.borderRadius=8] - Raio da borda * @param {boolean} [options.isHovered=false] - Se o botão está em estado de hover * @param {string} [options.hoverEffect="glow"] - Efeito de hover ("glow", "scale", "shadow", "color") * @param {string} [options.hoverColor="#0056b3"] - Cor do botão em estado de hover */ function drawInteractiveButton(ctx, x, y, width, height, text, options = {}) { // Valores padrão const defaults = { backgroundColor: "#007BFF", textColor: "#FFFFFF", font: "bold 24px Poppins", borderRadius: 8, isHovered: false, hoverEffect: "glow", hoverColor: "#0056b3" }; // Mescla as opções com os valores padrão const settings = { ...defaults, ...options }; // Salva o estado atual do contexto ctx.save(); // Aplica o efeito de hover se necessário if (settings.isHovered) { switch (settings.hoverEffect) { case "glow": animationEffects.addGlowEffect( ctx, x - 5, y - 5, width + 10, height + 10, settings.backgroundColor, 0.7, 15 ); break; case "scale": // Aumenta ligeiramente o tamanho do botão x -= width * 0.05; y -= height * 0.05; width *= 1.1; height *= 1.1; break; case "shadow": animationEffects.add3DShadowEffect( ctx, x, y, width, height, 8, "#000000", 0.3 ); break; case "color": // Muda a cor de fundo settings.backgroundColor = settings.hoverColor; break; } } // Desenha o botão utils.roundRect(ctx, x, y, width, height, settings.borderRadius, true); // Configura o texto ctx.font = settings.font; ctx.fillStyle = settings.textColor; ctx.textAlign = "center"; ctx.textBaseline = "middle"; // Desenha o texto ctx.fillText(text, x + width / 2, y + height / 2); // Restaura o estado do contexto ctx.restore(); } /** * Cria um campo de entrada de texto * * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {number} x - Posição X do campo * @param {number} y - Posição Y do campo * @param {number} width - Largura do campo * @param {number} height - Altura do campo * @param {string} [placeholder="Digite aqui..."] - Texto de placeholder * @param {string} [value=""] - Valor atual do campo * @param {Object} [options={}] - Opções de configuração * @param {string} [options.backgroundColor="#FFFFFF"] - Cor de fundo do campo * @param {string} [options.textColor="#333333"] - Cor do texto * @param {string} [options.placeholderColor="#999999"] - Cor do placeholder * @param {string} [options.borderColor="#CCCCCC"] - Cor da borda * @param {number} [options.borderWidth=1] - Largura da borda * @param {number} [options.borderRadius=4] - Raio da borda * @param {string} [options.font="16px Poppins"] - Fonte do texto * @param {boolean} [options.isFocused=false] - Se o campo está em foco * @param {boolean} [options.showCursor=false] - Se deve mostrar o cursor */ function drawInputField(ctx, x, y, width, height, placeholder = "Digite aqui...", value = "", options = {}) { // Valores padrão const defaults = { backgroundColor: "#FFFFFF", textColor: "#333333", placeholderColor: "#999999", borderColor: "#CCCCCC", borderWidth: 1, borderRadius: 4, font: "16px Poppins", isFocused: false, showCursor: false }; // Mescla as opções com os valores padrão const settings = { ...defaults, ...options }; // Salva o estado atual do contexto ctx.save(); // Desenha o fundo do campo ctx.fillStyle = settings.backgroundColor; utils.roundRect(ctx, x, y, width, height, settings.borderRadius, true); // Desenha a borda ctx.strokeStyle = settings.isFocused ? "#007BFF" : settings.borderColor; ctx.lineWidth = settings.borderWidth; utils.roundRect(ctx, x, y, width, height, settings.borderRadius, false, true); // Configura o texto ctx.font = settings.font; ctx.textAlign = "left"; ctx.textBaseline = "middle"; // Padding interno const paddingX = 10; // Desenha o texto ou placeholder if (value) { ctx.fillStyle = settings.textColor; // Limita o texto à largura do campo const maxTextWidth = width - paddingX * 2; const text = utils.truncateText(ctx, value, maxTextWidth); ctx.fillText(text, x + paddingX, y + height / 2); // Desenha o cursor se necessário if (settings.isFocused && settings.showCursor) { const textWidth = ctx.measureText(text).width; ctx.fillRect(x + paddingX + textWidth + 2, y + height * 0.25, 2, height * 0.5); } } else { ctx.fillStyle = settings.placeholderColor; ctx.fillText(placeholder, x + paddingX, y + height / 2); } // Restaura o estado do contexto ctx.restore(); } /** * Cria um checkbox * * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {number} x - Posição X do checkbox * @param {number} y - Posição Y do checkbox * @param {number} size - Tamanho do checkbox * @param {string} label - Texto do label * @param {boolean} [checked=false] - Se o checkbox está marcado * @param {Object} [options={}] - Opções de configuração * @param {string} [options.backgroundColor="#FFFFFF"] - Cor de fundo do checkbox * @param {string} [options.checkedColor="#007BFF"] - Cor quando marcado * @param {string} [options.borderColor="#CCCCCC"] - Cor da borda * @param {string} [options.textColor="#333333"] - Cor do texto * @param {string} [options.font="16px Poppins"] - Fonte do texto * @param {boolean} [options.isHovered=false] - Se o checkbox está em estado de hover */ function drawCheckbox(ctx, x, y, size, label, checked = false, options = {}) { // Valores padrão const defaults = { backgroundColor: "#FFFFFF", checkedColor: "#007BFF", borderColor: "#CCCCCC", textColor: "#333333", font: "16px Poppins", isHovered: false }; // Mescla as opções com os valores padrão const settings = { ...defaults, ...options }; // Salva o estado atual do contexto ctx.save(); // Desenha o fundo do checkbox ctx.fillStyle = settings.backgroundColor; ctx.strokeStyle = settings.isHovered ? settings.checkedColor : settings.borderColor; ctx.lineWidth = 2; // Desenha o quadrado do checkbox ctx.beginPath(); ctx.rect(x, y, size, size); ctx.fill(); ctx.stroke(); // Desenha o check se estiver marcado if (checked) { ctx.fillStyle = settings.checkedColor; // Desenha um check estilizado ctx.beginPath(); ctx.moveTo(x + size * 0.2, y + size * 0.5); ctx.lineTo(x + size * 0.4, y + size * 0.7); ctx.lineTo(x + size * 0.8, y + size * 0.3); ctx.lineWidth = 3; ctx.strokeStyle = settings.checkedColor; ctx.stroke(); } // Desenha o label ctx.font = settings.font; ctx.fillStyle = settings.textColor; ctx.textAlign = "left"; ctx.textBaseline = "middle"; ctx.fillText(label, x + size + 10, y + size / 2); // Restaura o estado do contexto ctx.restore(); } /** * Cria um radio button * * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {number} x - Posição X do radio button * @param {number} y - Posição Y do radio button * @param {number} size - Tamanho do radio button * @param {string} label - Texto do label * @param {boolean} [selected=false] - Se o radio button está selecionado * @param {Object} [options={}] - Opções de configuração * @param {string} [options.backgroundColor="#FFFFFF"] - Cor de fundo do radio button * @param {string} [options.selectedColor="#007BFF"] - Cor quando selecionado * @param {string} [options.borderColor="#CCCCCC"] - Cor da borda * @param {string} [options.textColor="#333333"] - Cor do texto * @param {string} [options.font="16px Poppins"] - Fonte do texto * @param {boolean} [options.isHovered=false] - Se o radio button está em estado de hover */ function drawRadioButton(ctx, x, y, size, label, selected = false, options = {}) { // Valores padrão const defaults = { backgroundColor: "#FFFFFF", selectedColor: "#007BFF", borderColor: "#CCCCCC", textColor: "#333333", font: "16px Poppins", isHovered: false }; // Mescla as opções com os valores padrão const settings = { ...defaults, ...options }; // Salva o estado atual do contexto ctx.save(); // Desenha o círculo externo ctx.beginPath(); ctx.arc(x + size / 2, y + size / 2, size / 2, 0, Math.PI * 2); ctx.fillStyle = settings.backgroundColor; ctx.fill(); ctx.strokeStyle = settings.isHovered ? settings.selectedColor : settings.borderColor; ctx.lineWidth = 2; ctx.stroke(); // Desenha o círculo interno se estiver selecionado if (selected) { ctx.beginPath(); ctx.arc(x + size / 2, y + size / 2, size / 4, 0, Math.PI * 2); ctx.fillStyle = settings.selectedColor; ctx.fill(); } // Desenha o label ctx.font = settings.font; ctx.fillStyle = settings.textColor; ctx.textAlign = "left"; ctx.textBaseline = "middle"; ctx.fillText(label, x + size + 10, y + size / 2); // Restaura o estado do contexto ctx.restore(); } /** * Cria um slider * * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {number} x - Posição X do slider * @param {number} y - Posição Y do slider * @param {number} width - Largura do slider * @param {number} [value=0.5] - Valor atual do slider (0-1) * @param {Object} [options={}] - Opções de configuração * @param {string} [options.trackColor="#CCCCCC"] - Cor da trilha * @param {string} [options.progressColor="#007BFF"] - Cor do progresso * @param {string} [options.handleColor="#FFFFFF"] - Cor do manipulador * @param {number} [options.handleSize=20] - Tamanho do manipulador * @param {number} [options.trackHeight=6] - Altura da trilha * @param {boolean} [options.showValue=false] - Se deve mostrar o valor * @param {number} [options.min=0] - Valor mínimo * @param {number} [options.max=100] - Valor máximo * @param {string} [options.valueFormat="{value}"] - Formato do valor * @param {boolean} [options.isActive=false] - Se o slider está ativo */ function drawSlider(ctx, x, y, width, value = 0.5, options = {}) { // Valores padrão const defaults = { trackColor: "#CCCCCC", progressColor: "#007BFF", handleColor: "#FFFFFF", handleSize: 20, trackHeight: 6, showValue: false, min: 0, max: 100, valueFormat: "{value}", isActive: false }; // Mescla as opções com os valores padrão const settings = { ...defaults, ...options }; // Limita o valor entre 0 e 1 value = Math.max(0, Math.min(1, value)); // Salva o estado atual do contexto ctx.save(); // Desenha a trilha ctx.fillStyle = settings.trackColor; utils.roundRect(ctx, x, y + settings.handleSize / 2 - settings.trackHeight / 2, width, settings.trackHeight, settings.trackHeight / 2, true); // Desenha o progresso const progressWidth = width * value; ctx.fillStyle = settings.progressColor; utils.roundRect(ctx, x, y + settings.handleSize / 2 - settings.trackHeight / 2, progressWidth, settings.trackHeight, settings.trackHeight / 2, true); // Desenha o manipulador const handleX = x + progressWidth - settings.handleSize / 2; const handleY = y; // Adiciona sombra se estiver ativo if (settings.isActive) { ctx.shadowColor = "rgba(0,0,0,0.3)"; ctx.shadowBlur = 10; ctx.shadowOffsetX = 2; ctx.shadowOffsetY = 2; } ctx.fillStyle = settings.handleColor; ctx.strokeStyle = settings.progressColor; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(handleX, handleY + settings.handleSize / 2, settings.handleSize / 2, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); // Remove a sombra ctx.shadowColor = "transparent"; ctx.shadowBlur = 0; ctx.shadowOffsetX = 0; ctx.shadowOffsetY = 0; // Mostra o valor se necessário if (settings.showValue) { const actualValue = Math.round(settings.min + (settings.max - settings.min) * value); const valueText = settings.valueFormat.replace("{value}", actualValue); ctx.font = "14px Poppins"; ctx.fillStyle = "#333333"; ctx.textAlign = "center"; ctx.fillText(valueText, x + width / 2, y + settings.handleSize + 20); } // Restaura o estado do contexto ctx.restore(); } /** * Cria um toggle switch * * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {number} x - Posição X do toggle * @param {number} y - Posição Y do toggle * @param {number} width - Largura do toggle * @param {number} height - Altura do toggle * @param {boolean} [checked=false] - Se o toggle está ligado * @param {Object} [options={}] - Opções de configuração * @param {string} [options.onColor="#007BFF"] - Cor quando ligado * @param {string} [options.offColor="#CCCCCC"] - Cor quando desligado * @param {string} [options.handleColor="#FFFFFF"] - Cor do manipulador * @param {string} [options.label=""] - Texto do label * @param {string} [options.labelColor="#333333"] - Cor do label * @param {string} [options.font="16px Poppins"] - Fonte do label * @param {boolean} [options.isHovered=false] - Se o toggle está em estado de hover */ function drawToggleSwitch(ctx, x, y, width, height, checked = false, options = {}) { // Valores padrão const defaults = { onColor: "#007BFF", offColor: "#CCCCCC", handleColor: "#FFFFFF", label: "", labelColor: "#333333", font: "16px Poppins", isHovered: false }; // Mescla as opções com os valores padrão const settings = { ...defaults, ...options }; // Salva o estado atual do contexto ctx.save(); // Desenha o fundo do toggle ctx.fillStyle = checked ? settings.onColor : settings.offColor; utils.roundRect(ctx, x, y, width, height, height / 2, true); // Calcula a posição do manipulador const handleSize = height - 4; const handleX = checked ? x + width - handleSize - 2 : x + 2; const handleY = y + 2; // Adiciona sombra se estiver em hover if (settings.isHovered) { ctx.shadowColor = "rgba(0,0,0,0.2)"; ctx.shadowBlur = 5; ctx.shadowOffsetX = 1; ctx.shadowOffsetY = 1; } // Desenha o manipulador ctx.fillStyle = settings.handleColor; ctx.beginPath(); ctx.arc(handleX + handleSize / 2, handleY + handleSize / 2, handleSize / 2, 0, Math.PI * 2); ctx.fill(); // Remove a sombra ctx.shadowColor = "transparent"; ctx.shadowBlur = 0; ctx.shadowOffsetX = 0; ctx.shadowOffsetY = 0; // Desenha o label se necessário if (settings.label) { ctx.font = settings.font; ctx.fillStyle = settings.labelColor; ctx.textAlign = "left"; ctx.textBaseline = "middle"; ctx.fillText(settings.label, x + width + 10, y + height / 2); } // Restaura o estado do contexto ctx.restore(); } /** * Cria um dropdown * * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {number} x - Posição X do dropdown * @param {number} y - Posição Y do dropdown * @param {number} width - Largura do dropdown * @param {number} height - Altura do dropdown * @param {string} [selectedOption="Selecione..."] - Opção selecionada * @param {Array<string>} [options=[]] - Lista de opções * @param {Object} [settings={}] - Opções de configuração * @param {string} [settings.backgroundColor="#FFFFFF"] - Cor de fundo do dropdown * @param {string} [settings.textColor="#333333"] - Cor do texto * @param {string} [settings.borderColor="#CCCCCC"] - Cor da borda * @param {number} [settings.borderWidth=1] - Largura da borda * @param {number} [settings.borderRadius=4] - Raio da borda * @param {string} [settings.font="16px Poppins"] - Fonte do texto * @param {boolean} [settings.isOpen=false] - Se o dropdown está aberto * @param {boolean} [settings.isHovered=false] - Se o dropdown está em estado de hover * @param {number} [settings.maxHeight=200] - Altura máxima do dropdown aberto */ function drawDropdown(ctx, x, y, width, height, selectedOption = "Selecione...", options = [], settings = {}) { // Valores padrão const defaults = { backgroundColor: "#FFFFFF", textColor: "#333333", borderColor: "#CCCCCC", borderWidth: 1, borderRadius: 4, font: "16px Poppins", isOpen: false, isHovered: false, maxHeight: 200 }; // Mescla as opções com os valores padrão const config = { ...defaults, ...settings }; // Salva o estado atual do contexto ctx.save(); // Desenha o dropdown fechado ctx.fillStyle = config.backgroundColor; utils.roundRect(ctx, x, y, width, height, config.borderRadius, true); // Desenha a borda ctx.strokeStyle = config.isHovered || config.isOpen ? "#007BFF" : config.borderColor; ctx.lineWidth = config.borderWidth; utils.roundRect(ctx, x, y, width, height, config.borderRadius, false, true); // Configura o texto ctx.font = config.font; ctx.fillStyle = config.textColor; ctx.textAlign = "left"; ctx.textBaseline = "middle"; // Padding interno const paddingX = 10; // Desenha o texto selecionado const maxTextWidth = width - paddingX * 2 - 20; // 20px para o ícone de seta const truncatedText = utils.truncateText(ctx, selectedOption, maxTextWidth); ctx.fillText(truncatedText, x + paddingX, y + height / 2); // Desenha o ícone de seta const arrowX = x + width - 20; const arrowY = y + height / 2; ctx.beginPath(); if (config.isOpen) { // Seta para cima ctx.moveTo(arrowX - 5, arrowY + 3); ctx.lineTo(arrowX, arrowY - 3); ctx.lineTo(arrowX + 5, arrowY + 3); } else { // Seta para baixo ctx.moveTo(arrowX - 5, arrowY - 3); ctx.lineTo(arrowX, arrowY + 3); ctx.lineTo(arrowX + 5, arrowY - 3); } ctx.fillStyle = config.textColor; ctx.fill(); // Desenha o dropdown aberto se necessário if (config.isOpen && options.length > 0) { const optionHeight = height; const dropdownHeight = Math.min(options.length * optionHeight, config.maxHeight); // Desenha o fundo do dropdown ctx.fillStyle = config.backgroundColor; utils.roundRect(ctx, x, y + height, width, dropdownHeight, config.borderRadius, true); // Desenha a borda ctx.strokeStyle = config.borderColor; ctx.lineWidth = config.borderWidth; utils.roundRect(ctx, x, y + height, width, dropdownHeight, config.borderRadius, false, true); // Desenha as opções for (let i = 0; i < options.length; i++) { const optionY = y + height + i * optionHeight; // Verifica se a opção está visível (para scrolling) if (optionY < y + height + dropdownHeight) { // Destaca a opção selecionada if (options[i] === selectedOption) { ctx.fillStyle = "#F0F0F0"; ctx.fillRect(x + 1, optionY, width - 2, optionHeight); } // Desenha o texto da opção ctx.fillStyle = config.textColor; const truncatedOption = utils.truncateText(ctx, options[i], width - paddingX * 2); ctx.fillText(truncatedOption, x + paddingX, optionY + optionHeight / 2); } } } // Restaura o estado do contexto ctx.restore(); } /** * Cria um tooltip * * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {number} x - Posição X do tooltip * @param {number} y - Posição Y do tooltip * @param {number} width - Largura do tooltip * @param {number} height - Altura do tooltip * @param {string} text - Texto do tooltip * @param {Object} [options={}] - Opções de configuração * @param {string} [options.backgroundColor="#333333"] - Cor de fundo do tooltip * @param {string} [options.textColor="#FFFFFF"] - Cor do texto * @param {number} [options.borderRadius=4] - Raio da borda * @param {string} [options.position="top"] - Posição do tooltip ("top", "bottom", "left", "right") * @param {string} [options.font="14px Poppins"] - Fonte do texto * @param {number} [options.arrowSize=8] - Tamanho da seta */ function drawTooltip(ctx, x, y, width, height, text, options = {}) { // Valores padrão const defaults = { backgroundColor: "#333333", textColor: "#FFFFFF", borderRadius: 4, position: "top", font: "14px Poppins", arrowSize: 8 }; // Mescla as opções com os valores padrão const settings = { ...defaults, ...options }; // Salva o estado atual do contexto ctx.save(); // Calcula a posição do tooltip e da seta let tooltipX = x; let tooltipY = y; let arrowX, arrowY; switch (settings.position) { case "bottom": tooltipY = y + settings.arrowSize; arrowX = tooltipX + width / 2; arrowY = tooltipY - settings.arrowSize; break; case "left": tooltipX = x - width - settings.arrowSize; arrowX = tooltipX + width; arrowY = tooltipY + height / 2; break; case "right": tooltipX = x + settings.arrowSize; arrowX = tooltipX - settings.arrowSize; arrowY = tooltipY + height / 2; break; case "top": default: tooltipY = y - height - settings.arrowSize; arrowX = tooltipX + width / 2; arrowY = tooltipY + height; break; } // Adiciona sombra ctx.shadowColor = "rgba(0,0,0,0.3)"; ctx.shadowBlur = 5; ctx.shadowOffsetX = 2; ctx.shadowOffsetY = 2; // Desenha o fundo do tooltip ctx.fillStyle = settings.backgroundColor; utils.roundRect(ctx, tooltipX, tooltipY, width, height, settings.borderRadius, true); // Remove a sombra para a seta ctx.shadowColor = "transparent"; ctx.shadowBlur = 0; ctx.shadowOffsetX = 0; ctx.shadowOffsetY = 0; // Desenha a seta ctx.fillStyle = settings.backgroundColor; ctx.beginPath(); switch (settings.position) { case "bottom": ctx.moveTo(arrowX - settings.arrowSize, arrowY + settings.arrowSize); ctx.lineTo(arrowX, arrowY); ctx.lineTo(arrowX + settings.arrowSize, arrowY + settings.arrowSize); break; case "left": ctx.moveTo(arrowX - settings.arrowSize, arrowY - settings.arrowSize); ctx.lineTo(arrowX, arrowY); ctx.lineTo(arrowX - settings.arrowSize, arrowY + settings.arrowSize); break; case "right": ctx.moveTo(arrowX + settings.arrowSize, arrowY - settings.arrowSize); ctx.lineTo(arrowX, arrowY); ctx.lineTo(arrowX + settings.arrowSize, arrowY + settings.arrowSize); break; case "top": default: ctx.moveTo(arrowX - settings.arrowSize, arrowY - settings.arrowSize); ctx.lineTo(arrowX, arrowY); ctx.lineTo(arrowX + settings.arrowSize, arrowY - settings.arrowSize); break; } ctx.closePath(); ctx.fill(); // Desenha o texto ctx.font = settings.font; ctx.fillStyle = settings.textColor; ctx.textAlign = "center"; ctx.textBaseline = "middle"; // Quebra o texto se necessário utils.wrapTextCentered( ctx, text, tooltipX + width / 2, tooltipY + height / 2, width - 16, 20 ); // Restaura o estado do contexto ctx.restore(); } /** * Cria um badge * * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {number} x - Posição X do badge * @param {number} y - Posição Y do badge * @param {string} text - Texto do badge * @param {Object} [options={}] - Opções de configuração * @param {string} [options.backgroundColor="#007BFF"] - Cor de fundo do badge * @param {string} [options.textColor="#FFFFFF"] - Cor do texto * @param {number} [options.borderRadius=12] - Raio da borda * @param {string} [options.font="bold 12px Poppins"] - Fonte do texto * @param {number} [options.paddingX=8] - Padding horizontal * @param {number} [options.paddingY=4] - Padding vertical * @param {boolean} [options.isAnimated=false] - Se o badge deve ter efeito de pulso */ function drawBadge(ctx, x, y, text, options = {}) { // Valores padrão const defaults = { backgroundColor: "#007BFF", textColor: "#FFFFFF", borderRadius: 12, font: "bold 12px Poppins", paddingX: 8, paddingY: 4, isAnimated: false }; // Mescla as opções com os valores padrão const settings = { ...defaults, ...options }; // Salva o estado atual do contexto ctx.save(); // Configura o texto para medir suas dimensões ctx.font = settings.font; const textWidth = ctx.measureText(text).width; const textHeight = parseInt(settings.font) * 0.7; // Aproximação da altura // Calcula as dimensões do badge const width = textWidth + settings.paddingX * 2; const height = textHeight + settings.paddingY * 2; // Adiciona efeito de pulso se necessário if (settings.isAnimated) { animationEffects.addPulseEffect( ctx, x + width / 2, y + height / 2, Math.max(width, height) / 2, settings.backgroundColor, 0.3 ); } // Desenha o fundo do badge ctx.fillStyle = settings.backgroundColor; utils.roundRect(ctx, x, y, width, height, settings.borderRadius, true); // Desenha o texto ctx.fillStyle = settings.textColor; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText(text, x + width / 2, y + height / 2); // Restaura o estado do contexto ctx.restore(); // Retorna as dimensões do badge para uso posterior return { width, height }; } /** * Cria um spinner de carregamento * * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {number} x - Posição X do spinner * @param {number} y - Posição Y do spinner * @param {number} radius - Raio do spinner * @param {Object} [options={}] - Opções de configuração * @param {string} [options.color="#007BFF"] - Cor do spinner * @param {number} [options.lineWidth=3] - Largura da linha * @param {number} [options.segments=8] - Número de segmentos * @param {number} [options.rotation=0.7] - Rotação simulada (0-1) * @param {boolean} [options.showText=false] - Se deve mostrar texto * @param {string} [options.text="Carregando..."] - Texto a ser mostrado * @param {string} [options.textColor="#333333"] - Cor do texto * @param {string} [options.font="14px Poppins"] - Fonte do texto */ function drawLoadingSpinner(ctx, x, y, radius, options = {}) { // Valores padrão const defaults = { color: "#007BFF", lineWidth: 3, segments: 8, rotation: 0.7, showText: false, text: "Carregando...", textColor: "#333333", font: "14px Poppins" }; // Mescla as opções com os valores padrão const settings = { ...defaults, ...options }; // Salva o estado atual do contexto ctx.save(); // Desenha os segmentos do spinner const angleStep = (Math.PI * 2) / settings.segments; const startAngle = settings.rotation * Math.PI * 2; for (let i = 0; i < settings.segments; i++) { const angle = startAngle + i * angleStep; const opacity = 0.2 + (0.8 * i) / settings.segments; ctx.beginPath(); ctx.arc(x, y, radius, angle, angle + angleStep * 0.8); ctx.strokeStyle = utils.hexToRgba(settings.color, opacity); ctx.lineWidth = settings.lineWidth; ctx.stroke(); } // Desenha o texto se necessário if (settings.showText) { ctx.font = settings.font; ctx.fillStyle = settings.textColor; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText(settings.text, x, y + radius + 20); } // Restaura o estado do contexto ctx.restore(); } /** * Cria uma barra de progresso * * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {number} x - Posição X da barra * @param {number} y - Posição Y da barra * @param {number} width - Largura da barra * @param {number} height - Altura da barra * @param {number} [progress=0.5] - Progresso (0-1) * @param {Object} [options={}] - Opções de configuração * @param {string} [options.backgroundColor="#EEEEEE"] - Cor de fundo da barra * @param {string} [options.progressColor="#007BFF"] - Cor do progresso * @param {number} [options.borderRadius=4] - Raio da borda * @param {boolean} [options.showPercentage=false] - Se deve mostrar porcentagem * @param {string} [options.textColor="#333333"] - Cor do texto * @param {string} [options.font="12px Poppins"] - Fonte do texto * @param {boolean} [options.isAnimated=false] - Se a barra deve ter efeito de brilho */ function drawProgressBar(ctx, x, y, width, height, progress = 0.5, options = {}) { // Valores padrão const defaults = { backgroundColor: "#EEEEEE", progressColor: "#007BFF", borderRadius: 4, showPercentage: false, textColor: "#333333", font: "12px Poppins", isAnimated: false }; // Mescla as opções com os valores padrão const settings = { ...defaults, ...options }; // Limita o progresso entre 0 e 1 progress = Math.max(0, Math.min(1, progress)); // Salva o estado atual do contexto ctx.save(); // Desenha o fundo da barra ctx.fillStyle = settings.backgroundColor; utils.roundRect(ctx, x, y, width, height, settings.borderRadius, true); // Calcula a largura do progresso const progressWidth = width * progress; // Desenha o progresso if (progressWidth > 0) { ctx.fillStyle = settings.progressColor; utils.roundRect(ctx, x, y, progressWidth, height, settings.borderRadius, true); // Adiciona efeito de brilho se necessário if (settings.isAnimated) { animationEffects.addHighlightEffect( ctx, x, y, progressWidth, height, "#FFFFFF", 45, 0.3 ); } } // Desenha a porcentagem se necessário if (settings.showPercentage) { const percentage = Math.round(progress * 100); ctx.font = settings.font; ctx.fillStyle = settings.textColor; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText(`${percentage}%`, x + width / 2, y + height / 2); } // Restaura o estado do contexto ctx.restore(); } /** * Cria um botão de mídia social * * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {number} x - Posição X do botão * @param {number} y - Posição Y do botão * @param {number} size - Tamanho do botão * @param {string} platform - Plataforma de mídia social ("facebook", "twitter", "instagram", "linkedin", "youtube", "tiktok") * @param {Object} [options={}] - Opções de configuração * @param {boolean} [options.isHovered=false] - Se o botão está em estado de hover * @param {boolean} [options.isRound=true] - Se o botão deve ser redondo * @param {boolean} [options.showName=false] - Se deve mostrar o nome da plataforma * @param {string} [options.namePosition="right"] - Posição do nome ("right", "bottom") * @param {string} [options.font="14px Poppins"] - Fonte do nome */ function drawSocialButton(ctx, x, y, size, platform, options = {}) { // Valores padrão const defaults = { isHovered: false, isRound: true, showName: false, namePosition: "right", font: "14px Poppins" }; // Mescla as opções com os valores padrão const settings = { ...defaults, ...options }; // Configurações específicas para cada plataforma const platforms = { facebook: { color: "#1877F2", icon: "f", // Simplificado para exemplo name: "Facebook" }, twitter: { color: "#1DA1F2", icon: "t", name: "Twitter" }, instagram: { color: "#E4405F", icon: "i", name: "Instagram" }, linkedin: { color: "#0A66C2", icon: "in", name: "LinkedIn" }, youtube: { color: "#FF0000", icon: "yt", name: "YouTube" }, tiktok: { color: "#000000", icon: "tk", name: "TikTok" } }; // Obtém as configurações da plataforma const platformConfig = platforms[platform.toLowerCase()] || { color: "#999999", icon: "?", name: platform }; // Salva o estado atual do contexto ctx.save(); // Adiciona efeito de hover se necessário if (settings.isHovered) { ctx.shadowColor = "rgba(0,0,0,0.3)"; ctx.shadowBlur = 10; ctx.shadowOffsetX = 0; ctx.shadowOffsetY = 0; } // Desenha o fundo do botão ctx.fillStyle = platformConfig.color; if (settings.isRound) { ctx.beginPath(); ctx.arc(x + size / 2, y + size / 2, size / 2, 0, Math.PI * 2); ctx.fill(); } else { utils.roundRect(ctx, x, y, size, size, size * 0.2, true); } // Remove a sombra ctx.shadowColor = "transparent"; ctx.shadowBlur = 0; ctx.shadowOffsetX = 0; ctx.shadowOffsetY = 0; // Desenha o ícone (simplificado para exemplo) ctx.fillStyle = "#FFFFFF"; ctx.font = `bold ${size * 0.5}px Arial`; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText(platformConfig.icon, x + size / 2, y + size / 2); // Desenha o nome se necessário if (settings.showName) { ctx.font = settings.font; ctx.fillStyle = "#333333"; ctx.textAlign = "left"; if (settings.namePosition === "bottom") { ctx.textAlign = "center"; ctx.fillText(platformConfig.name, x + size / 2, y + size + 20); } else { ctx.textBaseline = "middle"; ctx.fillText(platformConfig.name, x + size + 10, y + size / 2); } } // Restaura o estado do contexto ctx.restore(); } /** * Cria um botão de reprodução de mídia * * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {number} x - Posição X do botão * @param {number} y - Posição Y do botão * @param {number} size - Tamanho do botão * @param {string} type - Tipo do botão ("play", "pause", "stop", "next", "previous") * @param {Object} [options={}] - Opções de configuração * @param {string} [options.backgroundColor="rgba(0,0,0,0.7)"] - Cor de fundo do botão * @param {string} [options.iconColor="#FFFFFF"] - Cor do ícone * @param {boolean} [options.isHovered=false] - Se o botão está em estado de hover * @param {boolean} [options.isActive=false] - Se o botão está ativo */ function drawMediaButton(ctx, x, y, size, type, options = {}) { // Valores padrão const defaults = { backgroundColor: "rgba(0,0,0,0.7)", iconColor: "#FFFFFF", isHovered: false, isActive: false }; // Mescla as opções com os valores padrão const settings = { ...defaults, ...options }; // Salva o estado atual do contexto ctx.save(); // Adiciona efeito de hover ou ativo if (settings.isHovered || settings.isActive) { ctx.shadowColor = "rgba(0,0,0,0.5)"; ctx.shadowBlur = 10; ctx.shadowOffsetX = 0; ctx.shadowOffsetY = 0; } // Desenha o fundo do botão ctx.fillStyle = settings.isActive ? utils.adjustColor(settings.backgroundColor, 0.2) : settings.backgroundColor; ctx.beginPath(); ctx.arc(x + size / 2, y + size / 2, size / 2, 0, Math.PI * 2); ctx.fill(); // Remove a sombra ctx.shadowColor = "transparent"; ctx.shadowBlur = 0; ctx.shadowOffsetX = 0; ctx.shadowOffsetY = 0; // Desenha o ícone ctx.fillStyle = settings.iconColor; const iconSize = size * 0.4; const centerX = x + size / 2; const centerY = y + size / 2; switch (type) { case "play": ctx.beginPath(); ctx.moveTo(centerX - iconSize / 3, centerY - iconSize / 2); ctx.lineTo(centerX - iconSize / 3, centerY + iconSize / 2); ctx.lineTo(centerX + iconSize / 2, centerY); ctx.closePath(); ctx.fill(); break; case "pause": ctx.fillRect(centerX - iconSize / 2, centerY - iconSize / 2, iconSize / 3, iconSize); ctx.fillRect(centerX + iconSize / 6, centerY - iconSize / 2, iconSize / 3, iconSize); break; case "stop": ctx.fillRect(centerX - iconSize / 2, centerY - iconSize / 2, iconSize, iconSize); break; case "next": ctx.beginPath(); ctx.moveTo(centerX - iconSize / 2, centerY - iconSize / 2); ctx.lineTo(centerX, centerY); ctx.lineTo(centerX - iconSize / 2, centerY + iconSize / 2); ctx.closePath(); ctx.fill(); ctx.beginPath(); ctx.moveTo(centerX, centerY - iconSize / 2); ctx.lineTo(centerX + iconSize / 2, centerY); ctx.lineTo(centerX, centerY + iconSize / 2); ctx.closePath(); ctx.fill(); break; case "previous": ctx.beginPath(); ctx.moveTo(centerX + iconSize / 2, centerY - iconSize / 2); ctx.lineTo(centerX, centerY); ctx.lineTo(centerX + iconSize / 2, centerY + iconSize / 2); ctx.closePath(); ctx.fill(); ctx.beginPath(); ctx.moveTo(centerX, centerY - iconSize / 2); ctx.lineTo(centerX - iconSize / 2, centerY); ctx.lineTo(centerX, centerY + iconSize / 2); ctx.closePath(); ctx.fill(); break; } // Restaura o estado do contexto ctx.restore(); } module.exports = { drawInteractiveButton, drawInputField, drawCheckbox, drawRadioButton, drawSlider, drawToggleSwitch, drawDropdown, drawTooltip, drawBadge, drawLoadingSpinner, drawProgressBar, drawSocialButton, drawMediaButton };