mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-14 23:34:01 -04:00
349 lines
15 KiB
JavaScript
349 lines
15 KiB
JavaScript
.pragma library
|
|
|
|
const edges = ["top", "bottom", "left", "right"];
|
|
|
|
function edgeName(position) {
|
|
return typeof position === "number" ? (edges[position] ?? "") : "";
|
|
}
|
|
|
|
function option(config, key, defaults, fallback) {
|
|
return config?.[key] === undefined ? (defaults?.[key] ?? fallback) : config[key];
|
|
}
|
|
|
|
function screenModelIndex(screen, screens) {
|
|
if (!screen?.model)
|
|
return -1;
|
|
const matches = screens.filter(candidate => candidate.model === screen.model).slice().sort((a, b) => a.x - b.x || a.y - b.y);
|
|
return matches.length > 1 ? matches.findIndex(candidate => candidate.name === screen.name) : -1;
|
|
}
|
|
|
|
function screenMatches(screen, preferences, screens, displayNameMode) {
|
|
if (!screen)
|
|
return false;
|
|
const modelIndex = screenModelIndex(screen, screens);
|
|
const displayName = displayNameMode === "model" && screen.model ? screen.model + (modelIndex >= 0 ? "-" + modelIndex : "") : screen.name;
|
|
return preferences.some(pref => {
|
|
if (typeof pref === "string")
|
|
return pref === "all" || pref === screen.name || pref === (displayNameMode === "model" ? displayName : screen.model);
|
|
if (displayNameMode !== "model")
|
|
return pref.name === screen.name;
|
|
return !!pref.model && !!screen.model && pref.model === screen.model && (pref.modelIndex === undefined || pref.modelIndex === modelIndex);
|
|
});
|
|
}
|
|
|
|
function coversScreen(config, screen, screens, displayNameMode) {
|
|
const preferences = config?.screenPreferences || ["all"];
|
|
return preferences.includes("all") || screenMatches(screen, preferences, screens, displayNameMode) || ((config?.showOnLastDisplay ?? false) && screens.length === 1);
|
|
}
|
|
|
|
function islandMetrics(config, defaults) {
|
|
const value = key => config?.[key] ?? defaults[key];
|
|
const reserve = Math.max(24, Math.min(128, value("islandReserveThickness")));
|
|
const compact = Math.max(24, Math.min(72, value("islandCompactThickness")));
|
|
const gap = value("islandNotch") ? 0 : Math.max(0, Math.min(48, value("islandOuterGap")));
|
|
return {
|
|
reserve,
|
|
compact,
|
|
gap,
|
|
thickness: Math.max(reserve, gap + compact)
|
|
};
|
|
}
|
|
|
|
function islandThickness(config, defaults) {
|
|
return islandMetrics(config, defaults).thickness;
|
|
}
|
|
|
|
function isIsland(config) {
|
|
return config.island === true || config.dot === true;
|
|
}
|
|
|
|
// First island entry across the sections, the same one SettingsData.islandWidgetEntry reads.
|
|
function hostsIslandWidget(config) {
|
|
for (const key of ["leftWidgets", "centerWidgets", "rightWidgets"]) {
|
|
const entry = (config?.[key] ?? []).find(entry => (typeof entry === "string" ? entry : entry?.id) === "island");
|
|
if (entry !== undefined)
|
|
return typeof entry === "string" || entry.enabled !== false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function overviewStandInHost(input, selected) {
|
|
const config = input.config;
|
|
if (isIsland(config) || config.visible !== false || !config.openOnOverview)
|
|
return null;
|
|
return selected.find(other => !isIsland(other.config) && !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);
|
|
const bars = enabled.filter(input => !isIsland(input.config));
|
|
// One island surface per screen: an island-layout bar owns it, else the first bar hosting the island widget.
|
|
const hostOwner = enabled.some(input => input.config.island === true) ? null : bars.find(input => hostsIslandWidget(input.config)) ?? null;
|
|
const islands = enabled.filter(input => isIsland(input.config)).concat(hostOwner ? [hostOwner] : []);
|
|
const frameConfigured = screenMatches(screen, options.framePreferences, options.screens, options.displayNameMode);
|
|
const frameStyled = options.effectiveFrameEnabled && frameConfigured;
|
|
const frameHosted = options.effectiveConnected && frameStyled;
|
|
const bands = {};
|
|
const instances = [];
|
|
// Free islands and dots float anywhere on the screen, so they never join an edge band.
|
|
const isFree = input => input.config.dot === true || (isIsland(input.config) && (input.islandPlacement ?? input.config.islandPlacement) === "free");
|
|
for (const input of enabled.filter(isFree)) {
|
|
const dot = input.config.dot === true;
|
|
// A free island keeps its satellites in a bar window on its configured edge; that band never reserves or shadows.
|
|
const satellites = !dot && (input.islandSatellites ?? input.config.islandSatellitesEnabled ?? true);
|
|
instances.push({
|
|
key: JSON.stringify([screen.name, input.config.id]),
|
|
screenName: screen.name,
|
|
barId: input.config.id,
|
|
configOrder: inputs.indexOf(input),
|
|
edge: "",
|
|
kind: "island",
|
|
free: true,
|
|
dot,
|
|
hostsIsland: true,
|
|
satelliteEdge: satellites ? edgeName(input.config.position ?? 0) || "top" : "",
|
|
row: 0,
|
|
rowThickness: 0,
|
|
rowOffset: 0,
|
|
reservation: 0,
|
|
exclusiveZone: -1,
|
|
exclusionSize: 0,
|
|
paintedBounds: {
|
|
x: 0,
|
|
y: 0,
|
|
width: 0,
|
|
height: 0
|
|
},
|
|
margins: {
|
|
top: 0,
|
|
bottom: 0,
|
|
left: 0,
|
|
right: 0
|
|
}
|
|
});
|
|
}
|
|
for (const edge of edges) {
|
|
const selected = enabled.filter(input => !isFree(input) && (edgeName(input.config.position ?? 0) || "top") === edge);
|
|
const active = selected.filter(input => !isIsland(input.config));
|
|
const shown = active.filter(input => input.config.visible !== false);
|
|
const hosted = active.filter(input => !input.config.useOverlayLayer);
|
|
let offset = 0;
|
|
let reservation = 0;
|
|
const rowOffsets = new Map();
|
|
const place = (input, rowOffset) => {
|
|
const config = input.config;
|
|
const kind = isIsland(config) ? "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, 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,
|
|
hostsIsland: kind === "island" || input === hostOwner,
|
|
row: selected.indexOf(input),
|
|
rowThickness: paintedThickness,
|
|
rowOffset,
|
|
reservation: contribution
|
|
});
|
|
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 => isIsland(input.config));
|
|
bands[edge] = {
|
|
island: island?.config ?? null,
|
|
islandThickness: selected.filter(input => isIsland(input.config)).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),
|
|
frameReservation: shown.length ? Math.max(options.frameThickness, offset) : options.frameThickness,
|
|
overviewFrameReservation: active.length ? Math.max(options.frameThickness, offset) : options.frameThickness,
|
|
frameExclusionEnabled: frameStyled && (!shown.length || (frameHosted && !shown.some(input => input.config.useOverlayLayer)))
|
|
};
|
|
}
|
|
const manualPlacement = edges.some(edge => instances.filter(instance => instance.edge === edge).length > 1);
|
|
if (!manualPlacement && frameStyled) {
|
|
for (const instance of instances) {
|
|
if (instance.kind !== "island" || instance.free)
|
|
continue;
|
|
const band = bands[instance.edge];
|
|
instance.rowOffset += options.frameThickness;
|
|
band.occupancy += options.frameThickness;
|
|
band.reservation = options.frameThickness + instance.reservation;
|
|
}
|
|
}
|
|
for (const instance of instances) {
|
|
if (instance.free)
|
|
continue;
|
|
const band = bands[instance.edge];
|
|
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
|
|
};
|
|
if (!manualPlacement)
|
|
continue;
|
|
instance.margins[instance.edge] = instance.rowOffset;
|
|
if (instance.edge === "left" || instance.edge === "right") {
|
|
instance.margins.top = bands.top.occupancy;
|
|
instance.margins.bottom = bands.bottom.occupancy;
|
|
}
|
|
}
|
|
if (manualPlacement && frameStyled) {
|
|
for (const edge of edges)
|
|
bands[edge].frameExclusionEnabled = true;
|
|
}
|
|
instances.sort((a, b) => a.configOrder - b.configOrder);
|
|
return {
|
|
screen,
|
|
assigned,
|
|
bars,
|
|
islands,
|
|
instances,
|
|
edges: bands,
|
|
frameConfigured,
|
|
frameStyled,
|
|
manualPlacement
|
|
};
|
|
}
|
|
|
|
function hostsBarWindow(instance) {
|
|
return instance.free ? !!instance.satelliteEdge : instance.kind === "bar" || instance.kind === "island";
|
|
}
|
|
|
|
function adjacentBar(layout, edge, config) {
|
|
if (!layout || config?.autoHide)
|
|
return null;
|
|
const position = edges.indexOf(edge);
|
|
return layout.bars.find(input => input.config.id !== config?.id && !input.config.autoHide && (input.config.visible ?? true) && input.config.position === position) ?? null;
|
|
}
|
|
|
|
function adjacentInfo(layout, config, defaults) {
|
|
const result = {
|
|
topBar: 0,
|
|
bottomBar: 0,
|
|
leftBar: 0,
|
|
rightBar: 0
|
|
};
|
|
if (!layout || !config || config.autoHide)
|
|
return result;
|
|
for (const edge of edges) {
|
|
const rows = layout.instances.filter(instance => instance.edge === edge && instance.barId !== config.id);
|
|
result[edge + "Bar"] = rows.reduce((sum, instance) => {
|
|
const input = layout.assigned.find(input => input.config.id === instance.barId);
|
|
const other = input.config;
|
|
if (instance.kind === "island")
|
|
return sum + instance.rowThickness;
|
|
if (other.autoHide || other.visible === false)
|
|
return sum;
|
|
const spacing = option(other, "spacing", defaults, 4);
|
|
const gap = option(other, "popupGapsAuto", defaults, true) ? Math.max(4, spacing) : option(other, "popupGapsManual", defaults, 4);
|
|
return sum + input.popupThickness + spacing + gap;
|
|
}, 0);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function barBounds(layout, thickness, position, config, defaults, connected, wingSize) {
|
|
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
|
|
});
|
|
}
|
|
|
|
function rowBounds(screen, edge, offset, thickness, bands) {
|
|
const horizontal = edge === "top" || edge === "bottom";
|
|
const width = horizontal ? screen.width : thickness;
|
|
const height = horizontal ? thickness : Math.max(0, screen.height - bands.top.occupancy - bands.bottom.occupancy);
|
|
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
|
|
};
|
|
}
|
|
|
|
function surfaceOrigin(layout, width, height, anchors, margins) {
|
|
const screen = layout.screen;
|
|
const left = margins?.left ?? 0;
|
|
const right = margins?.right ?? 0;
|
|
const top = margins?.top ?? 0;
|
|
const bottom = margins?.bottom ?? 0;
|
|
let x = anchors.right && !anchors.left ? screen.width - width - right : left;
|
|
let y = anchors.bottom && !anchors.top ? screen.height - height - bottom : top;
|
|
if (anchors.left && anchors.right)
|
|
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
|
|
};
|
|
}
|
|
|
|
function popupTrigger(pos, screen, thickness, width, spacing, position, config, defaults, connected) {
|
|
const bottomGap = connected ? 0 : Math.max(0, option(config, "bottomGap", defaults, 0));
|
|
const autoGap = option(config, "popupGapsAuto", defaults, true);
|
|
const gap = connected ? 0 : autoGap ? Math.max(4, spacing) : option(config, "popupGapsManual", defaults, 4);
|
|
const offset = thickness + (connected ? 0 : spacing) + gap;
|
|
switch (position) {
|
|
case 2:
|
|
return {
|
|
x: offset,
|
|
y: pos.y,
|
|
width
|
|
};
|
|
case 3:
|
|
return {
|
|
x: (screen?.width || 0) - offset,
|
|
y: pos.y,
|
|
width
|
|
};
|
|
case 1:
|
|
return {
|
|
x: pos.x,
|
|
y: (screen?.height || 0) - offset - bottomGap,
|
|
width
|
|
};
|
|
default:
|
|
return {
|
|
x: pos.x,
|
|
y: offset + bottomGap,
|
|
width
|
|
};
|
|
}
|
|
}
|
|
|
|
function availablePositions(layouts, config) {
|
|
return [0, 1, 2, 3];
|
|
}
|