Stage 3b: transparent native window so the Aero frame is a real frame
.window's 6px radius and its outer drop shadow are painted by the webview OUTSIDE the window's own rectangle, so on an opaque surface they were simply clipped — square corners, no shadow. chromeOptions now passes `transparent: true`, the page paints no background of its own, and .window takes a 6px margin as the gutter the shadow falls into (h-screen shrinks by the same amount so the gutter does not produce a scrollbar). Confirmed on Linux/WebKitGTK in a real run, which is the half the Firefox harness cannot model: GTK: Created window without decorations (custom titlebar) GTK: Created transparent window GTK WebKit: Applied transparent background to webview Also caught by the run rather than shipped: `transparent` is set in chromeOptions, which openSecondaryWindow spreads too — so the editor and connection-manager windows became transparent while still rendering bare content, i.e. floating widgets on nothing. Both roots now take the same .window.glass frame and their own windowFocused signal. The editor's blur binding had to merge with the existing flushDraft() call; a duplicated host key is a TS1117 build error rather than a merge, which is how it surfaced. The old anti-flash <style> in index.html is gone with the opaque backgrounds it guarded — windows are created hidden and revealed on dom-ready anyway. Not handled yet: a maximized Win7 window has no radius, shadow or gutter. The 6px margin currently applies in every state. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+8
-1
@@ -51,7 +51,14 @@ export function buildPreload(
|
|||||||
* gtk_window_set_decorated(false).
|
* gtk_window_set_decorated(false).
|
||||||
*/
|
*/
|
||||||
export function chromeOptions() {
|
export function chromeOptions() {
|
||||||
return { titleBarStyle: "hidden" as const };
|
// `transparent` is what lets the Aero frame be a real frame: .window's 6px
|
||||||
|
// radius and its outer drop shadow are drawn by the webview OUTSIDE the
|
||||||
|
// window's own rectangle, so on an opaque surface they are simply clipped —
|
||||||
|
// square corners and no shadow. The UI reserves the shadow's gutter (see
|
||||||
|
// the .window margin in styles.css).
|
||||||
|
// PORTABILITY: on Linux this needs a compositor granting an RGBA visual;
|
||||||
|
// without one the frame still draws, just without the shadow falloff.
|
||||||
|
return { titleBarStyle: "hidden" as const, transparent: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<div class="flex h-screen flex-col bg-background text-foreground">
|
<div class="window glass flex h-screen flex-col" [class.active]="windowFocused()">
|
||||||
<!-- win7 skin only: frameless window. Fixed-size modal, so Close only. -->
|
<!-- Frameless window. Fixed-size modal, so Close only. -->
|
||||||
<app-title-bar
|
<app-title-bar
|
||||||
[title]="i18n.t('connMgr.title')"
|
[title]="i18n.t('connMgr.title')"
|
||||||
[resizable]="false"
|
[resizable]="false"
|
||||||
|
|||||||
@@ -78,10 +78,16 @@ function fromDraft(d: Draft): SavedConnection {
|
|||||||
host: {
|
host: {
|
||||||
'(window:keydown.escape)': 'onEscape()',
|
'(window:keydown.escape)': 'onEscape()',
|
||||||
'(window:contextmenu)': 'blockContextMenu($event)',
|
'(window:contextmenu)': 'blockContextMenu($event)',
|
||||||
|
// Aero desaturates an unfocused window's chrome — see .window.active.
|
||||||
|
'(window:focus)': 'windowFocused.set(true)',
|
||||||
|
'(window:blur)': 'windowFocused.set(false)',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export class ConnectionManager {
|
export class ConnectionManager {
|
||||||
readonly i18n = inject(I18nService);
|
readonly i18n = inject(I18nService);
|
||||||
|
|
||||||
|
/** Drives `.window.active` on this window's Aero frame. */
|
||||||
|
readonly windowFocused = signal(document.hasFocus());
|
||||||
readonly store = inject(ConnectionsService);
|
readonly store = inject(ConnectionsService);
|
||||||
readonly dialogs = inject(DialogsService);
|
readonly dialogs = inject(DialogsService);
|
||||||
private readonly rpc = inject(RpcService);
|
private readonly rpc = inject(RpcService);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<div class="flex h-screen flex-col bg-background text-foreground">
|
<div class="window glass flex h-screen flex-col" [class.active]="windowFocused()">
|
||||||
@if (!ctx) {
|
@if (!ctx) {
|
||||||
<div class="flex flex-1 items-center justify-center">
|
<div class="flex flex-1 items-center justify-center">
|
||||||
<div class="max-w-sm rounded-lg border border-border bg-card p-6 text-center shadow-sm">
|
<div class="max-w-sm rounded-lg border border-border bg-card p-6 text-center shadow-sm">
|
||||||
@@ -10,8 +10,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
<!-- win7 skin only: the window is frameless. The X runs the SAME dirty
|
<!-- The window is frameless. The X runs the SAME dirty prompt as ⌘W and
|
||||||
prompt as ⌘W and the Close button — never a raw window close. -->
|
the Close button — never a raw window close. -->
|
||||||
<app-title-bar [title]="ctx.name + (dirty() ? ' *' : '')" (closeRequested)="requestClose()" />
|
<app-title-bar [title]="ctx.name + (dirty() ? ' *' : '')" (closeRequested)="requestClose()" />
|
||||||
<!-- header: on macOS this row IS the titlebar (hiddenInset + drag region),
|
<!-- header: on macOS this row IS the titlebar (hiddenInset + drag region),
|
||||||
elsewhere the native titlebar sits above and this is a compact strip -->
|
elsewhere the native titlebar sits above and this is a compact strip -->
|
||||||
|
|||||||
@@ -35,12 +35,19 @@ const DRAFT_DEBOUNCE_MS = 400;
|
|||||||
templateUrl: './editor-window.html',
|
templateUrl: './editor-window.html',
|
||||||
host: {
|
host: {
|
||||||
'(window:keydown)': 'onKeydown($event)',
|
'(window:keydown)': 'onKeydown($event)',
|
||||||
'(window:blur)': 'flushDraft()',
|
|
||||||
'(window:contextmenu)': 'blockContextMenu($event)',
|
'(window:contextmenu)': 'blockContextMenu($event)',
|
||||||
|
// Aero desaturates an unfocused window's chrome — see .window.active.
|
||||||
|
// blur already had to flush the draft; both run from the one binding
|
||||||
|
// (a duplicated key is a TS1117 build error, not a merge).
|
||||||
|
'(window:focus)': 'windowFocused.set(true)',
|
||||||
|
'(window:blur)': 'flushDraft(); windowFocused.set(false)',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export class EditorWindow {
|
export class EditorWindow {
|
||||||
readonly i18n = inject(I18nService);
|
readonly i18n = inject(I18nService);
|
||||||
|
|
||||||
|
/** Drives `.window.active` on this window's Aero frame. */
|
||||||
|
readonly windowFocused = signal(document.hasFocus());
|
||||||
readonly ctx = EDITOR_CONTEXT;
|
readonly ctx = EDITOR_CONTEXT;
|
||||||
readonly modLabel = MOD_LABEL;
|
readonly modLabel = MOD_LABEL;
|
||||||
readonly altLabel = ALT_LABEL;
|
readonly altLabel = ALT_LABEL;
|
||||||
|
|||||||
+6
-17
@@ -6,24 +6,13 @@
|
|||||||
<base href="/" />
|
<base href="/" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||||
<!-- Pre-stylesheet paint: the window background must be right BEFORE
|
|
||||||
styles.css parses or new windows flash white/inverted. Only the
|
|
||||||
html background decision lives here — values must stay in sync with
|
|
||||||
the corresponding --background tokens in styles.css. The data-*
|
|
||||||
attributes are baked in by the bun preload (buildPreload in
|
|
||||||
src/bun/windows.ts) so forced themes are correct at first paint. -->
|
|
||||||
<style>
|
<style>
|
||||||
/* Four theme states, mirroring the .win7 --background values in
|
/* The native window is transparent (chromeOptions), so the page itself
|
||||||
styles.css. The preload adds the class before any page script, so it is
|
must not paint a background — the Aero frame in styles.css is the only
|
||||||
already on <html> here. Omitting this makes every window flash white
|
opaque surface, and its radius/drop shadow need to fall outside it.
|
||||||
before styles.css parses. */
|
Windows are created hidden and revealed on dom-ready, so there is no
|
||||||
html.win7 { background: #ffffff; }
|
unthemed flash to guard against here any more. */
|
||||||
html.win7[data-theme='dark'] { background: #1f1f1f; }
|
html.win7, html.win7 body { background: transparent; }
|
||||||
html.win7[data-theme='dark'][data-dark-tone='darker'] { background: #121212; }
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
html.win7:not([data-theme='light']) { background: #1f1f1f; }
|
|
||||||
html.win7:not([data-theme='light'])[data-dark-tone='darker'] { background: #121212; }
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -639,6 +639,17 @@ body {
|
|||||||
border-color: var(--w7-li-bd-hl);
|
border-color: var(--w7-li-bd-hl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- Window frame ----
|
||||||
|
The native window is transparent (chromeOptions in src/bun/windows.ts), so
|
||||||
|
nothing but .window paints an opaque surface. The margin is the gutter the
|
||||||
|
drop shadow falls into — without it the shadow has no room and the frame
|
||||||
|
reads as square. h-screen (100vh) has to shrink by the same amount or the
|
||||||
|
window overflows into a scrollbar. */
|
||||||
|
.win7 .window {
|
||||||
|
margin: 6px;
|
||||||
|
height: calc(100vh - 12px);
|
||||||
|
}
|
||||||
|
|
||||||
/* Constant row geometry. 7.css gives ONLY tr.highlighted a 1px border, and
|
/* Constant row geometry. 7.css gives ONLY tr.highlighted a 1px border, and
|
||||||
the table is border-collapse: collapse — so a selected row physically grew
|
the table is border-collapse: collapse — so a selected row physically grew
|
||||||
1px at top and bottom and shoved every row below it down by 2px as the
|
1px at top and bottom and shoved every row below it down by 2px as the
|
||||||
|
|||||||
Reference in New Issue
Block a user