Files
NetMesh/electron/bridges/trayPanelBounds.cjs
zhaolei 3c72efcb7f
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

169 lines
4.3 KiB
JavaScript

/**
* Tray panel size and placement.
*
* The panel is a fixed 360x520 overlay. Electron click / right-click events
* already carry live tray bounds; those are trusted even when the cursor is
* on another monitor (keyboard / accessibility activation).
*
* `tray.getBounds()` on Windows is a weaker fallback: it often reports a
* zero-size rect or y=0 while the icon sits on a bottom taskbar. Only that
* fallback is checked against the cursor.
*/
const TRAY_PANEL_WIDTH = 360;
const TRAY_PANEL_HEIGHT = 520;
const TRAY_PANEL_GAP = 6;
const ANCHOR_CURSOR_SLOP_PX = 96;
function isFiniteNumber(value) {
return typeof value === "number" && Number.isFinite(value);
}
function isValidRect(rect) {
return Boolean(
rect
&& isFiniteNumber(rect.x)
&& isFiniteNumber(rect.y)
&& isFiniteNumber(rect.width)
&& isFiniteNumber(rect.height)
&& rect.width > 0
&& rect.height > 0,
);
}
function isValidPoint(point) {
return Boolean(point && isFiniteNumber(point.x) && isFiniteNumber(point.y));
}
function copyRect(rect) {
return {
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
};
}
function rectNearPoint(rect, point, slop = ANCHOR_CURSOR_SLOP_PX) {
if (!isValidRect(rect) || !isValidPoint(point)) return false;
return point.x >= rect.x - slop
&& point.x <= rect.x + rect.width + slop
&& point.y >= rect.y - slop
&& point.y <= rect.y + rect.height + slop;
}
/**
* Pick an anchor rectangle for the tray icon.
*
* Event bounds win unconditionally when they are a usable rect. Cursor
* proximity is only used to reject a stale `tray.getBounds()` fallback.
*/
function resolveTrayAnchor({
eventBounds,
trayBounds,
cursorPoint,
workArea,
} = {}) {
if (isValidRect(eventBounds)) {
return copyRect(eventBounds);
}
if (isValidRect(trayBounds) && (!isValidPoint(cursorPoint) || rectNearPoint(trayBounds, cursorPoint))) {
return copyRect(trayBounds);
}
if (isValidPoint(cursorPoint)) {
return { x: cursorPoint.x, y: cursorPoint.y, width: 1, height: 1 };
}
const area = isValidRect(workArea) ? workArea : { x: 0, y: 0, width: 0, height: 0 };
return {
x: area.x + Math.max(area.width, 0),
y: area.y + Math.max(area.height, 0),
width: 1,
height: 1,
};
}
/**
* Display lookup point. Event bounds choose the monitor; otherwise the
* cursor (so a y=0 getBounds lie cannot pick the wrong screen).
*/
function resolveTrayDisplayPoint({ eventBounds, trayBounds, cursorPoint } = {}) {
if (isValidRect(eventBounds)) {
return { x: eventBounds.x, y: eventBounds.y };
}
if (isValidPoint(cursorPoint)) {
return { x: cursorPoint.x, y: cursorPoint.y };
}
if (isValidRect(trayBounds)) {
return { x: trayBounds.x, y: trayBounds.y };
}
return { x: 0, y: 0 };
}
function clamp(value, min, max) {
if (max < min) return min;
return Math.min(Math.max(value, min), max);
}
/**
* Place the designed panel next to the tray: below when there is room
* (macOS menu bar), otherwise above (Windows / Linux bottom taskbar).
*/
function placeTrayPanel({
anchor,
workArea,
width = TRAY_PANEL_WIDTH,
height = TRAY_PANEL_HEIGHT,
gap = TRAY_PANEL_GAP,
} = {}) {
const area = isValidRect(workArea)
? workArea
: { x: 0, y: 0, width: Math.max(width, 1), height: Math.max(height, 1) };
const panelWidth = Math.min(Math.max(1, width), area.width || width);
const panelHeight = Math.min(Math.max(1, height), area.height || height);
const resolvedAnchor = isValidRect(anchor)
? anchor
: { x: area.x, y: area.y, width: 1, height: 1 };
const minX = area.x;
const maxX = area.x + area.width - panelWidth;
const x = Math.round(clamp(
resolvedAnchor.x + resolvedAnchor.width / 2 - panelWidth / 2,
minX,
maxX,
));
const minY = area.y;
const maxY = area.y + area.height - panelHeight;
const below = resolvedAnchor.y + resolvedAnchor.height + gap;
const above = resolvedAnchor.y - panelHeight - gap;
let y;
if (below + panelHeight <= area.y + area.height) {
y = below;
} else if (above >= area.y) {
y = above;
} else {
y = clamp(below, minY, maxY);
}
return {
x,
y: Math.round(clamp(y, minY, maxY)),
width: panelWidth,
height: panelHeight,
};
}
module.exports = {
TRAY_PANEL_WIDTH,
TRAY_PANEL_HEIGHT,
TRAY_PANEL_GAP,
isValidRect,
resolveTrayAnchor,
resolveTrayDisplayPoint,
placeTrayPanel,
};