<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Advanced 2D Grid Rectangle Drawer with Enhanced Interaction</title>
    <style>
        body {
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            height: 100vh;
            margin: 0;
            background-color: #f0f0f0;
            font-family: Arial, sans-serif;
        }
        #controls {
            margin-bottom: 10px;
        }
        canvas {
            border: 1px solid #000;
            background-color: #fff;
            cursor: crosshair;
        }
        /* Optional: Style for buttons */
        button {
            margin-left: 20px;
            padding: 5px 10px;
            font-size: 14px;
            cursor: pointer;
        }
    </style>
</head>
<body>
    <div id="controls">
        <label for="colorPicker">Select Rectangle Color: </label>
        <input type="color" id="colorPicker" value="#0080ff">
        
        <!-- Transparency Slider -->
        <label for="transparencySlider" style="margin-left: 20px;">Transparency: </label>
        <input type="range" id="transparencySlider" min="0" max="1" step="0.05" value="0.5">
        <span id="transparencyValue">50%</span>
        
        <!-- New Buttons: Horizontal and Vertical Iteration -->
        <button id="horizontalIteration">Horizontal Iteration</button>
        <button id="verticalIteration">Vertical Iteration</button>
    </div>
    <canvas id="gridCanvas" width="800" height="600"></canvas>

    <script>
        const canvas = document.getElementById('gridCanvas');
        const ctx = canvas.getContext('2d');

        const colorPicker = document.getElementById('colorPicker');
        const transparencySlider = document.getElementById('transparencySlider');
        const transparencyValue = document.getElementById('transparencyValue');
        const horizontalIterationBtn = document.getElementById('horizontalIteration');
        const verticalIterationBtn = document.getElementById('verticalIteration'); // New Button

        const gridSize = 20; // Size of each grid cell in pixels
        const rectangles = []; // Array to store drawn rectangles

        // State variables
        let isDrawing = false;
        let isDragging = false;
        let isDuplicating = false;
        let isResizing = false;
        let resizeHandle = null;
        let startX = 0;
        let startY = 0;
        let currentRect = null;
        let dragOffsetX = 0;
        let dragOffsetY = 0;
        let selectedRect = null;
        let initialMousePos = { x: 0, y: 0 };

        let duplicationTimer = null; // Timer for press-and-hold duplication
        const DUPLICATION_DELAY = 500; // Duration in milliseconds to trigger duplication
        const MOVE_THRESHOLD = 5; // Movement threshold in pixels to distinguish drag from click

        /**
         * Converts a hex color to an RGBA string with the specified alpha.
         * @param {string} hex - The hex color string (e.g., "#0080ff").
         * @param {number} alpha - The alpha value (0 to 1).
         * @returns {string} The RGBA color string.
         */
        function hexToRgba(hex, alpha) {
            // Remove '#' if present
            hex = hex.replace('#', '');

            // Parse r, g, b values
            let r, g, b;

            if (hex.length === 3) {
                r = parseInt(hex[0] + hex[0], 16);
                g = parseInt(hex[1] + hex[1], 16);
                b = parseInt(hex[2] + hex[2], 16);
            } else if (hex.length === 6) {
                r = parseInt(hex.substring(0, 2), 16);
                g = parseInt(hex.substring(2, 4), 16);
                b = parseInt(hex.substring(4, 6), 16);
            } else {
                throw new Error('Invalid hex color.');
            }

            return `rgba(${r}, ${g}, ${b}, ${alpha})`;
        }

        // Update transparency display
        transparencySlider.addEventListener('input', () => {
            transparencyValue.textContent = `${Math.round(transparencySlider.value * 100)}%`;
            // If a rectangle is currently being drawn, update its transparency
            if (isDrawing && currentRect) {
                currentRect.alpha = parseFloat(transparencySlider.value);
                drawAll();
            }
            // If a rectangle is selected and not being resized or dragged, redraw to reflect new transparency
            if (selectedRect && !isResizing && !isDragging) {
                drawAll();
            }
        });

        /**
         * Draws the grid lines on the canvas.
         */
        function drawGridLines() {
            ctx.strokeStyle = '#e0e0e0';
            ctx.lineWidth = 1;
            for (let x = 0; x <= canvas.width; x += gridSize) {
                ctx.beginPath();
                ctx.moveTo(x, 0);
                ctx.lineTo(x, canvas.height);
                ctx.stroke();
            }
            for (let y = 0; y <= canvas.height; y += gridSize) {
                ctx.beginPath();
                ctx.moveTo(0, y);
                ctx.lineTo(canvas.width, y);
                ctx.stroke();
            }
        }

        /**
         * Draws all rectangles, including resize handles if selected.
         */
        function drawRectangles() {
            rectangles.forEach(rect => {
                // Set fill style with transparency
                ctx.fillStyle = hexToRgba(rect.color, rect.alpha);
                ctx.strokeStyle = 'black';
                ctx.lineWidth = 1;
                ctx.fillRect(rect.x, rect.y, rect.width, rect.height);
                ctx.strokeRect(rect.x, rect.y, rect.width, rect.height);

                // If rectangle is selected, draw resize handles
                if (rect === selectedRect) {
                    drawResizeHandles(rect);
                }
            });

            // Draw the current rectangle being drawn
            if (isDrawing && currentRect) {
                ctx.fillStyle = hexToRgba(currentRect.color, currentRect.alpha);
                ctx.strokeStyle = 'black';
                ctx.lineWidth = 1;
                ctx.fillRect(currentRect.x, currentRect.y, currentRect.width, currentRect.height);
                ctx.strokeRect(currentRect.x, currentRect.y, currentRect.width, currentRect.height);
            }
        }

        /**
         * Draws resize handles on the corners of a rectangle.
         * @param {Object} rect - The rectangle object.
         */
        function drawResizeHandles(rect) {
            const handleSize = 8;
            const halfSize = handleSize / 2;
            const handles = getRectangleHandles(rect);

            ctx.fillStyle = 'white';
            ctx.strokeStyle = 'black';
            ctx.lineWidth = 1;
            handles.forEach(handle => {
                ctx.beginPath();
                ctx.rect(handle.x - halfSize, handle.y - halfSize, handleSize, handleSize);
                ctx.fill();
                ctx.stroke();
            });
        }

        /**
         * Returns the positions of the resize handles for a rectangle.
         * @param {Object} rect - The rectangle object.
         * @returns {Array} Array of handle positions.
         */
        function getRectangleHandles(rect) {
            return [
                { x: rect.x, y: rect.y, position: 'nw' }, // Top-left
                { x: rect.x + rect.width, y: rect.y, position: 'ne' }, // Top-right
                { x: rect.x, y: rect.y + rect.height, position: 'sw' }, // Bottom-left
                { x: rect.x + rect.width, y: rect.y + rect.height, position: 'se' } // Bottom-right
            ];
        }

        /**
         * Determines if the given mouse coordinates are over a resize handle.
         * @param {number} x - Mouse x-coordinate.
         * @param {number} y - Mouse y-coordinate.
         * @returns {string|null} The handle position or null if not over a handle.
         */
        function getHandleUnderMouse(x, y) {
            if (!selectedRect) return null;

            const handleSize = 8;
            const halfSize = handleSize / 2;
            const handles = getRectangleHandles(selectedRect);

            for (let handle of handles) {
                if (
                    x >= handle.x - halfSize &&
                    x <= handle.x + halfSize &&
                    y >= handle.y - halfSize &&
                    y <= handle.y + halfSize
                ) {
                    return handle.position; // 'nw', 'ne', 'sw', 'se'
                }
            }

            return null;
        }

        /**
         * Draws the entire grid and all rectangles.
         */
        function drawAll() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            drawGridLines();
            drawRectangles();
        }

        /**
         * Snaps the given coordinate to the nearest grid line.
         * @param {number} x - The x-coordinate.
         * @param {number} y - The y-coordinate.
         * @returns {Object} The snapped coordinates.
         */
        function snapToGrid(x, y) {
            return {
                x: Math.floor(x / gridSize) * gridSize,
                y: Math.floor(y / gridSize) * gridSize
            };
        }

        /**
         * Checks if a point is inside a rectangle.
         * @param {number} x - The x-coordinate.
         * @param {number} y - The y-coordinate.
         * @param {Object} rect - The rectangle object.
         * @returns {boolean} True if the point is inside the rectangle.
         */
        function isPointInRect(x, y, rect) {
            return (
                x >= rect.x &&
                x <= rect.x + rect.width &&
                y >= rect.y &&
                y <= rect.y + rect.height
            );
        }

        /**
         * Checks if a point is inside any rectangle.
         * @param {number} x - The x-coordinate.
         * @param {number} y - The y-coordinate.
         * @returns {boolean} True if the point is inside any rectangle.
         */
        function isPointInAnyRect(x, y) {
            for (let i = rectangles.length - 1; i >= 0; i--) {
                if (isPointInRect(x, y, rectangles[i])) {
                    return true;
                }
            }
            return false;
        }

        /**
         * Handles mousedown events on the canvas.
         * @param {MouseEvent} e - The mousedown event.
         */
        canvas.addEventListener('mousedown', (e) => {
            const rectCanvas = canvas.getBoundingClientRect();
            const mouseX = e.clientX - rectCanvas.left;
            const mouseY = e.clientY - rectCanvas.top;
            initialMousePos = { x: mouseX, y: mouseY };

            // Check if clicking on a resize handle
            const handle = getHandleUnderMouse(mouseX, mouseY);
            if (handle) {
                isResizing = true;
                resizeHandle = handle;
                return;
            }

            // Check if clicking on an existing rectangle (from top to bottom)
            for (let i = rectangles.length - 1; i >= 0; i--) {
                if (isPointInRect(mouseX, mouseY, rectangles[i])) {
                    const clickedRect = rectangles[i];

                    if (clickedRect === selectedRect) {
                        // If already selected, start duplication timer
                        duplicationTimer = setTimeout(() => {
                            // Determine duplication direction based on modifier keys
                            // For future enhancements; currently, handle button clicks only
                            duplicationTimer = null;
                            // After duplication, keep selection on the new rectangle
                            // selectedRect = null; // Optionally keep it selected
                            drawAll();
                        }, DUPLICATION_DELAY);

                        // Set flags
                        isDragging = false;
                        isDuplicating = true;

                        // Change cursor
                        canvas.style.cursor = 'move';

                        return;
                    } else {
                        // Select the new rectangle
                        selectedRect = clickedRect;

                        // Bring the selected rectangle to the top layer
                        rectangles.splice(i, 1); // Remove from current position
                        rectangles.push(selectedRect); // Add to the top

                        // Start duplication timer
                        duplicationTimer = setTimeout(() => {
                            // Determine duplication direction based on modifier keys
                            duplicationTimer = null;
                            // After duplication, keep selection on the new rectangle
                            // selectedRect = null; // Optionally keep it selected
                            drawAll();
                        }, DUPLICATION_DELAY);

                        // Set flags
                        isDragging = false;
                        isDuplicating = true;

                        // Change cursor
                        canvas.style.cursor = 'move';

                        drawAll();
                        return;
                    }
                }
            }

            // If not clicking on any rectangle or handle, start drawing a new rectangle
            const snapped = snapToGrid(mouseX, mouseY);
            startX = snapped.x;
            startY = snapped.y;
            isDrawing = true;
            currentRect = {
                x: startX,
                y: startY,
                width: 0,
                height: 0,
                color: colorPicker.value,
                alpha: parseFloat(transparencySlider.value) // Add alpha
            };

            // Deselect any previously selected rectangle
            selectedRect = null;
            drawAll();
        });

        /**
         * Handles mousemove events on the canvas.
         * @param {MouseEvent} e - The mousemove event.
         */
        canvas.addEventListener('mousemove', (e) => {
            const rectCanvas = canvas.getBoundingClientRect();
            const mouseX = e.clientX - rectCanvas.left;
            const mouseY = e.clientY - rectCanvas.top;
            const movementX = Math.abs(mouseX - initialMousePos.x);
            const movementY = Math.abs(mouseY - initialMousePos.y);

            // If resizing
            if (isResizing && selectedRect) {
                let newX = selectedRect.x;
                let newY = selectedRect.y;
                let newWidth = selectedRect.width;
                let newHeight = selectedRect.height;

                const snapped = snapToGrid(mouseX, mouseY);

                switch (resizeHandle) {
                    case 'nw':
                        newWidth = selectedRect.width + (selectedRect.x - snapped.x);
                        newHeight = selectedRect.height + (selectedRect.y - snapped.y);
                        newX = snapped.x;
                        newY = snapped.y;
                        break;
                    case 'ne':
                        newWidth = snapped.x - selectedRect.x;
                        newHeight = selectedRect.height + (selectedRect.y - snapped.y);
                        newY = snapped.y;
                        break;
                    case 'sw':
                        newWidth = selectedRect.width + (selectedRect.x - snapped.x);
                        newHeight = snapped.y - selectedRect.y;
                        newX = snapped.x;
                        break;
                    case 'se':
                        newWidth = snapped.x - selectedRect.x;
                        newHeight = snapped.y - selectedRect.y;
                        break;
                }

                // Ensure minimum size
                newWidth = Math.max(gridSize, newWidth);
                newHeight = Math.max(gridSize, newHeight);

                // Snap new position and size
                newX = Math.floor(newX / gridSize) * gridSize;
                newY = Math.floor(newY / gridSize) * gridSize;
                newWidth = Math.floor(newWidth / gridSize) * gridSize;
                newHeight = Math.floor(newHeight / gridSize) * gridSize;

                // Update selected rectangle
                selectedRect.x = newX;
                selectedRect.y = newY;
                selectedRect.width = newWidth;
                selectedRect.height = newHeight;

                drawAll();
                return;
            }

            // If dragging
            if (isDragging && selectedRect) {
                const snapped = snapToGrid(mouseX - dragOffsetX, mouseY - dragOffsetY);
                selectedRect.x = snapped.x;
                selectedRect.y = snapped.y;
                drawAll();
                return;
            }

            // If duplicating and movement exceeds threshold, cancel duplication and start dragging
            if (isDuplicating) {
                if (movementX > MOVE_THRESHOLD || movementY > MOVE_THRESHOLD) {
                    clearTimeout(duplicationTimer);
                    duplicationTimer = null;
                    isDuplicating = false;
                    isDragging = true;
                    dragOffsetX = mouseX - selectedRect.x;
                    dragOffsetY = mouseY - selectedRect.y;
                    canvas.style.cursor = 'move';
                }
            }

            // If drawing a new rectangle
            if (isDrawing && currentRect) {
                const snapped = snapToGrid(mouseX, mouseY);

                const x = Math.min(startX, snapped.x);
                const y = Math.min(startY, snapped.y);
                const width = Math.abs(snapped.x - startX);
                const height = Math.abs(snapped.y - startY);

                currentRect = { 
                    x, 
                    y, 
                    width, 
                    height, 
                    color: colorPicker.value,
                    alpha: parseFloat(transparencySlider.value) // Update alpha
                };
                drawAll();
                return;
            }

            // Handle cursor change based on hover state
            if (!isResizing && !isDragging && !isDrawing) {
                const handle = getHandleUnderMouse(mouseX, mouseY);
                if (handle) {
                    // Change cursor based on handle position
                    switch (handle) {
                        case 'nw':
                        case 'se':
                            canvas.style.cursor = 'nwse-resize';
                            break;
                        case 'ne':
                        case 'sw':
                            canvas.style.cursor = 'nesw-resize';
                            break;
                        default:
                            canvas.style.cursor = 'crosshair';
                    }
                } else {
                    // Change cursor based on whether hovering over a rectangle
                    let hovering = false;
                    for (let i = rectangles.length - 1; i >= 0; i--) {
                        if (isPointInRect(mouseX, mouseY, rectangles[i])) {
                            hovering = true;
                            break;
                        }
                    }
                    canvas.style.cursor = hovering ? 'pointer' : 'crosshair';
                }
            }
        });

        /**
         * Handles mouseup events on the canvas.
         * @param {MouseEvent} e - The mouseup event.
         */
        canvas.addEventListener('mouseup', (e) => {
            // If resizing
            if (isResizing) {
                isResizing = false;
                resizeHandle = null;
                drawAll();
                return;
            }

            // If duplicating
            if (isDuplicating) {
                // If duplication hasn't occurred yet, treat it as a click
                clearTimeout(duplicationTimer);
                duplicationTimer = null;

                // Do not deselect to keep resize handles active
                isDuplicating = false;
                drawAll();
                return;
            }

            // If dragging
            if (isDragging) {
                isDragging = false;
                drawAll();
                return;
            }

            // If drawing
            if (isDrawing) {
                isDrawing = false;
                if (currentRect.width > 0 && currentRect.height > 0) {
                    rectangles.push(currentRect);
                }
                currentRect = null;
                drawAll();
                return;
            }
        });

        /**
         * Handles mouseout events on the canvas.
         * @param {MouseEvent} e - The mouseout event.
         */
        canvas.addEventListener('mouseout', (e) => {
            // Cancel any ongoing actions
            if (isResizing) {
                isResizing = false;
                resizeHandle = null;
            }
            if (isDuplicating) {
                clearTimeout(duplicationTimer);
                duplicationTimer = null;
                isDuplicating = false;
                // Do not deselect to keep resize handles active
            }
            if (isDragging) {
                isDragging = false;
            }
            if (isDrawing) {
                isDrawing = false;
                currentRect = null;
            }
            canvas.style.cursor = 'crosshair';
            drawAll();
        });

        // Prevent default drag behavior
        canvas.addEventListener('dragstart', (e) => {
            e.preventDefault();
        });

        /**
         * Handles keyboard events for deleting selected rectangles.
         * @param {KeyboardEvent} e - The keyboard event.
         */
        window.addEventListener('keydown', (e) => {
            if (e.key === 'Delete' || e.key === 'Backspace') {
                if (selectedRect) {
                    // Remove the selected rectangle from the array
                    const index = rectangles.indexOf(selectedRect);
                    if (index > -1) {
                        rectangles.splice(index, 1);
                        selectedRect = null;
                        drawAll();
                    }
                    // Prevent default behavior to avoid unwanted actions
                    e.preventDefault();
                }
            }
        });

        /**
         * Function to duplicate a rectangle, shifting it horizontally or vertically based on direction.
         * @param {Object} rect - The rectangle to duplicate.
         * @param {string} direction - The direction to duplicate ('horizontal' or 'vertical').
         */
        function duplicateRectangle(rect, direction) {
            let newRect = {
                x: rect.x,
                y: rect.y,
                width: rect.width,
                height: rect.height,
                color: rect.color,
                alpha: rect.alpha
            };

            if (direction === 'horizontal') {
                newRect.x += rect.width; // Shift to the right
            } else if (direction === 'vertical') {
                newRect.y += rect.height; // Shift downward
            }

            // Snap new position to grid
            newRect.x = Math.floor(newRect.x / gridSize) * gridSize;
            newRect.y = Math.floor(newRect.y / gridSize) * gridSize;

            // Ensure the new rectangle stays within canvas boundaries
            if (newRect.x + newRect.width > canvas.width || newRect.y + newRect.height > canvas.height) {
                alert(`Cannot duplicate: Not enough space to the ${direction}.`);
                return;
            }

            // Check for overlap with existing rectangles
            const overlaps = rectangles.some(existingRect => {
                return (
                    !(newRect.x + newRect.width <= existingRect.x ||
                    newRect.x >= existingRect.x + existingRect.width ||
                    newRect.y + newRect.height <= existingRect.y ||
                    newRect.y >= existingRect.y + existingRect.height)
                );
            });

            if (overlaps) {
                alert(`Cannot duplicate: The duplicated rectangle would overlap with existing rectangles.`);
                return;
            }

            // Add the duplicated rectangle to the array
            rectangles.push(newRect);
            selectedRect = newRect; // Automatically select the new rectangle
            drawAll();
        }

        /**
         * Handles click events on the "Horizontal Iteration" button.
         */
        horizontalIterationBtn.addEventListener('click', () => {
            if (selectedRect) {
                duplicateRectangle(selectedRect, 'horizontal');
            } else {
                alert('Please select a rectangle to duplicate horizontally.');
            }
        });

        /**
         * Handles click events on the "Vertical Iteration" button.
         */
        verticalIterationBtn.addEventListener('click', () => {
            if (selectedRect) {
                duplicateRectangle(selectedRect, 'vertical');
            } else {
                alert('Please select a rectangle to duplicate vertically.');
            }
        });

        // Initial draw
        drawAll();
    </script>
</body>
</html>   this is the code for a drawing app. i want multiselection of rectangles with ctrl and mouse click. when iterating if multiple objects is selected then all of them must be iterated.