mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
424 lines
14 KiB
QML
424 lines
14 KiB
QML
import QtQuick
|
|
import QtQuick.Controls
|
|
import QtQuick.Layouts
|
|
import Quickshell
|
|
import qs.Commons
|
|
import qs.Services
|
|
import qs.Widgets
|
|
|
|
ColumnLayout {
|
|
id: root
|
|
spacing: Style.marginL
|
|
|
|
// Helper functions to update arrays immutably
|
|
function addMonitor(list, name) {
|
|
const arr = (list || []).slice()
|
|
if (!arr.includes(name))
|
|
arr.push(name)
|
|
return arr
|
|
}
|
|
function removeMonitor(list, name) {
|
|
return (list || []).filter(function (n) {
|
|
return n !== name
|
|
})
|
|
}
|
|
|
|
// Handler for drag start - disables panel background clicks
|
|
function handleDragStart() {
|
|
var panel = PanelService.getPanel("settingsPanel")
|
|
if (panel && panel.disableBackgroundClick) {
|
|
panel.disableBackgroundClick()
|
|
}
|
|
}
|
|
|
|
// Handler for drag end - re-enables panel background clicks
|
|
function handleDragEnd() {
|
|
var panel = PanelService.getPanel("settingsPanel")
|
|
if (panel && panel.enableBackgroundClick) {
|
|
panel.enableBackgroundClick()
|
|
}
|
|
}
|
|
|
|
NHeader {
|
|
label: I18n.tr("settings.bar.appearance.section.label")
|
|
description: I18n.tr("settings.bar.appearance.section.description")
|
|
}
|
|
|
|
NComboBox {
|
|
Layout.fillWidth: true
|
|
label: I18n.tr("settings.bar.appearance.position.label")
|
|
description: I18n.tr("settings.bar.appearance.position.description")
|
|
model: [{
|
|
"key": "top",
|
|
"name": I18n.tr("options.bar.position.top")
|
|
}, {
|
|
"key": "bottom",
|
|
"name": I18n.tr("options.bar.position.bottom")
|
|
}, {
|
|
"key": "left",
|
|
"name": I18n.tr("options.bar.position.left")
|
|
}, {
|
|
"key": "right",
|
|
"name": I18n.tr("options.bar.position.right")
|
|
}]
|
|
currentKey: Settings.data.bar.position
|
|
onSelected: key => Settings.data.bar.position = key
|
|
}
|
|
|
|
NComboBox {
|
|
Layout.fillWidth: true
|
|
label: I18n.tr("settings.bar.appearance.density.label")
|
|
description: I18n.tr("settings.bar.appearance.density.description")
|
|
model: [{
|
|
"key": "mini",
|
|
"name": I18n.tr("options.bar.density.mini")
|
|
}, {
|
|
"key": "compact",
|
|
"name": I18n.tr("options.bar.density.compact")
|
|
}, {
|
|
"key": "default",
|
|
"name": I18n.tr("options.bar.density.default")
|
|
}, {
|
|
"key": "comfortable",
|
|
"name": I18n.tr("options.bar.density.comfortable")
|
|
}]
|
|
currentKey: Settings.data.bar.density
|
|
onSelected: key => Settings.data.bar.density = key
|
|
}
|
|
|
|
NToggle {
|
|
Layout.fillWidth: true
|
|
label: I18n.tr("settings.bar.appearance.show-capsule.label")
|
|
description: I18n.tr("settings.bar.appearance.show-capsule.description")
|
|
checked: Settings.data.bar.showCapsule
|
|
onToggled: checked => Settings.data.bar.showCapsule = checked
|
|
}
|
|
|
|
NToggle {
|
|
Layout.fillWidth: true
|
|
label: I18n.tr("settings.bar.appearance.floating.label")
|
|
description: I18n.tr("settings.bar.appearance.floating.description")
|
|
checked: Settings.data.bar.floating
|
|
onToggled: checked => Settings.data.bar.floating = checked
|
|
}
|
|
|
|
// Floating bar options - only show when floating is enabled
|
|
ColumnLayout {
|
|
visible: Settings.data.bar.floating
|
|
spacing: Style.marginS
|
|
Layout.fillWidth: true
|
|
|
|
NLabel {
|
|
label: I18n.tr("settings.bar.appearance.margins.label")
|
|
description: I18n.tr("settings.bar.appearance.margins.description")
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
spacing: Style.marginL
|
|
|
|
ColumnLayout {
|
|
spacing: Style.marginXXS
|
|
|
|
NText {
|
|
text: I18n.tr("settings.bar.appearance.margins.vertical")
|
|
pointSize: Style.fontSizeXS
|
|
color: Color.mOnSurfaceVariant
|
|
}
|
|
|
|
NValueSlider {
|
|
Layout.fillWidth: true
|
|
from: 0
|
|
to: 1
|
|
stepSize: 0.01
|
|
value: Settings.data.bar.marginVertical
|
|
onMoved: value => Settings.data.bar.marginVertical = value
|
|
text: Math.round(Settings.data.bar.marginVertical * 100) + "%"
|
|
}
|
|
}
|
|
|
|
ColumnLayout {
|
|
spacing: Style.marginXXS
|
|
|
|
NText {
|
|
text: I18n.tr("settings.bar.appearance.margins.horizontal")
|
|
pointSize: Style.fontSizeXS
|
|
color: Color.mOnSurfaceVariant
|
|
}
|
|
|
|
NValueSlider {
|
|
Layout.fillWidth: true
|
|
from: 0
|
|
to: 1
|
|
stepSize: 0.01
|
|
value: Settings.data.bar.marginHorizontal
|
|
onMoved: value => Settings.data.bar.marginHorizontal = value
|
|
text: Math.round(Settings.data.bar.marginHorizontal * 100) + "%"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ColumnLayout {
|
|
spacing: Style.marginXXS
|
|
Layout.fillWidth: true
|
|
|
|
NLabel {
|
|
label: I18n.tr("settings.bar.appearance.background-opacity.label")
|
|
description: I18n.tr("settings.bar.appearance.background-opacity.description")
|
|
}
|
|
|
|
NValueSlider {
|
|
Layout.fillWidth: true
|
|
from: 0
|
|
to: 1
|
|
stepSize: 0.01
|
|
value: Settings.data.bar.backgroundOpacity
|
|
onMoved: value => Settings.data.bar.backgroundOpacity = value
|
|
text: Math.floor(Settings.data.bar.backgroundOpacity * 100) + "%"
|
|
}
|
|
}
|
|
|
|
NDivider {
|
|
Layout.fillWidth: true
|
|
Layout.topMargin: Style.marginXL
|
|
Layout.bottomMargin: Style.marginXL
|
|
}
|
|
|
|
// Widgets Management Section
|
|
ColumnLayout {
|
|
spacing: Style.marginXXS
|
|
Layout.fillWidth: true
|
|
|
|
NHeader {
|
|
label: I18n.tr("settings.bar.widgets.section.label")
|
|
description: I18n.tr("settings.bar.widgets.section.description")
|
|
}
|
|
|
|
// Bar Sections
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
Layout.topMargin: Style.marginM
|
|
spacing: Style.marginM
|
|
|
|
// Left Section
|
|
NSectionEditor {
|
|
sectionName: "Left"
|
|
sectionId: "left"
|
|
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Settings/Bar/BarWidgetSettingsDialog.qml")
|
|
widgetRegistry: BarWidgetRegistry
|
|
widgetModel: Settings.data.bar.widgets.left
|
|
availableWidgets: availableWidgets
|
|
onAddWidget: (widgetId, section) => _addWidgetToSection(widgetId, section)
|
|
onRemoveWidget: (section, index) => _removeWidgetFromSection(section, index)
|
|
onReorderWidget: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex)
|
|
onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings)
|
|
onMoveWidget: (fromSection, index, toSection) => _moveWidgetBetweenSections(fromSection, index, toSection)
|
|
onDragPotentialStarted: root.handleDragStart()
|
|
onDragPotentialEnded: root.handleDragEnd()
|
|
}
|
|
|
|
// Center Section
|
|
NSectionEditor {
|
|
sectionName: "Center"
|
|
sectionId: "center"
|
|
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Settings/Bar/BarWidgetSettingsDialog.qml")
|
|
widgetRegistry: BarWidgetRegistry
|
|
widgetModel: Settings.data.bar.widgets.center
|
|
availableWidgets: availableWidgets
|
|
onAddWidget: (widgetId, section) => _addWidgetToSection(widgetId, section)
|
|
onRemoveWidget: (section, index) => _removeWidgetFromSection(section, index)
|
|
onReorderWidget: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex)
|
|
onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings)
|
|
onMoveWidget: (fromSection, index, toSection) => _moveWidgetBetweenSections(fromSection, index, toSection)
|
|
onDragPotentialStarted: root.handleDragStart()
|
|
onDragPotentialEnded: root.handleDragEnd()
|
|
}
|
|
|
|
// Right Section
|
|
NSectionEditor {
|
|
sectionName: "Right"
|
|
sectionId: "right"
|
|
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Settings/Bar/BarWidgetSettingsDialog.qml")
|
|
widgetRegistry: BarWidgetRegistry
|
|
widgetModel: Settings.data.bar.widgets.right
|
|
availableWidgets: availableWidgets
|
|
onAddWidget: (widgetId, section) => _addWidgetToSection(widgetId, section)
|
|
onRemoveWidget: (section, index) => _removeWidgetFromSection(section, index)
|
|
onReorderWidget: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex)
|
|
onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings)
|
|
onMoveWidget: (fromSection, index, toSection) => _moveWidgetBetweenSections(fromSection, index, toSection)
|
|
onDragPotentialStarted: root.handleDragStart()
|
|
onDragPotentialEnded: root.handleDragEnd()
|
|
}
|
|
}
|
|
}
|
|
|
|
NDivider {
|
|
Layout.fillWidth: true
|
|
Layout.topMargin: Style.marginXL
|
|
Layout.bottomMargin: Style.marginXL
|
|
}
|
|
|
|
// Monitor Configuration
|
|
ColumnLayout {
|
|
spacing: Style.marginM
|
|
Layout.fillWidth: true
|
|
|
|
NHeader {
|
|
label: I18n.tr("settings.bar.monitors.section.label")
|
|
description: I18n.tr("settings.bar.monitors.section.description")
|
|
}
|
|
|
|
Repeater {
|
|
model: Quickshell.screens || []
|
|
delegate: NCheckbox {
|
|
Layout.fillWidth: true
|
|
label: modelData.name || "Unknown"
|
|
description: {
|
|
const compositorScale = CompositorService.getDisplayScale(modelData.name)
|
|
I18n.tr("system.monitor-description", {
|
|
"model": modelData.model,
|
|
"width": modelData.width * compositorScale,
|
|
"height": modelData.height * compositorScale,
|
|
"scale": compositorScale
|
|
})
|
|
}
|
|
checked: (Settings.data.bar.monitors || []).indexOf(modelData.name) !== -1
|
|
onToggled: checked => {
|
|
if (checked) {
|
|
Settings.data.bar.monitors = addMonitor(Settings.data.bar.monitors, modelData.name)
|
|
} else {
|
|
Settings.data.bar.monitors = removeMonitor(Settings.data.bar.monitors, modelData.name)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
NDivider {
|
|
Layout.fillWidth: true
|
|
Layout.topMargin: Style.marginXL
|
|
Layout.bottomMargin: Style.marginXL
|
|
}
|
|
|
|
// Signal functions
|
|
function _addWidgetToSection(widgetId, section) {
|
|
var newWidget = {
|
|
"id": widgetId
|
|
}
|
|
if (BarWidgetRegistry.widgetHasUserSettings(widgetId)) {
|
|
var metadata = BarWidgetRegistry.widgetMetadata[widgetId]
|
|
if (metadata) {
|
|
Object.keys(metadata).forEach(function (key) {
|
|
if (key !== "allowUserSettings") {
|
|
newWidget[key] = metadata[key]
|
|
}
|
|
})
|
|
}
|
|
}
|
|
Settings.data.bar.widgets[section].push(newWidget)
|
|
}
|
|
|
|
function _removeWidgetFromSection(section, index) {
|
|
if (index >= 0 && index < Settings.data.bar.widgets[section].length) {
|
|
var newArray = Settings.data.bar.widgets[section].slice()
|
|
var removedWidgets = newArray.splice(index, 1)
|
|
Settings.data.bar.widgets[section] = newArray
|
|
|
|
// Check that we still have a control center
|
|
if (removedWidgets[0].id === "ControlCenter" && BarService.lookupWidget("ControlCenter") === undefined) {
|
|
ToastService.showWarning(I18n.tr("toast.missing-control-center.label"), I18n.tr("toast.missing-control-center.description"), 12000)
|
|
}
|
|
}
|
|
}
|
|
|
|
function _reorderWidgetInSection(section, fromIndex, toIndex) {
|
|
if (fromIndex >= 0 && fromIndex < Settings.data.bar.widgets[section].length && toIndex >= 0 && toIndex < Settings.data.bar.widgets[section].length) {
|
|
|
|
// Create a new array to avoid modifying the original
|
|
var newArray = Settings.data.bar.widgets[section].slice()
|
|
var item = newArray[fromIndex]
|
|
newArray.splice(fromIndex, 1)
|
|
newArray.splice(toIndex, 0, item)
|
|
|
|
Settings.data.bar.widgets[section] = newArray
|
|
//Logger.i("BarTab", "Widget reordered. New array:", JSON.stringify(newArray))
|
|
}
|
|
}
|
|
|
|
function _updateWidgetSettingsInSection(section, index, settings) {
|
|
// Update the widget settings in the Settings data
|
|
Settings.data.bar.widgets[section][index] = settings
|
|
//Logger.i("BarTab", `Updated widget settings for ${settings.id} in ${section} section`)
|
|
}
|
|
|
|
function _moveWidgetBetweenSections(fromSection, index, toSection) {
|
|
// Get the widget from the source section
|
|
if (index >= 0 && index < Settings.data.bar.widgets[fromSection].length) {
|
|
var widget = Settings.data.bar.widgets[fromSection][index]
|
|
|
|
// Remove from source section
|
|
var sourceArray = Settings.data.bar.widgets[fromSection].slice()
|
|
sourceArray.splice(index, 1)
|
|
Settings.data.bar.widgets[fromSection] = sourceArray
|
|
|
|
// Add to target section
|
|
var targetArray = Settings.data.bar.widgets[toSection].slice()
|
|
targetArray.push(widget)
|
|
Settings.data.bar.widgets[toSection] = targetArray
|
|
|
|
//Logger.i("BarTab", `Moved widget ${widget.id} from ${fromSection} to ${toSection}`)
|
|
}
|
|
}
|
|
|
|
// Data model functions
|
|
function getWidgetLocations(widgetId) {
|
|
if (!BarService)
|
|
return []
|
|
const instances = BarService.getAllRegisteredWidgets()
|
|
const locations = {}
|
|
for (var i = 0; i < instances.length; i++) {
|
|
if (instances[i].widgetId === widgetId) {
|
|
const section = instances[i].section
|
|
if (section === "left")
|
|
locations["L"] = true
|
|
else if (section === "center")
|
|
locations["C"] = true
|
|
else if (section === "right")
|
|
locations["R"] = true
|
|
}
|
|
}
|
|
return Object.keys(locations).join('')
|
|
}
|
|
|
|
function updateAvailableWidgetsModel() {
|
|
availableWidgets.clear()
|
|
const widgets = BarWidgetRegistry.getAvailableWidgets()
|
|
widgets.forEach(entry => {
|
|
availableWidgets.append({
|
|
"key": entry,
|
|
"name": entry,
|
|
"badgeLocations": getWidgetLocations(entry)
|
|
})
|
|
})
|
|
}
|
|
|
|
// Base list model for all combo boxes
|
|
ListModel {
|
|
id: availableWidgets
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
updateAvailableWidgetsModel()
|
|
}
|
|
|
|
Connections {
|
|
target: BarService
|
|
function onActiveWidgetsChanged() {
|
|
updateAvailableWidgetsModel()
|
|
}
|
|
}
|
|
}
|