feat: Improve info panel dragging with smoother animation, visual feedback, and robust position initialization and fallback logic.

This commit is contained in:
AEmotionStudio
2026-01-01 11:15:24 -08:00
parent 6ef88dbbc6
commit e4beab8c82
10 changed files with 238 additions and 106 deletions
+59 -21
View File
@@ -67,23 +67,24 @@ export class EventManager {
setupDragEvents(): void {
if (!this.panelElement) return;
// Simple drag implementation for the Vue panel
// The dragging logic could be inside Vue, but if we want to update global state/position, we can do it here.
// For now, let's look for the header element.
// We need to wait for Vue to render the header?
// Since we pass the container, and Vue mounts to it, the header might be inside.
// We can use event delegation on the container.
let isDragging = false;
let rafId: number | null = null;
let targetX = 0;
let targetY = 0;
const startDrag = (e: MouseEvent) => {
if (!this.panelElement) return;
const target = e.target as HTMLElement;
// Only allow dragging from header
if (!target.closest('.mag-panel-header')) return;
// Only allow dragging from header (expand clickable area)
const header = target.closest('.panel-header');
if (!header) return;
if (target.closest('button')) return;
e.preventDefault();
e.stopPropagation();
isDragging = true;
const startX = e.clientX;
const startY = e.clientY;
@@ -91,31 +92,68 @@ export class EventManager {
const startLeft = rect.left;
const startTop = rect.top;
// Use requestAnimationFrame for smoother dragging?
// Visual feedback - dragging state
this.panelElement.style.cursor = 'grabbing';
this.panelElement.style.opacity = '0.9';
this.panelElement.style.transition = 'none'; // Disable transitions during drag
document.body.style.cursor = 'grabbing';
document.body.style.userSelect = 'none';
const onMouseMove = (moveEvent: MouseEvent) => {
if (!isDragging) return;
const dx = moveEvent.clientX - startX;
const dy = moveEvent.clientY - startY;
if (this.panelElement) {
this.panelElement.style.left = `${startLeft + dx}px`;
this.panelElement.style.top = `${startTop + dy}px`;
targetX = startLeft + dx;
targetY = startTop + dy;
// Keep within viewport bounds
const panelWidth = this.panelElement?.offsetWidth || 0;
const panelHeight = this.panelElement?.offsetHeight || 0;
targetX = Math.max(0, Math.min(targetX, window.innerWidth - panelWidth));
targetY = Math.max(0, Math.min(targetY, window.innerHeight - panelHeight));
// Use RAF for smooth updates
if (rafId === null) {
rafId = requestAnimationFrame(() => {
if (this.panelElement && isDragging) {
this.panelElement.style.left = `${targetX}px`;
this.panelElement.style.top = `${targetY}px`;
}
rafId = null;
});
}
};
const onMouseUp = () => {
isDragging = false;
// Cancel any pending RAF
if (rafId !== null) {
cancelAnimationFrame(rafId);
rafId = null;
}
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
// Save pinned position
// Restore visual state
if (this.panelElement) {
const finalRect = this.panelElement.getBoundingClientRect();
this.stateManager.state.pinnedPosition = { x: finalRect.left, y: finalRect.top };
// Auto-pin on drag end?
if (!this.stateManager.state.isPanelPinned) {
// callbacks.togglePin? Or just set state?
// Better to let user manually pin, or follow existing logic.
}
this.panelElement.style.cursor = '';
this.panelElement.style.opacity = '';
this.panelElement.style.transition = '';
// Final position update
this.panelElement.style.left = `${targetX}px`;
this.panelElement.style.top = `${targetY}px`;
// Save pinned position
this.stateManager.state.pinnedPosition = { x: targetX, y: targetY };
}
document.body.style.cursor = '';
document.body.style.userSelect = '';
};
document.addEventListener('mousemove', onMouseMove);
+65 -34
View File
@@ -31,8 +31,20 @@ export class PositionManager {
if (!this.panelElement) return;
let { x, y } = this.stateManager.state.pinnedPosition;
const panelWidth = this.panelElement.offsetWidth;
const panelHeight = this.panelElement.offsetHeight;
// If pinnedPosition is uninitialized (0,0), use calculateNormalPosition to get a valid position
if (x === 0 && y === 0) {
this.calculateNormalPosition();
// Save the calculated position as the new pinned position
if (this.panelElement) {
const rect = this.panelElement.getBoundingClientRect();
this.stateManager.state.pinnedPosition = { x: rect.left, y: rect.top };
}
return;
}
const panelWidth = this.panelElement.offsetWidth || 300;
const panelHeight = this.panelElement.offsetHeight || 400;
const margin = 10;
const boundedX = Math.max(margin, Math.min(x, window.innerWidth - panelWidth - margin));
@@ -47,46 +59,65 @@ export class PositionManager {
if (!magnifyGlass || !this.panelElement) return;
const settings = this.stateManager.state.settings;
const panelWidth = settings["🔍MagnifyGlass.InfoPanelWidth"];
const panelHeight = this.panelElement.offsetHeight; // Use actual height
const panelWidth = settings["🔍MagnifyGlass.InfoPanelWidth"] || 300;
const panelHeight = this.panelElement.offsetHeight || 400;
let left: number;
let top: number;
const margin = 15;
// Check for valid glass rect first
const glassRect = magnifyGlass.ui.glassDiv?.getBoundingClientRect();
const hasValidGlassRect = glassRect && (glassRect.right > 0 || glassRect.top > 0);
// Check for valid mouse position
const mouseX = magnifyGlass.lastKnownMousePosition?.x || 0;
const mouseY = magnifyGlass.lastKnownMousePosition?.y || 0;
const hasValidMousePosition = mouseX > 0 || mouseY > 0;
if (!this.stateManager.state.isGlassPreviewVisible) {
left = magnifyGlass.lastKnownMousePosition.x - (panelWidth / 2);
top = magnifyGlass.lastKnownMousePosition.y - 20;
} else {
const glassRect = magnifyGlass.ui.glassDiv?.getBoundingClientRect();
if (glassRect) {
const position = settings["🔍MagnifyGlass.InfoPanelPosition"];
switch (position) {
case "Right":
left = glassRect.right + margin;
top = glassRect.top;
break;
case "Left":
left = glassRect.left - panelWidth - margin;
top = glassRect.top;
break;
case "Top":
left = glassRect.left;
top = glassRect.top - panelHeight - margin;
break;
case "Bottom":
left = glassRect.left;
top = glassRect.bottom + margin;
break;
default:
left = glassRect.right + margin;
top = glassRect.top;
break;
}
// Glass is hidden, use mouse position
if (hasValidMousePosition) {
left = mouseX - (panelWidth / 2);
top = mouseY - 20;
} else {
left = magnifyGlass.lastKnownMousePosition.x - (panelWidth / 2);
top = magnifyGlass.lastKnownMousePosition.y - 20;
// Fallback: right side of screen
left = window.innerWidth - panelWidth - 50;
top = 100;
}
} else if (hasValidGlassRect) {
// Position relative to glass
const position = settings["🔍MagnifyGlass.InfoPanelPosition"];
switch (position) {
case "Right":
left = glassRect!.right + margin;
top = glassRect!.top;
break;
case "Left":
left = glassRect!.left - panelWidth - margin;
top = glassRect!.top;
break;
case "Top":
left = glassRect!.left;
top = glassRect!.top - panelHeight - margin;
break;
case "Bottom":
left = glassRect!.left;
top = glassRect!.bottom + margin;
break;
default:
left = glassRect!.right + margin;
top = glassRect!.top;
break;
}
} else if (hasValidMousePosition) {
// Fall back to mouse position
left = mouseX - (panelWidth / 2);
top = mouseY - 20;
} else {
// Final fallback: right side of screen
left = window.innerWidth - panelWidth - 50;
top = 100;
}
left = Math.max(10, Math.min(left, window.innerWidth - panelWidth - 10));
+3 -3
View File
@@ -169,11 +169,11 @@ export class UIManager {
this.stateManager.state.isGlassPreviewVisible = !this.stateManager.state.isGlassPreviewVisible;
this.updateControlStates();
// Toggle glass visibility on the magnify glass
if (window.magnifyGlass) {
if (window.comfyUIMagnifyGlass) {
if (this.stateManager.state.isGlassPreviewVisible) {
window.magnifyGlass.ui.show();
window.comfyUIMagnifyGlass.ui.show();
} else {
window.magnifyGlass.ui.hide();
window.comfyUIMagnifyGlass.ui.hide();
}
}
break;
+10
View File
@@ -244,6 +244,16 @@
background: linear-gradient(135deg, rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.05));
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
user-select: none;
cursor: grab;
transition: background 0.15s ease;
}
.panel-header:hover {
background: linear-gradient(135deg, rgba(0, 0, 0, 0.15), rgba(255, 255, 255, 0.08));
}
.panel-header:active {
cursor: grabbing;
}
.header-content {
+42 -7
View File
@@ -36,33 +36,68 @@ class EventManager {
}
setupDragEvents() {
if (!this.panelElement) return;
let isDragging = false;
let rafId = null;
let targetX = 0;
let targetY = 0;
const startDrag = (e) => {
if (!this.panelElement) return;
const target = e.target;
if (!target.closest(".mag-panel-header")) return;
const header = target.closest(".panel-header");
if (!header) return;
if (target.closest("button")) return;
e.preventDefault();
e.stopPropagation();
isDragging = true;
const startX = e.clientX;
const startY = e.clientY;
const rect = this.panelElement.getBoundingClientRect();
const startLeft = rect.left;
const startTop = rect.top;
this.panelElement.style.cursor = "grabbing";
this.panelElement.style.opacity = "0.9";
this.panelElement.style.transition = "none";
document.body.style.cursor = "grabbing";
document.body.style.userSelect = "none";
const onMouseMove = (moveEvent) => {
var _a, _b;
if (!isDragging) return;
const dx = moveEvent.clientX - startX;
const dy = moveEvent.clientY - startY;
if (this.panelElement) {
this.panelElement.style.left = `${startLeft + dx}px`;
this.panelElement.style.top = `${startTop + dy}px`;
targetX = startLeft + dx;
targetY = startTop + dy;
const panelWidth = ((_a = this.panelElement) == null ? void 0 : _a.offsetWidth) || 0;
const panelHeight = ((_b = this.panelElement) == null ? void 0 : _b.offsetHeight) || 0;
targetX = Math.max(0, Math.min(targetX, window.innerWidth - panelWidth));
targetY = Math.max(0, Math.min(targetY, window.innerHeight - panelHeight));
if (rafId === null) {
rafId = requestAnimationFrame(() => {
if (this.panelElement && isDragging) {
this.panelElement.style.left = `${targetX}px`;
this.panelElement.style.top = `${targetY}px`;
}
rafId = null;
});
}
};
const onMouseUp = () => {
isDragging = false;
if (rafId !== null) {
cancelAnimationFrame(rafId);
rafId = null;
}
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
if (this.panelElement) {
const finalRect = this.panelElement.getBoundingClientRect();
this.stateManager.state.pinnedPosition = { x: finalRect.left, y: finalRect.top };
if (!this.stateManager.state.isPanelPinned) ;
this.panelElement.style.cursor = "";
this.panelElement.style.opacity = "";
this.panelElement.style.transition = "";
this.panelElement.style.left = `${targetX}px`;
this.panelElement.style.top = `${targetY}px`;
this.stateManager.state.pinnedPosition = { x: targetX, y: targetY };
}
document.body.style.cursor = "";
document.body.style.userSelect = "";
};
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
File diff suppressed because one or more lines are too long
+53 -35
View File
@@ -19,8 +19,16 @@ class PositionManager {
applyPinnedPosition() {
if (!this.panelElement) return;
let { x, y } = this.stateManager.state.pinnedPosition;
const panelWidth = this.panelElement.offsetWidth;
const panelHeight = this.panelElement.offsetHeight;
if (x === 0 && y === 0) {
this.calculateNormalPosition();
if (this.panelElement) {
const rect = this.panelElement.getBoundingClientRect();
this.stateManager.state.pinnedPosition = { x: rect.left, y: rect.top };
}
return;
}
const panelWidth = this.panelElement.offsetWidth || 300;
const panelHeight = this.panelElement.offsetHeight || 400;
const margin = 10;
const boundedX = Math.max(margin, Math.min(x, window.innerWidth - panelWidth - margin));
const boundedY = Math.max(margin, Math.min(y, window.innerHeight - panelHeight - margin));
@@ -28,48 +36,58 @@ class PositionManager {
this.panelElement.style.top = `${boundedY}px`;
}
calculateNormalPosition() {
var _a;
var _a, _b, _c;
const magnifyGlass = window.comfyUIMagnifyGlass;
if (!magnifyGlass || !this.panelElement) return;
const settings = this.stateManager.state.settings;
const panelWidth = settings["🔍MagnifyGlass.InfoPanelWidth"];
const panelHeight = this.panelElement.offsetHeight;
const panelWidth = settings["🔍MagnifyGlass.InfoPanelWidth"] || 300;
const panelHeight = this.panelElement.offsetHeight || 400;
let left;
let top;
const margin = 15;
const glassRect = (_a = magnifyGlass.ui.glassDiv) == null ? void 0 : _a.getBoundingClientRect();
const hasValidGlassRect = glassRect && (glassRect.right > 0 || glassRect.top > 0);
const mouseX = ((_b = magnifyGlass.lastKnownMousePosition) == null ? void 0 : _b.x) || 0;
const mouseY = ((_c = magnifyGlass.lastKnownMousePosition) == null ? void 0 : _c.y) || 0;
const hasValidMousePosition = mouseX > 0 || mouseY > 0;
if (!this.stateManager.state.isGlassPreviewVisible) {
left = magnifyGlass.lastKnownMousePosition.x - panelWidth / 2;
top = magnifyGlass.lastKnownMousePosition.y - 20;
} else {
const glassRect = (_a = magnifyGlass.ui.glassDiv) == null ? void 0 : _a.getBoundingClientRect();
if (glassRect) {
const position = settings["🔍MagnifyGlass.InfoPanelPosition"];
switch (position) {
case "Right":
left = glassRect.right + margin;
top = glassRect.top;
break;
case "Left":
left = glassRect.left - panelWidth - margin;
top = glassRect.top;
break;
case "Top":
left = glassRect.left;
top = glassRect.top - panelHeight - margin;
break;
case "Bottom":
left = glassRect.left;
top = glassRect.bottom + margin;
break;
default:
left = glassRect.right + margin;
top = glassRect.top;
break;
}
if (hasValidMousePosition) {
left = mouseX - panelWidth / 2;
top = mouseY - 20;
} else {
left = magnifyGlass.lastKnownMousePosition.x - panelWidth / 2;
top = magnifyGlass.lastKnownMousePosition.y - 20;
left = window.innerWidth - panelWidth - 50;
top = 100;
}
} else if (hasValidGlassRect) {
const position = settings["🔍MagnifyGlass.InfoPanelPosition"];
switch (position) {
case "Right":
left = glassRect.right + margin;
top = glassRect.top;
break;
case "Left":
left = glassRect.left - panelWidth - margin;
top = glassRect.top;
break;
case "Top":
left = glassRect.left;
top = glassRect.top - panelHeight - margin;
break;
case "Bottom":
left = glassRect.left;
top = glassRect.bottom + margin;
break;
default:
left = glassRect.right + margin;
top = glassRect.top;
break;
}
} else if (hasValidMousePosition) {
left = mouseX - panelWidth / 2;
top = mouseY - 20;
} else {
left = window.innerWidth - panelWidth - 50;
top = 100;
}
left = Math.max(10, Math.min(left, window.innerWidth - panelWidth - 10));
top = Math.max(10, Math.min(top, window.innerHeight - panelHeight - 10));
File diff suppressed because one or more lines are too long
+3 -3
View File
@@ -119,11 +119,11 @@ class UIManager {
case "toggle-glass":
this.stateManager.state.isGlassPreviewVisible = !this.stateManager.state.isGlassPreviewVisible;
this.updateControlStates();
if (window.magnifyGlass) {
if (window.comfyUIMagnifyGlass) {
if (this.stateManager.state.isGlassPreviewVisible) {
window.magnifyGlass.ui.show();
window.comfyUIMagnifyGlass.ui.show();
} else {
window.magnifyGlass.ui.hide();
window.comfyUIMagnifyGlass.ui.hide();
}
}
break;
File diff suppressed because one or more lines are too long