diff --git a/ac-electron/main.js b/ac-electron/main.js index 67bad19d38..cdd9e52a3d 100644 --- a/ac-electron/main.js +++ b/ac-electron/main.js @@ -82,6 +82,11 @@ let preferences = { // default). When false (new default), they behave like normal windows // and can be ordered underneath. Toggled from the tray menu. alwaysOnTop: false, + // When the Notepat Overlay is open, should it pass mouse events + // through to the app underneath (visual-only widget) or capture them + // (playable floating piano)? Toggled from the tray while the overlay + // is open. + overlayClickThrough: false, }; function loadPreferences() { @@ -846,6 +851,27 @@ function rebuildTrayMenu() { click: () => openNotepatWindow() }); + // Overlay variant — transparent, chromeless, floats above fullscreen + // apps and every Space. Click-through is a sibling checkbox that only + // takes effect while the overlay is open. + menuItems.push({ + label: 'Open Notepat Overlay 🪟', + click: () => openNotepatOverlayWindow() + }); + + menuItems.push({ + label: 'Overlay: Click-Through', + type: 'checkbox', + checked: !!preferences.overlayClickThrough, + click: (item) => { + preferences.overlayClickThrough = !!item.checked; + savePreferences(); + if (notepatOverlayWindow && !notepatOverlayWindow.isDestroyed()) { + notepatOverlayWindow.setIgnoreMouseEvents(preferences.overlayClickThrough, { forward: true }); + } + } + }); + // Always-on-top toggle. Persisted to preferences.json and applied to // every current AC window on change; new windows inherit via their // BrowserWindow opts. @@ -1261,6 +1287,73 @@ function openNotepatWindow() { return notepatWindow; } +// Open a Notepat Overlay — transparent, chromeless, screen-saver-level +// window that floats above every app and follows the user across Spaces. +// Like a macOS HUD / widget. Click-through can be toggled from the tray +// so the overlay becomes visual-only (mouse events pass to whatever's +// behind it). The notepat piece reads ?overlay=true and skips its +// background wipe so the desktop shows through between keys. +let notepatOverlayWindow = null; +function openNotepatOverlayWindow() { + if (notepatOverlayWindow && !notepatOverlayWindow.isDestroyed()) { + notepatOverlayWindow.show(); + notepatOverlayWindow.focus(); + return notepatOverlayWindow; + } + + const baseUrl = startInDevMode ? 'http://localhost:8888' : 'https://aesthetic.computer'; + const display = screen.getPrimaryDisplay().workAreaSize; + const w = 340, h = 180; + + notepatOverlayWindow = new BrowserWindow({ + width: w, + height: h, + x: display.width - w - 24, + y: display.height - h - 24, + minWidth: 220, + minHeight: 130, + title: 'Notepat Overlay', + frame: false, + transparent: true, + hasShadow: false, + resizable: true, + skipTaskbar: true, + backgroundColor: '#00000000', + type: process.platform === 'darwin' ? 'panel' : undefined, + webPreferences: { + nodeIntegration: true, + contextIsolation: false, + webviewTag: true, + backgroundThrottling: false, + }, + }); + + // screen-saver level sits above fullscreen apps on macOS. Follow the + // user across Spaces (and into fullscreen Spaces) so the overlay is + // actually always visible. + notepatOverlayWindow.setAlwaysOnTop(true, 'screen-saver'); + notepatOverlayWindow.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true }); + + if (preferences.overlayClickThrough) { + notepatOverlayWindow.setIgnoreMouseEvents(true, { forward: true }); + } + + notepatOverlayWindow.loadFile( + getAppPath('renderer/notepat-overlay.html'), + { query: { piece: 'notepat', base: baseUrl } }, + ); + + const windowId = windowIdCounter++; + windows.set(windowId, { window: notepatOverlayWindow, mode: 'notepat-overlay' }); + + notepatOverlayWindow.on('closed', () => { + windows.delete(windowId); + notepatOverlayWindow = null; + }); + + return notepatOverlayWindow; +} + // Open KidLisp window (kidlisp.com) function openKidLispWindow() { const { width, height } = screen.getPrimaryDisplay().workAreaSize; diff --git a/ac-electron/package-lock.json b/ac-electron/package-lock.json index 24780df371..93ff8d6415 100644 --- a/ac-electron/package-lock.json +++ b/ac-electron/package-lock.json @@ -1,12 +1,12 @@ { "name": "aesthetic-computer", - "version": "0.1.38", + "version": "0.1.39", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "aesthetic-computer", - "version": "0.1.38", + "version": "0.1.39", "license": "MIT", "dependencies": { "@electron/rebuild": "^4.0.2", diff --git a/ac-electron/package.json b/ac-electron/package.json index d2df5d14d9..f02f1fe27e 100644 --- a/ac-electron/package.json +++ b/ac-electron/package.json @@ -1,6 +1,6 @@ { "name": "aesthetic-computer", - "version": "0.1.38", + "version": "0.1.39", "description": "Aesthetic Computer", "homepage": "https://aesthetic.computer", "author": "Aesthetic Computer ", diff --git a/ac-electron/renderer/notepat-overlay.html b/ac-electron/renderer/notepat-overlay.html new file mode 100644 index 0000000000..dac0789ebe --- /dev/null +++ b/ac-electron/renderer/notepat-overlay.html @@ -0,0 +1,117 @@ + + + + + Notepat Overlay + + + +
+ +
+ + + diff --git a/system/public/aesthetic.computer/disks/notepat.mjs b/system/public/aesthetic.computer/disks/notepat.mjs index f5565dfe3d..18aeef6863 100644 --- a/system/public/aesthetic.computer/disks/notepat.mjs +++ b/system/public/aesthetic.computer/disks/notepat.mjs @@ -1065,6 +1065,7 @@ let metronomeBallPos = 0; // 0-1 bouncing ball position (bounces between beats) // 🎹 DAW sync state (Ableton Live via M4L) let dawMode = false; // Set from query param OR auto-detected from DAW messages +let overlayMode = false; // ?overlay=true — transparent bg for electron overlay window let dawAutoDetected = false; // True if we auto-detected DAW mode from messages let dawSynced = false; // True when receiving valid DAW data let dawBpm = null; // BPM from DAW @@ -1439,6 +1440,11 @@ async function boot({ // 🎹 Check if we're in DAW mode (loaded from Ableton M4L) dawMode = query?.daw === "1" || query?.daw === 1 || query?.daw === true; console.log("🎹 Notepat: dawMode =", dawMode, "query.daw =", query?.daw, typeof query?.daw); + + // ?overlay=true → transparent-bg mode for the ac-electron Notepat Overlay + // window. Skip background wipes in paint() so the desktop shows through. + overlayMode = query?.overlay === "true" || query?.overlay === true || query?.overlay === "1"; + if (overlayMode) console.log("🪟 Notepat: overlay mode — skipping background wipes"); // Also check if we already have DAW data (survives hot reload) if (!dawMode && sound.daw?.bpm) { @@ -3095,8 +3101,14 @@ function paint({ sidePanelWidth: autopatConfig.sidePanelWidth, }).layout; - // 🎨 KidLisp visualization — bounded to available space above/between buttons - if (kidlispBgEnabled && kidlispBackground && !paintPictureOverlay) { + // 🪟 Overlay mode: skip background fill entirely so the electron window's + // transparency lets the desktop show through between keys. All branches + // below (kidlisp viz, fullscreen visualizer, recital, plain bg) paint a + // base background each frame — none of them are wanted here. + if (overlayMode) { + // Also skip the background visualizers (viz, recital, kidlisp viz) — + // overlay mode is strictly the piano keys + labels on transparent. + } else if (kidlispBgEnabled && kidlispBackground && !paintPictureOverlay) { wipe(bg); // Base background first const klY = OS_BAR_BOTTOM; const klBottom = earlyLayout.topButtonY;