From 3a5c605b610735a791a8f3e62bb1ee67564427d8 Mon Sep 17 00:00:00 2001 From: sunag Date: Thu, 6 Nov 2025 23:40:17 -0300 Subject: [PATCH 1/5] add window boundary constraints --- examples/jsm/inspector/ui/Profiler.js | 286 +++++++++++++++++++++++--- 1 file changed, 260 insertions(+), 26 deletions(-) diff --git a/examples/jsm/inspector/ui/Profiler.js b/examples/jsm/inspector/ui/Profiler.js index 80648137890952..4d16c4cf71d3a7 100644 --- a/examples/jsm/inspector/ui/Profiler.js +++ b/examples/jsm/inspector/ui/Profiler.js @@ -27,6 +27,9 @@ export class Profiler { } + // Setup window resize listener to constrain detached windows + this.setupWindowResizeListener(); + } detectMobile() { @@ -69,6 +72,72 @@ export class Profiler { } + setupWindowResizeListener() { + + const constrainDetachedWindows = () => { + + this.detachedWindows.forEach( detachedWindow => { + + this.constrainWindowToBounds( detachedWindow.panel ); + + } ); + + }; + + // Listen for window resize events + window.addEventListener( 'resize', constrainDetachedWindows ); + + } + + constrainWindowToBounds( windowPanel ) { + + const windowWidth = window.innerWidth; + const windowHeight = window.innerHeight; + + const panelWidth = windowPanel.offsetWidth; + const panelHeight = windowPanel.offsetHeight; + + let left = parseFloat( windowPanel.style.left ) || windowPanel.offsetLeft || 0; + let top = parseFloat( windowPanel.style.top ) || windowPanel.offsetTop || 0; + + // Allow window to extend half its width/height outside the screen + const halfWidth = panelWidth / 2; + const halfHeight = panelHeight / 2; + + // Constrain horizontal position (allow half width to extend beyond right edge) + if ( left + panelWidth > windowWidth + halfWidth ) { + + left = windowWidth + halfWidth - panelWidth; + + } + + // Constrain horizontal position (allow half width to extend beyond left edge) + if ( left < - halfWidth ) { + + left = - halfWidth; + + } + + // Constrain vertical position (allow half height to extend beyond bottom edge) + if ( top + panelHeight > windowHeight + halfHeight ) { + + top = windowHeight + halfHeight - panelHeight; + + } + + // Constrain vertical position (allow half height to extend beyond top edge) + if ( top < - halfHeight ) { + + top = - halfHeight; + + } + + // Apply constrained position + windowPanel.style.left = `${ left }px`; + windowPanel.style.top = `${ top }px`; + + } + setupShell() { this.domElement = document.createElement( 'div' ); @@ -146,8 +215,8 @@ export class Profiler { this.isResizing = true; this.panel.classList.add( 'resizing' ); - const startX = e.clientX || e.touches[ 0 ].clientX; - const startY = e.clientY || e.touches[ 0 ].clientY; + const startX = e.clientX || ( e.touches && e.touches[ 0 ] ? e.touches[ 0 ].clientX : 0 ); + const startY = e.clientY || ( e.touches && e.touches[ 0 ] ? e.touches[ 0 ].clientY : 0 ); const startHeight = this.panel.offsetHeight; const startWidth = this.panel.offsetWidth; @@ -155,8 +224,8 @@ export class Profiler { if ( ! this.isResizing ) return; moveEvent.preventDefault(); - const currentX = moveEvent.clientX || moveEvent.touches[ 0 ].clientX; - const currentY = moveEvent.clientY || moveEvent.touches[ 0 ].clientY; + const currentX = moveEvent.clientX || ( moveEvent.touches && moveEvent.touches[ 0 ] ? moveEvent.touches[ 0 ].clientX : 0 ); + const currentY = moveEvent.clientY || ( moveEvent.touches && moveEvent.touches[ 0 ] ? moveEvent.touches[ 0 ].clientY : 0 ); if ( this.position === 'bottom' ) { @@ -407,8 +476,8 @@ export class Profiler { const onDragStart = ( e ) => { - startX = e.clientX || e.touches[ 0 ].clientX; - startY = e.clientY || e.touches[ 0 ].clientY; + startX = e.clientX || ( e.touches && e.touches[ 0 ] ? e.touches[ 0 ].clientX : 0 ); + startY = e.clientY || ( e.touches && e.touches[ 0 ] ? e.touches[ 0 ].clientY : 0 ); isDragging = false; hasMoved = false; @@ -416,8 +485,8 @@ export class Profiler { const onDragMove = ( e ) => { - const currentX = e.clientX || e.touches[ 0 ].clientX; - const currentY = e.clientY || e.touches[ 0 ].clientY; + const currentX = e.clientX || ( e.touches && e.touches[ 0 ] ? e.touches[ 0 ].clientX : 0 ); + const currentY = e.clientY || ( e.touches && e.touches[ 0 ] ? e.touches[ 0 ].clientY : 0 ); const deltaX = Math.abs( currentX - startX ); const deltaY = Math.abs( currentY - startY ); @@ -664,10 +733,43 @@ export class Profiler { createDetachedWindow( tab, x, y ) { + // Constrain initial position to window bounds + const windowWidth = window.innerWidth; + const windowHeight = window.innerHeight; + const estimatedWidth = 400; // Default detached window width + const estimatedHeight = 300; // Default detached window height + + let constrainedX = x - 200; + let constrainedY = y - 20; + + if ( constrainedX + estimatedWidth > windowWidth ) { + + constrainedX = windowWidth - estimatedWidth; + + } + + if ( constrainedX < 0 ) { + + constrainedX = 0; + + } + + if ( constrainedY + estimatedHeight > windowHeight ) { + + constrainedY = windowHeight - estimatedHeight; + + } + + if ( constrainedY < 0 ) { + + constrainedY = 0; + + } + const windowPanel = document.createElement( 'div' ); windowPanel.className = 'detached-tab-panel'; - windowPanel.style.left = `${ x - 200 }px`; - windowPanel.style.top = `${ y - 20 }px`; + windowPanel.style.left = `${ constrainedX }px`; + windowPanel.style.top = `${ constrainedY }px`; if ( ! this.panel.classList.contains( 'visible' ) ) { @@ -790,8 +892,8 @@ export class Profiler { isDragging = true; header.style.cursor = 'grabbing'; - startX = e.clientX || e.touches[ 0 ].clientX; - startY = e.clientY || e.touches[ 0 ].clientY; + startX = e.clientX || ( e.touches && e.touches[ 0 ] ? e.touches[ 0 ].clientX : 0 ); + startY = e.clientY || ( e.touches && e.touches[ 0 ] ? e.touches[ 0 ].clientY : 0 ); const rect = windowPanel.getBoundingClientRect(); startLeft = rect.left; @@ -805,14 +907,53 @@ export class Profiler { e.preventDefault(); - const currentX = e.clientX || e.touches[ 0 ].clientX; - const currentY = e.clientY || e.touches[ 0 ].clientY; + const currentX = e.clientX || ( e.touches && e.touches[ 0 ] ? e.touches[ 0 ].clientX : 0 ); + const currentY = e.clientY || ( e.touches && e.touches[ 0 ] ? e.touches[ 0 ].clientY : 0 ); const deltaX = currentX - startX; const deltaY = currentY - startY; - windowPanel.style.left = `${ startLeft + deltaX }px`; - windowPanel.style.top = `${ startTop + deltaY }px`; + let newLeft = startLeft + deltaX; + let newTop = startTop + deltaY; + + // Constrain to window bounds (allow half width/height to extend outside) + const windowWidth = window.innerWidth; + const windowHeight = window.innerHeight; + const panelWidth = windowPanel.offsetWidth; + const panelHeight = windowPanel.offsetHeight; + const halfWidth = panelWidth / 2; + const halfHeight = panelHeight / 2; + + // Allow window to extend half its width beyond right edge + if ( newLeft + panelWidth > windowWidth + halfWidth ) { + + newLeft = windowWidth + halfWidth - panelWidth; + + } + + // Allow window to extend half its width beyond left edge + if ( newLeft < - halfWidth ) { + + newLeft = - halfWidth; + + } + + // Allow window to extend half its height beyond bottom edge + if ( newTop + panelHeight > windowHeight + halfHeight ) { + + newTop = windowHeight + halfHeight - panelHeight; + + } + + // Allow window to extend half its height beyond top edge + if ( newTop < - halfHeight ) { + + newTop = - halfHeight; + + } + + windowPanel.style.left = `${ newLeft }px`; + windowPanel.style.top = `${ newTop }px`; // Check if cursor is over the inspector panel const panelRect = this.panel.getBoundingClientRect(); @@ -912,8 +1053,8 @@ export class Profiler { // Bring window to front when resizing this.bringWindowToFront( windowPanel ); - startX = e.clientX || e.touches[ 0 ].clientX; - startY = e.clientY || e.touches[ 0 ].clientY; + startX = e.clientX || ( e.touches && e.touches[ 0 ] ? e.touches[ 0 ].clientX : 0 ); + startY = e.clientY || ( e.touches && e.touches[ 0 ] ? e.touches[ 0 ].clientY : 0 ); startWidth = windowPanel.offsetWidth; startHeight = windowPanel.offsetHeight; startLeft = windowPanel.offsetLeft; @@ -927,16 +1068,21 @@ export class Profiler { e.preventDefault(); - const currentX = e.clientX || e.touches[ 0 ].clientX; - const currentY = e.clientY || e.touches[ 0 ].clientY; + const currentX = e.clientX || ( e.touches && e.touches[ 0 ] ? e.touches[ 0 ].clientX : 0 ); + const currentY = e.clientY || ( e.touches && e.touches[ 0 ] ? e.touches[ 0 ].clientY : 0 ); const deltaX = currentX - startX; const deltaY = currentY - startY; + const windowWidth = window.innerWidth; + const windowHeight = window.innerHeight; + if ( direction === 'right' || direction === 'corner' ) { const newWidth = startWidth + deltaX; - if ( newWidth >= minWidth ) { + const maxWidth = windowWidth - startLeft; + + if ( newWidth >= minWidth && newWidth <= maxWidth ) { windowPanel.style.width = `${ newWidth }px`; @@ -947,7 +1093,9 @@ export class Profiler { if ( direction === 'bottom' || direction === 'corner' ) { const newHeight = startHeight + deltaY; - if ( newHeight >= minHeight ) { + const maxHeight = windowHeight - startTop; + + if ( newHeight >= minHeight && newHeight <= maxHeight ) { windowPanel.style.height = `${ newHeight }px`; @@ -958,10 +1106,18 @@ export class Profiler { if ( direction === 'left' ) { const newWidth = startWidth - deltaX; + const maxLeft = startLeft + startWidth - minWidth; + if ( newWidth >= minWidth ) { - windowPanel.style.width = `${ newWidth }px`; - windowPanel.style.left = `${ startLeft + deltaX }px`; + const newLeft = startLeft + deltaX; + + if ( newLeft >= 0 && newLeft <= maxLeft ) { + + windowPanel.style.width = `${ newWidth }px`; + windowPanel.style.left = `${ newLeft }px`; + + } } @@ -970,10 +1126,18 @@ export class Profiler { if ( direction === 'top' ) { const newHeight = startHeight - deltaY; + const maxTop = startTop + startHeight - minHeight; + if ( newHeight >= minHeight ) { - windowPanel.style.height = `${ newHeight }px`; - windowPanel.style.top = `${ startTop + deltaY }px`; + const newTop = startTop + deltaY; + + if ( newTop >= 0 && newTop <= maxTop ) { + + windowPanel.style.height = `${ newHeight }px`; + windowPanel.style.top = `${ newTop }px`; + + } } @@ -1289,6 +1453,73 @@ export class Profiler { const layout = JSON.parse( savedLayout ); + // Constrain detached tabs positions to current screen bounds + if ( layout.detachedTabs && layout.detachedTabs.length > 0 ) { + + const windowWidth = window.innerWidth; + const windowHeight = window.innerHeight; + + layout.detachedTabs = layout.detachedTabs.map( detachedTabData => { + + let { left, top, width, height } = detachedTabData; + + // Ensure width and height are within bounds + if ( width > windowWidth ) { + + width = windowWidth - 100; // Leave some margin + + } + + if ( height > windowHeight ) { + + height = windowHeight - 100; // Leave some margin + + } + + // Allow window to extend half its width/height outside the screen + const halfWidth = width / 2; + const halfHeight = height / 2; + + // Constrain horizontal position (allow half width to extend beyond right edge) + if ( left + width > windowWidth + halfWidth ) { + + left = windowWidth + halfWidth - width; + + } + + // Constrain horizontal position (allow half width to extend beyond left edge) + if ( left < - halfWidth ) { + + left = - halfWidth; + + } + + // Constrain vertical position (allow half height to extend beyond bottom edge) + if ( top + height > windowHeight + halfHeight ) { + + top = windowHeight + halfHeight - height; + + } + + // Constrain vertical position (allow half height to extend beyond top edge) + if ( top < - halfHeight ) { + + top = - halfHeight; + + } + + return { + ...detachedTabData, + left, + top, + width, + height + }; + + } ); + + } + // Restore position and dimensions if ( layout.position ) { @@ -1397,6 +1628,9 @@ export class Profiler { detachedWindow.panel.style.width = `${ detachedTabData.width }px`; detachedWindow.panel.style.height = `${ detachedTabData.height }px`; + // Constrain window to bounds after restoring position and size + this.constrainWindowToBounds( detachedWindow.panel ); + this.detachedWindows.push( detachedWindow ); tab.isDetached = true; From 31fc8059305179c2f5635f50c63e0d2213743fc7 Mon Sep 17 00:00:00 2001 From: sunag Date: Thu, 6 Nov 2025 23:54:39 -0300 Subject: [PATCH 2/5] fix hide maximize button when no-tabs active --- examples/jsm/inspector/ui/Profiler.js | 3 +++ examples/jsm/inspector/ui/Style.js | 15 ++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/examples/jsm/inspector/ui/Profiler.js b/examples/jsm/inspector/ui/Profiler.js index 4d16c4cf71d3a7..226d8bdb0bf21f 100644 --- a/examples/jsm/inspector/ui/Profiler.js +++ b/examples/jsm/inspector/ui/Profiler.js @@ -205,6 +205,9 @@ export class Profiler { this.domElement.append( this.toggleButton, this.panel ); + // Set initial position class + this.panel.classList.add( `position-${this.position}` ); + } setupResizing() { diff --git a/examples/jsm/inspector/ui/Style.js b/examples/jsm/inspector/ui/Style.js index 91a83c785e0dfa..f563ddc9f9f707 100644 --- a/examples/jsm/inspector/ui/Style.js +++ b/examples/jsm/inspector/ui/Style.js @@ -341,13 +341,6 @@ export class Style { border-bottom: 1px solid var(--profiler-border); } -#profiler-panel.position-right.no-tabs #maximize-btn, -#profiler-panel.position-left.no-tabs #maximize-btn, -#profiler-panel.position-bottom.no-tabs #maximize-btn, -#profiler-panel.position-top.no-tabs #maximize-btn { - display: none; -} - #profiler-panel.position-right.no-tabs .profiler-content-wrapper, #profiler-panel.position-left.no-tabs .profiler-content-wrapper { display: none; @@ -521,6 +514,14 @@ export class Style { color: var(--text-primary); } +/* Hide maximize button when there are no tabs */ +#profiler-panel.position-right.no-tabs #maximize-btn, +#profiler-panel.position-left.no-tabs #maximize-btn, +#profiler-panel.position-bottom.no-tabs #maximize-btn, +#profiler-panel.position-top.no-tabs #maximize-btn { + display: none !important; +} + .profiler-content-wrapper { flex-grow: 1; overflow: hidden; From 78f8935bd60e1b4bf8034ebe73b0c889cd2d5ff2 Mon Sep 17 00:00:00 2001 From: sunag Date: Fri, 7 Nov 2025 00:28:35 -0300 Subject: [PATCH 3/5] use pointer events --- examples/jsm/inspector/ui/Profiler.js | 132 ++++++++++---------------- examples/jsm/inspector/ui/Style.js | 8 ++ 2 files changed, 60 insertions(+), 80 deletions(-) diff --git a/examples/jsm/inspector/ui/Profiler.js b/examples/jsm/inspector/ui/Profiler.js index 226d8bdb0bf21f..7e9bf5ecd813a3 100644 --- a/examples/jsm/inspector/ui/Profiler.js +++ b/examples/jsm/inspector/ui/Profiler.js @@ -218,8 +218,9 @@ export class Profiler { this.isResizing = true; this.panel.classList.add( 'resizing' ); - const startX = e.clientX || ( e.touches && e.touches[ 0 ] ? e.touches[ 0 ].clientX : 0 ); - const startY = e.clientY || ( e.touches && e.touches[ 0 ] ? e.touches[ 0 ].clientY : 0 ); + resizer.setPointerCapture( e.pointerId ); + const startX = e.clientX; + const startY = e.clientY; const startHeight = this.panel.offsetHeight; const startWidth = this.panel.offsetWidth; @@ -227,8 +228,8 @@ export class Profiler { if ( ! this.isResizing ) return; moveEvent.preventDefault(); - const currentX = moveEvent.clientX || ( moveEvent.touches && moveEvent.touches[ 0 ] ? moveEvent.touches[ 0 ].clientX : 0 ); - const currentY = moveEvent.clientY || ( moveEvent.touches && moveEvent.touches[ 0 ] ? moveEvent.touches[ 0 ].clientY : 0 ); + const currentX = moveEvent.clientX; + const currentY = moveEvent.clientY; if ( this.position === 'bottom' ) { @@ -260,10 +261,9 @@ export class Profiler { this.isResizing = false; this.panel.classList.remove( 'resizing' ); - document.removeEventListener( 'mousemove', onMove ); - document.removeEventListener( 'mouseup', onEnd ); - document.removeEventListener( 'touchmove', onMove ); - document.removeEventListener( 'touchend', onEnd ); + resizer.removeEventListener( 'pointermove', onMove ); + resizer.removeEventListener( 'pointerup', onEnd ); + resizer.removeEventListener( 'pointercancel', onEnd ); if ( ! this.panel.classList.contains( 'maximized' ) ) { // Save dimensions based on current position @@ -284,15 +284,13 @@ export class Profiler { }; - document.addEventListener( 'mousemove', onMove ); - document.addEventListener( 'mouseup', onEnd ); - document.addEventListener( 'touchmove', onMove, { passive: false } ); - document.addEventListener( 'touchend', onEnd ); + resizer.addEventListener( 'pointermove', onMove ); + resizer.addEventListener( 'pointerup', onEnd ); + resizer.addEventListener( 'pointercancel', onEnd ); }; - resizer.addEventListener( 'mousedown', onStart ); - resizer.addEventListener( 'touchstart', onStart ); + resizer.addEventListener( 'pointerdown', onStart ); } @@ -479,17 +477,18 @@ export class Profiler { const onDragStart = ( e ) => { - startX = e.clientX || ( e.touches && e.touches[ 0 ] ? e.touches[ 0 ].clientX : 0 ); - startY = e.clientY || ( e.touches && e.touches[ 0 ] ? e.touches[ 0 ].clientY : 0 ); + startX = e.clientX; + startY = e.clientY; isDragging = false; hasMoved = false; + tab.button.setPointerCapture( e.pointerId ); }; const onDragMove = ( e ) => { - const currentX = e.clientX || ( e.touches && e.touches[ 0 ] ? e.touches[ 0 ].clientX : 0 ); - const currentY = e.clientY || ( e.touches && e.touches[ 0 ] ? e.touches[ 0 ].clientY : 0 ); + const currentX = e.clientX; + const currentY = e.clientY; const deltaX = Math.abs( currentX - startX ); const deltaY = Math.abs( currentY - startY ); @@ -560,26 +559,18 @@ export class Profiler { hasMoved = false; previewWindow = null; - document.removeEventListener( 'mousemove', onDragMove ); - document.removeEventListener( 'mouseup', onDragEnd ); - document.removeEventListener( 'touchmove', onDragMove ); - document.removeEventListener( 'touchend', onDragEnd ); + tab.button.removeEventListener( 'pointermove', onDragMove ); + tab.button.removeEventListener( 'pointerup', onDragEnd ); + tab.button.removeEventListener( 'pointercancel', onDragEnd ); }; - tab.button.addEventListener( 'mousedown', ( e ) => { + tab.button.addEventListener( 'pointerdown', ( e ) => { onDragStart( e ); - document.addEventListener( 'mousemove', onDragMove ); - document.addEventListener( 'mouseup', onDragEnd ); - - } ); - - tab.button.addEventListener( 'touchstart', ( e ) => { - - onDragStart( e ); - document.addEventListener( 'touchmove', onDragMove, { passive: false } ); - document.addEventListener( 'touchend', onDragEnd ); + tab.button.addEventListener( 'pointermove', onDragMove ); + tab.button.addEventListener( 'pointerup', onDragEnd ); + tab.button.addEventListener( 'pointercancel', onDragEnd ); } ); @@ -869,13 +860,7 @@ export class Profiler { let startX, startY, startLeft, startTop; // Bring window to front when clicking anywhere on it - windowPanel.addEventListener( 'mousedown', () => { - - this.bringWindowToFront( windowPanel ); - - } ); - - windowPanel.addEventListener( 'touchstart', () => { + windowPanel.addEventListener( 'pointerdown', () => { this.bringWindowToFront( windowPanel ); @@ -894,9 +879,10 @@ export class Profiler { isDragging = true; header.style.cursor = 'grabbing'; + header.setPointerCapture( e.pointerId ); - startX = e.clientX || ( e.touches && e.touches[ 0 ] ? e.touches[ 0 ].clientX : 0 ); - startY = e.clientY || ( e.touches && e.touches[ 0 ] ? e.touches[ 0 ].clientY : 0 ); + startX = e.clientX; + startY = e.clientY; const rect = windowPanel.getBoundingClientRect(); startLeft = rect.left; @@ -910,8 +896,8 @@ export class Profiler { e.preventDefault(); - const currentX = e.clientX || ( e.touches && e.touches[ 0 ] ? e.touches[ 0 ].clientX : 0 ); - const currentY = e.clientY || ( e.touches && e.touches[ 0 ] ? e.touches[ 0 ].clientY : 0 ); + const currentX = e.clientX; + const currentY = e.clientY; const deltaX = currentX - startX; const deltaY = currentY - startY; @@ -987,8 +973,8 @@ export class Profiler { this.panel.style.outline = ''; // Check if dropped over the inspector panel - const currentX = e.clientX || ( e.changedTouches && e.changedTouches[ 0 ].clientX ); - const currentY = e.clientY || ( e.changedTouches && e.changedTouches[ 0 ].clientY ); + const currentX = e.clientX; + const currentY = e.clientY; if ( currentX !== undefined && currentY !== undefined ) { @@ -1010,26 +996,18 @@ export class Profiler { } - document.removeEventListener( 'mousemove', onDragMove ); - document.removeEventListener( 'mouseup', onDragEnd ); - document.removeEventListener( 'touchmove', onDragMove ); - document.removeEventListener( 'touchend', onDragEnd ); + header.removeEventListener( 'pointermove', onDragMove ); + header.removeEventListener( 'pointerup', onDragEnd ); + header.removeEventListener( 'pointercancel', onDragEnd ); }; - header.addEventListener( 'mousedown', ( e ) => { + header.addEventListener( 'pointerdown', ( e ) => { onDragStart( e ); - document.addEventListener( 'mousemove', onDragMove ); - document.addEventListener( 'mouseup', onDragEnd ); - - } ); - - header.addEventListener( 'touchstart', ( e ) => { - - onDragStart( e ); - document.addEventListener( 'touchmove', onDragMove, { passive: false } ); - document.addEventListener( 'touchend', onDragEnd ); + header.addEventListener( 'pointermove', onDragMove ); + header.addEventListener( 'pointerup', onDragEnd ); + header.addEventListener( 'pointercancel', onDragEnd ); } ); @@ -1056,8 +1034,10 @@ export class Profiler { // Bring window to front when resizing this.bringWindowToFront( windowPanel ); - startX = e.clientX || ( e.touches && e.touches[ 0 ] ? e.touches[ 0 ].clientX : 0 ); - startY = e.clientY || ( e.touches && e.touches[ 0 ] ? e.touches[ 0 ].clientY : 0 ); + resizer.setPointerCapture( e.pointerId ); + + startX = e.clientX; + startY = e.clientY; startWidth = windowPanel.offsetWidth; startHeight = windowPanel.offsetHeight; startLeft = windowPanel.offsetLeft; @@ -1071,8 +1051,8 @@ export class Profiler { e.preventDefault(); - const currentX = e.clientX || ( e.touches && e.touches[ 0 ] ? e.touches[ 0 ].clientX : 0 ); - const currentY = e.clientY || ( e.touches && e.touches[ 0 ] ? e.touches[ 0 ].clientY : 0 ); + const currentX = e.clientX; + const currentY = e.clientY; const deltaX = currentX - startX; const deltaY = currentY - startY; @@ -1152,29 +1132,21 @@ export class Profiler { isResizing = false; - document.removeEventListener( 'mousemove', onResizeMove ); - document.removeEventListener( 'mouseup', onResizeEnd ); - document.removeEventListener( 'touchmove', onResizeMove ); - document.removeEventListener( 'touchend', onResizeEnd ); + resizer.removeEventListener( 'pointermove', onResizeMove ); + resizer.removeEventListener( 'pointerup', onResizeEnd ); + resizer.removeEventListener( 'pointercancel', onResizeEnd ); // Save layout after resizing detached window this.saveLayout(); }; - resizer.addEventListener( 'mousedown', ( e ) => { - - onResizeStart( e ); - document.addEventListener( 'mousemove', onResizeMove ); - document.addEventListener( 'mouseup', onResizeEnd ); - - } ); - - resizer.addEventListener( 'touchstart', ( e ) => { + resizer.addEventListener( 'pointerdown', ( e ) => { onResizeStart( e ); - document.addEventListener( 'touchmove', onResizeMove, { passive: false } ); - document.addEventListener( 'touchend', onResizeEnd ); + resizer.addEventListener( 'pointermove', onResizeMove ); + resizer.addEventListener( 'pointerup', onResizeEnd ); + resizer.addEventListener( 'pointercancel', onResizeEnd ); } ); diff --git a/examples/jsm/inspector/ui/Style.js b/examples/jsm/inspector/ui/Style.js index f563ddc9f9f707..5a54cb4694d451 100644 --- a/examples/jsm/inspector/ui/Style.js +++ b/examples/jsm/inspector/ui/Style.js @@ -220,6 +220,7 @@ export class Style { height: 5px; cursor: ns-resize; z-index: 1001; + touch-action: none; } #profiler-panel.position-top .panel-resizer { @@ -457,6 +458,7 @@ export class Style { font-size: 14px; user-select: none; transition: opacity 0.2s, transform 0.2s; + touch-action: none; } .tab-btn.active { @@ -1016,6 +1018,7 @@ body:has(#profiler-panel:not(.visible)) .detached-tab-panel { flex-shrink: 0; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; + touch-action: none; } .detached-tab-header:active { @@ -1101,6 +1104,7 @@ body:has(#profiler-panel:not(.visible)) .detached-tab-panel { height: 20px; cursor: nwse-resize; z-index: 10; + touch-action: none; } .detached-tab-resizer::after { @@ -1130,6 +1134,7 @@ body:has(#profiler-panel:not(.visible)) .detached-tab-panel { height: 5px; cursor: ns-resize; z-index: 10; + touch-action: none; } .detached-tab-resizer-right { @@ -1140,6 +1145,7 @@ body:has(#profiler-panel:not(.visible)) .detached-tab-panel { width: 5px; cursor: ew-resize; z-index: 10; + touch-action: none; } .detached-tab-resizer-bottom { @@ -1150,6 +1156,7 @@ body:has(#profiler-panel:not(.visible)) .detached-tab-panel { height: 5px; cursor: ns-resize; z-index: 10; + touch-action: none; } .detached-tab-resizer-left { @@ -1160,6 +1167,7 @@ body:has(#profiler-panel:not(.visible)) .detached-tab-panel { width: 5px; cursor: ew-resize; z-index: 10; + touch-action: none; } /* Input number spin buttons - hide arrows */ From ab41927ed0e7a6dec275e96e39493983b0034e4c Mon Sep 17 00:00:00 2001 From: sunag Date: Fri, 7 Nov 2025 00:33:22 -0300 Subject: [PATCH 4/5] constrain panel size on window resize --- examples/jsm/inspector/ui/Profiler.js | 59 ++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/examples/jsm/inspector/ui/Profiler.js b/examples/jsm/inspector/ui/Profiler.js index 7e9bf5ecd813a3..2ed5296fdf7d29 100644 --- a/examples/jsm/inspector/ui/Profiler.js +++ b/examples/jsm/inspector/ui/Profiler.js @@ -84,8 +84,49 @@ export class Profiler { }; + const constrainMainPanel = () => { + + // Skip if panel is maximized (it should always fill the screen) + if ( this.panel.classList.contains( 'maximized' ) ) return; + + const windowWidth = window.innerWidth; + const windowHeight = window.innerHeight; + + if ( this.position === 'bottom' ) { + + const currentHeight = this.panel.offsetHeight; + const maxHeight = windowHeight - 50; // Leave 50px margin + + if ( currentHeight > maxHeight ) { + + this.panel.style.height = `${ maxHeight }px`; + this.lastHeightBottom = maxHeight; + + } + + } else if ( this.position === 'right' ) { + + const currentWidth = this.panel.offsetWidth; + const maxWidth = windowWidth - 50; // Leave 50px margin + + if ( currentWidth > maxWidth ) { + + this.panel.style.width = `${ maxWidth }px`; + this.lastWidthRight = maxWidth; + + } + + } + + }; + // Listen for window resize events - window.addEventListener( 'resize', constrainDetachedWindows ); + window.addEventListener( 'resize', () => { + + constrainDetachedWindows(); + constrainMainPanel(); + + } ); } @@ -1514,6 +1555,22 @@ export class Profiler { } + // Constrain saved dimensions to current screen bounds + const windowWidth = window.innerWidth; + const windowHeight = window.innerHeight; + + if ( this.lastHeightBottom > windowHeight - 50 ) { + + this.lastHeightBottom = windowHeight - 50; + + } + + if ( this.lastWidthRight > windowWidth - 50 ) { + + this.lastWidthRight = windowWidth - 50; + + } + // Apply the saved position after shell is set up if ( this.position === 'right' ) { From e6cbe2c2d2604acb4341435c20abb0c1a0c9b6b3 Mon Sep 17 00:00:00 2001 From: sunag Date: Fri, 7 Nov 2025 01:11:01 -0300 Subject: [PATCH 5/5] improve touch interaction for panel resizers --- examples/jsm/inspector/ui/Style.js | 38 ++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/examples/jsm/inspector/ui/Style.js b/examples/jsm/inspector/ui/Style.js index 5a54cb4694d451..cd6e7d656a7e22 100644 --- a/examples/jsm/inspector/ui/Style.js +++ b/examples/jsm/inspector/ui/Style.js @@ -966,6 +966,44 @@ export class Style { } +/* Touch device optimizations */ +@media (hover: none) and (pointer: coarse) { + + .panel-resizer { + top: -10px !important; + height: 20px !important; + } + + #profiler-panel.position-top .panel-resizer { + top: auto !important; + bottom: -10px !important; + height: 20px !important; + } + + #profiler-panel.position-left .panel-resizer { + right: -10px !important; + width: 20px !important; + height: 100% !important; + } + + #profiler-panel.position-right .panel-resizer { + left: -10px !important; + width: 20px !important; + height: 100% !important; + } + + .detached-tab-resizer-top, + .detached-tab-resizer-bottom { + height: 10px !important; + } + + .detached-tab-resizer-left, + .detached-tab-resizer-right { + width: 10px !important; + } + +} + .drag-preview-indicator { position: fixed; background-color: rgba(0, 170, 255, 0.2);