Unrestrict 3D layer view camera — full x/y/z orbit
- Add rotateY axis (drag left/right rotates Y, up/down rotates X) - Shift+drag for Z-axis roll - Remove 10-80° clamp on rotateX — full ±180° range - Remove backface-visibility:hidden so layers visible from all angles - Fix overflow:hidden → overflow:visible for proper 3D perspective - Increase layer spacing 80→120px for more dramatic depth - Increase viewport height 340→420px, perspective origin centered Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
d62a5e9b15
commit
bf28e96ae6
|
|
@ -101,6 +101,7 @@ export class RStackTabBar extends HTMLElement {
|
||||||
#flowDialogTargetId = "";
|
#flowDialogTargetId = "";
|
||||||
// 3D scene state
|
// 3D scene state
|
||||||
#sceneRotX = 55;
|
#sceneRotX = 55;
|
||||||
|
#sceneRotY = 0;
|
||||||
#sceneRotZ = -15;
|
#sceneRotZ = -15;
|
||||||
#scenePerspective = 1200;
|
#scenePerspective = 1200;
|
||||||
#orbitDragging = false;
|
#orbitDragging = false;
|
||||||
|
|
@ -555,7 +556,7 @@ export class RStackTabBar extends HTMLElement {
|
||||||
const layerCount = this.#layers.length;
|
const layerCount = this.#layers.length;
|
||||||
if (layerCount === 0) return "";
|
if (layerCount === 0) return "";
|
||||||
|
|
||||||
const layerSpacing = 80;
|
const layerSpacing = 120;
|
||||||
const animDuration = 2 / this.#simSpeed;
|
const animDuration = 2 / this.#simSpeed;
|
||||||
|
|
||||||
// Build layer planes
|
// Build layer planes
|
||||||
|
|
@ -676,7 +677,7 @@ export class RStackTabBar extends HTMLElement {
|
||||||
<div class="stack-view-3d" id="stack-3d"
|
<div class="stack-view-3d" id="stack-3d"
|
||||||
style="perspective:${this.#scenePerspective}px;">
|
style="perspective:${this.#scenePerspective}px;">
|
||||||
<div class="stack-scene" id="stack-scene"
|
<div class="stack-scene" id="stack-scene"
|
||||||
style="transform: rotateX(${this.#sceneRotX}deg) rotateZ(${this.#sceneRotZ}deg);">
|
style="transform: rotateX(${this.#sceneRotX}deg) rotateY(${this.#sceneRotY}deg) rotateZ(${this.#sceneRotZ}deg);">
|
||||||
${layersHtml}
|
${layersHtml}
|
||||||
${tubesHtml}
|
${tubesHtml}
|
||||||
${particlesHtml}
|
${particlesHtml}
|
||||||
|
|
@ -684,7 +685,7 @@ export class RStackTabBar extends HTMLElement {
|
||||||
</div>
|
</div>
|
||||||
<div class="stack-legend">${legendHtml}</div>
|
<div class="stack-legend">${legendHtml}</div>
|
||||||
${scrubberHtml}
|
${scrubberHtml}
|
||||||
${this.#layers.length >= 2 ? `<div class="stack-hint">Click an output port to wire, or drag between layers · Drag empty space to orbit</div>` : ""}
|
${this.#layers.length >= 2 ? `<div class="stack-hint">Drag to orbit · Shift+drag to roll · Scroll to zoom · Click output ports to wire</div>` : ""}
|
||||||
${this.#flowDialogOpen ? this.#renderFlowDialog() : ""}
|
${this.#flowDialogOpen ? this.#renderFlowDialog() : ""}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
@ -777,7 +778,7 @@ export class RStackTabBar extends HTMLElement {
|
||||||
scene.querySelectorAll(".flow-tube, .flow-particle").forEach(el => el.remove());
|
scene.querySelectorAll(".flow-tube, .flow-particle").forEach(el => el.remove());
|
||||||
|
|
||||||
const layerZMap = new Map<string, number>();
|
const layerZMap = new Map<string, number>();
|
||||||
const layerSpacing = 80;
|
const layerSpacing = 120;
|
||||||
this.#layers.forEach((layer, i) => layerZMap.set(layer.id, i * layerSpacing));
|
this.#layers.forEach((layer, i) => layerZMap.set(layer.id, i * layerSpacing));
|
||||||
|
|
||||||
const animDuration = 2 / this.#simSpeed;
|
const animDuration = 2 / this.#simSpeed;
|
||||||
|
|
@ -1123,12 +1124,18 @@ export class RStackTabBar extends HTMLElement {
|
||||||
if (this.#orbitDragging) {
|
if (this.#orbitDragging) {
|
||||||
const dx = e.clientX - this.#orbitLastX;
|
const dx = e.clientX - this.#orbitLastX;
|
||||||
const dy = e.clientY - this.#orbitLastY;
|
const dy = e.clientY - this.#orbitLastY;
|
||||||
this.#sceneRotZ += dx * 0.3;
|
if (e.shiftKey) {
|
||||||
this.#sceneRotX = Math.max(10, Math.min(80, this.#sceneRotX - dy * 0.3));
|
// Shift+drag: rotate around Z axis
|
||||||
|
this.#sceneRotZ += dx * 0.3;
|
||||||
|
} else {
|
||||||
|
// Normal drag: rotate around X and Y axes
|
||||||
|
this.#sceneRotY += dx * 0.3;
|
||||||
|
this.#sceneRotX = Math.max(-180, Math.min(180, this.#sceneRotX - dy * 0.3));
|
||||||
|
}
|
||||||
this.#orbitLastX = e.clientX;
|
this.#orbitLastX = e.clientX;
|
||||||
this.#orbitLastY = e.clientY;
|
this.#orbitLastY = e.clientY;
|
||||||
const scene = this.#shadow.getElementById("stack-scene");
|
const scene = this.#shadow.getElementById("stack-scene");
|
||||||
if (scene) scene.style.transform = `rotateX(${this.#sceneRotX}deg) rotateZ(${this.#sceneRotZ}deg)`;
|
if (scene) scene.style.transform = `rotateX(${this.#sceneRotX}deg) rotateY(${this.#sceneRotY}deg) rotateZ(${this.#sceneRotZ}deg)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drag-to-connect: track target via bounding rects (robust for 3D)
|
// Drag-to-connect: track target via bounding rects (robust for 3D)
|
||||||
|
|
@ -1781,8 +1788,8 @@ const STYLES = `
|
||||||
|
|
||||||
.stack-view {
|
.stack-view {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
overflow: hidden;
|
overflow: visible;
|
||||||
max-height: 60vh;
|
max-height: 80vh;
|
||||||
transition: max-height 0.3s ease;
|
transition: max-height 0.3s ease;
|
||||||
position: relative;
|
position: relative;
|
||||||
background: color-mix(in srgb, var(--rs-bg-surface) 50%, transparent);
|
background: color-mix(in srgb, var(--rs-bg-surface) 50%, transparent);
|
||||||
|
|
@ -1790,14 +1797,15 @@ const STYLES = `
|
||||||
}
|
}
|
||||||
|
|
||||||
.stack-view-3d {
|
.stack-view-3d {
|
||||||
perspective-origin: 50% 40%;
|
perspective-origin: 50% 50%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 340px;
|
height: 420px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stack-scene {
|
.stack-scene {
|
||||||
|
|
@ -1819,7 +1827,6 @@ const STYLES = `
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: border-color 0.2s, box-shadow 0.2s;
|
transition: border-color 0.2s, box-shadow 0.2s;
|
||||||
transform-style: preserve-3d;
|
transform-style: preserve-3d;
|
||||||
backface-visibility: hidden;
|
|
||||||
background: color-mix(in srgb, var(--rs-bg-surface) 65%, transparent);
|
background: color-mix(in srgb, var(--rs-bg-surface) 65%, transparent);
|
||||||
backdrop-filter: blur(8px);
|
backdrop-filter: blur(8px);
|
||||||
-webkit-backdrop-filter: blur(8px);
|
-webkit-backdrop-filter: blur(8px);
|
||||||
|
|
@ -2297,7 +2304,7 @@ const STYLES = `
|
||||||
.tab-label { display: none; }
|
.tab-label { display: none; }
|
||||||
.tab { padding: 4px 8px; }
|
.tab { padding: 4px 8px; }
|
||||||
.stack-view { max-height: 40vh; }
|
.stack-view { max-height: 40vh; }
|
||||||
.stack-view-3d { height: 260px; }
|
.stack-view-3d { height: 320px; }
|
||||||
.stack-scene { width: 240px; }
|
.stack-scene { width: 240px; }
|
||||||
.layer-plane { width: 240px; min-height: 40px; padding: 8px 10px; }
|
.layer-plane { width: 240px; min-height: 40px; padding: 8px 10px; }
|
||||||
.io-chip { font-size: 0.5rem; padding: 1px 5px; max-width: 100px; }
|
.io-chip { font-size: 0.5rem; padding: 1px 5px; max-width: 100px; }
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue