Three defects reported from the real window, each with a distinct cause: 1. "Top and sides do not connect" — 7.css couples the glass treatment to the title bar with a DIRECT-child selector (.window.glass > .title-bar), and our .title-bar lived inside an inner div of <app-title-bar>, so the rule never matched: the title bar fell back to the opaque base style with its own border and radius, a blue slab sitting on the frame. The .title-bar class (and the drag region + dblclick-maximize) moved onto the component HOST, so the selector now matches and the title bar is the translucent streaked glass fused with the frame. 2. "Black background near the window buttons" — .window.glass::after is backdrop-filter: blur(4px). CSS cannot sample the DESKTOP behind a transparent native window (there is no backdrop to read) and WebKitGTK paints the unsampleable layer BLACK, so every see-through part of the glass — the ring, the area around the controls — composited over black. Neutralized in skinfix; the glass tint now shows the real desktop through the transparent window, which is closer to Aero than a blur we cannot have. (True blur-behind needs compositor support; GNOME exposes none to X11/XWayland clients.) 3. "Shadow way too strong" — the 6px gutter hard-clipped 7.css's 2px 2px 10px 1px #000000b3 shadow into a solid dark band. The gutter now matches the shadow's extents exactly: blur+spread-offset = 9px up/left, offset+blur+spread = 13px down/right, so it falls off to nothing. Verified on the REAL window this time: captured the running app with ImageMagick import (the app runs on XWayland, so X11 capture works) before and after. Before: black block around min/max/close, murky dark ring, detached blue title strip. After: streaks span the top, controls hang from the frame edge as on real Win7, ring is glass-blue, no black anywhere. Known remaining (pre-existing, next stages): the tab-bar/toolbar row overlaps itself top-left (tabs conversion, Stage 4); maximized windows should drop radius/shadow/gutter. Co-Authored-By: Claude <noreply@anthropic.com>
78 lines
3.0 KiB
TypeScript
78 lines
3.0 KiB
TypeScript
import { Component, inject, input, output, signal } from '@angular/core';
|
|
import { RpcService } from '../rpc.service';
|
|
|
|
/**
|
|
* The Aero title bar. Rendered by all three window kinds. The native frame is
|
|
* gone on every platform (titleBarStyle 'hidden'), so this row IS the window's
|
|
* chrome.
|
|
*
|
|
* The markup is 7.css's contract, not ours: `.title-bar` > `.title-bar-text` +
|
|
* `.title-bar-controls` > buttons keyed by `aria-label` (Minimize / Maximize /
|
|
* Restore / Close each get their own glyph). Nothing here is styled with
|
|
* Tailwind on purpose — the whole row comes from @layer skin.
|
|
*
|
|
* `.title-bar` lives on the HOST element, not on an inner div. 7.css couples
|
|
* the glass treatment with a DIRECT-child selector — `.window.glass >
|
|
* .title-bar` — and an inner div puts <app-title-bar> in between, so the rule
|
|
* never matched: the title bar rendered as the opaque fallback with its own
|
|
* border, visibly disconnected from the frame, instead of the translucent
|
|
* streaked glass that fuses with it.
|
|
*
|
|
* Dragging rides on electrobun's preload drag-region classes. Two things
|
|
* electrobun does NOT provide and this component implements:
|
|
* - double-click to maximize (it only wires mousedown/mouseup)
|
|
* - the maximize/restore glyph state, which has no event; bun derives it
|
|
* from the native resize and pushes `windowState` (see trackMaximizeState).
|
|
*/
|
|
@Component({
|
|
selector: 'app-title-bar',
|
|
host: {
|
|
class: 'title-bar electrobun-webkit-app-region-drag',
|
|
'(dblclick)': 'toggle()',
|
|
},
|
|
template: `
|
|
<div class="title-bar-text">{{ title() }}</div>
|
|
<div class="title-bar-controls electrobun-webkit-app-region-no-drag">
|
|
@if (resizable()) {
|
|
<button aria-label="Minimize" (click)="minimize()"></button>
|
|
<button
|
|
[attr.aria-label]="maximized() ? 'Restore' : 'Maximize'"
|
|
(click)="toggle()"
|
|
></button>
|
|
}
|
|
<button aria-label="Close" (click)="closeRequested.emit()"></button>
|
|
</div>
|
|
`,
|
|
})
|
|
export class TitleBar {
|
|
readonly title = input('');
|
|
/** The manager window is a fixed-size modal — it gets Close only. */
|
|
readonly resizable = input(true);
|
|
/** Hosts decide what closing means (quit / dirty prompt / managerClose). */
|
|
readonly closeRequested = output<void>();
|
|
|
|
protected readonly maximized = signal(false);
|
|
|
|
private readonly rpc = inject(RpcService);
|
|
|
|
constructor() {
|
|
if (!this.rpc.available) return;
|
|
// No maximize event exists, so seed from a one-shot read and then follow
|
|
// the resize-derived push (which also covers Aero Snap and Win+Up).
|
|
void this.rpc.request.windowIsMaximized({}).then((res) => {
|
|
if (res.ok) this.maximized.set(res.maximized);
|
|
});
|
|
this.rpc.onWindowState((maximized) => this.maximized.set(maximized));
|
|
}
|
|
|
|
protected minimize(): void {
|
|
void this.rpc.request.windowMinimize({});
|
|
}
|
|
|
|
protected toggle(): void {
|
|
void this.rpc.request.windowToggleMaximize({}).then((res) => {
|
|
if (res.ok) this.maximized.set(res.maximized);
|
|
});
|
|
}
|
|
}
|