From c9860cac9eec054f364c027c0266e4ca48219129 Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Sat, 9 Aug 2025 16:15:11 +0200 Subject: [PATCH] Add Master Visibility Toggle to Layers Panel Introduce a three-state checkbox in CanvasLayersPanel header to control visibility of all layers at once. Supports automatic state updates and integrates with renderLayers() for seamless layer management. --- js/CanvasLayersPanel.js | 65 +++++++++++++++++++++++++++++++++ js/css/layers_panel.css | 79 ++++++++++++++++++++++++++++++++++++++++ src/CanvasLayersPanel.ts | 73 ++++++++++++++++++++++++++++++++++++- src/css/layers_panel.css | 79 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 295 insertions(+), 1 deletion(-) diff --git a/js/CanvasLayersPanel.js b/js/CanvasLayersPanel.js index dc46007..61c2b3e 100644 --- a/js/CanvasLayersPanel.js +++ b/js/CanvasLayersPanel.js @@ -103,6 +103,7 @@ export class CanvasLayersPanel { this.container.tabIndex = 0; // Umożliwia fokus na panelu this.container.innerHTML = `
+
Layers
@@ -115,6 +116,7 @@ export class CanvasLayersPanel { this.layersContainer = this.container.querySelector('#layers-container'); // Setup event listeners dla przycisków this.setupControlButtons(); + this.setupMasterVisibilityToggle(); // Dodaj listener dla klawiatury, aby usuwanie działało z panelu this.container.addEventListener('keydown', (e) => { if (e.key === 'Delete' || e.key === 'Backspace') { @@ -142,6 +144,67 @@ export class CanvasLayersPanel { // Initial button state update this.updateButtonStates(); } + setupMasterVisibilityToggle() { + if (!this.container) + return; + const toggleContainer = this.container.querySelector('.master-visibility-toggle'); + if (!toggleContainer) + return; + const updateToggleState = () => { + const total = this.canvas.layers.length; + const visibleCount = this.canvas.layers.filter(l => l.visible).length; + toggleContainer.innerHTML = ''; + const checkboxContainer = document.createElement('div'); + checkboxContainer.className = 'checkbox-container'; + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + checkbox.id = 'master-visibility-checkbox'; + const customCheckbox = document.createElement('span'); + customCheckbox.className = 'custom-checkbox'; + checkboxContainer.appendChild(checkbox); + checkboxContainer.appendChild(customCheckbox); + if (visibleCount === 0) { + checkbox.checked = false; + checkbox.indeterminate = false; + customCheckbox.classList.remove('checked', 'indeterminate'); + } + else if (visibleCount === total) { + checkbox.checked = true; + checkbox.indeterminate = false; + customCheckbox.classList.add('checked'); + customCheckbox.classList.remove('indeterminate'); + } + else { + checkbox.checked = false; + checkbox.indeterminate = true; + customCheckbox.classList.add('indeterminate'); + customCheckbox.classList.remove('checked'); + } + checkboxContainer.addEventListener('click', (e) => { + e.stopPropagation(); + let newVisible; + if (checkbox.indeterminate) { + newVisible = false; // hide all when mixed + } + else if (checkbox.checked) { + newVisible = false; // toggle to hide all + } + else { + newVisible = true; // toggle to show all + } + this.canvas.layers.forEach(layer => { + layer.visible = newVisible; + }); + this.canvas.render(); + this.canvas.requestSaveState(); + updateToggleState(); + this.renderLayers(); + }); + toggleContainer.appendChild(checkboxContainer); + }; + updateToggleState(); + this._updateMasterVisibilityToggle = updateToggleState; + } renderLayers() { if (!this.layersContainer) { log.warn('Layers container not initialized'); @@ -158,6 +221,8 @@ export class CanvasLayersPanel { if (this.layersContainer) this.layersContainer.appendChild(layerElement); }); + if (this._updateMasterVisibilityToggle) + this._updateMasterVisibilityToggle(); log.debug(`Rendered ${sortedLayers.length} layers`); } createLayerElement(layer, index) { diff --git a/js/css/layers_panel.css b/js/css/layers_panel.css index d98d211..a0d636b 100644 --- a/js/css/layers_panel.css +++ b/js/css/layers_panel.css @@ -23,6 +23,85 @@ margin-bottom: 8px; } +.checkbox-container { + display: flex; + align-items: center; + gap: 8px; + padding: 5px 0; + border-radius: 5px; + cursor: pointer; + transition: background-color 0.2s; + position: relative; +} + +.checkbox-container:hover { + background-color: #4a4a4a; +} + +.checkbox-container input[type="checkbox"] { + position: absolute; + opacity: 0; + cursor: pointer; + height: 0; + width: 0; +} + +.checkbox-container .custom-checkbox { + height: 16px; + width: 16px; + background-color: #2a2a2a; + border: 1px solid #666; + border-radius: 3px; + transition: all 0.2s; + position: relative; + flex-shrink: 0; +} + +.checkbox-container input:checked ~ .custom-checkbox { + background-color: #3a76d6; + border-color: #3a76d6; +} + +.checkbox-container .custom-checkbox::after { + content: ""; + position: absolute; + display: none; + left: 5px; + top: 1px; + width: 4px; + height: 9px; + border: solid white; + border-width: 0 2px 2px 0; + transform: rotate(45deg); +} + +.checkbox-container input:checked ~ .custom-checkbox::after { + display: block; +} + +.checkbox-container input:indeterminate ~ .custom-checkbox { + background-color: #3a76d6; + border-color: #3a76d6; +} + +.checkbox-container input:indeterminate ~ .custom-checkbox::after { + display: block; + content: ""; + position: absolute; + top: 7px; + left: 3px; + width: 8px; + height: 2px; + background-color: white; + border: none; + transform: none; + box-shadow: none; +} + +.checkbox-container:hover { + background-color: #4a4a4a; +} + .layers-panel-title { font-weight: bold; color: #ffffff; diff --git a/src/CanvasLayersPanel.ts b/src/CanvasLayersPanel.ts index 219e836..2ee7280 100644 --- a/src/CanvasLayersPanel.ts +++ b/src/CanvasLayersPanel.ts @@ -121,6 +121,7 @@ export class CanvasLayersPanel { this.container.tabIndex = 0; // Umożliwia fokus na panelu this.container.innerHTML = `
+
Layers
@@ -135,6 +136,7 @@ export class CanvasLayersPanel { // Setup event listeners dla przycisków this.setupControlButtons(); + this.setupMasterVisibilityToggle(); // Dodaj listener dla klawiatury, aby usuwanie działało z panelu this.container.addEventListener('keydown', (e: KeyboardEvent) => { @@ -169,6 +171,74 @@ export class CanvasLayersPanel { this.updateButtonStates(); } + setupMasterVisibilityToggle(): void { + if (!this.container) return; + const toggleContainer = this.container.querySelector('.master-visibility-toggle') as HTMLElement; + if (!toggleContainer) return; + + const updateToggleState = () => { + const total = this.canvas.layers.length; + const visibleCount = this.canvas.layers.filter(l => l.visible).length; + toggleContainer.innerHTML = ''; + + const checkboxContainer = document.createElement('div'); + checkboxContainer.className = 'checkbox-container'; + + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + checkbox.id = 'master-visibility-checkbox'; + + const customCheckbox = document.createElement('span'); + customCheckbox.className = 'custom-checkbox'; + + checkboxContainer.appendChild(checkbox); + checkboxContainer.appendChild(customCheckbox); + + if (visibleCount === 0) { + checkbox.checked = false; + checkbox.indeterminate = false; + customCheckbox.classList.remove('checked', 'indeterminate'); + } else if (visibleCount === total) { + checkbox.checked = true; + checkbox.indeterminate = false; + customCheckbox.classList.add('checked'); + customCheckbox.classList.remove('indeterminate'); + } else { + checkbox.checked = false; + checkbox.indeterminate = true; + customCheckbox.classList.add('indeterminate'); + customCheckbox.classList.remove('checked'); + } + + checkboxContainer.addEventListener('click', (e) => { + e.stopPropagation(); + let newVisible: boolean; + if (checkbox.indeterminate) { + newVisible = false; // hide all when mixed + } else if (checkbox.checked) { + newVisible = false; // toggle to hide all + } else { + newVisible = true; // toggle to show all + } + + this.canvas.layers.forEach(layer => { + layer.visible = newVisible; + }); + this.canvas.render(); + this.canvas.requestSaveState(); + updateToggleState(); + this.renderLayers(); + }); + + toggleContainer.appendChild(checkboxContainer); + }; + + updateToggleState(); + this._updateMasterVisibilityToggle = updateToggleState; + } + + private _updateMasterVisibilityToggle?: () => void; + renderLayers(): void { if (!this.layersContainer) { log.warn('Layers container not initialized'); @@ -186,10 +256,11 @@ export class CanvasLayersPanel { sortedLayers.forEach((layer: Layer, index: number) => { const layerElement = this.createLayerElement(layer, index); - if(this.layersContainer) + if (this.layersContainer) this.layersContainer.appendChild(layerElement); }); + if (this._updateMasterVisibilityToggle) this._updateMasterVisibilityToggle(); log.debug(`Rendered ${sortedLayers.length} layers`); } diff --git a/src/css/layers_panel.css b/src/css/layers_panel.css index d98d211..a0d636b 100644 --- a/src/css/layers_panel.css +++ b/src/css/layers_panel.css @@ -23,6 +23,85 @@ margin-bottom: 8px; } +.checkbox-container { + display: flex; + align-items: center; + gap: 8px; + padding: 5px 0; + border-radius: 5px; + cursor: pointer; + transition: background-color 0.2s; + position: relative; +} + +.checkbox-container:hover { + background-color: #4a4a4a; +} + +.checkbox-container input[type="checkbox"] { + position: absolute; + opacity: 0; + cursor: pointer; + height: 0; + width: 0; +} + +.checkbox-container .custom-checkbox { + height: 16px; + width: 16px; + background-color: #2a2a2a; + border: 1px solid #666; + border-radius: 3px; + transition: all 0.2s; + position: relative; + flex-shrink: 0; +} + +.checkbox-container input:checked ~ .custom-checkbox { + background-color: #3a76d6; + border-color: #3a76d6; +} + +.checkbox-container .custom-checkbox::after { + content: ""; + position: absolute; + display: none; + left: 5px; + top: 1px; + width: 4px; + height: 9px; + border: solid white; + border-width: 0 2px 2px 0; + transform: rotate(45deg); +} + +.checkbox-container input:checked ~ .custom-checkbox::after { + display: block; +} + +.checkbox-container input:indeterminate ~ .custom-checkbox { + background-color: #3a76d6; + border-color: #3a76d6; +} + +.checkbox-container input:indeterminate ~ .custom-checkbox::after { + display: block; + content: ""; + position: absolute; + top: 7px; + left: 3px; + width: 8px; + height: 2px; + background-color: white; + border: none; + transform: none; + box-shadow: none; +} + +.checkbox-container:hover { + background-color: #4a4a4a; +} + .layers-panel-title { font-weight: bold; color: #ffffff;