diff --git a/src/client/graphics/layers/PerformanceOverlay.ts b/src/client/graphics/layers/PerformanceOverlay.ts index bf6f64272..3da3809c8 100644 --- a/src/client/graphics/layers/PerformanceOverlay.ts +++ b/src/client/graphics/layers/PerformanceOverlay.ts @@ -72,7 +72,6 @@ export class PerformanceOverlay extends LitElement implements Layer { private fpsHistory: number[] = []; private lastSecondTime: number = 0; private framesThisSecond: number = 0; - private dragStart: { x: number; y: number } = { x: 0, y: 0 }; private tickExecutionTimes: number[] = []; private tickDelayTimes: number[] = []; private tickTimestamps: number[] = []; @@ -88,6 +87,11 @@ export class PerformanceOverlay extends LitElement implements Layer { pendingWidthPx: number; } | null = null; + private dragState: { + pointerId: number; + dragStart: { x: number; y: number }; + } | null = null; + // Smoothed per-layer render timings (EMA over recent frames) private layerStats: Map< string, @@ -154,13 +158,18 @@ export class PerformanceOverlay extends LitElement implements Layer { font-size: 12px; z-index: 9999; user-select: none; - cursor: move; + cursor: default; transition: none; box-sizing: border-box; width: var(--overlay-width, min(460px, calc(100vw - 16px))); max-width: calc(100vw - 16px); max-height: calc(100vh - 16px); + overflow: hidden; + } + + .overlay-scroll { overflow: auto; + max-height: calc(100vh - 56px); } .performance-overlay.dragging { @@ -169,6 +178,21 @@ export class PerformanceOverlay extends LitElement implements Layer { opacity: 0.5; } + .drag-handle { + position: absolute; + top: 0; + left: 0; + right: 12px; /* leave space for the resize handle */ + height: 32px; + cursor: grab; + touch-action: none; + pointer-events: auto; + } + + .performance-overlay.dragging .drag-handle { + cursor: grabbing; + } + .performance-line { margin: 2px 0; } @@ -377,6 +401,14 @@ export class PerformanceOverlay extends LitElement implements Layer { this.resizeState = null; } + if (!visible && this.dragState) { + globalThis.removeEventListener("pointermove", this.onDragPointerMove); + globalThis.removeEventListener("pointerup", this.onDragPointerUp); + globalThis.removeEventListener("pointercancel", this.onDragPointerUp); + this.dragState = null; + this.isDragging = false; + } + this.requestUpdate(); } @@ -386,35 +418,11 @@ export class PerformanceOverlay extends LitElement implements Layer { this.userSettings.set("settings.performanceOverlay", nextVisible); } - private handleMouseDown = (e: MouseEvent) => { - // Don't start dragging if clicking on close button - const target = e.target as HTMLElement; - if ( - target.classList.contains("close-button") || - target.classList.contains("reset-button") || - target.classList.contains("copy-json-button") || - target.classList.contains("collapse-button") || - target.classList.contains("resize-handle") - ) { - return; - } + private onDragPointerMove = (e: PointerEvent) => { + if (!this.dragState || e.pointerId !== this.dragState.pointerId) return; - this.isDragging = true; - this.dragStart = { - x: e.clientX - this.position.x, - y: e.clientY - this.position.y, - }; - - document.addEventListener("mousemove", this.handleMouseMove); - document.addEventListener("mouseup", this.handleMouseUp); - e.preventDefault(); - }; - - private handleMouseMove = (e: MouseEvent) => { - if (!this.isDragging) return; - - const newX = e.clientX - this.dragStart.x; - const newY = e.clientY - this.dragStart.y; + const newX = e.clientX - this.dragState.dragStart.x; + const newY = e.clientY - this.dragState.dragStart.y; const margin = 8; const viewportWidth = window.innerWidth; @@ -436,20 +444,44 @@ export class PerformanceOverlay extends LitElement implements Layer { this.requestUpdate(); }; - private handleMouseUp = () => { + private onDragPointerUp = (e: PointerEvent) => { + if (!this.dragState || e.pointerId !== this.dragState.pointerId) return; + + globalThis.removeEventListener("pointermove", this.onDragPointerMove); + globalThis.removeEventListener("pointerup", this.onDragPointerUp); + globalThis.removeEventListener("pointercancel", this.onDragPointerUp); + + this.dragState = null; this.isDragging = false; - document.removeEventListener("mousemove", this.handleMouseMove); - document.removeEventListener("mouseup", this.handleMouseUp); + this.requestUpdate(); + }; + + private handleDragPointerDown = (e: PointerEvent) => { + e.preventDefault(); + e.stopPropagation(); + + this.isDragging = true; + this.dragState = { + pointerId: e.pointerId, + dragStart: { + x: e.clientX - this.position.x, + y: e.clientY - this.position.y, + }, + }; + + globalThis.addEventListener("pointermove", this.onDragPointerMove); + globalThis.addEventListener("pointerup", this.onDragPointerUp); + globalThis.addEventListener("pointercancel", this.onDragPointerUp); }; private onResizePointerMove = (e: PointerEvent) => { if (!this.resizeState || e.pointerId !== this.resizeState.pointerId) return; const margin = 8; - const minWidthPx = 260; const viewportWidth = window.innerWidth; const left = Math.max(margin, Math.min(this.position.x, viewportWidth)); - const maxWidthPx = Math.max(minWidthPx, viewportWidth - left - margin); + const maxWidthPx = Math.max(120, viewportWidth - left - margin); + const minWidthPx = Math.min(260, maxWidthPx); const delta = e.clientX - this.resizeState.startClientX; const nextWidth = this.resizeState.startWidthPx + delta; @@ -937,8 +969,11 @@ export class PerformanceOverlay extends LitElement implements Layer {
+
@@ -954,157 +989,160 @@ export class PerformanceOverlay extends LitElement implements Layer { class="resize-handle" @pointerdown=${this.handleResizePointerDown} >
-
- ${translateText("performance_overlay.fps")} - ${this.currentFPS} -
-
- ${translateText("performance_overlay.avg_60s")} - ${this.averageFPS} -
-
- ${translateText("performance_overlay.frame")} - ${this.frameTime}ms -
-
- ${translateText("performance_overlay.tps")} - ${this.currentTPS} - (${translateText("performance_overlay.tps_avg_60s")} - ${this.averageTPS}) -
-
- ${translateText("performance_overlay.tick_exec")} - ${this.tickExecutionAvg.toFixed(2)}ms - (max: ${this.tickExecutionMax}ms) -
-
- ${translateText("performance_overlay.tick_delay")} - ${this.tickDelayAvg.toFixed(2)}ms - (max: ${this.tickDelayMax}ms) -
- ${this.layerBreakdown.length - ? html`
-
- ${translateText("performance_overlay.layers_header")} - -
-
- ${translateText("performance_overlay.render_layers_summary", { - frames: this.renderLastTickFrameCount, - ms: this.renderLastTickLayerTotalMs.toFixed(2), - })} -
- ${this.renderLayersExpanded - ? html`
- - - ${translateText( - "performance_overlay.render_layers_table_header", - )} - -
- ${renderLayersToShow.map((layer) => { - const width = Math.min( - 100, - (layer.avg / maxLayerAvg) * 100 || 0, - ); - const perTickRenderMs = - this.renderLastTickLayerDurations[layer.name] ?? 0; - const perTickRenderAvgMs = - this.renderPerTickLayerStats.get(layer.name)?.avg ?? 0; - const isInactive = perTickRenderMs <= 0.01; - const title = `${layer.name} | last tick render: ${perTickRenderMs.toFixed( - 2, - )}ms`; - return html`
- ${layer.name} - +
+
+ ${translateText("performance_overlay.fps")} + ${this.currentFPS} +
+
+ ${translateText("performance_overlay.avg_60s")} + ${this.averageFPS} +
+
+ ${translateText("performance_overlay.frame")} + ${this.frameTime}ms +
+
+ ${translateText("performance_overlay.tps")} + ${this.currentTPS} + (${translateText("performance_overlay.tps_avg_60s")} + ${this.averageTPS}) +
+
+ ${translateText("performance_overlay.tick_exec")} + ${this.tickExecutionAvg.toFixed(2)}ms + (max: ${this.tickExecutionMax}ms) +
+
+ ${translateText("performance_overlay.tick_delay")} + ${this.tickDelayAvg.toFixed(2)}ms + (max: ${this.tickDelayMax}ms) +
+ ${this.layerBreakdown.length + ? html`
+
+ ${translateText("performance_overlay.layers_header")} + +
+
+ ${translateText("performance_overlay.render_layers_summary", { + frames: this.renderLastTickFrameCount, + ms: this.renderLastTickLayerTotalMs.toFixed(2), + })} +
+ ${this.renderLayersExpanded + ? html`
+ - ${layer.avg.toFixed(2)} / ${layer.max.toFixed(2)}ms | - ${perTickRenderAvgMs.toFixed(2)}ms + ${translateText( + "performance_overlay.render_layers_table_header", + )} -
`; - })}` - : html``} -
` - : html``} - ${this.tickLayerBreakdown.length - ? html`
-
- ${translateText( - "performance_overlay.tick_layers_header", - )} - -
-
- ${translateText("performance_overlay.tick_layers_summary", { - count: this.tickLayerLastCount, - ms: this.tickLayerLastTotalMs.toFixed(2), - })} -
- ${this.tickLayersExpanded - ? html`
- - - ${translateText( - "performance_overlay.tick_layers_table_header", - )} - -
- ${tickLayersToShow.map((layer) => { - const width = Math.min( - 100, - (layer.avg / maxTickLayerAvg) * 100 || 0, - ); - const lastTickMs = - this.tickLayerLastDurations[layer.name] ?? 0; - const isInactive = lastTickMs <= 0.01; - const title = `${layer.name} | last tick: ${lastTickMs.toFixed(2)}ms`; - return html`
- ${layer.name} + ${renderLayersToShow.map((layer) => { + const width = Math.min( + 100, + (layer.avg / maxLayerAvg) * 100 || 0, + ); + const perTickRenderMs = + this.renderLastTickLayerDurations[layer.name] ?? 0; + const perTickRenderAvgMs = + this.renderPerTickLayerStats.get(layer.name)?.avg ?? + 0; + const isInactive = perTickRenderMs <= 0.01; + const title = `${layer.name} | last tick render: ${perTickRenderMs.toFixed( + 2, + )}ms`; + return html`
+ ${layer.name} + + + ${layer.avg.toFixed(2)} / ${layer.max.toFixed(2)}ms + | ${perTickRenderAvgMs.toFixed(2)}ms + +
`; + })}` + : html``} +
` + : html``} + ${this.tickLayerBreakdown.length + ? html`
+
+ ${translateText( + "performance_overlay.tick_layers_header", + )} + +
+
+ ${translateText("performance_overlay.tick_layers_summary", { + count: this.tickLayerLastCount, + ms: this.tickLayerLastTotalMs.toFixed(2), + })} +
+ ${this.tickLayersExpanded + ? html`
+ - ${layer.avg.toFixed(2)} / ${layer.max.toFixed(2)}ms + ${translateText( + "performance_overlay.tick_layers_table_header", + )} -
`; - })}` - : html``} -
` - : html``} +
+ ${tickLayersToShow.map((layer) => { + const width = Math.min( + 100, + (layer.avg / maxTickLayerAvg) * 100 || 0, + ); + const lastTickMs = + this.tickLayerLastDurations[layer.name] ?? 0; + const isInactive = lastTickMs <= 0.01; + const title = `${layer.name} | last tick: ${lastTickMs.toFixed(2)}ms`; + return html`
+ ${layer.name} + + ${layer.avg.toFixed(2)} / ${layer.max.toFixed(2)}ms + +
`; + })}` + : html``} +
` + : html``} +
`; }