layout: let an overview-only bar share the row of a bar that hides in the overview

fixes #3527
This commit is contained in:
bbedward
2026-09-21 18:09:25 -04:00
parent 74e146a1e8
commit a84e84c39b
2 changed files with 115 additions and 22 deletions
+99 -22
View File
@@ -41,13 +41,25 @@ function islandMetrics(config, defaults) {
const reserve = Math.max(24, Math.min(128, value("islandReserveThickness")));
const compact = Math.max(24, Math.min(72, value("islandCompactThickness")));
const gap = Math.max(0, Math.min(48, value("islandOuterGap")));
return { reserve, compact, gap, thickness: Math.max(reserve, gap + compact) };
return {
reserve,
compact,
gap,
thickness: Math.max(reserve, gap + compact)
};
}
function islandThickness(config, defaults) {
return islandMetrics(config, defaults).thickness;
}
function overviewStandInHost(input, selected) {
const config = input.config;
if (config.island === true || config.visible !== false || !config.openOnOverview)
return null;
return selected.find(other => other.config.island !== true && !other.config.openOnOverview) ?? null;
}
function resolveScreen(inputs, screen, options) {
const assigned = inputs.filter(input => coversScreen(input.config, screen, options.screens, options.displayNameMode));
const enabled = assigned.filter(input => input.config.enabled);
@@ -64,30 +76,47 @@ function resolveScreen(inputs, screen, options) {
const hosted = active.filter(input => !input.config.useOverlayLayer);
let offset = 0;
let reservation = 0;
for (const input of selected) {
const rowOffsets = new Map();
const place = (input, rowOffset) => {
const config = input.config;
const kind = config.island === true ? "island" : frameHosted && !config.useOverlayLayer ? "frame" : "bar";
const spacing = frameStyled || config.attachToScreenEdge ? 0 : config.spacing ?? 4;
const thickness = kind === "island" ? input.islandThickness : frameStyled ? Math.round(Math.round(options.frameBarSize * (screen.scale || 1)) / (screen.scale || 1)) : input.barThickness + spacing + (config.bottomGap ?? 0);
const paintedThickness = thickness + (kind !== "island" && !frameStyled ? input.wingSize ?? 0 : 0);
const reserves = kind === "island" ? !(input.islandFloating ?? config.islandFloating)
: config.visible !== false && (!config.autoHide || frameStyled);
const contribution = reserves ? Math.max(0, offset + thickness - reservation) : 0;
const reserves = kind === "island" ? !(input.islandFloating ?? config.islandFloating) : config.visible !== false && (!config.autoHide || frameStyled);
const contribution = reserves ? Math.max(0, rowOffset + thickness - reservation) : 0;
instances.push({
key: JSON.stringify([screen.name, config.id]), screenName: screen.name,
barId: config.id, configOrder: inputs.indexOf(input), edge, kind,
row: selected.indexOf(input), rowThickness: paintedThickness, rowOffset: offset,
key: JSON.stringify([screen.name, config.id]),
screenName: screen.name,
barId: config.id,
configOrder: inputs.indexOf(input),
edge,
kind,
row: selected.indexOf(input),
rowThickness: paintedThickness,
rowOffset,
reservation: contribution
});
offset += paintedThickness;
reservation += contribution;
rowOffsets.set(input, rowOffset);
return paintedThickness;
};
const standIns = frameStyled ? [] : selected.filter(input => overviewStandInHost(input, selected));
for (const input of selected) {
if (standIns.includes(input))
continue;
offset += place(input, offset);
}
for (const input of standIns)
place(input, rowOffsets.get(overviewStandInHost(input, selected)));
const island = selected.find(input => input.config.island === true);
bands[edge] = {
island: island?.config ?? null,
islandThickness: selected.filter(input => input.config.island === true).reduce((sum, input) => sum + input.islandThickness, 0),
bars: active.map(input => input.config), hostedBars: hosted.map(input => input.config),
occupancy: offset, reservation: Math.max(frameStyled ? options.frameThickness : 0, reservation),
bars: active.map(input => input.config),
hostedBars: hosted.map(input => input.config),
occupancy: offset,
reservation: Math.max(frameStyled ? options.frameThickness : 0, reservation),
frameReservation: active.length ? Math.max(options.frameThickness, offset) : options.frameThickness,
frameExclusionEnabled: frameStyled && (!active.length || (frameHosted && !active.some(input => input.config.useOverlayLayer)))
};
@@ -108,7 +137,12 @@ function resolveScreen(inputs, screen, options) {
instance.exclusiveZone = manualPlacement || instance.kind === "frame" ? -1 : instance.reservation || -1;
instance.exclusionSize = manualPlacement && !frameStyled && instance.row === 0 ? band.reservation : 0;
instance.paintedBounds = rowBounds(screen, instance.edge, instance.rowOffset, instance.rowThickness, bands);
instance.margins = { top: 0, bottom: 0, left: 0, right: 0 };
instance.margins = {
top: 0,
bottom: 0,
left: 0,
right: 0
};
if (!manualPlacement)
continue;
instance.margins[instance.edge] = instance.rowOffset;
@@ -122,7 +156,17 @@ function resolveScreen(inputs, screen, options) {
bands[edge].frameExclusionEnabled = true;
}
instances.sort((a, b) => a.configOrder - b.configOrder);
return { screen, assigned, bars, islands, instances, edges: bands, frameConfigured, frameStyled, manualPlacement };
return {
screen,
assigned,
bars,
islands,
instances,
edges: bands,
frameConfigured,
frameStyled,
manualPlacement
};
}
function adjacentBar(layout, edge, config) {
@@ -133,7 +177,12 @@ function adjacentBar(layout, edge, config) {
}
function adjacentInfo(layout, config, defaults) {
const result = { topBar: 0, bottomBar: 0, leftBar: 0, rightBar: 0 };
const result = {
topBar: 0,
bottomBar: 0,
leftBar: 0,
rightBar: 0
};
if (!layout || !config || config.autoHide)
return result;
for (const edge of edges) {
@@ -154,11 +203,19 @@ function adjacentInfo(layout, config, defaults) {
}
function barBounds(layout, thickness, position, config, defaults, connected, wingSize) {
const empty = { x: 0, y: 0, width: 0, height: 0, wingSize: 0 };
const empty = {
x: 0,
y: 0,
width: 0,
height: 0,
wingSize: 0
};
if (!layout || !edgeName(position))
return empty;
const instance = layout.instances.find(instance => instance.barId === config?.id);
return Object.assign(rowBounds(layout.screen, edgeName(position), instance?.rowOffset ?? 0, thickness + wingSize, layout.edges), { wingSize });
return Object.assign(rowBounds(layout.screen, edgeName(position), instance?.rowOffset ?? 0, thickness + wingSize, layout.edges), {
wingSize
});
}
function rowBounds(screen, edge, offset, thickness, bands) {
@@ -168,7 +225,8 @@ function rowBounds(screen, edge, offset, thickness, bands) {
return {
x: horizontal ? 0 : edge === "right" ? screen.width - width - offset : offset,
y: horizontal ? edge === "bottom" ? screen.height - height - offset : offset : bands.top.occupancy,
width, height
width,
height
};
}
@@ -184,7 +242,10 @@ function surfaceOrigin(layout, width, height, anchors, margins) {
x += Math.min(Math.max(0, screen.width - width - left - right), layout.edges?.left?.reservation ?? 0);
if (anchors.top && anchors.bottom)
y += Math.min(Math.max(0, screen.height - height - top - bottom), layout.edges?.top?.reservation ?? 0);
return { x, y };
return {
x,
y
};
}
function popupTrigger(pos, screen, thickness, width, spacing, position, config, defaults, connected) {
@@ -194,13 +255,29 @@ function popupTrigger(pos, screen, thickness, width, spacing, position, config,
const offset = thickness + (connected ? 0 : spacing) + gap;
switch (position) {
case 2:
return { x: offset, y: pos.y, width };
return {
x: offset,
y: pos.y,
width
};
case 3:
return { x: (screen?.width || 0) - offset, y: pos.y, width };
return {
x: (screen?.width || 0) - offset,
y: pos.y,
width
};
case 1:
return { x: pos.x, y: (screen?.height || 0) - offset - bottomGap, width };
return {
x: pos.x,
y: (screen?.height || 0) - offset - bottomGap,
width
};
default:
return { x: pos.x, y: offset + bottomGap, width };
return {
x: pos.x,
y: offset + bottomGap,
width
};
}
}
+16
View File
@@ -145,3 +145,19 @@ test("surface origins include native cross-edge exclusions without adding manual
assert.equal(frameIsland.instances[0].rowOffset, 12);
assert.equal(frameIsland.edges.top.reservation, 54);
});
test("an overview-only bar shares the row of a same-edge bar that retracts for the overview", () => {
const main = bar("main");
const standIn = bar("standin", 0, { visible: false, openOnOverview: true });
const always = bar("always", 0, { openOnOverview: true });
const offsets = layout => Object.fromEntries(layout.instances.map(instance => [instance.barId, instance.rowOffset]));
const shared = resolve([main, standIn]);
assert.deepEqual(offsets(shared), { main: 0, standin: 0 });
assert.equal(shared.instances.find(instance => instance.barId === "standin").margins.top, 0);
assert.equal(shared.edges.top.occupancy, 44);
assert.equal(shared.edges.top.reservation, 44);
assert.deepEqual(offsets(resolve([standIn, main])), { standin: 0, main: 0 });
assert.deepEqual(offsets(resolve([main, always, standIn])), { main: 0, always: 44, standin: 0 });
assert.deepEqual(offsets(resolve([always, standIn])), { always: 0, standin: 44 });
assert.deepEqual(offsets(resolve([main, standIn], { effectiveFrameEnabled: true })), { main: 0, standin: 48 });
});