diff --git a/packages/pi-tui/src/overlay-layout.ts b/packages/pi-tui/src/overlay-layout.ts index 1896c5bba..d3c2d8b6a 100644 --- a/packages/pi-tui/src/overlay-layout.ts +++ b/packages/pi-tui/src/overlay-layout.ts @@ -6,7 +6,7 @@ */ import type { OverlayAnchor, OverlayOptions, SizeValue } from "./tui.js"; -import { extractSegments, sliceByColumn, sliceWithWidth, truncateToWidth, visibleWidth } from "./utils.js"; +import { applyBackgroundToLine, extractSegments, sliceByColumn, sliceWithWidth, truncateToWidth, visibleWidth } from "./utils.js"; import { isImageLine } from "./terminal-image.js"; import { CURSOR_MARKER } from "./tui.js"; @@ -324,6 +324,17 @@ export function compositeOverlays( const viewportStart = Math.max(0, workingHeight - termHeight); + // Apply backdrop dimming if any visible overlay requests it + const hasBackdrop = visibleEntries.some((e) => e.options?.backdrop); + if (hasBackdrop) { + const dimFn = (text: string) => `\x1b[2m${text}\x1b[22m`; + for (let i = viewportStart; i < result.length; i++) { + if (!isImageLine(result[i]) && result[i].length > 0) { + result[i] = applyBackgroundToLine(result[i], termWidth, dimFn); + } + } + } + // Composite each overlay for (const { overlayLines, row, col, w } of rendered) { for (let i = 0; i < overlayLines.length; i++) { diff --git a/packages/pi-tui/src/tui.ts b/packages/pi-tui/src/tui.ts index 8e3db6f05..0bdb8c746 100644 --- a/packages/pi-tui/src/tui.ts +++ b/packages/pi-tui/src/tui.ts @@ -141,6 +141,8 @@ export interface OverlayOptions { visible?: (termWidth: number, termHeight: number) => boolean; /** If true, don't capture keyboard focus when shown */ nonCapturing?: boolean; + /** If true, dim the background behind the overlay */ + backdrop?: boolean; } /** diff --git a/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts b/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts index 9abe3fbb8..1e0faf7a0 100644 --- a/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +++ b/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts @@ -44,6 +44,7 @@ export function registerShortcuts(pi: ExtensionAPI): void { minWidth: 60, maxHeight: "88%", anchor: "center", + backdrop: true, }, }, ); diff --git a/src/resources/extensions/gsd/commands/handlers/notifications-handler.ts b/src/resources/extensions/gsd/commands/handlers/notifications-handler.ts index e66309b95..16d30d49a 100644 --- a/src/resources/extensions/gsd/commands/handlers/notifications-handler.ts +++ b/src/resources/extensions/gsd/commands/handlers/notifications-handler.ts @@ -105,6 +105,7 @@ export async function handleNotificationsCommand( minWidth: 60, maxHeight: "88%", anchor: "center", + backdrop: true, }, }, ); diff --git a/src/resources/extensions/gsd/notification-overlay.ts b/src/resources/extensions/gsd/notification-overlay.ts index ec25c440a..6ab73f729 100644 --- a/src/resources/extensions/gsd/notification-overlay.ts +++ b/src/resources/extensions/gsd/notification-overlay.ts @@ -164,6 +164,11 @@ export class GSDNotificationOverlay { this.scrollOffset = Math.min(this.scrollOffset, maxScroll); const visibleContent = content.slice(this.scrollOffset, this.scrollOffset + visibleContentRows); + // Pad to fill viewport so the overlay covers underlying content + while (visibleContent.length < visibleContentRows) { + visibleContent.push(""); + } + const lines = this.wrapInBox(visibleContent, width); this.cachedWidth = width;