mirror of
https://github.com/Azornes/Comfyui-LayerForge.git
synced 2026-03-25 14:25:44 -03:00
Refactor: Move blend mode menu styles to CSS file
Moved all blend mode menu styles from CanvasLayers.ts to a dedicated CSS file. Replaced inline styles with CSS classes and preserved all functionality.
This commit is contained in:
@@ -238,7 +238,8 @@ export class CanvasIO {
|
|||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
log.error(`Failed to send data for node ${nodeId}:`, error);
|
log.error(`Failed to send data for node ${nodeId}:`, error);
|
||||||
throw new Error(`Failed to get confirmation from server for node ${nodeId}. The workflow might not have the latest canvas data.`);
|
throw new Error(`Failed to get confirmation from server for node ${nodeId}. ` +
|
||||||
|
`Make sure that the nodeId: (${nodeId}) matches the "node_id" value in the node options. If they don't match, you may need to manually set the node_id to ${nodeId}.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async addInputToCanvas(inputImage, inputMask) {
|
async addInputToCanvas(inputImage, inputMask) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { createModuleLogger } from "./utils/LoggerUtils.js";
|
|||||||
import { generateUUID, generateUniqueFileName, createCanvas } from "./utils/CommonUtils.js";
|
import { generateUUID, generateUniqueFileName, createCanvas } from "./utils/CommonUtils.js";
|
||||||
import { withErrorHandling, createValidationError } from "./ErrorHandler.js";
|
import { withErrorHandling, createValidationError } from "./ErrorHandler.js";
|
||||||
import { showErrorNotification } from "./utils/NotificationUtils.js";
|
import { showErrorNotification } from "./utils/NotificationUtils.js";
|
||||||
|
import { addStylesheet, getUrl } from "./utils/ResourceManager.js";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { app } from "../../scripts/app.js";
|
import { app } from "../../scripts/app.js";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -122,6 +123,8 @@ export class CanvasLayers {
|
|||||||
this.isAdjustingOpacity = false;
|
this.isAdjustingOpacity = false;
|
||||||
this.internalClipboard = [];
|
this.internalClipboard = [];
|
||||||
this.clipboardPreference = 'system';
|
this.clipboardPreference = 'system';
|
||||||
|
// Load CSS for blend mode menu
|
||||||
|
addStylesheet(getUrl('./css/blend_mode_menu.css'));
|
||||||
}
|
}
|
||||||
async copySelectedLayers() {
|
async copySelectedLayers() {
|
||||||
if (this.canvas.canvasSelection.selectedLayers.length === 0)
|
if (this.canvas.canvasSelection.selectedLayers.length === 0)
|
||||||
@@ -785,65 +788,14 @@ export class CanvasLayers {
|
|||||||
const menu = document.createElement('div');
|
const menu = document.createElement('div');
|
||||||
this.blendMenuElement = menu;
|
this.blendMenuElement = menu;
|
||||||
menu.id = 'blend-mode-menu';
|
menu.id = 'blend-mode-menu';
|
||||||
menu.style.cssText = `
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
background: #2a2a2a;
|
|
||||||
border: 1px solid #3a3a3a;
|
|
||||||
border-radius: 4px;
|
|
||||||
z-index: 10000;
|
|
||||||
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
|
|
||||||
min-width: 200px;
|
|
||||||
`;
|
|
||||||
const titleBar = document.createElement('div');
|
const titleBar = document.createElement('div');
|
||||||
titleBar.style.cssText = `
|
titleBar.className = 'blend-menu-title-bar';
|
||||||
background: #3a3a3a;
|
|
||||||
color: white;
|
|
||||||
padding: 8px 10px;
|
|
||||||
cursor: move;
|
|
||||||
user-select: none;
|
|
||||||
border-radius: 3px 3px 0 0;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: bold;
|
|
||||||
border-bottom: 1px solid #4a4a4a;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
`;
|
|
||||||
const titleText = document.createElement('span');
|
const titleText = document.createElement('span');
|
||||||
titleText.textContent = `Blend Mode: ${selectedLayer.name}`;
|
titleText.textContent = `Blend Mode: ${selectedLayer.name}`;
|
||||||
titleText.style.cssText = `
|
titleText.className = 'blend-menu-title-text';
|
||||||
flex: 1;
|
|
||||||
cursor: move;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
`;
|
|
||||||
const closeButton = document.createElement('button');
|
const closeButton = document.createElement('button');
|
||||||
closeButton.textContent = '×';
|
closeButton.textContent = '×';
|
||||||
closeButton.style.cssText = `
|
closeButton.className = 'blend-menu-close-button';
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: white;
|
|
||||||
font-size: 18px;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 3px;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
`;
|
|
||||||
closeButton.onmouseover = () => {
|
|
||||||
closeButton.style.backgroundColor = '#4a4a4a';
|
|
||||||
};
|
|
||||||
closeButton.onmouseout = () => {
|
|
||||||
closeButton.style.backgroundColor = 'transparent';
|
|
||||||
};
|
|
||||||
closeButton.onclick = (e) => {
|
closeButton.onclick = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this.closeBlendModeMenu();
|
this.closeBlendModeMenu();
|
||||||
@@ -851,18 +803,19 @@ export class CanvasLayers {
|
|||||||
titleBar.appendChild(titleText);
|
titleBar.appendChild(titleText);
|
||||||
titleBar.appendChild(closeButton);
|
titleBar.appendChild(closeButton);
|
||||||
const content = document.createElement('div');
|
const content = document.createElement('div');
|
||||||
content.style.cssText = `padding: 5px;`;
|
content.className = 'blend-menu-content';
|
||||||
menu.appendChild(titleBar);
|
menu.appendChild(titleBar);
|
||||||
menu.appendChild(content);
|
menu.appendChild(content);
|
||||||
const blendAreaContainer = document.createElement('div');
|
const blendAreaContainer = document.createElement('div');
|
||||||
blendAreaContainer.style.cssText = `padding: 5px 10px; border-bottom: 1px solid #4a4a4a;`;
|
blendAreaContainer.className = 'blend-area-container';
|
||||||
const blendAreaLabel = document.createElement('label');
|
const blendAreaLabel = document.createElement('label');
|
||||||
blendAreaLabel.textContent = 'Blend Area';
|
blendAreaLabel.textContent = 'Blend Area';
|
||||||
blendAreaLabel.style.color = 'white';
|
blendAreaLabel.className = 'blend-area-label';
|
||||||
const blendAreaSlider = document.createElement('input');
|
const blendAreaSlider = document.createElement('input');
|
||||||
blendAreaSlider.type = 'range';
|
blendAreaSlider.type = 'range';
|
||||||
blendAreaSlider.min = '0';
|
blendAreaSlider.min = '0';
|
||||||
blendAreaSlider.max = '100';
|
blendAreaSlider.max = '100';
|
||||||
|
blendAreaSlider.className = 'blend-area-slider';
|
||||||
blendAreaSlider.value = selectedLayer?.blendArea?.toString() ?? '0';
|
blendAreaSlider.value = selectedLayer?.blendArea?.toString() ?? '0';
|
||||||
blendAreaSlider.oninput = () => {
|
blendAreaSlider.oninput = () => {
|
||||||
if (selectedLayer) {
|
if (selectedLayer) {
|
||||||
@@ -912,20 +865,19 @@ export class CanvasLayers {
|
|||||||
this.blendModes.forEach((mode) => {
|
this.blendModes.forEach((mode) => {
|
||||||
const container = document.createElement('div');
|
const container = document.createElement('div');
|
||||||
container.className = 'blend-mode-container';
|
container.className = 'blend-mode-container';
|
||||||
container.style.cssText = `margin-bottom: 5px;`;
|
|
||||||
const option = document.createElement('div');
|
const option = document.createElement('div');
|
||||||
option.style.cssText = `padding: 5px 10px; color: white; cursor: pointer; transition: background-color 0.2s;`;
|
option.className = 'blend-mode-option';
|
||||||
option.textContent = `${mode.label} (${mode.name})`;
|
option.textContent = `${mode.label} (${mode.name})`;
|
||||||
const slider = document.createElement('input');
|
const slider = document.createElement('input');
|
||||||
slider.type = 'range';
|
slider.type = 'range';
|
||||||
slider.min = '0';
|
slider.min = '0';
|
||||||
slider.max = '100';
|
slider.max = '100';
|
||||||
|
slider.className = 'blend-opacity-slider';
|
||||||
const selectedLayer = this.canvas.canvasSelection.selectedLayers[0];
|
const selectedLayer = this.canvas.canvasSelection.selectedLayers[0];
|
||||||
slider.value = selectedLayer ? String(Math.round(selectedLayer.opacity * 100)) : '100';
|
slider.value = selectedLayer ? String(Math.round(selectedLayer.opacity * 100)) : '100';
|
||||||
slider.style.cssText = `width: 100%; margin: 5px 0; display: none;`;
|
|
||||||
if (selectedLayer && selectedLayer.blendMode === mode.name) {
|
if (selectedLayer && selectedLayer.blendMode === mode.name) {
|
||||||
slider.style.display = 'block';
|
container.classList.add('active');
|
||||||
option.style.backgroundColor = '#3a3a3a';
|
option.classList.add('active');
|
||||||
}
|
}
|
||||||
option.onclick = () => {
|
option.onclick = () => {
|
||||||
// Re-check selected layer at the time of click
|
// Re-check selected layer at the time of click
|
||||||
@@ -933,19 +885,17 @@ export class CanvasLayers {
|
|||||||
if (!currentSelectedLayer) {
|
if (!currentSelectedLayer) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Hide only the opacity sliders within other blend mode containers
|
// Remove active class from all containers and options
|
||||||
content.querySelectorAll('.blend-mode-container').forEach(c => {
|
content.querySelectorAll('.blend-mode-container').forEach(c => {
|
||||||
const opacitySlider = c.querySelector('input[type="range"]');
|
c.classList.remove('active');
|
||||||
if (opacitySlider) {
|
const optionDiv = c.querySelector('.blend-mode-option');
|
||||||
opacitySlider.style.display = 'none';
|
|
||||||
}
|
|
||||||
const optionDiv = c.querySelector('div');
|
|
||||||
if (optionDiv) {
|
if (optionDiv) {
|
||||||
optionDiv.style.backgroundColor = '';
|
optionDiv.classList.remove('active');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
slider.style.display = 'block';
|
// Add active class to current container and option
|
||||||
option.style.backgroundColor = '#3a3a3a';
|
container.classList.add('active');
|
||||||
|
option.classList.add('active');
|
||||||
currentSelectedLayer.blendMode = mode.name;
|
currentSelectedLayer.blendMode = mode.name;
|
||||||
this.canvas.render();
|
this.canvas.render();
|
||||||
};
|
};
|
||||||
|
|||||||
170
js/css/blend_mode_menu.css
Normal file
170
js/css/blend_mode_menu.css
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
/* Blend Mode Menu Styles */
|
||||||
|
#blend-mode-menu {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background: #2a2a2a;
|
||||||
|
border: 1px solid #3a3a3a;
|
||||||
|
border-radius: 4px;
|
||||||
|
z-index: 10000;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-menu-title-bar {
|
||||||
|
background: #3a3a3a;
|
||||||
|
color: white;
|
||||||
|
padding: 8px 10px;
|
||||||
|
cursor: move;
|
||||||
|
user-select: none;
|
||||||
|
border-radius: 3px 3px 0 0;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
border-bottom: 1px solid #4a4a4a;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-menu-title-text {
|
||||||
|
flex: 1;
|
||||||
|
cursor: move;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-menu-close-button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
font-size: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-menu-close-button:hover {
|
||||||
|
background-color: #4a4a4a;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-menu-close-button:focus {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-menu-content {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-area-container {
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-bottom: 1px solid #4a4a4a;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-area-label {
|
||||||
|
color: white;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-area-slider {
|
||||||
|
width: 100%;
|
||||||
|
margin: 5px 0;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
height: 4px;
|
||||||
|
background: #555;
|
||||||
|
border-radius: 2px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-area-slider::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
background: #e0e0e0;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 2px solid #555;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-area-slider::-webkit-slider-thumb:hover {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-area-slider::-moz-range-thumb {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
background: #e0e0e0;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 2px solid #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-mode-container {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-mode-option {
|
||||||
|
padding: 5px 10px;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-mode-option:hover {
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-mode-option.active {
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-opacity-slider {
|
||||||
|
width: 100%;
|
||||||
|
margin: 5px 0;
|
||||||
|
display: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
height: 4px;
|
||||||
|
background: #555;
|
||||||
|
border-radius: 2px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-mode-container.active .blend-opacity-slider {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-opacity-slider::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
background: #e0e0e0;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 2px solid #555;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-opacity-slider::-webkit-slider-thumb:hover {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-opacity-slider::-moz-range-thumb {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
background: #e0e0e0;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 2px solid #555;
|
||||||
|
}
|
||||||
@@ -269,7 +269,10 @@ export class CanvasIO {
|
|||||||
log.error(`Failed to send data for node ${nodeId}:`, error);
|
log.error(`Failed to send data for node ${nodeId}:`, error);
|
||||||
|
|
||||||
|
|
||||||
throw new Error(`Failed to get confirmation from server for node ${nodeId}. The workflow might not have the latest canvas data.`);
|
throw new Error(
|
||||||
|
`Failed to get confirmation from server for node ${nodeId}. ` +
|
||||||
|
`Make sure that the nodeId: (${nodeId}) matches the "node_id" value in the node options. If they don't match, you may need to manually set the node_id to ${nodeId}.`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {createModuleLogger} from "./utils/LoggerUtils.js";
|
|||||||
import {generateUUID, generateUniqueFileName, createCanvas} from "./utils/CommonUtils.js";
|
import {generateUUID, generateUniqueFileName, createCanvas} from "./utils/CommonUtils.js";
|
||||||
import {withErrorHandling, createValidationError} from "./ErrorHandler.js";
|
import {withErrorHandling, createValidationError} from "./ErrorHandler.js";
|
||||||
import {showErrorNotification, showSuccessNotification} from "./utils/NotificationUtils.js";
|
import {showErrorNotification, showSuccessNotification} from "./utils/NotificationUtils.js";
|
||||||
|
import { addStylesheet, getUrl } from "./utils/ResourceManager.js";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import {app} from "../../scripts/app.js";
|
import {app} from "../../scripts/app.js";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -57,6 +58,9 @@ export class CanvasLayers {
|
|||||||
this.isAdjustingOpacity = false;
|
this.isAdjustingOpacity = false;
|
||||||
this.internalClipboard = [];
|
this.internalClipboard = [];
|
||||||
this.clipboardPreference = 'system';
|
this.clipboardPreference = 'system';
|
||||||
|
|
||||||
|
// Load CSS for blend mode menu
|
||||||
|
addStylesheet(getUrl('./css/blend_mode_menu.css'));
|
||||||
}
|
}
|
||||||
|
|
||||||
async copySelectedLayers(): Promise<void> {
|
async copySelectedLayers(): Promise<void> {
|
||||||
@@ -916,70 +920,17 @@ export class CanvasLayers {
|
|||||||
const menu = document.createElement('div');
|
const menu = document.createElement('div');
|
||||||
this.blendMenuElement = menu;
|
this.blendMenuElement = menu;
|
||||||
menu.id = 'blend-mode-menu';
|
menu.id = 'blend-mode-menu';
|
||||||
menu.style.cssText = `
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
background: #2a2a2a;
|
|
||||||
border: 1px solid #3a3a3a;
|
|
||||||
border-radius: 4px;
|
|
||||||
z-index: 10000;
|
|
||||||
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
|
|
||||||
min-width: 200px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const titleBar = document.createElement('div');
|
const titleBar = document.createElement('div');
|
||||||
titleBar.style.cssText = `
|
titleBar.className = 'blend-menu-title-bar';
|
||||||
background: #3a3a3a;
|
|
||||||
color: white;
|
|
||||||
padding: 8px 10px;
|
|
||||||
cursor: move;
|
|
||||||
user-select: none;
|
|
||||||
border-radius: 3px 3px 0 0;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: bold;
|
|
||||||
border-bottom: 1px solid #4a4a4a;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const titleText = document.createElement('span');
|
const titleText = document.createElement('span');
|
||||||
titleText.textContent = `Blend Mode: ${selectedLayer.name}`;
|
titleText.textContent = `Blend Mode: ${selectedLayer.name}`;
|
||||||
titleText.style.cssText = `
|
titleText.className = 'blend-menu-title-text';
|
||||||
flex: 1;
|
|
||||||
cursor: move;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const closeButton = document.createElement('button');
|
const closeButton = document.createElement('button');
|
||||||
closeButton.textContent = '×';
|
closeButton.textContent = '×';
|
||||||
closeButton.style.cssText = `
|
closeButton.className = 'blend-menu-close-button';
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: white;
|
|
||||||
font-size: 18px;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 3px;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
`;
|
|
||||||
|
|
||||||
closeButton.onmouseover = () => {
|
|
||||||
closeButton.style.backgroundColor = '#4a4a4a';
|
|
||||||
};
|
|
||||||
|
|
||||||
closeButton.onmouseout = () => {
|
|
||||||
closeButton.style.backgroundColor = 'transparent';
|
|
||||||
};
|
|
||||||
|
|
||||||
closeButton.onclick = (e: MouseEvent) => {
|
closeButton.onclick = (e: MouseEvent) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -990,22 +941,23 @@ export class CanvasLayers {
|
|||||||
titleBar.appendChild(closeButton);
|
titleBar.appendChild(closeButton);
|
||||||
|
|
||||||
const content = document.createElement('div');
|
const content = document.createElement('div');
|
||||||
content.style.cssText = `padding: 5px;`;
|
content.className = 'blend-menu-content';
|
||||||
|
|
||||||
menu.appendChild(titleBar);
|
menu.appendChild(titleBar);
|
||||||
menu.appendChild(content);
|
menu.appendChild(content);
|
||||||
|
|
||||||
const blendAreaContainer = document.createElement('div');
|
const blendAreaContainer = document.createElement('div');
|
||||||
blendAreaContainer.style.cssText = `padding: 5px 10px; border-bottom: 1px solid #4a4a4a;`;
|
blendAreaContainer.className = 'blend-area-container';
|
||||||
|
|
||||||
const blendAreaLabel = document.createElement('label');
|
const blendAreaLabel = document.createElement('label');
|
||||||
blendAreaLabel.textContent = 'Blend Area';
|
blendAreaLabel.textContent = 'Blend Area';
|
||||||
blendAreaLabel.style.color = 'white';
|
blendAreaLabel.className = 'blend-area-label';
|
||||||
|
|
||||||
const blendAreaSlider = document.createElement('input');
|
const blendAreaSlider = document.createElement('input');
|
||||||
blendAreaSlider.type = 'range';
|
blendAreaSlider.type = 'range';
|
||||||
blendAreaSlider.min = '0';
|
blendAreaSlider.min = '0';
|
||||||
blendAreaSlider.max = '100';
|
blendAreaSlider.max = '100';
|
||||||
|
blendAreaSlider.className = 'blend-area-slider';
|
||||||
|
|
||||||
blendAreaSlider.value = selectedLayer?.blendArea?.toString() ?? '0';
|
blendAreaSlider.value = selectedLayer?.blendArea?.toString() ?? '0';
|
||||||
|
|
||||||
@@ -1064,23 +1016,22 @@ export class CanvasLayers {
|
|||||||
this.blendModes.forEach((mode: BlendMode) => {
|
this.blendModes.forEach((mode: BlendMode) => {
|
||||||
const container = document.createElement('div');
|
const container = document.createElement('div');
|
||||||
container.className = 'blend-mode-container';
|
container.className = 'blend-mode-container';
|
||||||
container.style.cssText = `margin-bottom: 5px;`;
|
|
||||||
|
|
||||||
const option = document.createElement('div');
|
const option = document.createElement('div');
|
||||||
option.style.cssText = `padding: 5px 10px; color: white; cursor: pointer; transition: background-color 0.2s;`;
|
option.className = 'blend-mode-option';
|
||||||
option.textContent = `${mode.label} (${mode.name})`;
|
option.textContent = `${mode.label} (${mode.name})`;
|
||||||
|
|
||||||
const slider = document.createElement('input');
|
const slider = document.createElement('input');
|
||||||
slider.type = 'range';
|
slider.type = 'range';
|
||||||
slider.min = '0';
|
slider.min = '0';
|
||||||
slider.max = '100';
|
slider.max = '100';
|
||||||
|
slider.className = 'blend-opacity-slider';
|
||||||
const selectedLayer = this.canvas.canvasSelection.selectedLayers[0];
|
const selectedLayer = this.canvas.canvasSelection.selectedLayers[0];
|
||||||
slider.value = selectedLayer ? String(Math.round(selectedLayer.opacity * 100)) : '100';
|
slider.value = selectedLayer ? String(Math.round(selectedLayer.opacity * 100)) : '100';
|
||||||
slider.style.cssText = `width: 100%; margin: 5px 0; display: none;`;
|
|
||||||
|
|
||||||
if (selectedLayer && selectedLayer.blendMode === mode.name) {
|
if (selectedLayer && selectedLayer.blendMode === mode.name) {
|
||||||
slider.style.display = 'block';
|
container.classList.add('active');
|
||||||
option.style.backgroundColor = '#3a3a3a';
|
option.classList.add('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
option.onclick = () => {
|
option.onclick = () => {
|
||||||
@@ -1090,20 +1041,18 @@ export class CanvasLayers {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide only the opacity sliders within other blend mode containers
|
// Remove active class from all containers and options
|
||||||
content.querySelectorAll<HTMLDivElement>('.blend-mode-container').forEach(c => {
|
content.querySelectorAll<HTMLDivElement>('.blend-mode-container').forEach(c => {
|
||||||
const opacitySlider = c.querySelector<HTMLInputElement>('input[type="range"]');
|
c.classList.remove('active');
|
||||||
if (opacitySlider) {
|
const optionDiv = c.querySelector<HTMLDivElement>('.blend-mode-option');
|
||||||
opacitySlider.style.display = 'none';
|
|
||||||
}
|
|
||||||
const optionDiv = c.querySelector<HTMLDivElement>('div');
|
|
||||||
if (optionDiv) {
|
if (optionDiv) {
|
||||||
optionDiv.style.backgroundColor = '';
|
optionDiv.classList.remove('active');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
slider.style.display = 'block';
|
// Add active class to current container and option
|
||||||
option.style.backgroundColor = '#3a3a3a';
|
container.classList.add('active');
|
||||||
|
option.classList.add('active');
|
||||||
|
|
||||||
currentSelectedLayer.blendMode = mode.name;
|
currentSelectedLayer.blendMode = mode.name;
|
||||||
this.canvas.render();
|
this.canvas.render();
|
||||||
|
|||||||
170
src/css/blend_mode_menu.css
Normal file
170
src/css/blend_mode_menu.css
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
/* Blend Mode Menu Styles */
|
||||||
|
#blend-mode-menu {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background: #2a2a2a;
|
||||||
|
border: 1px solid #3a3a3a;
|
||||||
|
border-radius: 4px;
|
||||||
|
z-index: 10000;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-menu-title-bar {
|
||||||
|
background: #3a3a3a;
|
||||||
|
color: white;
|
||||||
|
padding: 8px 10px;
|
||||||
|
cursor: move;
|
||||||
|
user-select: none;
|
||||||
|
border-radius: 3px 3px 0 0;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
border-bottom: 1px solid #4a4a4a;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-menu-title-text {
|
||||||
|
flex: 1;
|
||||||
|
cursor: move;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-menu-close-button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
font-size: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-menu-close-button:hover {
|
||||||
|
background-color: #4a4a4a;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-menu-close-button:focus {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-menu-content {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-area-container {
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-bottom: 1px solid #4a4a4a;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-area-label {
|
||||||
|
color: white;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-area-slider {
|
||||||
|
width: 100%;
|
||||||
|
margin: 5px 0;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
height: 4px;
|
||||||
|
background: #555;
|
||||||
|
border-radius: 2px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-area-slider::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
background: #e0e0e0;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 2px solid #555;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-area-slider::-webkit-slider-thumb:hover {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-area-slider::-moz-range-thumb {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
background: #e0e0e0;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 2px solid #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-mode-container {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-mode-option {
|
||||||
|
padding: 5px 10px;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-mode-option:hover {
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-mode-option.active {
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-opacity-slider {
|
||||||
|
width: 100%;
|
||||||
|
margin: 5px 0;
|
||||||
|
display: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
height: 4px;
|
||||||
|
background: #555;
|
||||||
|
border-radius: 2px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-mode-container.active .blend-opacity-slider {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-opacity-slider::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
background: #e0e0e0;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 2px solid #555;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-opacity-slider::-webkit-slider-thumb:hover {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blend-mode-menu .blend-opacity-slider::-moz-range-thumb {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
background: #e0e0e0;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 2px solid #555;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user