Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions src/Menubar.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -605,3 +605,45 @@ describe('Menubar trigger option', () => {
});
});
});

describe('Menubar repositioning on resize', () => {
beforeEach(() => {
vi.clearAllMocks();
});

const findHandler = (
win: BrowserWindow,
event: string,
): ((...args: unknown[]) => void) | undefined => {
const call = (win.on as Mock).mock.calls.find(([name]) => name === event);
return call?.[1] as ((...args: unknown[]) => void) | undefined;
};

it('positions the window on `showWindow`', async () => {
const mb = new Menubar(app, { preloadWindow: true });
await new Promise<void>((resolve) => mb.on('ready', () => resolve()));

await mb.showWindow();

expect(mb.window!.setPosition).toHaveBeenCalledTimes(1);
const [x, y] = (mb.window!.setPosition as Mock).mock.calls[0] as [
number,
number,
];
expect(Number.isInteger(x)).toBe(true);
expect(Number.isInteger(y)).toBe(true);
});

it('repositions the window when it resizes (e.g. via `setSize`)', async () => {
const mb = new Menubar(app, { preloadWindow: true });
await new Promise<void>((resolve) => mb.on('ready', () => resolve()));
await mb.showWindow();

const onResize = findHandler(mb.window!, 'resize');
expect(onResize, 'a resize handler should be registered').toBeDefined();

onResize!();

expect(mb.window!.setPosition).toHaveBeenCalledTimes(2);
});
});
60 changes: 38 additions & 22 deletions src/Menubar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,32 +273,48 @@ export class Menubar extends EventEmitter {
throw new Error('Window has been initialized just above. qed.');
}

// 'Windows' taskbar: sync windows position each time before showing
// https://github.com/maxogden/menubar/issues/232
if (['win32', 'linux'].includes(process.platform)) {
// Fill in this._options.windowPosition when taskbar position is available
this._options.windowPosition = getWindowPosition(this.tray);
}

this.emit('show');

// Cache fresh tray bounds (or fall back to existing cache / tray bounds)
// so `positionWindow` can reposition without an event payload — for
// example when the window resizes via `setSize`.
if (trayPos && trayPos.x !== 0) {
// Cache the bounds
this._cachedBounds = trayPos;
} else if (this._cachedBounds) {
// Cached value will be used if showWindow is called without bounds data
trayPos = this._cachedBounds;
} else if (this.tray.getBounds) {
// Get the current tray bounds
trayPos = this.tray.getBounds();
} else if (!this._cachedBounds && this.tray.getBounds) {
this._cachedBounds = this.tray.getBounds();
}

this.positionWindow();
this._browserWindow.show();
this._isVisible = true;
this.emit('after-show');
this.refreshLinuxContextMenu();
}

/**
* Compute and apply the tray-anchored position of the browser window. Safe
* to call any time after `createWindow` has run — invoked from
* {@link showWindow} on every show, and from the window's `resize` event so
* `setSize` calls reposition the window correctly.
*/
private positionWindow = (): void => {
if (!this._browserWindow || !this._tray) {
return;
}

// 'Windows' taskbar: sync window position each time before positioning.
// https://github.com/maxogden/menubar/issues/232
if (['win32', 'linux'].includes(process.platform)) {
this._options.windowPosition = getWindowPosition(this._tray);
}

const trayPos = this._cachedBounds ?? this._tray.getBounds?.();

// Default the window to the right if `trayPos` bounds are undefined or null.
let noBoundsPosition: Options['windowPosition'];
if (
(trayPos === undefined || trayPos.x === 0) &&
this._options.windowPosition &&
this._options.windowPosition.startsWith('tray')
this._options.windowPosition?.startsWith('tray')
) {
noBoundsPosition =
process.platform === 'win32' ? 'bottomRight' : 'topRight';
Expand All @@ -322,12 +338,7 @@ export class Menubar extends EventEmitter {
// `.setPosition` crashed on non-integers
// https://github.com/maxogden/menubar/issues/233
this._browserWindow.setPosition(Math.round(x), Math.round(y));
this._browserWindow.show();
this._isVisible = true;
this.emit('after-show');
this.refreshLinuxContextMenu();
return;
}
};

private async appReady(): Promise<void> {
if (this.app.dock && !this._options.showDockIcon) {
Expand Down Expand Up @@ -539,6 +550,11 @@ export class Menubar extends EventEmitter {
// `mb.window` and call `event.preventDefault()` without racing our cleanup.
this._browserWindow.on('closed', this.windowClear.bind(this));

// Re-anchor the window to the tray when its size changes (e.g. via
// `mb.window.setSize(...)`), so the window doesn't end up clipped under
// the taskbar. https://github.com/maxogden/menubar/issues/349
this._browserWindow.on('resize', this.positionWindow);

this.emit('before-load');

// If the user explicity set options.index to false, we don't loadURL
Expand Down
Loading