mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
364 lines
12 KiB
QML
364 lines
12 KiB
QML
pragma Singleton
|
|
|
|
import QtQuick
|
|
import Quickshell
|
|
import qs.Commons
|
|
|
|
Singleton {
|
|
id: root
|
|
|
|
property bool hasAudioVisualizer: false
|
|
property bool isVisible: true
|
|
property var readyBars: ({})
|
|
|
|
// Registry to store actual widget instances
|
|
// Key format: "screenName|section|widgetId|index"
|
|
property var widgetInstances: ({})
|
|
|
|
signal activeWidgetsChanged
|
|
signal barReadyChanged(string screenName)
|
|
|
|
// onHasAudioVisualizerChanged: {
|
|
// Logger.d("BarService", "hasAudioVisualizer", hasAudioVisualizer)
|
|
// }
|
|
|
|
// Simple timer that run once when the widget structure has changed
|
|
// and determine if any MediaMini widget has the visualizer on
|
|
Timer {
|
|
id: timerCheckVisualizer
|
|
interval: 100
|
|
repeat: false
|
|
onTriggered: {
|
|
hasAudioVisualizer = false;
|
|
if (getAllWidgetInstances("AudioVisualizer").length > 0) {
|
|
hasAudioVisualizer = true;
|
|
return;
|
|
}
|
|
const widgets = getAllWidgetInstances("MediaMini");
|
|
for (var i = 0; i < widgets.length; i++) {
|
|
const widget = widgets[i];
|
|
if (widget.showVisualizer) {
|
|
hasAudioVisualizer = true;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
Logger.i("BarService", "Service started");
|
|
}
|
|
|
|
// Function for the Bar to call when it's ready
|
|
function registerBar(screenName) {
|
|
if (!readyBars[screenName]) {
|
|
readyBars[screenName] = true;
|
|
Logger.d("BarService", "Bar is ready on screen:", screenName);
|
|
barReadyChanged(screenName);
|
|
}
|
|
}
|
|
|
|
// Function for the Dock to check if the bar is ready
|
|
function isBarReady(screenName) {
|
|
return readyBars[screenName] || false;
|
|
}
|
|
|
|
// Register a widget instance
|
|
function registerWidget(screenName, section, widgetId, index, instance) {
|
|
const key = [screenName, section, widgetId, index].join("|");
|
|
widgetInstances[key] = {
|
|
"key": key,
|
|
"screenName": screenName,
|
|
"section": section,
|
|
"widgetId": widgetId,
|
|
"index": index,
|
|
"instance": instance
|
|
};
|
|
|
|
timerCheckVisualizer.restart();
|
|
|
|
Logger.d("BarService", "Registered widget:", key);
|
|
root.activeWidgetsChanged();
|
|
}
|
|
|
|
// Unregister a widget instance
|
|
function unregisterWidget(screenName, section, widgetId, index) {
|
|
const key = [screenName, section, widgetId, index].join("|");
|
|
delete widgetInstances[key];
|
|
Logger.d("BarService", "Unregistered widget:", key);
|
|
root.activeWidgetsChanged();
|
|
}
|
|
|
|
// Lookup a specific widget instance (returns the actual QML instance)
|
|
function lookupWidget(widgetId, screenName = null, section = null, index = null) {
|
|
// If looking for a specific instance
|
|
if (screenName && section !== null) {
|
|
for (var key in widgetInstances) {
|
|
var widget = widgetInstances[key];
|
|
if (widget.widgetId === widgetId && widget.screenName === screenName && widget.section === section) {
|
|
if (index === null) {
|
|
return widget.instance;
|
|
} else if (widget.index == index) {
|
|
return widget.instance;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return first match if no specific screen/section specified
|
|
for (var key in widgetInstances) {
|
|
var widget = widgetInstances[key];
|
|
if (widget.widgetId === widgetId) {
|
|
if (!screenName || widget.screenName === screenName) {
|
|
if (section === null || widget.section === section) {
|
|
return widget.instance;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
// Get all instances of a widget type
|
|
function getAllWidgetInstances(widgetId = null, screenName = null, section = null) {
|
|
var instances = [];
|
|
|
|
for (var key in widgetInstances) {
|
|
var widget = widgetInstances[key];
|
|
|
|
var matches = true;
|
|
if (widgetId && widget.widgetId !== widgetId)
|
|
matches = false;
|
|
if (screenName && widget.screenName !== screenName)
|
|
matches = false;
|
|
if (section !== null && widget.section !== section)
|
|
matches = false;
|
|
|
|
if (matches) {
|
|
instances.push(widget.instance);
|
|
}
|
|
}
|
|
|
|
return instances;
|
|
}
|
|
|
|
// Get widget with full metadata
|
|
function getWidgetWithMetadata(widgetId, screenName = null, section = null) {
|
|
for (var key in widgetInstances) {
|
|
var widget = widgetInstances[key];
|
|
if (widget.widgetId === widgetId) {
|
|
if (!screenName || widget.screenName === screenName) {
|
|
if (section === null || widget.section === section) {
|
|
return widget;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
// Get all widgets in a specific section
|
|
function getWidgetsBySection(section, screenName = null) {
|
|
var widgets = [];
|
|
|
|
for (var key in widgetInstances) {
|
|
var widget = widgetInstances[key];
|
|
if (widget.section === section) {
|
|
if (!screenName || widget.screenName === screenName) {
|
|
widgets.push(widget.instance);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sort by index to maintain order
|
|
widgets.sort(function (a, b) {
|
|
var aWidget = getWidgetWithMetadata(a.widgetId, a.screen?.name, a.section);
|
|
var bWidget = getWidgetWithMetadata(b.widgetId, b.screen?.name, b.section);
|
|
return (aWidget?.index || 0) - (bWidget?.index || 0);
|
|
});
|
|
|
|
return widgets;
|
|
}
|
|
|
|
// Get all registered widgets (for debugging)
|
|
function getAllRegisteredWidgets() {
|
|
var result = [];
|
|
for (var key in widgetInstances) {
|
|
result.push({
|
|
"key": key,
|
|
"widgetId": widgetInstances[key].widgetId,
|
|
"section": widgetInstances[key].section,
|
|
"screenName": widgetInstances[key].screenName,
|
|
"index": widgetInstances[key].index
|
|
});
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Check if a widget type exists in a section
|
|
function hasWidget(widgetId, section = null, screenName = null) {
|
|
for (var key in widgetInstances) {
|
|
var widget = widgetInstances[key];
|
|
if (widget.widgetId === widgetId) {
|
|
if (section === null || widget.section === section) {
|
|
if (!screenName || widget.screenName === screenName) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Get pill direction for a widget instance
|
|
function getPillDirection(widgetInstance) {
|
|
try {
|
|
if (widgetInstance.section === "left") {
|
|
return true;
|
|
} else if (widgetInstance.section === "right") {
|
|
return false;
|
|
} else {
|
|
// middle section
|
|
if (widgetInstance.sectionWidgetIndex < widgetInstance.sectionWidgetsCount / 2) {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
} catch (e) {
|
|
Logger.e(e);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function getTooltipDirection() {
|
|
switch (Settings.data.bar.position) {
|
|
case "right":
|
|
return "left";
|
|
case "left":
|
|
return "right";
|
|
case "bottom":
|
|
return "top";
|
|
default:
|
|
return "bottom";
|
|
}
|
|
}
|
|
|
|
// Calculate context menu position based on bar position
|
|
// Parameters:
|
|
// anchorItem: The widget item to anchor the menu to
|
|
// menuWidth: Width of the context menu (optional, defaults to 180)
|
|
// menuHeight: Height of the context menu (optional, defaults to 100)
|
|
// Returns: { x: number, y: number }
|
|
// Note: Anchor position is top-left corner, so we calculate from center
|
|
function getContextMenuPosition(anchorItem, menuWidth, menuHeight) {
|
|
if (!anchorItem) {
|
|
Logger.w("BarService", "getContextMenuPosition: anchorItem is null");
|
|
return {
|
|
"x": 0,
|
|
"y": 0
|
|
};
|
|
}
|
|
|
|
const mWidth = menuWidth || 180;
|
|
const mHeight = menuHeight || 100;
|
|
const barPosition = Settings.data.bar.position;
|
|
let menuX = 0;
|
|
let menuY = 0;
|
|
|
|
// Calculate center-based positioning for consistent spacing
|
|
const anchorCenterX = anchorItem.width / 2;
|
|
const anchorCenterY = anchorItem.height / 2;
|
|
|
|
if (barPosition === "left") {
|
|
// For left bar: position menu to the right of anchor, vertically centered
|
|
menuX = anchorItem.width + Style.marginM;
|
|
menuY = anchorCenterY - (mHeight / 2);
|
|
} else if (barPosition === "right") {
|
|
// For right bar: position menu to the left of anchor, vertically centered
|
|
menuX = -mWidth - Style.marginM;
|
|
menuY = anchorCenterY - (mHeight / 2);
|
|
} else if (barPosition === "top") {
|
|
// For top bar: position menu below bar, horizontally centered
|
|
menuX = anchorCenterX - (mWidth / 2);
|
|
menuY = Style.barHeight;
|
|
} else {
|
|
// For bottom bar: position menu above, horizontally centered
|
|
menuX = anchorCenterX - (mWidth / 2);
|
|
menuY = -mHeight - Style.marginM;
|
|
}
|
|
|
|
return {
|
|
"x": menuX,
|
|
"y": menuY
|
|
};
|
|
}
|
|
|
|
// Open widget settings dialog for a bar widget
|
|
// Parameters:
|
|
// screen: The screen to show the dialog on
|
|
// section: Section id ("left", "center", "right")
|
|
// index: Widget index in section
|
|
// widgetId: Widget type id (e.g., "Volume")
|
|
// widgetData: Current widget settings object
|
|
function openWidgetSettings(screen, section, index, widgetId, widgetData) {
|
|
// Get the popup menu window to use as parent (avoids clipping issues with bar height)
|
|
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
|
|
if (!popupMenuWindow) {
|
|
Logger.e("BarService", "No popup menu window found for screen");
|
|
return;
|
|
}
|
|
|
|
var component = Qt.createComponent(Quickshell.shellDir + "/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml");
|
|
|
|
function instantiateAndOpen() {
|
|
// Use dialogParent (Item) instead of window directly for proper Popup anchoring
|
|
var dialog = component.createObject(popupMenuWindow.dialogParent, {
|
|
"widgetIndex": index,
|
|
"widgetData": widgetData,
|
|
"widgetId": widgetId,
|
|
"sectionId": section
|
|
});
|
|
|
|
if (dialog) {
|
|
dialog.updateWidgetSettings.connect((sec, idx, settings) => {
|
|
var widgets = Settings.data.bar.widgets[sec];
|
|
if (widgets && idx < widgets.length) {
|
|
widgets[idx] = Object.assign({}, widgets[idx], settings);
|
|
Settings.data.bar.widgets[sec] = widgets;
|
|
Settings.saveImmediate();
|
|
}
|
|
});
|
|
// Enable keyboard focus for the popup menu window when dialog is open
|
|
popupMenuWindow.hasDialog = true;
|
|
// Close the popup menu window when dialog closes
|
|
dialog.closed.connect(() => {
|
|
popupMenuWindow.hasDialog = false;
|
|
popupMenuWindow.close();
|
|
dialog.destroy();
|
|
});
|
|
// Show the popup menu window and open the dialog
|
|
popupMenuWindow.open();
|
|
dialog.open();
|
|
} else {
|
|
Logger.e("BarService", "Failed to create widget settings dialog");
|
|
}
|
|
}
|
|
|
|
if (component.status === Component.Ready) {
|
|
instantiateAndOpen();
|
|
} else if (component.status === Component.Error) {
|
|
Logger.e("BarService", "Error loading widget settings dialog:", component.errorString());
|
|
} else {
|
|
component.statusChanged.connect(function () {
|
|
if (component.status === Component.Ready) {
|
|
instantiateAndOpen();
|
|
} else if (component.status === Component.Error) {
|
|
Logger.e("BarService", "Error loading widget settings dialog:", component.errorString());
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|