1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-06 05:25:41 -05:00
Files
DankMaterialShell/quickshell/Modules/DankBar/CenterSection.qml
Guilherme Pagano db3610fcdb feat: add support for geometric centering (#856)
Introduces a configurable centering mode.
- Adds 'geometric' option.
- Retains 'index' as the default value to preserve existing behavior.
2025-12-02 14:43:51 -05:00

577 lines
22 KiB
QML

import QtQuick
import qs.Common
import qs.Services
Item {
id: root
property var widgetsModel: null
property var components: null
property bool noBackground: false
required property var axis
property string section: "center"
property var parentScreen: null
property real widgetThickness: 30
property real barThickness: 48
property real barSpacing: 4
property var barConfig: null
property bool overrideAxisLayout: false
property bool forceVerticalLayout: false
readonly property bool isVertical: overrideAxisLayout ? forceVerticalLayout : (axis?.isVertical ?? false)
readonly property real spacing: {
const baseSpacing = noBackground ? 2 : Theme.spacingXS;
const outlineThickness = (barConfig?.widgetOutlineEnabled ?? false) ? (barConfig?.widgetOutlineThickness ?? 1) : 0;
return baseSpacing + (outlineThickness * 2);
}
property var centerWidgets: []
property int totalWidgets: 0
property real totalSize: 0
function updateLayout() {
if (SettingsData.centeringMode === "geometric") {
applyGeometricLayout();
} else {
// Default to index layout or if value is not 'geometric'
applyIndexLayout();
}
}
function applyGeometricLayout() {
if ((isVertical ? height : width) <= 0 || !visible) {
return;
}
centerWidgets = [];
totalWidgets = 0;
totalSize = 0;
for (var i = 0; i < centerRepeater.count; i++) {
const item = centerRepeater.itemAt(i);
if (item && item.active && item.item && getWidgetVisible(item.widgetId)) {
centerWidgets.push(item.item);
totalWidgets++;
totalSize += isVertical ? item.item.height : item.item.width;
}
}
if (totalWidgets === 0) {
return;
}
if (totalWidgets > 1) {
totalSize += spacing * (totalWidgets - 1);
}
positionWidgetsGeometric();
}
function positionWidgetsGeometric() {
const parentLength = isVertical ? height : width;
const parentCenter = parentLength / 2;
let currentPos = parentCenter - (totalSize / 2);
centerWidgets.forEach(widget => {
if (isVertical) {
widget.anchors.verticalCenter = undefined;
widget.y = currentPos;
} else {
widget.anchors.horizontalCenter = undefined;
widget.x = currentPos;
}
const widgetSize = isVertical ? widget.height : widget.width;
currentPos += widgetSize + spacing;
});
}
function applyIndexLayout() {
if ((isVertical ? height : width) <= 0 || !visible) {
return;
}
centerWidgets = [];
totalWidgets = 0;
totalSize = 0;
let configuredWidgets = 0;
let configuredMiddleWidget = null;
let configuredLeftWidget = null;
let configuredRightWidget = null;
for (var i = 0; i < centerRepeater.count; i++) {
const item = centerRepeater.itemAt(i);
if (item && getWidgetVisible(item.widgetId)) {
configuredWidgets++;
}
}
const isOddConfigured = configuredWidgets % 2 === 1;
const configuredMiddlePos = Math.floor(configuredWidgets / 2);
const configuredLeftPos = isOddConfigured ? -1 : ((configuredWidgets / 2) - 1);
const configuredRightPos = isOddConfigured ? -1 : (configuredWidgets / 2);
let currentConfigIndex = 0;
for (var i = 0; i < centerRepeater.count; i++) {
const item = centerRepeater.itemAt(i);
if (item && getWidgetVisible(item.widgetId)) {
if (isOddConfigured && currentConfigIndex === configuredMiddlePos && item.active && item.item) {
configuredMiddleWidget = item.item;
}
if (!isOddConfigured && currentConfigIndex === configuredLeftPos && item.active && item.item) {
configuredLeftWidget = item.item;
}
if (!isOddConfigured && currentConfigIndex === configuredRightPos && item.active && item.item) {
configuredRightWidget = item.item;
}
if (item.active && item.item) {
centerWidgets.push(item.item);
totalWidgets++;
totalSize += isVertical ? item.item.height : item.item.width;
}
currentConfigIndex++;
}
}
if (totalWidgets === 0) {
return;
}
if (totalWidgets > 1) {
totalSize += spacing * (totalWidgets - 1);
}
positionWidgetsByIndex(configuredWidgets, configuredMiddleWidget, configuredLeftWidget, configuredRightWidget);
}
function positionWidgetsByIndex(configuredWidgets, configuredMiddleWidget, configuredLeftWidget, configuredRightWidget) {
const parentCenter = (isVertical ? height : width) / 2;
const isOddConfigured = configuredWidgets % 2 === 1;
centerWidgets.forEach(widget => {
if (isVertical) {
widget.anchors.verticalCenter = undefined;
} else {
widget.anchors.horizontalCenter = undefined;
}
});
if (isOddConfigured && configuredMiddleWidget) {
const middleWidget = configuredMiddleWidget;
const middleIndex = centerWidgets.indexOf(middleWidget);
const middleSize = isVertical ? middleWidget.height : middleWidget.width;
if (isVertical) {
middleWidget.y = parentCenter - (middleSize / 2);
} else {
middleWidget.x = parentCenter - (middleSize / 2);
}
let currentPos = isVertical ? middleWidget.y : middleWidget.x;
for (var i = middleIndex - 1; i >= 0; i--) {
const size = isVertical ? centerWidgets[i].height : centerWidgets[i].width;
currentPos -= (spacing + size);
if (isVertical) {
centerWidgets[i].y = currentPos;
} else {
centerWidgets[i].x = currentPos;
}
}
currentPos = (isVertical ? middleWidget.y : middleWidget.x) + middleSize;
for (var i = middleIndex + 1; i < totalWidgets; i++) {
currentPos += spacing;
if (isVertical) {
centerWidgets[i].y = currentPos;
} else {
centerWidgets[i].x = currentPos;
}
currentPos += isVertical ? centerWidgets[i].height : centerWidgets[i].width;
}
} else {
if (totalWidgets === 1) {
const widget = centerWidgets[0];
const size = isVertical ? widget.height : widget.width;
if (isVertical) {
widget.y = parentCenter - (size / 2);
} else {
widget.x = parentCenter - (size / 2);
}
return;
}
if (!configuredLeftWidget || !configuredRightWidget) {
if (totalWidgets % 2 === 1) {
const middleIndex = Math.floor(totalWidgets / 2);
const middleWidget = centerWidgets[middleIndex];
if (!middleWidget) {
return;
}
const middleSize = isVertical ? middleWidget.height : middleWidget.width;
if (isVertical) {
middleWidget.y = parentCenter - (middleSize / 2);
} else {
middleWidget.x = parentCenter - (middleSize / 2);
}
let currentPos = isVertical ? middleWidget.y : middleWidget.x;
for (var i = middleIndex - 1; i >= 0; i--) {
const size = isVertical ? centerWidgets[i].height : centerWidgets[i].width;
currentPos -= (spacing + size);
if (isVertical) {
centerWidgets[i].y = currentPos;
} else {
centerWidgets[i].x = currentPos;
}
}
currentPos = (isVertical ? middleWidget.y : middleWidget.x) + middleSize;
for (var i = middleIndex + 1; i < totalWidgets; i++) {
currentPos += spacing;
if (isVertical) {
centerWidgets[i].y = currentPos;
} else {
centerWidgets[i].x = currentPos;
}
currentPos += isVertical ? centerWidgets[i].height : centerWidgets[i].width;
}
} else {
const leftIndex = (totalWidgets / 2) - 1;
const rightIndex = totalWidgets / 2;
const fallbackLeft = centerWidgets[leftIndex];
const fallbackRight = centerWidgets[rightIndex];
if (!fallbackLeft || !fallbackRight) {
return;
}
const halfSpacing = spacing / 2;
const leftSize = isVertical ? fallbackLeft.height : fallbackLeft.width;
if (isVertical) {
fallbackLeft.y = parentCenter - halfSpacing - leftSize;
fallbackRight.y = parentCenter + halfSpacing;
} else {
fallbackLeft.x = parentCenter - halfSpacing - leftSize;
fallbackRight.x = parentCenter + halfSpacing;
}
let currentPos = isVertical ? fallbackLeft.y : fallbackLeft.x;
for (var i = leftIndex - 1; i >= 0; i--) {
const size = isVertical ? centerWidgets[i].height : centerWidgets[i].width;
currentPos -= (spacing + size);
if (isVertical) {
centerWidgets[i].y = currentPos;
} else {
centerWidgets[i].x = currentPos;
}
}
currentPos = (isVertical ? fallbackRight.y + fallbackRight.height : fallbackRight.x + fallbackRight.width);
for (var i = rightIndex + 1; i < totalWidgets; i++) {
currentPos += spacing;
if (isVertical) {
centerWidgets[i].y = currentPos;
} else {
centerWidgets[i].x = currentPos;
}
currentPos += isVertical ? centerWidgets[i].height : centerWidgets[i].width;
}
}
return;
}
const leftWidget = configuredLeftWidget;
const rightWidget = configuredRightWidget;
const leftIndex = centerWidgets.indexOf(leftWidget);
const rightIndex = centerWidgets.indexOf(rightWidget);
const halfSpacing = spacing / 2;
const leftSize = isVertical ? leftWidget.height : leftWidget.width;
if (isVertical) {
leftWidget.y = parentCenter - halfSpacing - leftSize;
rightWidget.y = parentCenter + halfSpacing;
} else {
leftWidget.x = parentCenter - halfSpacing - leftSize;
rightWidget.x = parentCenter + halfSpacing;
}
let currentPos = isVertical ? leftWidget.y : leftWidget.x;
for (var i = leftIndex - 1; i >= 0; i--) {
const size = isVertical ? centerWidgets[i].height : centerWidgets[i].width;
currentPos -= (spacing + size);
if (isVertical) {
centerWidgets[i].y = currentPos;
} else {
centerWidgets[i].x = currentPos;
}
}
currentPos = (isVertical ? rightWidget.y + rightWidget.height : rightWidget.x + rightWidget.width);
for (var i = rightIndex + 1; i < totalWidgets; i++) {
currentPos += spacing;
if (isVertical) {
centerWidgets[i].y = currentPos;
} else {
centerWidgets[i].x = currentPos;
}
currentPos += isVertical ? centerWidgets[i].height : centerWidgets[i].width;
}
}
}
function getWidgetVisible(widgetId) {
const widgetVisibility = {
"cpuUsage": DgopService.dgopAvailable,
"memUsage": DgopService.dgopAvailable,
"cpuTemp": DgopService.dgopAvailable,
"gpuTemp": DgopService.dgopAvailable,
"network_speed_monitor": DgopService.dgopAvailable
};
return widgetVisibility[widgetId] ?? true;
}
function getWidgetComponent(widgetId) {
// Build dynamic component map including plugins
let baseMap = {
"launcherButton": "launcherButtonComponent",
"workspaceSwitcher": "workspaceSwitcherComponent",
"focusedWindow": "focusedWindowComponent",
"runningApps": "runningAppsComponent",
"clock": "clockComponent",
"music": "mediaComponent",
"weather": "weatherComponent",
"systemTray": "systemTrayComponent",
"privacyIndicator": "privacyIndicatorComponent",
"clipboard": "clipboardComponent",
"cpuUsage": "cpuUsageComponent",
"memUsage": "memUsageComponent",
"diskUsage": "diskUsageComponent",
"cpuTemp": "cpuTempComponent",
"gpuTemp": "gpuTempComponent",
"notificationButton": "notificationButtonComponent",
"battery": "batteryComponent",
"controlCenterButton": "controlCenterButtonComponent",
"idleInhibitor": "idleInhibitorComponent",
"spacer": "spacerComponent",
"separator": "separatorComponent",
"network_speed_monitor": "networkComponent",
"keyboard_layout_name": "keyboardLayoutNameComponent",
"vpn": "vpnComponent",
"notepadButton": "notepadButtonComponent",
"colorPicker": "colorPickerComponent",
"systemUpdate": "systemUpdateComponent"
};
// For built-in components, get from components property
const componentKey = baseMap[widgetId];
if (componentKey && root.components[componentKey]) {
return root.components[componentKey];
}
// For plugin components, get from PluginService
var parts = widgetId.split(":");
var pluginId = parts[0];
let pluginComponents = PluginService.getWidgetComponents();
return pluginComponents[pluginId] || null;
}
height: parent.height
width: parent.width
anchors.centerIn: parent
Timer {
id: layoutTimer
interval: 0
repeat: false
onTriggered: root.updateLayout()
}
Component.onCompleted: {
layoutTimer.restart();
}
onWidthChanged: {
if (width > 0) {
layoutTimer.restart();
}
}
onHeightChanged: {
if (height > 0) {
layoutTimer.restart();
}
}
onVisibleChanged: {
if (visible && (isVertical ? height : width) > 0) {
layoutTimer.restart();
}
}
Repeater {
id: centerRepeater
model: root.widgetsModel
Loader {
property var itemData: modelData
property string widgetId: itemData.widgetId
property var widgetData: itemData
property int spacerSize: itemData.size || 20
anchors.verticalCenter: !root.isVertical ? parent.verticalCenter : undefined
anchors.horizontalCenter: root.isVertical ? parent.horizontalCenter : undefined
active: root.getWidgetVisible(itemData.widgetId) && (itemData.widgetId !== "music" || MprisController.activePlayer !== null)
sourceComponent: root.getWidgetComponent(itemData.widgetId)
opacity: (itemData.enabled !== false) ? 1 : 0
asynchronous: false
onLoaded: {
if (!item) {
return;
}
item.widthChanged.connect(() => {
if (layoutTimer)
layoutTimer.restart();
});
item.heightChanged.connect(() => {
if (layoutTimer)
layoutTimer.restart();
});
if (root.axis && "axis" in item) {
item.axis = Qt.binding(() => root.axis);
}
if (root.axis && "isVertical" in item) {
try {
item.isVertical = Qt.binding(() => root.axis.isVertical);
} catch (e) {}
}
// Inject properties for plugin widgets
if ("section" in item) {
item.section = root.section;
}
if ("parentScreen" in item) {
item.parentScreen = Qt.binding(() => root.parentScreen);
}
if ("widgetThickness" in item) {
item.widgetThickness = Qt.binding(() => root.widgetThickness);
}
if ("barThickness" in item) {
item.barThickness = Qt.binding(() => root.barThickness);
}
if ("barSpacing" in item) {
item.barSpacing = Qt.binding(() => root.barSpacing);
}
if ("barConfig" in item) {
item.barConfig = Qt.binding(() => root.barConfig);
}
if ("sectionSpacing" in item) {
item.sectionSpacing = Qt.binding(() => root.spacing);
}
if ("widgetData" in item) {
item.widgetData = Qt.binding(() => widgetData);
}
if ("isFirst" in item) {
item.isFirst = Qt.binding(() => {
for (var i = 0; i < centerRepeater.count; i++) {
const checkItem = centerRepeater.itemAt(i);
if (checkItem && checkItem.active && checkItem.item) {
return checkItem.item === item;
}
}
return false;
});
}
if ("isLast" in item) {
item.isLast = Qt.binding(() => {
for (var i = centerRepeater.count - 1; i >= 0; i--) {
const checkItem = centerRepeater.itemAt(i);
if (checkItem && checkItem.active && checkItem.item) {
return checkItem.item === item;
}
}
return false;
});
}
if ("isLeftBarEdge" in item) {
item.isLeftBarEdge = false;
}
if ("isRightBarEdge" in item) {
item.isRightBarEdge = false;
}
if ("isTopBarEdge" in item) {
item.isTopBarEdge = false;
}
if ("isBottomBarEdge" in item) {
item.isBottomBarEdge = false;
}
if (item.pluginService !== undefined) {
var parts = model.widgetId.split(":");
var pluginId = parts[0];
var variantId = parts.length > 1 ? parts[1] : null;
if (item.pluginId !== undefined) {
item.pluginId = pluginId;
}
if (item.variantId !== undefined) {
item.variantId = variantId;
}
if (item.variantData !== undefined && variantId) {
item.variantData = PluginService.getPluginVariantData(pluginId, variantId);
}
item.pluginService = PluginService;
}
if (item.popoutService !== undefined) {
item.popoutService = PopoutService;
}
layoutTimer.restart();
}
onActiveChanged: {
layoutTimer.restart();
}
}
}
Connections {
target: PluginService
function onPluginLoaded(pluginId) {
// Force refresh of component lookups
for (var i = 0; i < centerRepeater.count; i++) {
var item = centerRepeater.itemAt(i);
if (item && item.widgetId.startsWith(pluginId)) {
item.sourceComponent = root.getWidgetComponent(item.widgetId);
}
}
}
function onPluginUnloaded(pluginId) {
// Force refresh of component lookups
for (var i = 0; i < centerRepeater.count; i++) {
var item = centerRepeater.itemAt(i);
if (item && item.widgetId.startsWith(pluginId)) {
item.sourceComponent = root.getWidgetComponent(item.widgetId);
}
}
}
}
Connections {
target: SettingsData
function onCenteringModeChanged() {
layoutTimer.restart();
}
}
}