// ==UserScript==
// @name Drawaria.online Selection Tool
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Adds pixel selection, copy, paste, and delete tool to Drawaria.online (client-side only).
// @author YouTubeDrawaria
// @match https://drawaria.online/*
// @grant GM_addStyle
// @icon https://www.google.com/s2/favicons?sz=64&domain=drawaria.online
// @run-at document-idle
// @license MIT
// ==/UserScript==
(function() {
'use_strict';
let gameCanvas = null;
let ctx = null;
let overlayCanvas = null;
let overlayCtx = null;
let isSelecting = false;
let selectionActive = false;
let startX, startY, currentX, currentY;
let selectionRect = { x: 0, y: 0, w: 0, h: 0 };
let copiedImageData = null;
let currentToolIsSelect = false;
let selectionContextMenu = null;
let selectToolButtonElement = null;
GM_addStyle(`
#selectionToolButton {
cursor: pointer; padding: 5px; margin: 2px; border: 1px solid #ccc;
background-color: #f0f0f0; display: flex; align-items: center;
justify-content: center; width: 40px; height: 40px; /* Ajustar a los botones de Drawaria */
box-sizing: border-box; font-size: 20px; user-select: none;
}
#selectionToolButton.active {
background-color: #a0e0a0 !important; border-color: #508050 !important;
}
#selectionContextMenu {
position: fixed; /* Usar fixed para posicionar relativo al viewport */
display: none; background: white; border: 1px solid #ccc;
box-shadow: 2px 2px 5px rgba(0,0,0,0.2); padding: 8px; z-index: 10001;
font-size: 14px;
}
#selectionContextMenu button {
display: block; width: 100%; margin-bottom: 5px; padding: 5px;
border: 1px solid #ddd; background-color: #f8f8f8; cursor: pointer;
text-align: left;
}
#selectionContextMenu button:hover { background-color: #e8e8e8; }
#selectionContextMenu button:disabled {
color: #aaa; cursor: default; background-color: #f0f0f0;
}
`);
function updateOverlayPositionAndSize() {
if (!gameCanvas || !overlayCanvas) return;
const rect = gameCanvas.getBoundingClientRect();
overlayCanvas.style.position = 'fixed'; // Usar fixed para que coincida con getBoundingClientRect
overlayCanvas.style.left = rect.left + 'px';
overlayCanvas.style.top = rect.top + 'px';
overlayCanvas.style.width = rect.width + 'px';
overlayCanvas.style.height = rect.height + 'px';
// Sincronizar el tamaño del canvas interno con el tamaño renderizado del gameCanvas
// Esto es importante si el gameCanvas es escalado por CSS.
overlayCanvas.width = gameCanvas.offsetWidth;
overlayCanvas.height = gameCanvas.offsetHeight;
if (overlayCtx) { // Limpiar si el contexto ya existe
overlayCtx.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height);
if (selectionActive && selectionRect.w > 0 && selectionRect.h > 0) {
// Redibujar la selección si está activa, ya que el canvas se recreó/redimensionó
overlayCtx.strokeStyle = 'rgba(0, 0, 0, 0.8)';
overlayCtx.lineWidth = 1;
overlayCtx.setLineDash([3, 3]);
overlayCtx.strokeRect(selectionRect.x, selectionRect.y, selectionRect.w, selectionRect.h);
}
}
}
function initializeTool() {
gameCanvas = document.getElementById('canvas');
const controlsParent = document.getElementById('drawcontrols');
if (!gameCanvas || !controlsParent) return false;
if (!ctx) ctx = gameCanvas.getContext('2d', { willReadFrequently: true });
if (!overlayCanvas) {
overlayCanvas = document.createElement('canvas');
overlayCanvas.id = 'selectionOverlay';
overlayCanvas.style.pointerEvents = 'none';
overlayCanvas.style.zIndex = '3'; // Encima del gameCanvas
document.body.appendChild(overlayCanvas);
overlayCtx = overlayCanvas.getContext('2d');
setupMouseListeners();
}
updateOverlayPositionAndSize();
if (!selectionContextMenu) setupContextMenu();
if (!addSelectButton(controlsParent)) return false;
window.addEventListener('resize', updateOverlayPositionAndSize);
// Escuchar scroll en el contenedor del canvas si es relevante (Drawaria no parece tenerlo a nivel de canvas)
// gameCanvas.parentElement.addEventListener('scroll', updateOverlayPositionAndSize);
return true;
}
function addSelectButton(controlsParent) {
let controlsContainer = controlsParent.querySelector('.tools-left'); // Contenedor específico
if (!controlsContainer) {
// Fallback más general si el anterior no se encuentra
const groups = controlsParent.querySelectorAll('div[data-ctrlgroup]');
if (groups.length > 0) {
controlsContainer = groups[0]; // Añadir al primer grupo de herramientas
} else {
controlsContainer = controlsParent; // Último fallback
}
}
if (selectToolButtonElement && selectToolButtonElement.parentElement === controlsContainer) {
return true;
}
if (selectToolButtonElement && selectToolButtonElement.parentElement) {
selectToolButtonElement.parentElement.removeChild(selectToolButtonElement); // Quitar del lugar antiguo si existe
}
if (!selectToolButtonElement) {
selectToolButtonElement = document.createElement('div');
selectToolButtonElement.innerHTML = '✂️';
selectToolButtonElement.title = 'Herramienta de Selección';
selectToolButtonElement.id = 'selectionToolButton';
selectToolButtonElement.onclick = toggleSelectTool; // Función separada para el toggle
}
controlsContainer.appendChild(selectToolButtonElement);
return true;
}
function toggleSelectTool() {
currentToolIsSelect = !currentToolIsSelect;
if (currentToolIsSelect) {
selectToolButtonElement.classList.add('active');
if (overlayCanvas) overlayCanvas.style.pointerEvents = 'auto';
document.addEventListener('mousedown', handleDocumentMouseDown, true); // Captura para ejecutarse primero
} else {
selectToolButtonElement.classList.remove('active');
if (overlayCanvas) overlayCanvas.style.pointerEvents = 'none';
clearSelectionRectangle();
if(selectionContextMenu) selectionContextMenu.style.display = 'none';
document.removeEventListener('mousedown', handleDocumentMouseDown, true);
}
console.log("Herramienta de selección activa:", currentToolIsSelect);
}
function handleDocumentMouseDown(e) {
if (!currentToolIsSelect) return;
// Si el clic es en el botón de selección, overlay, o menú, no hacer nada aquí
if (e.target === selectToolButtonElement ||
e.target === overlayCanvas ||
(selectionContextMenu && selectionContextMenu.contains(e.target))) {
return;
}
// Si el clic es en otra parte (p.ej. otra herramienta del juego), desactivar la selección.
console.log("Document click detected, deselecting tool.");
toggleSelectTool(); // Esto cambiará currentToolIsSelect a false y limpiará
}
function setupMouseListeners() {
// ... (igual que en la versión 0.3, asegurándose de usar overlayCanvas.getBoundingClientRect() para las coords)
const oldOverlay = overlayCanvas;
overlayCanvas = oldOverlay.cloneNode(true);
oldOverlay.parentNode.replaceChild(overlayCanvas, oldOverlay);
overlayCtx = overlayCanvas.getContext('2d');
overlayCanvas.addEventListener('mousedown', function(e) {
if (!currentToolIsSelect) return;
if (selectionContextMenu && selectionContextMenu.contains(e.target)) return;
const rect = overlayCanvas.getBoundingClientRect();
startX = e.clientX - rect.left;
startY = e.clientY - rect.top;
isSelecting = true;
selectionActive = false; // Una nueva selección se está iniciando
// No limpiar copiedImageData aquí, para permitir pegar después de una nueva selección (si no se copió nada nuevo)
if (selectionContextMenu) selectionContextMenu.style.display = 'none';
});
overlayCanvas.addEventListener('mousemove', function(e) {
if (!currentToolIsSelect || !isSelecting) return;
const rect = overlayCanvas.getBoundingClientRect();
currentX = e.clientX - rect.left;
currentY = e.clientY - rect.top;
drawSelectionRectangle();
});
overlayCanvas.addEventListener('mouseup', function(e) {
if (!currentToolIsSelect || !isSelecting) return;
isSelecting = false;
// Definir el rectángulo de selección final
selectionRect.x = Math.min(startX, currentX);
selectionRect.y = Math.min(startY, currentY);
selectionRect.w = Math.abs(currentX - startX);
selectionRect.h = Math.abs(currentY - startY);
if (selectionRect.w < 2 || selectionRect.h < 2) { // Ignorar selecciones muy pequeñas
clearSelectionRectangle(); // Esto pone selectionActive = false
} else {
selectionActive = true; // Solo activar si la selección es válida
console.log("Selección hecha:", selectionRect);
}
});
overlayCanvas.addEventListener('contextmenu', function(e) {
if (!currentToolIsSelect || !selectionActive || !selectionRect || selectionRect.w === 0 || selectionRect.h === 0) {
if(selectionContextMenu) selectionContextMenu.style.display = 'none';
return;
}
e.preventDefault();
if (!selectionContextMenu) setupContextMenu();
selectionContextMenu.style.left = e.clientX + 'px';
selectionContextMenu.style.top = e.clientY + 'px';
selectionContextMenu.style.display = 'block';
document.getElementById('ctxPaste').disabled = !copiedImageData;
});
}
function drawSelectionRectangle() {
// ... (igual que en la versión 0.3)
if (!overlayCtx || !overlayCanvas) return;
overlayCtx.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height);
overlayCtx.strokeStyle = 'rgba(0, 0, 0, 0.8)';
overlayCtx.lineWidth = 1; // Línea delgada para más precisión visual
overlayCtx.setLineDash([2, 2]); // Puntos más pequeños
overlayCtx.strokeRect(startX, startY, currentX - startX, currentY - startY);
}
function clearSelectionRectangle() {
// ... (igual que en la versión 0.3)
if (!overlayCtx || !overlayCanvas) return;
overlayCtx.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height);
selectionActive = false;
selectionRect = { x: 0, y: 0, w: 0, h: 0 };
}
function setupContextMenu() {
// ... (igual que en la versión 0.3, pero asegurar posicionamiento 'fixed')
if (document.getElementById('selectionContextMenu')) return;
selectionContextMenu = document.createElement('div');
selectionContextMenu.id = 'selectionContextMenu';
// Estilos ahora son GM_addStyle
selectionContextMenu.innerHTML = `
<button id="ctxCopy">Copiar</button><button id="ctxPaste" disabled>Pegar</button>
<button id="ctxDelete">Eliminar</button><hr style="margin: 5px 0;">
<button id="ctxClearSel">Limpiar Selección</button>`;
document.body.appendChild(selectionContextMenu);
document.getElementById('ctxCopy').onclick = function() {
if (selectionActive && selectionRect.w > 0 && selectionRect.h > 0 && ctx) {
try {
copiedImageData = ctx.getImageData(selectionRect.x, selectionRect.y, selectionRect.w, selectionRect.h);
} catch (err) { console.error("Error copying:", err); alert("Error al copiar: " + err.message); copiedImageData = null; }
}
selectionContextMenu.style.display = 'none';
};
document.getElementById('ctxPaste').onclick = function() {
if (copiedImageData && overlayCanvas && ctx) {
alert("Haz clic en el canvas donde quieras pegar.");
overlayCanvas.style.pointerEvents = 'auto'; overlayCanvas.style.cursor = 'crosshair';
const pasteHandler = function(e) {
const rect = overlayCanvas.getBoundingClientRect();
const pasteX = e.clientX - rect.left - copiedImageData.width / 2;
const pasteY = e.clientY - rect.top - copiedImageData.height / 2;
ctx.putImageData(copiedImageData, pasteX, pasteY);
overlayCanvas.removeEventListener('click', pasteHandler);
overlayCanvas.style.cursor = '';
overlayCanvas.style.pointerEvents = currentToolIsSelect ? 'auto' : 'none';
};
overlayCanvas.addEventListener('click', pasteHandler, { once: true });
}
selectionContextMenu.style.display = 'none';
};
document.getElementById('ctxDelete').onclick = function() {
if (selectionActive && selectionRect.w > 0 && selectionRect.h > 0 && ctx) {
ctx.fillStyle = 'white'; ctx.fillRect(selectionRect.x, selectionRect.y, selectionRect.w, selectionRect.h);
clearSelectionRectangle();
}
selectionContextMenu.style.display = 'none';
};
document.getElementById('ctxClearSel').onclick = function() {
clearSelectionRectangle(); selectionContextMenu.style.display = 'none';
};
document.addEventListener('mousedown', function(e) { // Cambiado a mousedown
if (selectionContextMenu && selectionContextMenu.style.display === 'block' &&
!selectionContextMenu.contains(e.target) && e.target !== overlayCanvas) {
selectionContextMenu.style.display = 'none';
}
}, true); // Usar captura
}
// --- Lógica de inicialización ---
let initializationAttempts = 0;
const MAX_INIT_ATTEMPTS = 20; // Más intentos
let toolFullyInitialized = false;
const observer = new MutationObserver((mutationsList, obs) => {
if (!toolFullyInitialized) {
toolFullyInitialized = initializeTool();
} else {
updateOverlayPositionAndSize(); // Mantener overlay alineado
const controlsParent = document.getElementById('drawcontrols');
if (controlsParent && selectToolButtonElement && !selectToolButtonElement.parentElement) {
// Si el botón fue removido del DOM por alguna razón, intentar re-añadirlo
addSelectButton(controlsParent);
} else if (controlsParent && selectToolButtonElement && selectToolButtonElement.parentElement.id !== controlsParent.id &&
!controlsParent.contains(selectToolButtonElement)) {
// Si el padre cambió o ya no lo contiene, intentar re-añadirlo
addSelectButton(controlsParent);
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
function delayedInitializeCheck() {
if (toolFullyInitialized || initializationAttempts >= MAX_INIT_ATTEMPTS) {
if (!toolFullyInitialized) console.error("Drawaria Tool: Failed to init.");
return;
}
initializationAttempts++;
toolFullyInitialized = initializeTool();
if (!toolFullyInitialized) setTimeout(delayedInitializeCheck, 500);
}
setTimeout(delayedInitializeCheck, 500); // Reducido el primer delay
})();