fedora
This commit is contained in:
+193
@@ -0,0 +1,193 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
// Widget Settings Dialog Component
|
||||
Popup {
|
||||
id: root
|
||||
|
||||
property int widgetIndex: -1
|
||||
property var widgetData: null
|
||||
property string widgetId: ""
|
||||
property string sectionId: ""
|
||||
property var screen: null
|
||||
property var settingsCache: ({})
|
||||
|
||||
readonly property real maxHeight: (screen ? screen.height : (parent ? parent.height : 800)) * 0.8
|
||||
|
||||
signal updateWidgetSettings(string section, int index, var settings)
|
||||
|
||||
width: Math.max(content.implicitWidth + padding * 2, 640)
|
||||
height: Math.min(content.implicitHeight + padding * 2, maxHeight)
|
||||
padding: Style.marginXL
|
||||
modal: true
|
||||
closePolicy: Popup.NoAutoClose
|
||||
dim: false
|
||||
anchors.centerIn: parent
|
||||
|
||||
onOpened: {
|
||||
// Load settings when popup opens with data
|
||||
if (widgetData && widgetId) {
|
||||
loadWidgetSettings();
|
||||
}
|
||||
// Request focus to ensure keyboard input works
|
||||
forceActiveFocus();
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
id: bgRect
|
||||
|
||||
color: Color.mSurface
|
||||
radius: Style.radiusL
|
||||
border.color: Color.mPrimary
|
||||
border.width: Style.borderM
|
||||
}
|
||||
|
||||
contentItem: FocusScope {
|
||||
id: focusScope
|
||||
focus: true
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
anchors.fill: parent
|
||||
spacing: Style.marginM
|
||||
|
||||
// Title
|
||||
RowLayout {
|
||||
id: titleRow
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: implicitHeight
|
||||
|
||||
NText {
|
||||
text: I18n.tr("system.widget-settings-title", {
|
||||
"widget": root.widgetId
|
||||
})
|
||||
pointSize: Style.fontSizeL
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mPrimary
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
tooltipText: I18n.tr("common.close")
|
||||
onClicked: saveAndClose()
|
||||
}
|
||||
}
|
||||
|
||||
// Separator
|
||||
Rectangle {
|
||||
id: separator
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
color: Color.mOutline
|
||||
}
|
||||
|
||||
// Scrollable settings area
|
||||
NScrollView {
|
||||
id: scrollView
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.minimumHeight: 100
|
||||
gradientColor: Color.mSurface
|
||||
|
||||
ColumnLayout {
|
||||
width: scrollView.availableWidth
|
||||
spacing: Style.marginM
|
||||
|
||||
// Settings based on widget type
|
||||
// Will be triggered via settingsLoader.setSource()
|
||||
Loader {
|
||||
id: settingsLoader
|
||||
Layout.fillWidth: true
|
||||
onLoaded: {
|
||||
// Try to focus the first focusable item in the loaded settings
|
||||
if (item) {
|
||||
Qt.callLater(() => {
|
||||
var firstInput = findFirstFocusable(item);
|
||||
if (firstInput) {
|
||||
firstInput.forceActiveFocus();
|
||||
} else {
|
||||
focusScope.forceActiveFocus();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function findFirstFocusable(item) {
|
||||
if (!item)
|
||||
return null;
|
||||
// Check if this item can accept focus
|
||||
if (item.focus !== undefined && item.focus === true)
|
||||
return item;
|
||||
// Check children
|
||||
if (item.children) {
|
||||
for (var i = 0; i < item.children.length; i++) {
|
||||
var child = item.children[i];
|
||||
if (child && child.focus !== undefined && child.focus === true)
|
||||
return child;
|
||||
var found = findFirstFocusable(child);
|
||||
if (found)
|
||||
return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: saveTimer
|
||||
running: false
|
||||
interval: 150
|
||||
onTriggered: {
|
||||
root.updateWidgetSettings(root.sectionId, root.widgetIndex, root.settingsCache);
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: settingsLoader.item
|
||||
ignoreUnknownSignals: true
|
||||
function onSettingsChanged(newSettings) {
|
||||
if (newSettings) {
|
||||
root.settingsCache = newSettings;
|
||||
saveTimer.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function saveAndClose() {
|
||||
if (settingsLoader.item && typeof settingsLoader.item.saveSettings === 'function') {
|
||||
var newSettings = settingsLoader.item.saveSettings();
|
||||
if (newSettings) {
|
||||
root.updateWidgetSettings(root.sectionId, root.widgetIndex, newSettings);
|
||||
}
|
||||
}
|
||||
root.close();
|
||||
}
|
||||
|
||||
function loadWidgetSettings() {
|
||||
const source = BarWidgetRegistry.widgetSettingsMap[widgetId];
|
||||
if (source) {
|
||||
var currentWidgetData = widgetData;
|
||||
if (sectionId && widgetIndex >= 0) {
|
||||
var widgets = Settings.getBarWidgetsForScreen(screen?.name || "")[sectionId];
|
||||
if (widgets && widgetIndex < widgets.length) {
|
||||
currentWidgetData = widgets[widgetIndex];
|
||||
}
|
||||
}
|
||||
settingsLoader.setSource(source, {
|
||||
"screen": screen,
|
||||
"widgetData": currentWidgetData,
|
||||
"widgetMetadata": BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import "../../../../Helpers/QtObj2JS.js" as QtObj2JS
|
||||
import qs.Commons
|
||||
import qs.Services.Noctalia
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
// Monitor Widgets Configuration (inline)
|
||||
NBox {
|
||||
id: root
|
||||
|
||||
required property var screen
|
||||
readonly property string screenName: screen?.name || ""
|
||||
// determine bar orientation
|
||||
readonly property string barPosition: Settings.getBarPositionForScreen(screenName)
|
||||
readonly property bool barIsVertical: barPosition === "left" || barPosition === "right"
|
||||
|
||||
color: Color.mSurfaceVariant
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: content.implicitHeight + Style.margin2L
|
||||
|
||||
// Helper to get widgets for this screen (ensures override exists)
|
||||
function _getWidgetsContainer() {
|
||||
if (!Settings.hasScreenOverride(screenName, "widgets")) {
|
||||
var currentWidgets = Settings.getBarWidgetsForScreen(screenName);
|
||||
var widgetsCopy = QtObj2JS.qtObjectToPlainObject(currentWidgets);
|
||||
Settings.setScreenOverride(screenName, "widgets", widgetsCopy);
|
||||
}
|
||||
var entry = Settings.getScreenOverrideEntry(screenName);
|
||||
return entry ? entry.widgets : Settings.data.bar.widgets;
|
||||
}
|
||||
|
||||
// Persist widget changes by reassigning the override (triggers change detection)
|
||||
function _saveWidgets(widgets) {
|
||||
Settings.setScreenOverride(screenName, "widgets", widgets);
|
||||
}
|
||||
|
||||
// Widget manipulation 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) {
|
||||
newWidget[key] = metadata[key];
|
||||
});
|
||||
}
|
||||
}
|
||||
var widgets = _getWidgetsContainer();
|
||||
widgets[section].push(newWidget);
|
||||
_saveWidgets(widgets);
|
||||
BarService.widgetsRevision++;
|
||||
}
|
||||
|
||||
function _removeWidgetFromSection(section, index) {
|
||||
var widgets = _getWidgetsContainer();
|
||||
if (index >= 0 && index < widgets[section].length) {
|
||||
var newArray = widgets[section].slice();
|
||||
var removedWidgets = newArray.splice(index, 1);
|
||||
widgets[section] = newArray;
|
||||
_saveWidgets(widgets);
|
||||
BarService.widgetsRevision++;
|
||||
|
||||
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) {
|
||||
var widgets = _getWidgetsContainer();
|
||||
if (fromIndex >= 0 && fromIndex < widgets[section].length && toIndex >= 0 && toIndex < widgets[section].length) {
|
||||
var newArray = widgets[section].slice();
|
||||
var item = newArray[fromIndex];
|
||||
newArray.splice(fromIndex, 1);
|
||||
newArray.splice(toIndex, 0, item);
|
||||
widgets[section] = newArray;
|
||||
_saveWidgets(widgets);
|
||||
BarService.widgetsRevision++;
|
||||
}
|
||||
}
|
||||
|
||||
// Note: _updateWidgetSettingsInSection does NOT increment revision
|
||||
// because it only changes settings, not widget structure
|
||||
function _updateWidgetSettingsInSection(section, index, settings) {
|
||||
var widgets = _getWidgetsContainer();
|
||||
widgets[section][index] = settings;
|
||||
_saveWidgets(widgets);
|
||||
}
|
||||
|
||||
function _moveWidgetBetweenSections(fromSection, index, toSection) {
|
||||
var widgets = _getWidgetsContainer();
|
||||
if (index >= 0 && index < widgets[fromSection].length) {
|
||||
var widget = widgets[fromSection][index];
|
||||
var sourceArray = widgets[fromSection].slice();
|
||||
sourceArray.splice(index, 1);
|
||||
widgets[fromSection] = sourceArray;
|
||||
var targetArray = widgets[toSection].slice();
|
||||
targetArray.push(widget);
|
||||
widgets[toSection] = targetArray;
|
||||
_saveWidgets(widgets);
|
||||
BarService.widgetsRevision++;
|
||||
}
|
||||
}
|
||||
|
||||
// Available widgets ListModel
|
||||
function updateAvailableWidgetsModel() {
|
||||
availableWidgetsModel.clear();
|
||||
var widgetIds = BarWidgetRegistry.getAvailableWidgets();
|
||||
if (!widgetIds)
|
||||
return;
|
||||
for (var i = 0; i < widgetIds.length; i++) {
|
||||
var id = widgetIds[i];
|
||||
var displayName = id;
|
||||
const badges = [];
|
||||
if (BarWidgetRegistry.isPluginWidget(id)) {
|
||||
var pluginId = id.replace("plugin:", "");
|
||||
var manifest = PluginRegistry.getPluginManifest(pluginId);
|
||||
if (manifest && manifest.name) {
|
||||
displayName = manifest.name;
|
||||
} else {
|
||||
displayName = pluginId;
|
||||
}
|
||||
badges.push({
|
||||
"icon": "plugin",
|
||||
"color": Color.mSecondary
|
||||
});
|
||||
}
|
||||
if (BarWidgetRegistry.isCpuIntensive(id)) {
|
||||
badges.push({
|
||||
"icon": "cpu-intensive",
|
||||
"color": Color.mSecondary
|
||||
});
|
||||
}
|
||||
availableWidgetsModel.append({
|
||||
"key": id,
|
||||
"name": displayName,
|
||||
"badges": badges
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: Qt.callLater(updateAvailableWidgetsModel)
|
||||
|
||||
ListModel {
|
||||
id: availableWidgetsModel
|
||||
}
|
||||
|
||||
// Get effective widgets for this screen
|
||||
readonly property var effectiveWidgets: Settings.getBarWidgetsForScreen(screenName)
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginL
|
||||
spacing: Style.marginM
|
||||
|
||||
NText {
|
||||
text: I18n.tr("panels.bar.widgets-desc")
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Left Section
|
||||
NSectionEditor {
|
||||
sectionName: root.barIsVertical ? I18n.tr("positions.top") : I18n.tr("positions.left")
|
||||
sectionId: "left"
|
||||
barIsVertical: root.barIsVertical
|
||||
screen: root.screen
|
||||
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml")
|
||||
widgetRegistry: BarWidgetRegistry
|
||||
widgetModel: root.effectiveWidgets.left
|
||||
availableWidgets: availableWidgetsModel
|
||||
onAddWidget: (widgetId, section) => root._addWidgetToSection(widgetId, section)
|
||||
onRemoveWidget: (section, index) => root._removeWidgetFromSection(section, index)
|
||||
onReorderWidget: (section, fromIndex, toIndex) => root._reorderWidgetInSection(section, fromIndex, toIndex)
|
||||
onUpdateWidgetSettings: (section, index, settings) => root._updateWidgetSettingsInSection(section, index, settings)
|
||||
onMoveWidget: (fromSection, index, toSection) => root._moveWidgetBetweenSections(fromSection, index, toSection)
|
||||
onOpenPluginSettingsRequested: manifest => pluginSettingsDialog.openPluginSettings(manifest)
|
||||
}
|
||||
|
||||
// Center Section
|
||||
NSectionEditor {
|
||||
sectionName: I18n.tr("positions.center")
|
||||
sectionId: "center"
|
||||
barIsVertical: root.barIsVertical
|
||||
screen: root.screen
|
||||
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml")
|
||||
widgetRegistry: BarWidgetRegistry
|
||||
widgetModel: root.effectiveWidgets.center
|
||||
availableWidgets: availableWidgetsModel
|
||||
onAddWidget: (widgetId, section) => root._addWidgetToSection(widgetId, section)
|
||||
onRemoveWidget: (section, index) => root._removeWidgetFromSection(section, index)
|
||||
onReorderWidget: (section, fromIndex, toIndex) => root._reorderWidgetInSection(section, fromIndex, toIndex)
|
||||
onUpdateWidgetSettings: (section, index, settings) => root._updateWidgetSettingsInSection(section, index, settings)
|
||||
onMoveWidget: (fromSection, index, toSection) => root._moveWidgetBetweenSections(fromSection, index, toSection)
|
||||
onOpenPluginSettingsRequested: manifest => pluginSettingsDialog.openPluginSettings(manifest)
|
||||
}
|
||||
|
||||
// Right Section
|
||||
NSectionEditor {
|
||||
sectionName: root.barIsVertical ? I18n.tr("positions.bottom") : I18n.tr("positions.right")
|
||||
sectionId: "right"
|
||||
barIsVertical: root.barIsVertical
|
||||
screen: root.screen
|
||||
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml")
|
||||
widgetRegistry: BarWidgetRegistry
|
||||
widgetModel: root.effectiveWidgets.right
|
||||
availableWidgets: availableWidgetsModel
|
||||
onAddWidget: (widgetId, section) => root._addWidgetToSection(widgetId, section)
|
||||
onRemoveWidget: (section, index) => root._removeWidgetFromSection(section, index)
|
||||
onReorderWidget: (section, fromIndex, toIndex) => root._reorderWidgetInSection(section, fromIndex, toIndex)
|
||||
onUpdateWidgetSettings: (section, index, settings) => root._updateWidgetSettingsInSection(section, index, settings)
|
||||
onMoveWidget: (fromSection, index, toSection) => root._moveWidgetBetweenSections(fromSection, index, toSection)
|
||||
onOpenPluginSettingsRequested: manifest => pluginSettingsDialog.openPluginSettings(manifest)
|
||||
}
|
||||
}
|
||||
|
||||
// Plugin settings dialog
|
||||
NPluginSettingsPopup {
|
||||
id: pluginSettingsDialog
|
||||
parent: Overlay.overlay
|
||||
showToastOnSave: false
|
||||
}
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var screen: null
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
// Local state
|
||||
property bool valueShowIcon: widgetData.showIcon !== undefined ? widgetData.showIcon : widgetMetadata.showIcon
|
||||
property bool valueShowText: widgetData.showText !== undefined ? widgetData.showText : widgetMetadata.showText
|
||||
property string valueHideMode: widgetData.hideMode !== undefined ? widgetData.hideMode : widgetMetadata.hideMode
|
||||
property string valueScrollingMode: widgetData.scrollingMode || widgetMetadata.scrollingMode
|
||||
property int valueMaxWidth: widgetData.maxWidth !== undefined ? widgetData.maxWidth : widgetMetadata.maxWidth
|
||||
property bool valueUseFixedWidth: widgetData.useFixedWidth !== undefined ? widgetData.useFixedWidth : widgetMetadata.useFixedWidth
|
||||
property bool valueColorizeIcons: widgetData.colorizeIcons !== undefined ? widgetData.colorizeIcons : widgetMetadata.colorizeIcons
|
||||
property string valueTextColor: widgetData.textColor !== undefined ? widgetData.textColor : widgetMetadata.textColor
|
||||
|
||||
Component.onCompleted: {
|
||||
if (widgetData && widgetData.hideMode !== undefined) {
|
||||
valueHideMode = widgetData.hideMode;
|
||||
}
|
||||
}
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.hideMode = valueHideMode;
|
||||
settings.showIcon = valueShowIcon;
|
||||
settings.showText = valueShowText;
|
||||
settings.scrollingMode = valueScrollingMode;
|
||||
settings.maxWidth = parseInt(widthInput.text) || widgetMetadata.maxWidth;
|
||||
settings.useFixedWidth = valueUseFixedWidth;
|
||||
settings.colorizeIcons = valueColorizeIcons;
|
||||
settings.textColor = valueTextColor;
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.taskbar.hide-mode-label")
|
||||
description: I18n.tr("bar.active-window.hide-mode-description")
|
||||
model: [
|
||||
{
|
||||
"key": "visible",
|
||||
"name": I18n.tr("hide-modes.visible")
|
||||
},
|
||||
{
|
||||
"key": "hidden",
|
||||
"name": I18n.tr("hide-modes.hidden")
|
||||
},
|
||||
{
|
||||
"key": "transparent",
|
||||
"name": I18n.tr("hide-modes.transparent")
|
||||
}
|
||||
]
|
||||
currentKey: root.valueHideMode
|
||||
onSelected: key => {
|
||||
root.valueHideMode = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.hideMode
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
label: I18n.tr("common.select-color")
|
||||
currentKey: valueTextColor
|
||||
onSelected: key => {
|
||||
valueTextColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.textColor
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.active-window.show-app-text-label")
|
||||
description: I18n.tr("bar.active-window.show-app-text-description")
|
||||
checked: root.valueShowText
|
||||
onToggled: checked => {
|
||||
root.valueShowText = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showText
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.active-window.show-app-icon-label")
|
||||
description: I18n.tr("bar.active-window.show-app-icon-description")
|
||||
checked: root.valueShowIcon
|
||||
onToggled: checked => {
|
||||
root.valueShowIcon = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showIcon
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.tray.colorize-icons-label")
|
||||
description: I18n.tr("bar.active-window.colorize-icons-description")
|
||||
checked: root.valueColorizeIcons
|
||||
onToggled: checked => {
|
||||
root.valueColorizeIcons = checked;
|
||||
saveSettings();
|
||||
}
|
||||
visible: root.valueShowIcon
|
||||
defaultValue: widgetMetadata.colorizeIcons
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
id: widthInput
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.taskbar.max-width-label")
|
||||
description: I18n.tr("bar.media-mini.max-width-description")
|
||||
placeholderText: widgetMetadata.maxWidth
|
||||
text: valueMaxWidth
|
||||
onTextChanged: saveSettings()
|
||||
defaultValue: String(widgetMetadata.maxWidth)
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.media-mini.use-fixed-width-label")
|
||||
description: I18n.tr("bar.media-mini.use-fixed-width-description")
|
||||
checked: valueUseFixedWidth
|
||||
onToggled: checked => {
|
||||
valueUseFixedWidth = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.useFixedWidth
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("bar.media-mini.scrolling-mode-label")
|
||||
description: I18n.tr("bar.active-window.scrolling-mode-description")
|
||||
model: [
|
||||
{
|
||||
"key": "always",
|
||||
"name": I18n.tr("options.scrolling-modes.always")
|
||||
},
|
||||
{
|
||||
"key": "hover",
|
||||
"name": I18n.tr("options.scrolling-modes.hover")
|
||||
},
|
||||
{
|
||||
"key": "never",
|
||||
"name": I18n.tr("options.scrolling-modes.never")
|
||||
}
|
||||
]
|
||||
currentKey: valueScrollingMode
|
||||
defaultValue: widgetMetadata.scrollingMode
|
||||
onSelected: key => {
|
||||
valueScrollingMode = key;
|
||||
saveSettings();
|
||||
}
|
||||
minimumWidth: 200
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var screen: null
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
// Local state
|
||||
property bool valueHideWhenIdle: widgetData.hideWhenIdle !== undefined ? widgetData.hideWhenIdle : widgetMetadata.hideWhenIdle
|
||||
property string valueColorName: widgetData.colorName !== undefined ? widgetData.colorName : widgetMetadata.colorName
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.width = parseInt(widthInput.text) || widgetMetadata.width;
|
||||
settings.hideWhenIdle = valueHideWhenIdle;
|
||||
settings.colorName = valueColorName;
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
id: widthInput
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("common.width")
|
||||
description: I18n.tr("bar.audio-visualizer.width-description")
|
||||
text: widgetData.width || widgetMetadata.width
|
||||
placeholderText: I18n.tr("placeholders.enter-width-pixels")
|
||||
onTextChanged: saveSettings()
|
||||
defaultValue: String(widgetMetadata.width)
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.audio-visualizer.color-name-label")
|
||||
description: I18n.tr("bar.audio-visualizer.color-name-description")
|
||||
currentKey: root.valueColorName
|
||||
onSelected: key => {
|
||||
root.valueColorName = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.colorName
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.audio-visualizer.hide-when-idle-label")
|
||||
description: I18n.tr("bar.audio-visualizer.hide-when-idle-description")
|
||||
checked: valueHideWhenIdle
|
||||
onToggled: checked => {
|
||||
valueHideWhenIdle = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.hideWhenIdle
|
||||
}
|
||||
}
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.Hardware
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var screen: null
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
// Local state
|
||||
property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode
|
||||
property string valueDeviceNativePath: widgetData.deviceNativePath !== undefined ? widgetData.deviceNativePath : widgetMetadata.deviceNativePath
|
||||
property bool valueShowPowerProfiles: widgetData.showPowerProfiles !== undefined ? widgetData.showPowerProfiles : widgetMetadata.showPowerProfiles
|
||||
property bool valueShowNoctaliaPerformance: widgetData.showNoctaliaPerformance !== undefined ? widgetData.showNoctaliaPerformance : widgetMetadata.showNoctaliaPerformance
|
||||
property bool valueHideIfNotDetected: widgetData.hideIfNotDetected !== undefined ? widgetData.hideIfNotDetected : widgetMetadata.hideIfNotDetected
|
||||
property bool valueHideIfIdle: widgetData.hideIfIdle !== undefined ? widgetData.hideIfIdle : widgetMetadata.hideIfIdle
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
if (widgetData && widgetData.id) {
|
||||
settings.id = widgetData.id;
|
||||
}
|
||||
settings.displayMode = valueDisplayMode;
|
||||
settings.showPowerProfiles = valueShowPowerProfiles;
|
||||
settings.showNoctaliaPerformance = valueShowNoctaliaPerformance;
|
||||
settings.hideIfNotDetected = valueHideIfNotDetected;
|
||||
settings.hideIfIdle = valueHideIfIdle;
|
||||
settings.deviceNativePath = valueDeviceNativePath;
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
id: deviceComboBox
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.battery.device-label")
|
||||
description: I18n.tr("bar.battery.device-description")
|
||||
minimumWidth: 240
|
||||
model: BatteryService.deviceModel
|
||||
currentKey: root.valueDeviceNativePath
|
||||
onSelected: key => {
|
||||
root.valueDeviceNativePath = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.deviceNativePath
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("common.display-mode")
|
||||
description: I18n.tr("bar.battery.display-mode-description")
|
||||
minimumWidth: 240
|
||||
model: [
|
||||
{
|
||||
"key": "graphic",
|
||||
"name": I18n.tr("bar.battery.display-mode-graphic")
|
||||
},
|
||||
{
|
||||
"key": "graphic-clean",
|
||||
"name": I18n.tr("bar.battery.display-mode-graphic-clean")
|
||||
},
|
||||
{
|
||||
"key": "icon-hover",
|
||||
"name": I18n.tr("bar.battery.display-mode-icon-hover")
|
||||
},
|
||||
{
|
||||
"key": "icon-always",
|
||||
"name": I18n.tr("bar.battery.display-mode-icon-always")
|
||||
},
|
||||
{
|
||||
"key": "icon-only",
|
||||
"name": I18n.tr("bar.battery.display-mode-icon-only")
|
||||
}
|
||||
]
|
||||
currentKey: root.valueDisplayMode
|
||||
onSelected: key => {
|
||||
root.valueDisplayMode = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.displayMode
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.battery.hide-if-not-detected-label")
|
||||
description: I18n.tr("bar.battery.hide-if-not-detected-description")
|
||||
checked: valueHideIfNotDetected
|
||||
onToggled: checked => {
|
||||
valueHideIfNotDetected = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.hideIfNotDetected
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.battery.hide-if-idle-label")
|
||||
description: I18n.tr("bar.battery.hide-if-idle-description")
|
||||
checked: valueHideIfIdle
|
||||
onToggled: checked => {
|
||||
valueHideIfIdle = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.hideIfIdle
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.battery.show-power-profile-label")
|
||||
description: I18n.tr("bar.battery.show-power-profile-description")
|
||||
checked: valueShowPowerProfiles
|
||||
onToggled: checked => {
|
||||
valueShowPowerProfiles = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showPowerProfiles
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.battery.show-noctalia-performance-label")
|
||||
description: I18n.tr("bar.battery.show-noctalia-performance-description")
|
||||
checked: valueShowNoctaliaPerformance
|
||||
onToggled: checked => {
|
||||
valueShowNoctaliaPerformance = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showNoctaliaPerformance
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var screen: null
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
// Local state
|
||||
property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode
|
||||
property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor
|
||||
property string valueTextColor: widgetData.textColor !== undefined ? widgetData.textColor : widgetMetadata.textColor
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.displayMode = valueDisplayMode;
|
||||
settings.iconColor = valueIconColor;
|
||||
settings.textColor = valueTextColor;
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("common.display-mode")
|
||||
description: I18n.tr("bar.volume.display-mode-description")
|
||||
minimumWidth: 200
|
||||
model: [
|
||||
{
|
||||
"key": "onhover",
|
||||
"name": I18n.tr("display-modes.on-hover")
|
||||
},
|
||||
{
|
||||
"key": "alwaysShow",
|
||||
"name": I18n.tr("display-modes.always-show")
|
||||
},
|
||||
{
|
||||
"key": "alwaysHide",
|
||||
"name": I18n.tr("display-modes.always-hide")
|
||||
}
|
||||
]
|
||||
currentKey: root.valueDisplayMode
|
||||
onSelected: key => {
|
||||
root.valueDisplayMode = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.displayMode
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
label: I18n.tr("common.select-icon-color")
|
||||
currentKey: valueIconColor
|
||||
onSelected: key => {
|
||||
valueIconColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.iconColor
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
currentKey: valueTextColor
|
||||
onSelected: key => {
|
||||
valueTextColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.textColor
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var screen: null
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
// Local state
|
||||
property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode
|
||||
property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor
|
||||
property string valueTextColor: widgetData.textColor !== undefined ? widgetData.textColor : widgetMetadata.textColor
|
||||
property bool valueApplyToAllMonitors: widgetData.applyToAllMonitors !== undefined ? widgetData.applyToAllMonitors : widgetMetadata.applyToAllMonitors
|
||||
|
||||
readonly property bool hasMultipleMonitors: (Quickshell.screens || []).length > 1
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.displayMode = valueDisplayMode;
|
||||
settings.iconColor = valueIconColor;
|
||||
settings.textColor = valueTextColor;
|
||||
settings.applyToAllMonitors = valueApplyToAllMonitors;
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("common.display-mode")
|
||||
description: I18n.tr("bar.volume.display-mode-description")
|
||||
minimumWidth: 200
|
||||
model: [
|
||||
{
|
||||
"key": "onhover",
|
||||
"name": I18n.tr("display-modes.on-hover")
|
||||
},
|
||||
{
|
||||
"key": "alwaysShow",
|
||||
"name": I18n.tr("display-modes.always-show")
|
||||
},
|
||||
{
|
||||
"key": "alwaysHide",
|
||||
"name": I18n.tr("display-modes.always-hide")
|
||||
}
|
||||
]
|
||||
currentKey: valueDisplayMode
|
||||
onSelected: key => {
|
||||
valueDisplayMode = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.displayMode
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
label: I18n.tr("common.select-icon-color")
|
||||
currentKey: valueIconColor
|
||||
onSelected: key => {
|
||||
valueIconColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.iconColor
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
currentKey: valueTextColor
|
||||
onSelected: key => {
|
||||
valueTextColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.textColor
|
||||
}
|
||||
|
||||
NToggle {
|
||||
visible: hasMultipleMonitors
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.brightness.apply-all-label")
|
||||
description: I18n.tr("bar.brightness.apply-all-description")
|
||||
checked: valueApplyToAllMonitors
|
||||
onToggled: checked => {
|
||||
valueApplyToAllMonitors = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.applyToAllMonitors
|
||||
}
|
||||
}
|
||||
+315
@@ -0,0 +1,315 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.System
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
width: 700
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var screen: null
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
// Local state
|
||||
property string valueClockColor: widgetData.clockColor !== undefined ? widgetData.clockColor : widgetMetadata.clockColor
|
||||
property bool valueUseCustomFont: widgetData.useCustomFont !== undefined ? widgetData.useCustomFont : widgetMetadata.useCustomFont
|
||||
property string valueCustomFont: widgetData.customFont !== undefined ? widgetData.customFont : widgetMetadata.customFont
|
||||
property string valueFormatHorizontal: widgetData.formatHorizontal !== undefined ? widgetData.formatHorizontal : widgetMetadata.formatHorizontal
|
||||
property string valueFormatVertical: widgetData.formatVertical !== undefined ? widgetData.formatVertical : widgetMetadata.formatVertical
|
||||
property string valueTooltipFormat: widgetData.tooltipFormat !== undefined ? widgetData.tooltipFormat : widgetMetadata.tooltipFormat
|
||||
|
||||
readonly property color textColor: Color.resolveColorKey(valueClockColor)
|
||||
|
||||
// Track the currently focused input field
|
||||
property var focusedInput: null
|
||||
property int focusedLineIndex: -1
|
||||
|
||||
readonly property var now: Time.now
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.clockColor = valueClockColor;
|
||||
settings.useCustomFont = valueUseCustomFont;
|
||||
settings.customFont = valueCustomFont;
|
||||
settings.formatHorizontal = valueFormatHorizontal.trim();
|
||||
settings.formatVertical = valueFormatVertical.trim();
|
||||
settings.tooltipFormat = valueTooltipFormat.trim();
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
// Function to insert token at cursor position in the focused input
|
||||
function insertToken(token) {
|
||||
if (!focusedInput || !focusedInput.inputItem) {
|
||||
// If no input is focused, default to horiz
|
||||
if (inputHoriz.inputItem) {
|
||||
inputHoriz.inputItem.focus = true;
|
||||
focusedInput = inputHoriz;
|
||||
}
|
||||
}
|
||||
|
||||
if (focusedInput && focusedInput.inputItem) {
|
||||
var input = focusedInput.inputItem;
|
||||
var cursorPos = input.cursorPosition;
|
||||
var currentText = input.text;
|
||||
|
||||
// Insert token at cursor position
|
||||
var newText = currentText.substring(0, cursorPos) + token + currentText.substring(cursorPos);
|
||||
input.text = newText + " ";
|
||||
|
||||
// Move cursor after the inserted token
|
||||
input.cursorPosition = cursorPos + token.length + 1;
|
||||
|
||||
// Ensure the input keeps focus
|
||||
input.focus = true;
|
||||
}
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
currentKey: valueClockColor
|
||||
onSelected: key => {
|
||||
valueClockColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.clockColor
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.clock.use-custom-font-label")
|
||||
description: I18n.tr("bar.clock.use-custom-font-description")
|
||||
checked: valueUseCustomFont
|
||||
onToggled: checked => {
|
||||
valueUseCustomFont = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.useCustomFont
|
||||
}
|
||||
|
||||
NSearchableComboBox {
|
||||
Layout.fillWidth: true
|
||||
visible: valueUseCustomFont
|
||||
label: I18n.tr("bar.clock.custom-font-label")
|
||||
description: I18n.tr("bar.clock.custom-font-description")
|
||||
model: FontService.availableFonts
|
||||
currentKey: valueCustomFont
|
||||
placeholder: I18n.tr("bar.clock.custom-font-placeholder")
|
||||
searchPlaceholder: I18n.tr("bar.clock.custom-font-search-placeholder")
|
||||
popupHeight: 420
|
||||
minimumWidth: 300
|
||||
onSelected: function (key) {
|
||||
valueCustomFont = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: Settings.data.ui.fontDefault
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("bar.clock.clock-display-label")
|
||||
description: I18n.tr("bar.clock.clock-display-description")
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: main
|
||||
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginM
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: 1 // Equal sizing hint
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
|
||||
|
||||
NTextInput {
|
||||
id: inputHoriz
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.clock.horizontal-bar-label")
|
||||
description: I18n.tr("bar.clock.horizontal-bar-description")
|
||||
placeholderText: "HH:mm ddd, MMM dd"
|
||||
text: valueFormatHorizontal
|
||||
onTextChanged: {
|
||||
valueFormatHorizontal = text;
|
||||
saveSettings();
|
||||
}
|
||||
Component.onCompleted: {
|
||||
if (inputItem) {
|
||||
inputItem.onActiveFocusChanged.connect(function () {
|
||||
if (inputItem.activeFocus) {
|
||||
root.focusedInput = inputHoriz;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
defaultValue: widgetMetadata.formatHorizontal
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
id: inputVert
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.clock.vertical-bar-label")
|
||||
description: I18n.tr("bar.clock.vertical-bar-description")
|
||||
// Tokens are Qt format tokens and must not be localized
|
||||
placeholderText: "HH mm dd MM"
|
||||
text: valueFormatVertical
|
||||
onTextChanged: {
|
||||
valueFormatVertical = text;
|
||||
saveSettings();
|
||||
}
|
||||
Component.onCompleted: {
|
||||
if (inputItem) {
|
||||
inputItem.onActiveFocusChanged.connect(function () {
|
||||
if (inputItem.activeFocus) {
|
||||
root.focusedInput = inputVert;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
defaultValue: widgetMetadata.formatVertical
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
id: inputTooltip
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.clock.tooltip-format-label")
|
||||
description: I18n.tr("bar.clock.tooltip-format-description")
|
||||
placeholderText: "HH:mm, ddd MMM dd"
|
||||
text: valueTooltipFormat
|
||||
onTextChanged: {
|
||||
valueTooltipFormat = text;
|
||||
saveSettings();
|
||||
}
|
||||
Component.onCompleted: {
|
||||
if (inputItem) {
|
||||
inputItem.onActiveFocusChanged.connect(function () {
|
||||
if (inputItem.activeFocus) {
|
||||
root.focusedInput = inputTooltip;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
defaultValue: widgetMetadata.tooltipFormat
|
||||
}
|
||||
}
|
||||
|
||||
// --------------
|
||||
// Preview
|
||||
ColumnLayout {
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
|
||||
Layout.fillWidth: false
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("bar.clock.preview")
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 320
|
||||
Layout.preferredHeight: 160 // Fixed height instead of fillHeight
|
||||
|
||||
color: Color.mSurfaceVariant
|
||||
radius: Style.radiusM
|
||||
border.color: Color.mSecondary
|
||||
border.width: Style.borderS
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginM
|
||||
anchors.centerIn: parent
|
||||
|
||||
ColumnLayout {
|
||||
spacing: -2
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
// Horizontal
|
||||
Repeater {
|
||||
Layout.topMargin: Style.marginM
|
||||
model: I18n.locale.toString(now, valueFormatHorizontal.trim()).split("\\n")
|
||||
delegate: NText {
|
||||
visible: text !== ""
|
||||
text: modelData
|
||||
family: valueUseCustomFont && valueCustomFont ? valueCustomFont : Settings.data.ui.fontDefault
|
||||
pointSize: Style.fontSizeM
|
||||
font.weight: Style.fontWeightBold
|
||||
color: textColor
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Vertical
|
||||
ColumnLayout {
|
||||
spacing: -2
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
Repeater {
|
||||
Layout.topMargin: Style.marginM
|
||||
model: I18n.locale.toString(now, valueFormatVertical.trim()).split(" ")
|
||||
delegate: NText {
|
||||
visible: text !== ""
|
||||
text: modelData
|
||||
family: valueUseCustomFont && valueCustomFont ? valueCustomFont : Settings.data.ui.fontDefault
|
||||
pointSize: Style.fontSizeM
|
||||
font.weight: Style.fontWeightBold
|
||||
color: textColor
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.topMargin: Style.marginM
|
||||
Layout.bottomMargin: Style.marginM
|
||||
}
|
||||
|
||||
NDateTimeTokens {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 300
|
||||
|
||||
// Connect to token clicked signal if NDateTimeTokens provides it
|
||||
onTokenClicked: token => root.insertToken(token)
|
||||
}
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var screen: null
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
// Local state
|
||||
property string valueIcon: widgetData.icon !== undefined ? widgetData.icon : widgetMetadata.icon
|
||||
property bool valueUseDistroLogo: widgetData.useDistroLogo !== undefined ? widgetData.useDistroLogo : widgetMetadata.useDistroLogo
|
||||
property string valueCustomIconPath: widgetData.customIconPath !== undefined ? widgetData.customIconPath : widgetMetadata.customIconPath
|
||||
property bool valueEnableColorization: widgetData.enableColorization !== undefined ? widgetData.enableColorization : widgetMetadata.enableColorization
|
||||
property string valueColorizeSystemIcon: widgetData.colorizeSystemIcon !== undefined ? widgetData.colorizeSystemIcon : widgetMetadata.colorizeSystemIcon
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.icon = valueIcon;
|
||||
settings.useDistroLogo = valueUseDistroLogo;
|
||||
settings.customIconPath = valueCustomIconPath;
|
||||
settings.enableColorization = valueEnableColorization;
|
||||
settings.colorizeSystemIcon = valueColorizeSystemIcon;
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.control-center.use-distro-logo-label")
|
||||
description: I18n.tr("bar.control-center.use-distro-logo-description")
|
||||
checked: valueUseDistroLogo
|
||||
onToggled: checked => {
|
||||
valueUseDistroLogo = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.useDistroLogo
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.custom-button.enable-colorization-label")
|
||||
description: I18n.tr("bar.control-center.enable-colorization-description")
|
||||
checked: valueEnableColorization
|
||||
onToggled: checked => {
|
||||
valueEnableColorization = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.enableColorization
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
visible: valueEnableColorization
|
||||
label: I18n.tr("common.select-icon-color")
|
||||
description: I18n.tr("bar.control-center.color-selection-description")
|
||||
currentKey: valueColorizeSystemIcon
|
||||
onSelected: function (key) {
|
||||
valueColorizeSystemIcon = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.colorizeSystemIcon
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginM
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("common.icon")
|
||||
description: I18n.tr("bar.control-center.icon-description")
|
||||
}
|
||||
|
||||
NImageRounded {
|
||||
Layout.preferredWidth: Style.fontSizeXL * 2
|
||||
Layout.preferredHeight: Style.fontSizeXL * 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
radius: Math.min(Style.radiusL, Layout.preferredWidth / 2)
|
||||
imagePath: valueCustomIconPath
|
||||
visible: valueCustomIconPath !== "" && !valueUseDistroLogo
|
||||
}
|
||||
|
||||
NIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
icon: valueIcon
|
||||
pointSize: Style.fontSizeXXL * 1.5
|
||||
visible: valueIcon !== "" && valueCustomIconPath === "" && !valueUseDistroLogo
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginM
|
||||
NButton {
|
||||
enabled: !valueUseDistroLogo
|
||||
text: I18n.tr("bar.control-center.browse-library")
|
||||
onClicked: iconPicker.open()
|
||||
}
|
||||
|
||||
NButton {
|
||||
enabled: !valueUseDistroLogo
|
||||
text: I18n.tr("bar.control-center.browse-file")
|
||||
onClicked: imagePicker.openFilePicker()
|
||||
}
|
||||
}
|
||||
|
||||
NIconPicker {
|
||||
id: iconPicker
|
||||
initialIcon: valueIcon
|
||||
onIconSelected: iconName => {
|
||||
valueIcon = iconName;
|
||||
valueCustomIconPath = "";
|
||||
saveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
NFilePicker {
|
||||
id: imagePicker
|
||||
title: I18n.tr("bar.control-center.select-custom-icon")
|
||||
selectionMode: "files"
|
||||
nameFilters: ImageCacheService.basicImageFilters.concat(["*.svg"])
|
||||
initialPath: Quickshell.env("HOME")
|
||||
onAccepted: paths => {
|
||||
if (paths.length > 0) {
|
||||
valueCustomIconPath = paths[0]; // Use first selected file
|
||||
saveSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+512
@@ -0,0 +1,512 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Window
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var screen: null
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
property string valueIcon: widgetData.icon !== undefined ? widgetData.icon : widgetMetadata.icon
|
||||
property bool valueTextStream: widgetData.textStream !== undefined ? widgetData.textStream : widgetMetadata.textStream
|
||||
property bool valueParseJson: widgetData.parseJson !== undefined ? widgetData.parseJson : widgetMetadata.parseJson
|
||||
property int valueMaxTextLengthHorizontal: widgetData?.maxTextLength?.horizontal ?? widgetMetadata?.maxTextLength?.horizontal
|
||||
property int valueMaxTextLengthVertical: widgetData?.maxTextLength?.vertical ?? widgetMetadata?.maxTextLength?.vertical
|
||||
property string valueHideMode: (widgetData.hideMode !== undefined) ? widgetData.hideMode : widgetMetadata.hideMode
|
||||
property bool valueShowIcon: (widgetData.showIcon !== undefined) ? widgetData.showIcon : widgetMetadata.showIcon
|
||||
property bool valueShowExecTooltip: widgetData.showExecTooltip !== undefined ? widgetData.showExecTooltip : widgetMetadata.showExecTooltip
|
||||
property bool valueShowTextTooltip: widgetData.showTextTooltip !== undefined ? widgetData.showTextTooltip : widgetMetadata.showTextTooltip
|
||||
property bool valueEnableColorization: widgetData.enableColorization !== undefined ? widgetData.enableColorization : widgetMetadata.enableColorization
|
||||
property string valueColorizeSystemIcon: widgetData.colorizeSystemIcon !== undefined ? widgetData.colorizeSystemIcon : widgetMetadata.colorizeSystemIcon
|
||||
property string valueIpcIdentifier: widgetData.ipcIdentifier !== undefined ? widgetData.ipcIdentifier : widgetMetadata.ipcIdentifier
|
||||
property string valueGeneralTooltipText: widgetData.generalTooltipText !== undefined ? widgetData.generalTooltipText : widgetMetadata.generalTooltipText
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.icon = valueIcon;
|
||||
settings.leftClickExec = leftClickExecInput.text;
|
||||
settings.leftClickUpdateText = leftClickUpdateText.checked;
|
||||
settings.rightClickExec = rightClickExecInput.text;
|
||||
settings.rightClickUpdateText = rightClickUpdateText.checked;
|
||||
settings.middleClickExec = middleClickExecInput.text;
|
||||
settings.middleClickUpdateText = middleClickUpdateText.checked;
|
||||
settings.wheelMode = separateWheelToggle.internalChecked ? "separate" : "unified";
|
||||
settings.wheelExec = wheelExecInput.text;
|
||||
settings.wheelUpExec = wheelUpExecInput.text;
|
||||
settings.wheelDownExec = wheelDownExecInput.text;
|
||||
settings.wheelUpdateText = wheelUpdateText.checked;
|
||||
settings.wheelUpUpdateText = wheelUpUpdateText.checked;
|
||||
settings.wheelDownUpdateText = wheelDownUpdateText.checked;
|
||||
settings.textCommand = textCommandInput.text;
|
||||
settings.textCollapse = textCollapseInput.text;
|
||||
settings.textStream = valueTextStream;
|
||||
settings.parseJson = valueParseJson;
|
||||
settings.showIcon = valueShowIcon;
|
||||
settings.showExecTooltip = valueShowExecTooltip;
|
||||
settings.showTextTooltip = valueShowTextTooltip;
|
||||
settings.hideMode = valueHideMode;
|
||||
settings.maxTextLength = {
|
||||
"horizontal": valueMaxTextLengthHorizontal,
|
||||
"vertical": valueMaxTextLengthVertical
|
||||
};
|
||||
settings.textIntervalMs = parseInt(textIntervalInput.text || textIntervalInput.placeholderText, 10);
|
||||
settings.enableColorization = valueEnableColorization;
|
||||
settings.colorizeSystemIcon = valueColorizeSystemIcon;
|
||||
settings.ipcIdentifier = valueIpcIdentifier;
|
||||
settings.generalTooltipText = valueGeneralTooltipText;
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginM
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("common.icon")
|
||||
description: I18n.tr("bar.custom-button.icon-description")
|
||||
}
|
||||
|
||||
NIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
icon: valueIcon
|
||||
pointSize: Style.fontSizeXL
|
||||
visible: valueIcon !== ""
|
||||
}
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("common.browse")
|
||||
onClicked: iconPicker.open()
|
||||
}
|
||||
}
|
||||
|
||||
NIconPicker {
|
||||
id: iconPicker
|
||||
initialIcon: valueIcon
|
||||
onIconSelected: function (iconName) {
|
||||
valueIcon = iconName;
|
||||
saveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: showIconToggle
|
||||
label: I18n.tr("bar.custom-button.show-icon-label")
|
||||
description: I18n.tr("bar.custom-button.show-icon-description")
|
||||
checked: valueShowIcon
|
||||
onToggled: checked => {
|
||||
valueShowIcon = checked;
|
||||
saveSettings();
|
||||
}
|
||||
visible: textCommandInput.text !== ""
|
||||
defaultValue: widgetMetadata.showIcon
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.custom-button.enable-colorization-label")
|
||||
description: I18n.tr("bar.custom-button.enable-colorization-description")
|
||||
checked: valueEnableColorization
|
||||
onToggled: checked => {
|
||||
valueEnableColorization = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.enableColorization
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
visible: valueEnableColorization
|
||||
label: I18n.tr("common.select-icon-color")
|
||||
description: I18n.tr("bar.custom-button.color-selection-description")
|
||||
currentKey: valueColorizeSystemIcon
|
||||
onSelected: key => {
|
||||
valueColorizeSystemIcon = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.colorizeSystemIcon
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.custom-button.general-tooltip-text-label")
|
||||
description: I18n.tr("bar.custom-button.general-tooltip-text-description")
|
||||
placeholderText: I18n.tr("placeholders.enter-tooltip")
|
||||
text: valueGeneralTooltipText
|
||||
onTextChanged: {
|
||||
valueGeneralTooltipText = text;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.generalTooltipText
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: showExecTooltipToggle
|
||||
label: I18n.tr("bar.custom-button.show-exec-tooltip-label")
|
||||
description: I18n.tr("bar.custom-button.show-exec-tooltip-description")
|
||||
checked: valueShowExecTooltip
|
||||
onToggled: checked => {
|
||||
valueShowExecTooltip = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showExecTooltip
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: showTextTooltipToggle
|
||||
label: I18n.tr("bar.custom-button.show-text-tooltip-label")
|
||||
description: I18n.tr("bar.custom-button.show-text-tooltip-description")
|
||||
checked: valueShowTextTooltip
|
||||
onToggled: checked => {
|
||||
valueShowTextTooltip = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showTextTooltip
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.custom-button.ipc-identifier-label")
|
||||
description: I18n.tr("bar.custom-button.ipc-identifier-description")
|
||||
placeholderText: I18n.tr("placeholders.enter-ipc-identifier")
|
||||
text: valueIpcIdentifier
|
||||
onTextChanged: {
|
||||
valueIpcIdentifier = text;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.ipcIdentifier
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginM
|
||||
|
||||
NTextInput {
|
||||
id: leftClickExecInput
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.custom-button.left-click-label")
|
||||
description: I18n.tr("bar.custom-button.left-click-description")
|
||||
placeholderText: I18n.tr("placeholders.enter-command")
|
||||
text: widgetData?.leftClickExec || widgetMetadata.leftClickExec
|
||||
onTextChanged: saveSettings()
|
||||
defaultValue: widgetMetadata.leftClickExec
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: leftClickUpdateText
|
||||
enabled: !valueTextStream
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
|
||||
Layout.bottomMargin: Style.marginS
|
||||
onEntered: TooltipService.show(leftClickUpdateText, I18n.tr("bar.custom-button.left-click-update-text"))
|
||||
onExited: TooltipService.hide()
|
||||
checked: widgetData?.leftClickUpdateText ?? widgetMetadata.leftClickUpdateText
|
||||
onToggled: isChecked => {
|
||||
checked = isChecked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.leftClickUpdateText
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginM
|
||||
|
||||
NTextInput {
|
||||
id: rightClickExecInput
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.custom-button.right-click-label")
|
||||
description: I18n.tr("bar.custom-button.right-click-description")
|
||||
placeholderText: I18n.tr("placeholders.enter-command")
|
||||
text: widgetData?.rightClickExec || widgetMetadata.rightClickExec
|
||||
onTextChanged: saveSettings()
|
||||
defaultValue: widgetMetadata.rightClickExec
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: rightClickUpdateText
|
||||
enabled: !valueTextStream
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
|
||||
Layout.bottomMargin: Style.marginS
|
||||
onEntered: TooltipService.show(rightClickUpdateText, I18n.tr("bar.custom-button.right-click-update-text"))
|
||||
onExited: TooltipService.hide()
|
||||
checked: widgetData?.rightClickUpdateText ?? widgetMetadata.rightClickUpdateText
|
||||
onToggled: isChecked => {
|
||||
checked = isChecked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.rightClickUpdateText
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginM
|
||||
|
||||
NTextInput {
|
||||
id: middleClickExecInput
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.custom-button.middle-click-label")
|
||||
description: I18n.tr("bar.custom-button.middle-click-description")
|
||||
placeholderText: I18n.tr("placeholders.enter-command")
|
||||
text: widgetData.middleClickExec || widgetMetadata.middleClickExec
|
||||
onTextChanged: saveSettings()
|
||||
defaultValue: widgetMetadata.middleClickExec
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: middleClickUpdateText
|
||||
enabled: !valueTextStream
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
|
||||
Layout.bottomMargin: Style.marginS
|
||||
onEntered: TooltipService.show(middleClickUpdateText, I18n.tr("bar.custom-button.middle-click-update-text"))
|
||||
onExited: TooltipService.hide()
|
||||
checked: widgetData?.middleClickUpdateText ?? widgetMetadata.middleClickUpdateText
|
||||
onToggled: isChecked => {
|
||||
checked = isChecked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.middleClickUpdateText
|
||||
}
|
||||
}
|
||||
|
||||
// Wheel command settings
|
||||
NToggle {
|
||||
id: separateWheelToggle
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.custom-button.wheel-mode-separate-label")
|
||||
description: I18n.tr("bar.custom-button.wheel-mode-separate-description")
|
||||
property bool internalChecked: (widgetData?.wheelMode || widgetMetadata?.wheelMode) === "separate"
|
||||
checked: internalChecked
|
||||
onToggled: checked => {
|
||||
internalChecked = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.wheelMode === "separate"
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
RowLayout {
|
||||
id: unifiedWheelLayout
|
||||
visible: !separateWheelToggle.checked
|
||||
spacing: Style.marginM
|
||||
|
||||
NTextInput {
|
||||
id: wheelExecInput
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.custom-button.wheel-label")
|
||||
description: I18n.tr("bar.custom-button.wheel-description")
|
||||
placeholderText: I18n.tr("placeholders.enter-command")
|
||||
text: widgetData?.wheelExec || widgetMetadata?.wheelExec
|
||||
onTextChanged: saveSettings()
|
||||
defaultValue: widgetMetadata.wheelExec
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: wheelUpdateText
|
||||
enabled: !valueTextStream
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
|
||||
Layout.bottomMargin: Style.marginS
|
||||
onEntered: TooltipService.show(wheelUpdateText, I18n.tr("bar.custom-button.wheel-update-text"))
|
||||
onExited: TooltipService.hide()
|
||||
checked: widgetData?.wheelUpdateText ?? widgetMetadata?.wheelUpdateText
|
||||
onToggled: isChecked => {
|
||||
checked = isChecked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.wheelUpdateText
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: separatedWheelLayout
|
||||
Layout.fillWidth: true
|
||||
visible: separateWheelToggle.checked
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginM
|
||||
|
||||
NTextInput {
|
||||
id: wheelUpExecInput
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.custom-button.wheel-up-label")
|
||||
description: I18n.tr("bar.custom-button.wheel-up-description")
|
||||
placeholderText: I18n.tr("placeholders.enter-command")
|
||||
text: widgetData?.wheelUpExec || widgetMetadata?.wheelUpExec
|
||||
onTextChanged: saveSettings()
|
||||
defaultValue: widgetMetadata.wheelUpExec
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: wheelUpUpdateText
|
||||
enabled: !valueTextStream
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
|
||||
Layout.bottomMargin: Style.marginS
|
||||
onEntered: TooltipService.show(wheelUpUpdateText, I18n.tr("bar.custom-button.wheel-update-text"))
|
||||
onExited: TooltipService.hide()
|
||||
checked: (widgetData?.wheelUpUpdateText !== undefined) ? widgetData.wheelUpUpdateText : widgetMetadata?.wheelUpUpdateText
|
||||
onToggled: isChecked => {
|
||||
checked = isChecked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.wheelUpUpdateText
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginM
|
||||
|
||||
NTextInput {
|
||||
id: wheelDownExecInput
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.custom-button.wheel-down-label")
|
||||
description: I18n.tr("bar.custom-button.wheel-down-description")
|
||||
placeholderText: I18n.tr("placeholders.enter-command")
|
||||
text: widgetData?.wheelDownExec || widgetMetadata?.wheelDownExec
|
||||
onTextChanged: saveSettings()
|
||||
defaultValue: widgetMetadata.wheelDownExec
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: wheelDownUpdateText
|
||||
enabled: !valueTextStream
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
|
||||
Layout.bottomMargin: Style.marginS
|
||||
onEntered: TooltipService.show(wheelDownUpdateText, I18n.tr("bar.custom-button.wheel-update-text"))
|
||||
onExited: TooltipService.hide()
|
||||
checked: (widgetData?.wheelDownUpdateText !== undefined) ? widgetData.wheelDownUpdateText : widgetMetadata?.wheelDownUpdateText
|
||||
onToggled: isChecked => {
|
||||
checked = isChecked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.wheelDownUpdateText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("bar.custom-button.dynamic-text")
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
label: I18n.tr("bar.custom-button.max-text-length-horizontal-label")
|
||||
description: I18n.tr("bar.custom-button.max-text-length-horizontal-description")
|
||||
from: 0
|
||||
to: 100
|
||||
value: valueMaxTextLengthHorizontal
|
||||
onValueChanged: {
|
||||
valueMaxTextLengthHorizontal = value;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.maxTextLength.horizontal
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
label: I18n.tr("bar.custom-button.max-text-length-vertical-label")
|
||||
description: I18n.tr("bar.custom-button.max-text-length-vertical-description")
|
||||
from: 0
|
||||
to: 100
|
||||
value: valueMaxTextLengthVertical
|
||||
onValueChanged: {
|
||||
valueMaxTextLengthVertical = value;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.maxTextLength.vertical
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: textStreamInput
|
||||
label: I18n.tr("bar.custom-button.text-stream-label")
|
||||
description: I18n.tr("bar.custom-button.text-stream-description")
|
||||
checked: valueTextStream
|
||||
onToggled: checked => {
|
||||
valueTextStream = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.textStream
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: parseJsonInput
|
||||
label: I18n.tr("bar.custom-button.parse-json-label")
|
||||
description: I18n.tr("bar.custom-button.parse-json-description")
|
||||
checked: valueParseJson
|
||||
onToggled: checked => {
|
||||
valueParseJson = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.parseJson
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
id: textCommandInput
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.custom-button.display-command-output-label")
|
||||
description: valueTextStream ? I18n.tr("bar.custom-button.display-command-output-stream-description") : I18n.tr("bar.custom-button.display-command-output-description")
|
||||
placeholderText: I18n.tr("placeholders.command-example")
|
||||
text: widgetData?.textCommand || widgetMetadata.textCommand
|
||||
onTextChanged: saveSettings()
|
||||
defaultValue: widgetMetadata.textCommand
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
id: textCollapseInput
|
||||
Layout.fillWidth: true
|
||||
visible: valueTextStream
|
||||
label: I18n.tr("bar.custom-button.collapse-condition-label")
|
||||
description: I18n.tr("bar.custom-button.collapse-condition-description")
|
||||
placeholderText: I18n.tr("placeholders.enter-text-to-collapse")
|
||||
text: widgetData?.textCollapse || widgetMetadata.textCollapse
|
||||
onTextChanged: saveSettings()
|
||||
defaultValue: widgetMetadata.textCollapse
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
id: textIntervalInput
|
||||
Layout.fillWidth: true
|
||||
visible: !valueTextStream
|
||||
label: I18n.tr("bar.custom-button.refresh-interval-label")
|
||||
description: I18n.tr("bar.custom-button.refresh-interval-description")
|
||||
placeholderText: String(widgetMetadata.textIntervalMs)
|
||||
text: widgetData && widgetData.textIntervalMs !== undefined ? String(widgetData.textIntervalMs) : ""
|
||||
onTextChanged: saveSettings()
|
||||
defaultValue: String(widgetMetadata.textIntervalMs)
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
id: hideModeComboBox
|
||||
label: I18n.tr("bar.custom-button.hide-mode-label")
|
||||
description: I18n.tr("bar.custom-button.hide-mode-description")
|
||||
model: [
|
||||
{
|
||||
name: I18n.tr("bar.custom-button.hide-mode-always-expanded"),
|
||||
key: "alwaysExpanded"
|
||||
},
|
||||
{
|
||||
name: I18n.tr("bar.custom-button.hide-mode-expand-with-output"),
|
||||
key: "expandWithOutput"
|
||||
},
|
||||
{
|
||||
name: I18n.tr("bar.custom-button.hide-mode-max-transparent"),
|
||||
key: "maxTransparent"
|
||||
}
|
||||
]
|
||||
currentKey: valueHideMode
|
||||
onSelected: key => {
|
||||
valueHideMode = key;
|
||||
saveSettings();
|
||||
}
|
||||
visible: textCommandInput.text !== "" && valueTextStream == true
|
||||
defaultValue: widgetMetadata.hideMode
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var screen: null
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.iconColor = valueIconColor;
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
label: I18n.tr("common.select-icon-color")
|
||||
currentKey: valueIconColor
|
||||
onSelected: key => {
|
||||
valueIconColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.iconColor
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var screen: null
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor
|
||||
property string valueTextColor: widgetData.textColor !== undefined ? widgetData.textColor : widgetMetadata.textColor
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.iconColor = valueIconColor;
|
||||
settings.textColor = valueTextColor;
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
label: I18n.tr("common.select-icon-color")
|
||||
currentKey: valueIconColor
|
||||
onSelected: key => {
|
||||
valueIconColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.iconColor
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
currentKey: valueTextColor
|
||||
onSelected: key => {
|
||||
valueTextColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.textColor
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var screen: null
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
// Local state
|
||||
property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode
|
||||
property bool valueShowIcon: widgetData.showIcon !== undefined ? widgetData.showIcon : widgetMetadata.showIcon
|
||||
property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor
|
||||
property string valueTextColor: widgetData.textColor !== undefined ? widgetData.textColor : widgetMetadata.textColor
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.displayMode = valueDisplayMode;
|
||||
settings.showIcon = valueShowIcon;
|
||||
settings.iconColor = valueIconColor;
|
||||
settings.textColor = valueTextColor;
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
visible: valueShowIcon // Hide display mode setting when icon is disabled
|
||||
label: I18n.tr("common.display-mode")
|
||||
description: I18n.tr("bar.volume.display-mode-description")
|
||||
minimumWidth: 200
|
||||
model: [
|
||||
{
|
||||
"key": "onhover",
|
||||
"name": I18n.tr("display-modes.on-hover")
|
||||
},
|
||||
{
|
||||
"key": "forceOpen",
|
||||
"name": I18n.tr("display-modes.force-open")
|
||||
},
|
||||
{
|
||||
"key": "alwaysHide",
|
||||
"name": I18n.tr("display-modes.always-hide")
|
||||
}
|
||||
]
|
||||
currentKey: valueDisplayMode
|
||||
onSelected: key => {
|
||||
valueDisplayMode = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.displayMode
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.custom-button.show-icon-label")
|
||||
description: I18n.tr("bar.keyboard-layout.show-icon-description")
|
||||
checked: valueShowIcon
|
||||
onToggled: checked => {
|
||||
valueShowIcon = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showIcon
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
label: I18n.tr("common.select-icon-color")
|
||||
currentKey: valueIconColor
|
||||
onSelected: key => {
|
||||
valueIconColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.iconColor
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
currentKey: valueTextColor
|
||||
onSelected: key => {
|
||||
valueTextColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.textColor
|
||||
}
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var screen: null
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
// Local state
|
||||
property string valueIcon: widgetData.icon !== undefined ? widgetData.icon : widgetMetadata.icon
|
||||
property bool valueUseDistroLogo: widgetData.useDistroLogo !== undefined ? widgetData.useDistroLogo : widgetMetadata.useDistroLogo
|
||||
property string valueCustomIconPath: widgetData.customIconPath !== undefined ? widgetData.customIconPath : widgetMetadata.customIconPath
|
||||
property bool valueEnableColorization: widgetData.enableColorization !== undefined ? widgetData.enableColorization : widgetMetadata.enableColorization
|
||||
property string valueColorizeSystemIcon: widgetData.colorizeSystemIcon !== undefined ? widgetData.colorizeSystemIcon : widgetMetadata.colorizeSystemIcon
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.icon = valueIcon;
|
||||
settings.useDistroLogo = valueUseDistroLogo;
|
||||
settings.customIconPath = valueCustomIconPath;
|
||||
settings.enableColorization = valueEnableColorization;
|
||||
settings.colorizeSystemIcon = valueColorizeSystemIcon;
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.control-center.use-distro-logo-label")
|
||||
description: I18n.tr("bar.control-center.use-distro-logo-description")
|
||||
checked: valueUseDistroLogo
|
||||
onToggled: checked => {
|
||||
valueUseDistroLogo = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.useDistroLogo
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.custom-button.enable-colorization-label")
|
||||
description: I18n.tr("bar.control-center.enable-colorization-description")
|
||||
checked: valueEnableColorization
|
||||
onToggled: checked => {
|
||||
valueEnableColorization = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.enableColorization
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
visible: valueEnableColorization
|
||||
label: I18n.tr("common.select-icon-color")
|
||||
description: I18n.tr("bar.control-center.color-selection-description")
|
||||
currentKey: valueColorizeSystemIcon
|
||||
onSelected: function (key) {
|
||||
valueColorizeSystemIcon = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.colorizeSystemIcon
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginM
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("common.icon")
|
||||
description: I18n.tr("bar.control-center.icon-description")
|
||||
}
|
||||
|
||||
NImageRounded {
|
||||
Layout.preferredWidth: Style.fontSizeXL * 2
|
||||
Layout.preferredHeight: Style.fontSizeXL * 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
radius: Math.min(Style.radiusL, Layout.preferredWidth / 2)
|
||||
imagePath: valueCustomIconPath
|
||||
visible: valueCustomIconPath !== "" && !valueUseDistroLogo
|
||||
}
|
||||
|
||||
NIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
icon: valueIcon
|
||||
pointSize: Style.fontSizeXXL * 1.5
|
||||
visible: valueIcon !== "" && valueCustomIconPath === "" && !valueUseDistroLogo
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginM
|
||||
NButton {
|
||||
enabled: !valueUseDistroLogo
|
||||
text: I18n.tr("bar.control-center.browse-library")
|
||||
onClicked: iconPicker.open()
|
||||
}
|
||||
|
||||
NButton {
|
||||
enabled: !valueUseDistroLogo
|
||||
text: I18n.tr("bar.control-center.browse-file")
|
||||
onClicked: imagePicker.openFilePicker()
|
||||
}
|
||||
}
|
||||
|
||||
NIconPicker {
|
||||
id: iconPicker
|
||||
initialIcon: valueIcon
|
||||
onIconSelected: iconName => {
|
||||
valueIcon = iconName;
|
||||
valueCustomIconPath = "";
|
||||
saveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
NFilePicker {
|
||||
id: imagePicker
|
||||
title: I18n.tr("bar.control-center.select-custom-icon")
|
||||
selectionMode: "files"
|
||||
nameFilters: ImageCacheService.basicImageFilters.concat(["*.svg"])
|
||||
initialPath: Quickshell.env("HOME")
|
||||
onAccepted: paths => {
|
||||
if (paths.length > 0) {
|
||||
valueCustomIconPath = paths[0];
|
||||
saveSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var screen: null
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
// Local state
|
||||
property bool valueShowCapsLock: widgetData.showCapsLock !== undefined ? widgetData.showCapsLock : widgetMetadata.showCapsLock
|
||||
property bool valueShowNumLock: widgetData.showNumLock !== undefined ? widgetData.showNumLock : widgetMetadata.showNumLock
|
||||
property bool valueShowScrollLock: widgetData.showScrollLock !== undefined ? widgetData.showScrollLock : widgetMetadata.showScrollLock
|
||||
|
||||
property string capsIcon: widgetData.capsLockIcon !== undefined ? widgetData.capsLockIcon : widgetMetadata.capsLockIcon
|
||||
property string numIcon: widgetData.numLockIcon !== undefined ? widgetData.numLockIcon : widgetMetadata.numLockIcon
|
||||
property string scrollIcon: widgetData.scrollLockIcon !== undefined ? widgetData.scrollLockIcon : widgetMetadata.scrollLockIcon
|
||||
|
||||
property bool valueHideWhenOff: widgetData.hideWhenOff !== undefined ? widgetData.hideWhenOff : (widgetMetadata.hideWhenOff !== undefined ? widgetMetadata.hideWhenOff : false)
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.showCapsLock = valueShowCapsLock;
|
||||
settings.showNumLock = valueShowNumLock;
|
||||
settings.showScrollLock = valueShowScrollLock;
|
||||
settings.capsLockIcon = capsIcon;
|
||||
settings.numLockIcon = numIcon;
|
||||
settings.scrollLockIcon = scrollIcon;
|
||||
settings.hideWhenOff = valueHideWhenOff;
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginM
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.lock-keys.show-caps-lock-label")
|
||||
description: I18n.tr("bar.lock-keys.show-caps-lock-description")
|
||||
checked: valueShowCapsLock
|
||||
onToggled: checked => {
|
||||
valueShowCapsLock = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showCapsLock
|
||||
}
|
||||
|
||||
NIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
icon: capsIcon
|
||||
pointSize: Style.fontSizeXL
|
||||
visible: capsIcon !== ""
|
||||
}
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("common.browse")
|
||||
onClicked: capsPicker.open()
|
||||
enabled: valueShowCapsLock
|
||||
}
|
||||
}
|
||||
|
||||
NIconPicker {
|
||||
id: capsPicker
|
||||
initialIcon: capsIcon
|
||||
query: "letter-c"
|
||||
onIconSelected: function (iconName) {
|
||||
capsIcon = iconName;
|
||||
saveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginM
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.lock-keys.show-num-lock-label")
|
||||
description: I18n.tr("bar.lock-keys.show-num-lock-description")
|
||||
checked: valueShowNumLock
|
||||
onToggled: checked => {
|
||||
valueShowNumLock = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showNumLock
|
||||
}
|
||||
|
||||
NIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
icon: numIcon
|
||||
pointSize: Style.fontSizeXL
|
||||
visible: numIcon !== ""
|
||||
}
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("common.browse")
|
||||
onClicked: numPicker.open()
|
||||
enabled: valueShowNumLock
|
||||
}
|
||||
}
|
||||
|
||||
NIconPicker {
|
||||
id: numPicker
|
||||
initialIcon: numIcon
|
||||
query: "letter-n"
|
||||
onIconSelected: function (iconName) {
|
||||
numIcon = iconName;
|
||||
saveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginM
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.lock-keys.show-scroll-lock-label")
|
||||
description: I18n.tr("bar.lock-keys.show-scroll-lock-description")
|
||||
checked: valueShowScrollLock
|
||||
onToggled: checked => {
|
||||
valueShowScrollLock = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showScrollLock
|
||||
}
|
||||
|
||||
NIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
icon: scrollIcon
|
||||
pointSize: Style.fontSizeXL
|
||||
visible: scrollIcon !== ""
|
||||
}
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("common.browse")
|
||||
onClicked: scrollPicker.open()
|
||||
enabled: valueShowScrollLock
|
||||
}
|
||||
}
|
||||
|
||||
NIconPicker {
|
||||
id: scrollPicker
|
||||
initialIcon: scrollIcon
|
||||
query: "letter-s"
|
||||
onIconSelected: function (iconName) {
|
||||
scrollIcon = iconName;
|
||||
saveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.lock-keys.hide-when-off-label")
|
||||
description: I18n.tr("bar.lock-keys.hide-when-off-description")
|
||||
checked: valueHideWhenOff
|
||||
onToggled: checked => {
|
||||
valueHideWhenOff = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.hideWhenOff
|
||||
}
|
||||
}
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var screen: null
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
// Local state
|
||||
property string valueHideMode: widgetData.hideMode !== undefined ? widgetData.hideMode : widgetMetadata.hideMode
|
||||
// Deprecated: hideWhenIdle now folded into hideMode = "idle"
|
||||
property bool valueHideWhenIdle: widgetData.hideWhenIdle !== undefined ? widgetData.hideWhenIdle : widgetMetadata.hideWhenIdle
|
||||
property bool valueShowAlbumArt: widgetData.showAlbumArt !== undefined ? widgetData.showAlbumArt : widgetMetadata.showAlbumArt
|
||||
property bool valuePanelShowAlbumArt: widgetData.panelShowAlbumArt !== undefined ? widgetData.panelShowAlbumArt : widgetMetadata.panelShowAlbumArt
|
||||
property bool valueShowArtistFirst: widgetData.showArtistFirst !== undefined ? widgetData.showArtistFirst : widgetMetadata.showArtistFirst
|
||||
property bool valueShowVisualizer: widgetData.showVisualizer !== undefined ? widgetData.showVisualizer : widgetMetadata.showVisualizer
|
||||
property string valueVisualizerType: widgetData.visualizerType !== undefined ? widgetData.visualizerType : widgetMetadata.visualizerType
|
||||
property string valueScrollingMode: widgetData.scrollingMode !== undefined ? widgetData.scrollingMode : widgetMetadata.scrollingMode
|
||||
property int valueMaxWidth: widgetData.maxWidth !== undefined ? widgetData.maxWidth : widgetMetadata.maxWidth
|
||||
property bool valueUseFixedWidth: widgetData.useFixedWidth !== undefined ? widgetData.useFixedWidth : widgetMetadata.useFixedWidth
|
||||
property bool valueShowProgressRing: widgetData.showProgressRing !== undefined ? widgetData.showProgressRing : widgetMetadata.showProgressRing
|
||||
property bool valueCompactMode: widgetData.compactMode !== undefined ? widgetData.compactMode : widgetMetadata.compactMode
|
||||
property string valueTextColor: widgetData.textColor !== undefined ? widgetData.textColor : widgetMetadata.textColor
|
||||
|
||||
Component.onCompleted: {
|
||||
if (widgetData && widgetData.hideMode !== undefined) {
|
||||
valueHideMode = widgetData.hideMode;
|
||||
}
|
||||
}
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.hideMode = valueHideMode;
|
||||
// No longer store hideWhenIdle separately; kept for backward compatibility only
|
||||
settings.showAlbumArt = valueShowAlbumArt;
|
||||
settings.panelShowAlbumArt = valuePanelShowAlbumArt;
|
||||
settings.showArtistFirst = valueShowArtistFirst;
|
||||
settings.showVisualizer = valueShowVisualizer;
|
||||
settings.visualizerType = valueVisualizerType;
|
||||
settings.scrollingMode = valueScrollingMode;
|
||||
settings.maxWidth = parseInt(widthInput.text) || widgetMetadata.maxWidth;
|
||||
settings.useFixedWidth = valueUseFixedWidth;
|
||||
settings.showProgressRing = valueShowProgressRing;
|
||||
settings.compactMode = valueCompactMode;
|
||||
settings.textColor = valueTextColor;
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.taskbar.hide-mode-label")
|
||||
description: I18n.tr("bar.media-mini.hide-mode-description")
|
||||
model: [
|
||||
{
|
||||
"key": "visible",
|
||||
"name": I18n.tr("hide-modes.visible")
|
||||
},
|
||||
{
|
||||
"key": "hidden",
|
||||
"name": I18n.tr("hide-modes.hidden")
|
||||
},
|
||||
{
|
||||
"key": "transparent",
|
||||
"name": I18n.tr("hide-modes.transparent")
|
||||
},
|
||||
{
|
||||
"key": "idle",
|
||||
"name": I18n.tr("hide-modes.idle")
|
||||
}
|
||||
]
|
||||
currentKey: root.valueHideMode
|
||||
onSelected: key => {
|
||||
root.valueHideMode = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.hideMode
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.media-mini.show-album-art-label")
|
||||
description: I18n.tr("bar.media-mini.show-album-art-description")
|
||||
checked: valueShowAlbumArt
|
||||
onToggled: checked => {
|
||||
valueShowAlbumArt = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showAlbumArt
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.media-mini.show-artist-first-label")
|
||||
description: I18n.tr("bar.media-mini.show-artist-first-description")
|
||||
checked: valueShowArtistFirst
|
||||
onToggled: checked => {
|
||||
valueShowArtistFirst = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showArtistFirst
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.media-mini.show-visualizer-label")
|
||||
description: I18n.tr("bar.media-mini.show-visualizer-description")
|
||||
checked: valueShowVisualizer
|
||||
onToggled: checked => {
|
||||
valueShowVisualizer = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showVisualizer
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
visible: valueShowVisualizer
|
||||
label: I18n.tr("bar.media-mini.visualizer-type-label")
|
||||
description: I18n.tr("bar.media-mini.visualizer-type-description")
|
||||
model: [
|
||||
{
|
||||
"key": "linear",
|
||||
"name": I18n.tr("options.visualizer-types.linear")
|
||||
},
|
||||
{
|
||||
"key": "mirrored",
|
||||
"name": I18n.tr("options.visualizer-types.mirrored")
|
||||
},
|
||||
{
|
||||
"key": "wave",
|
||||
"name": I18n.tr("options.visualizer-types.wave")
|
||||
}
|
||||
]
|
||||
currentKey: valueVisualizerType
|
||||
onSelected: key => {
|
||||
valueVisualizerType = key;
|
||||
saveSettings();
|
||||
}
|
||||
minimumWidth: 200
|
||||
defaultValue: widgetMetadata.visualizerType
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
id: widthInput
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.taskbar.max-width-label")
|
||||
description: I18n.tr("bar.media-mini.max-width-description")
|
||||
placeholderText: widgetMetadata.maxWidth
|
||||
text: valueMaxWidth
|
||||
onTextChanged: saveSettings()
|
||||
defaultValue: String(widgetMetadata.maxWidth)
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.media-mini.use-fixed-width-label")
|
||||
description: I18n.tr("bar.media-mini.use-fixed-width-description")
|
||||
checked: valueUseFixedWidth
|
||||
onToggled: checked => {
|
||||
valueUseFixedWidth = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.useFixedWidth
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.media-mini.show-progress-ring-label")
|
||||
description: I18n.tr("bar.media-mini.show-progress-ring-description")
|
||||
checked: valueShowProgressRing
|
||||
onToggled: checked => {
|
||||
valueShowProgressRing = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showProgressRing
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
currentKey: valueTextColor
|
||||
onSelected: key => {
|
||||
valueTextColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.textColor
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("bar.media-mini.scrolling-mode-label")
|
||||
description: I18n.tr("bar.media-mini.scrolling-mode-description")
|
||||
model: [
|
||||
{
|
||||
"key": "always",
|
||||
"name": I18n.tr("options.scrolling-modes.always")
|
||||
},
|
||||
{
|
||||
"key": "hover",
|
||||
"name": I18n.tr("options.scrolling-modes.hover")
|
||||
},
|
||||
{
|
||||
"key": "never",
|
||||
"name": I18n.tr("options.scrolling-modes.never")
|
||||
}
|
||||
]
|
||||
currentKey: valueScrollingMode
|
||||
onSelected: key => {
|
||||
valueScrollingMode = key;
|
||||
saveSettings();
|
||||
}
|
||||
minimumWidth: 200
|
||||
defaultValue: widgetMetadata.scrollingMode
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginS
|
||||
}
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("bar.media-mini.panel-section-label")
|
||||
description: I18n.tr("bar.media-mini.panel-section-description")
|
||||
labelColor: Color.mPrimary
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.media-mini.show-album-art-label")
|
||||
description: I18n.tr("bar.media-mini.show-album-art-description")
|
||||
checked: valuePanelShowAlbumArt
|
||||
onToggled: checked => {
|
||||
valuePanelShowAlbumArt = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.panelShowAlbumArt
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.media-mini.compact-mode-label")
|
||||
description: I18n.tr("bar.media-mini.compact-mode-description")
|
||||
checked: valueCompactMode
|
||||
onToggled: checked => {
|
||||
valueCompactMode = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.compactMode
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var screen: null
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
// Local state
|
||||
property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode
|
||||
property string valueMiddleClickCommand: widgetData.middleClickCommand !== undefined ? widgetData.middleClickCommand : widgetMetadata.middleClickCommand
|
||||
property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor
|
||||
property string valueTextColor: widgetData.textColor !== undefined ? widgetData.textColor : widgetMetadata.textColor
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.displayMode = valueDisplayMode;
|
||||
settings.middleClickCommand = valueMiddleClickCommand;
|
||||
settings.iconColor = valueIconColor;
|
||||
settings.textColor = valueTextColor;
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("common.display-mode")
|
||||
description: I18n.tr("bar.volume.display-mode-description")
|
||||
minimumWidth: 200
|
||||
model: [
|
||||
{
|
||||
"key": "onhover",
|
||||
"name": I18n.tr("display-modes.on-hover")
|
||||
},
|
||||
{
|
||||
"key": "alwaysShow",
|
||||
"name": I18n.tr("display-modes.always-show")
|
||||
},
|
||||
{
|
||||
"key": "alwaysHide",
|
||||
"name": I18n.tr("display-modes.always-hide")
|
||||
}
|
||||
]
|
||||
currentKey: valueDisplayMode
|
||||
onSelected: key => {
|
||||
valueDisplayMode = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.displayMode
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
label: I18n.tr("common.select-icon-color")
|
||||
currentKey: valueIconColor
|
||||
onSelected: key => {
|
||||
valueIconColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.iconColor
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
currentKey: valueTextColor
|
||||
onSelected: key => {
|
||||
valueTextColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.textColor
|
||||
}
|
||||
|
||||
// Middle click command
|
||||
NTextInput {
|
||||
label: I18n.tr("bar.custom-button.middle-click-label")
|
||||
description: I18n.tr("panels.audio.on-middle-clicked-description")
|
||||
placeholderText: I18n.tr("panels.audio.external-mixer-placeholder")
|
||||
text: valueMiddleClickCommand
|
||||
onTextChanged: {
|
||||
valueMiddleClickCommand = text;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.middleClickCommand
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var screen: null
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
// Local state
|
||||
property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode
|
||||
property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor
|
||||
property string valueTextColor: widgetData.textColor !== undefined ? widgetData.textColor : widgetMetadata.textColor
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.displayMode = valueDisplayMode;
|
||||
settings.iconColor = valueIconColor;
|
||||
settings.textColor = valueTextColor;
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("common.display-mode")
|
||||
description: I18n.tr("bar.volume.display-mode-description")
|
||||
minimumWidth: 200
|
||||
model: [
|
||||
{
|
||||
"key": "onhover",
|
||||
"name": I18n.tr("display-modes.on-hover")
|
||||
},
|
||||
{
|
||||
"key": "alwaysShow",
|
||||
"name": I18n.tr("display-modes.always-show")
|
||||
},
|
||||
{
|
||||
"key": "alwaysHide",
|
||||
"name": I18n.tr("display-modes.always-hide")
|
||||
}
|
||||
]
|
||||
currentKey: root.valueDisplayMode
|
||||
onSelected: key => {
|
||||
root.valueDisplayMode = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.displayMode
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
label: I18n.tr("common.select-icon-color")
|
||||
currentKey: valueIconColor
|
||||
onSelected: key => {
|
||||
valueIconColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.iconColor
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
currentKey: valueTextColor
|
||||
onSelected: key => {
|
||||
valueTextColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.textColor
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var screen: null
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.iconColor = valueIconColor;
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
label: I18n.tr("common.select-icon-color")
|
||||
currentKey: valueIconColor
|
||||
onSelected: key => {
|
||||
valueIconColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.iconColor
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var screen: null
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.iconColor = valueIconColor;
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
label: I18n.tr("common.select-icon-color")
|
||||
currentKey: valueIconColor
|
||||
onSelected: key => {
|
||||
valueIconColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.iconColor
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var screen: null
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
// Local state
|
||||
property bool valueShowUnreadBadge: widgetData.showUnreadBadge !== undefined ? widgetData.showUnreadBadge : widgetMetadata.showUnreadBadge
|
||||
property bool valueHideWhenZero: widgetData.hideWhenZero !== undefined ? widgetData.hideWhenZero : widgetMetadata.hideWhenZero
|
||||
property bool valueHideWhenZeroUnread: widgetData.hideWhenZeroUnread !== undefined ? widgetData.hideWhenZeroUnread : widgetMetadata.hideWhenZeroUnread
|
||||
property string valueUnreadBadgeColor: widgetData.unreadBadgeColor !== undefined ? widgetData.unreadBadgeColor : widgetMetadata.unreadBadgeColor
|
||||
property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.showUnreadBadge = valueShowUnreadBadge;
|
||||
settings.hideWhenZero = valueHideWhenZero;
|
||||
settings.hideWhenZeroUnread = valueHideWhenZeroUnread;
|
||||
settings.unreadBadgeColor = valueUnreadBadgeColor;
|
||||
settings.iconColor = valueIconColor;
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.notification-history.show-unread-badge-label")
|
||||
description: I18n.tr("bar.notification-history.show-unread-badge-description")
|
||||
checked: valueShowUnreadBadge
|
||||
onToggled: checked => {
|
||||
valueShowUnreadBadge = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showUnreadBadge
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
label: I18n.tr("common.select-icon-color")
|
||||
currentKey: valueIconColor
|
||||
onSelected: key => {
|
||||
valueIconColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.iconColor
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
label: I18n.tr("bar.notification-history.unread-badge-color-label")
|
||||
description: I18n.tr("bar.notification-history.unread-badge-color-description")
|
||||
currentKey: valueUnreadBadgeColor
|
||||
onSelected: key => {
|
||||
valueUnreadBadgeColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
visible: valueShowUnreadBadge
|
||||
defaultValue: widgetMetadata.unreadBadgeColor
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.notification-history.hide-widget-when-zero-label")
|
||||
description: I18n.tr("bar.notification-history.hide-widget-when-zero-description")
|
||||
checked: valueHideWhenZero
|
||||
onToggled: checked => {
|
||||
valueHideWhenZero = checked;
|
||||
saveSettings();
|
||||
}
|
||||
enabled: !valueHideWhenZeroUnread
|
||||
defaultValue: widgetMetadata.hideWhenZero
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.notification-history.hide-widget-when-zero-unread-label")
|
||||
description: I18n.tr("bar.notification-history.hide-widget-when-zero-unread-description")
|
||||
checked: valueHideWhenZeroUnread
|
||||
onToggled: checked => {
|
||||
valueHideWhenZeroUnread = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.hideWhenZeroUnread
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var screen: null
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.iconColor = valueIconColor;
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
label: I18n.tr("common.select-icon-color")
|
||||
currentKey: valueIconColor
|
||||
onSelected: key => {
|
||||
valueIconColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.iconColor
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.System
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
width: 700
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var screen: null
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
// Local state
|
||||
property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.iconColor = valueIconColor;
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
label: I18n.tr("common.select-icon-color")
|
||||
currentKey: root.valueIconColor
|
||||
onSelected: key => {
|
||||
root.valueIconColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.iconColor
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var screen: null
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
// Local state
|
||||
property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.iconColor = valueIconColor;
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
label: I18n.tr("common.select-icon-color")
|
||||
currentKey: valueIconColor
|
||||
onSelected: key => {
|
||||
valueIconColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
valueDefault: widgetMetadata.iconColor
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var screen: null
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.width = parseInt(widthInput.text) || widgetMetadata.width;
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
id: widthInput
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("common.width")
|
||||
description: I18n.tr("bar.spacer.width-description")
|
||||
text: widgetData.width || widgetMetadata.width
|
||||
placeholderText: I18n.tr("placeholders.enter-width-pixels")
|
||||
onTextChanged: saveSettings()
|
||||
defaultValue: String(widgetMetadata.width)
|
||||
}
|
||||
}
|
||||
+325
@@ -0,0 +1,325 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.System
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var screen: null
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name)
|
||||
readonly property bool isVerticalBar: barPosition === "left" || barPosition === "right"
|
||||
|
||||
// Local, editable state for checkboxes
|
||||
property bool valueCompactMode: widgetData.compactMode !== undefined ? widgetData.compactMode : widgetMetadata.compactMode
|
||||
property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor
|
||||
property string valueTextColor: widgetData.textColor !== undefined ? widgetData.textColor : widgetMetadata.textColor
|
||||
property bool valueUseMonospaceFont: widgetData.useMonospaceFont !== undefined ? widgetData.useMonospaceFont : widgetMetadata.useMonospaceFont
|
||||
property bool valueUsePadding: widgetData.usePadding !== undefined ? widgetData.usePadding : widgetMetadata.usePadding
|
||||
property bool valueShowCpuUsage: widgetData.showCpuUsage !== undefined ? widgetData.showCpuUsage : widgetMetadata.showCpuUsage
|
||||
property bool valueShowCpuCores: widgetData.showCpuCores !== undefined ? widgetData.showCpuCores : widgetMetadata.showCpuCores
|
||||
property bool valueShowCpuFreq: widgetData.showCpuFreq !== undefined ? widgetData.showCpuFreq : widgetMetadata.showCpuFreq
|
||||
property bool valueShowCpuTemp: widgetData.showCpuTemp !== undefined ? widgetData.showCpuTemp : widgetMetadata.showCpuTemp
|
||||
property bool valueShowGpuTemp: widgetData.showGpuTemp !== undefined ? widgetData.showGpuTemp : widgetMetadata.showGpuTemp
|
||||
property bool valueShowLoadAverage: widgetData.showLoadAverage !== undefined ? widgetData.showLoadAverage : widgetMetadata.showLoadAverage
|
||||
property bool valueShowMemoryUsage: widgetData.showMemoryUsage !== undefined ? widgetData.showMemoryUsage : widgetMetadata.showMemoryUsage
|
||||
property bool valueShowMemoryAsPercent: widgetData.showMemoryAsPercent !== undefined ? widgetData.showMemoryAsPercent : widgetMetadata.showMemoryAsPercent
|
||||
property bool valueShowSwapUsage: widgetData.showSwapUsage !== undefined ? widgetData.showSwapUsage : widgetMetadata.showSwapUsage
|
||||
property bool valueShowNetworkStats: widgetData.showNetworkStats !== undefined ? widgetData.showNetworkStats : widgetMetadata.showNetworkStats
|
||||
property bool valueShowDiskUsage: widgetData.showDiskUsage !== undefined ? widgetData.showDiskUsage : widgetMetadata.showDiskUsage
|
||||
property bool valueShowDiskUsageAsPercent: widgetData.showDiskUsageAsPercent !== undefined ? widgetData.showDiskUsageAsPercent : widgetMetadata.showDiskUsageAsPercent
|
||||
property bool valueShowDiskAvailable: widgetData.showDiskAvailable !== undefined ? widgetData.showDiskAvailable : widgetMetadata.showDiskAvailable
|
||||
property string valueDiskPath: widgetData.diskPath !== undefined ? widgetData.diskPath : widgetMetadata.diskPath
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.compactMode = valueCompactMode;
|
||||
settings.iconColor = valueIconColor;
|
||||
settings.textColor = valueTextColor;
|
||||
settings.useMonospaceFont = valueUseMonospaceFont;
|
||||
settings.usePadding = valueUsePadding;
|
||||
settings.showCpuUsage = valueShowCpuUsage;
|
||||
settings.showCpuCores = valueShowCpuCores;
|
||||
settings.showCpuFreq = valueShowCpuFreq;
|
||||
settings.showCpuTemp = valueShowCpuTemp;
|
||||
settings.showGpuTemp = valueShowGpuTemp;
|
||||
settings.showLoadAverage = valueShowLoadAverage;
|
||||
settings.showMemoryUsage = valueShowMemoryUsage;
|
||||
settings.showMemoryAsPercent = valueShowMemoryAsPercent;
|
||||
settings.showSwapUsage = valueShowSwapUsage;
|
||||
settings.showNetworkStats = valueShowNetworkStats;
|
||||
settings.showDiskUsage = valueShowDiskUsage;
|
||||
settings.showDiskUsageAsPercent = valueShowDiskUsageAsPercent;
|
||||
settings.showDiskAvailable = valueShowDiskAvailable;
|
||||
settings.diskPath = valueDiskPath;
|
||||
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.system-monitor.compact-mode-label")
|
||||
description: I18n.tr("bar.system-monitor.compact-mode-description")
|
||||
checked: valueCompactMode
|
||||
onToggled: checked => {
|
||||
valueCompactMode = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.compactMode
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
label: I18n.tr("common.select-icon-color")
|
||||
currentKey: valueIconColor
|
||||
onSelected: key => {
|
||||
valueIconColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.iconColor
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
currentKey: valueTextColor
|
||||
onSelected: key => {
|
||||
valueTextColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
visible: !valueCompactMode
|
||||
defaultValue: widgetMetadata.textColor
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.system-monitor.use-monospace-font-label")
|
||||
description: I18n.tr("bar.system-monitor.use-monospace-font-description")
|
||||
checked: valueUseMonospaceFont
|
||||
onToggled: checked => {
|
||||
valueUseMonospaceFont = checked;
|
||||
saveSettings();
|
||||
}
|
||||
visible: !valueCompactMode
|
||||
defaultValue: widgetMetadata.useMonospaceFont
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.system-monitor.use-padding-label")
|
||||
description: isVerticalBar ? I18n.tr("bar.system-monitor.use-padding-description-disabled-vertical") : !valueUseMonospaceFont ? I18n.tr("bar.system-monitor.use-padding-description-disabled-monospace-font") : I18n.tr("bar.system-monitor.use-padding-description")
|
||||
checked: valueUsePadding && !isVerticalBar && valueUseMonospaceFont
|
||||
onToggled: checked => {
|
||||
valueUsePadding = checked;
|
||||
saveSettings();
|
||||
}
|
||||
visible: !valueCompactMode
|
||||
enabled: !isVerticalBar && valueUseMonospaceFont
|
||||
defaultValue: widgetMetadata.usePadding
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: showCpuUsage
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.system-monitor.cpu-usage-label")
|
||||
description: I18n.tr("bar.system-monitor.cpu-usage-description")
|
||||
checked: valueShowCpuUsage
|
||||
onToggled: checked => {
|
||||
valueShowCpuUsage = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showCpuUsage
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: showCpuCores
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.system-monitor.cpu-cores-label")
|
||||
description: I18n.tr("bar.system-monitor.cpu-cores-description")
|
||||
checked: valueShowCpuCores
|
||||
onToggled: checked => {
|
||||
valueShowCpuCores = checked;
|
||||
saveSettings();
|
||||
}
|
||||
visible: valueCompactMode
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: showCpuFreq
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.system-monitor.cpu-frequency-label")
|
||||
description: I18n.tr("bar.system-monitor.cpu-frequency-description")
|
||||
checked: valueShowCpuFreq
|
||||
onToggled: checked => {
|
||||
valueShowCpuFreq = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showCpuFreq
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: showCpuTemp
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.system-monitor.cpu-temperature-label")
|
||||
description: I18n.tr("bar.system-monitor.cpu-temperature-description")
|
||||
checked: valueShowCpuTemp
|
||||
onToggled: checked => {
|
||||
valueShowCpuTemp = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showCpuTemp
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: showLoadAverage
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.system-monitor.load-average-label")
|
||||
description: I18n.tr("bar.system-monitor.load-average-description")
|
||||
checked: valueShowLoadAverage
|
||||
onToggled: checked => {
|
||||
valueShowLoadAverage = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showLoadAverage
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: showGpuTemp
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.system-monitor.gpu-section-label")
|
||||
description: I18n.tr("bar.system-monitor.gpu-temperature-description")
|
||||
checked: valueShowGpuTemp
|
||||
onToggled: checked => {
|
||||
valueShowGpuTemp = checked;
|
||||
saveSettings();
|
||||
}
|
||||
visible: SystemStatService.gpuAvailable
|
||||
defaultValue: widgetMetadata.showGpuTemp
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: showMemoryUsage
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.system-monitor.memory-usage-label")
|
||||
description: I18n.tr("bar.system-monitor.memory-usage-description")
|
||||
checked: valueShowMemoryUsage
|
||||
onToggled: checked => {
|
||||
valueShowMemoryUsage = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showMemoryUsage
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: showMemoryAsPercent
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.system-monitor.memory-percentage-label")
|
||||
description: I18n.tr("bar.system-monitor.memory-percentage-description")
|
||||
checked: valueShowMemoryAsPercent
|
||||
onToggled: checked => {
|
||||
valueShowMemoryAsPercent = checked;
|
||||
saveSettings();
|
||||
}
|
||||
visible: valueShowMemoryUsage
|
||||
defaultValue: widgetMetadata.showMemoryAsPercent
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: showSwapUsage
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.system-monitor.swap-usage-label")
|
||||
description: I18n.tr("bar.system-monitor.swap-usage-description")
|
||||
checked: valueShowSwapUsage
|
||||
onToggled: checked => {
|
||||
valueShowSwapUsage = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showSwapUsage
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: showNetworkStats
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.system-monitor.network-traffic-label")
|
||||
description: I18n.tr("bar.system-monitor.network-traffic-description")
|
||||
checked: valueShowNetworkStats
|
||||
onToggled: checked => {
|
||||
valueShowNetworkStats = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showNetworkStats
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: showDiskUsage
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.system-monitor.storage-usage-label")
|
||||
description: I18n.tr("bar.system-monitor.storage-usage-description")
|
||||
checked: valueShowDiskUsage
|
||||
onToggled: checked => {
|
||||
valueShowDiskUsage = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showDiskUsage
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: showDiskUsageAsPercent
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.system-monitor.storage-as-percentage-label")
|
||||
description: I18n.tr("bar.system-monitor.storage-as-percentage-description")
|
||||
checked: valueShowDiskUsageAsPercent
|
||||
onToggled: checked => {
|
||||
valueShowDiskUsageAsPercent = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showDiskUsageAsPercent
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: showDiskAvailable
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.system-monitor.storage-available-label")
|
||||
description: I18n.tr("bar.system-monitor.storage-available-description")
|
||||
checked: valueShowDiskAvailable
|
||||
onToggled: checked => {
|
||||
valueShowDiskAvailable = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showDiskAvailable
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
id: diskPathComboBox
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.system-monitor.disk-path-label")
|
||||
description: I18n.tr("bar.system-monitor.disk-path-description")
|
||||
model: {
|
||||
const paths = Object.keys(SystemStatService.diskPercents).sort();
|
||||
return paths.map(path => ({
|
||||
key: path,
|
||||
name: path
|
||||
}));
|
||||
}
|
||||
currentKey: valueDiskPath
|
||||
onSelected: key => {
|
||||
valueDiskPath = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.diskPath
|
||||
}
|
||||
}
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var screen: null
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
readonly property bool isVerticalBar: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
||||
|
||||
// Local state
|
||||
property string valueHideMode: widgetData.hideMode !== undefined ? widgetData.hideMode : widgetMetadata.hideMode
|
||||
property bool valueOnlyActiveWorkspaces: widgetData.onlyActiveWorkspaces !== undefined ? widgetData.onlyActiveWorkspaces : widgetMetadata.onlyActiveWorkspaces
|
||||
property bool valueOnlySameOutput: widgetData.onlySameOutput !== undefined ? widgetData.onlySameOutput : widgetMetadata.onlySameOutput
|
||||
property bool valueColorizeIcons: widgetData.colorizeIcons !== undefined ? widgetData.colorizeIcons : widgetMetadata.colorizeIcons
|
||||
property bool valueShowTitle: isVerticalBar ? false : widgetData.showTitle !== undefined ? widgetData.showTitle : widgetMetadata.showTitle
|
||||
property bool valueSmartWidth: widgetData.smartWidth !== undefined ? widgetData.smartWidth : widgetMetadata.smartWidth
|
||||
property int valueMaxTaskbarWidth: widgetData.maxTaskbarWidth !== undefined ? widgetData.maxTaskbarWidth : widgetMetadata.maxTaskbarWidth
|
||||
property int valueTitleWidth: widgetData.titleWidth !== undefined ? widgetData.titleWidth : widgetMetadata.titleWidth
|
||||
property bool valueShowPinnedApps: widgetData.showPinnedApps !== undefined ? widgetData.showPinnedApps : widgetMetadata.showPinnedApps
|
||||
property real valueIconScale: widgetData.iconScale !== undefined ? widgetData.iconScale : widgetMetadata.iconScale
|
||||
|
||||
Component.onCompleted: {
|
||||
if (widgetData && widgetData.hideMode !== undefined) {
|
||||
valueHideMode = widgetData.hideMode;
|
||||
} else if (widgetMetadata && widgetMetadata.hideMode !== undefined) {
|
||||
valueHideMode = widgetMetadata.hideMode;
|
||||
}
|
||||
}
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.hideMode = valueHideMode;
|
||||
settings.onlySameOutput = valueOnlySameOutput;
|
||||
settings.onlyActiveWorkspaces = valueOnlyActiveWorkspaces;
|
||||
settings.colorizeIcons = valueColorizeIcons;
|
||||
settings.showTitle = valueShowTitle;
|
||||
settings.smartWidth = valueSmartWidth;
|
||||
settings.maxTaskbarWidth = valueMaxTaskbarWidth;
|
||||
settings.titleWidth = parseInt(titleWidthInput.text) || widgetMetadata.titleWidth;
|
||||
settings.showPinnedApps = valueShowPinnedApps;
|
||||
settings.iconScale = valueIconScale;
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.taskbar.hide-mode-label")
|
||||
description: I18n.tr("bar.taskbar.hide-mode-description")
|
||||
model: [
|
||||
{
|
||||
"key": "visible",
|
||||
"name": I18n.tr("hide-modes.visible")
|
||||
},
|
||||
{
|
||||
"key": "hidden",
|
||||
"name": I18n.tr("hide-modes.hidden")
|
||||
},
|
||||
{
|
||||
"key": "transparent",
|
||||
"name": I18n.tr("hide-modes.transparent")
|
||||
}
|
||||
]
|
||||
currentKey: root.valueHideMode
|
||||
onSelected: key => {
|
||||
root.valueHideMode = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.hideMode
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.taskbar.only-same-monitor-label")
|
||||
description: I18n.tr("bar.taskbar.only-same-monitor-description")
|
||||
checked: root.valueOnlySameOutput
|
||||
onToggled: checked => {
|
||||
root.valueOnlySameOutput = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.onlySameOutput
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.taskbar.only-active-workspaces-label")
|
||||
description: I18n.tr("bar.taskbar.only-active-workspaces-description")
|
||||
checked: root.valueOnlyActiveWorkspaces
|
||||
onToggled: checked => {
|
||||
root.valueOnlyActiveWorkspaces = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.onlyActiveWorkspaces
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.tray.colorize-icons-label")
|
||||
description: I18n.tr("bar.taskbar.colorize-icons-description")
|
||||
checked: root.valueColorizeIcons
|
||||
onToggled: checked => {
|
||||
root.valueColorizeIcons = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.colorizeIcons
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.taskbar.show-pinned-apps-label")
|
||||
description: I18n.tr("bar.taskbar.show-pinned-apps-description")
|
||||
checked: root.valueShowPinnedApps
|
||||
onToggled: checked => {
|
||||
root.valueShowPinnedApps = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showPinnedApps
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.taskbar.icon-scale-label")
|
||||
description: I18n.tr("bar.taskbar.icon-scale-description")
|
||||
from: 0.5
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
showReset: true
|
||||
value: root.valueIconScale
|
||||
defaultValue: widgetMetadata.iconScale
|
||||
onMoved: value => {
|
||||
root.valueIconScale = value;
|
||||
saveSettings();
|
||||
}
|
||||
text: Math.round(root.valueIconScale * 100) + "%"
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.taskbar.show-title-label")
|
||||
description: isVerticalBar ? I18n.tr("bar.taskbar.show-title-description-disabled") : I18n.tr("bar.taskbar.show-title-description")
|
||||
checked: root.valueShowTitle
|
||||
onToggled: checked => {
|
||||
root.valueShowTitle = checked;
|
||||
saveSettings();
|
||||
}
|
||||
enabled: !isVerticalBar
|
||||
defaultValue: widgetMetadata.showTitle
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
id: titleWidthInput
|
||||
visible: root.valueShowTitle && !isVerticalBar
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.taskbar.title-width-label")
|
||||
description: I18n.tr("bar.taskbar.title-width-description")
|
||||
text: widgetData.titleWidth || widgetMetadata.titleWidth
|
||||
placeholderText: I18n.tr("placeholders.enter-width-pixels")
|
||||
onTextChanged: saveSettings()
|
||||
defaultValue: String(widgetMetadata.titleWidth)
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
visible: !isVerticalBar && root.valueShowTitle
|
||||
label: I18n.tr("bar.taskbar.smart-width-label")
|
||||
description: I18n.tr("bar.taskbar.smart-width-description")
|
||||
checked: root.valueSmartWidth
|
||||
onToggled: checked => {
|
||||
root.valueSmartWidth = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.smartWidth
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
visible: root.valueSmartWidth && !isVerticalBar
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.taskbar.max-width-label")
|
||||
description: I18n.tr("bar.taskbar.max-width-description")
|
||||
from: 10
|
||||
to: 100
|
||||
stepSize: 5
|
||||
showReset: true
|
||||
value: root.valueMaxTaskbarWidth
|
||||
defaultValue: widgetMetadata.maxTaskbarWidth
|
||||
onMoved: value => {
|
||||
root.valueMaxTaskbarWidth = Math.round(value);
|
||||
saveSettings();
|
||||
}
|
||||
text: Math.round(root.valueMaxTaskbarWidth) + "%"
|
||||
}
|
||||
}
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var screen: null
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
// Local state
|
||||
property var localBlacklist: widgetData.blacklist || []
|
||||
property bool valueColorizeIcons: widgetData.colorizeIcons !== undefined ? widgetData.colorizeIcons : widgetMetadata.colorizeIcons
|
||||
property string valueChevronColor: widgetData.chevronColor !== undefined ? widgetData.chevronColor : widgetMetadata.chevronColor
|
||||
property bool valueDrawerEnabled: widgetData.drawerEnabled !== undefined ? widgetData.drawerEnabled : widgetMetadata.drawerEnabled
|
||||
property bool valueHidePassive: widgetData.hidePassive !== undefined ? widgetData.hidePassive : widgetMetadata.hidePassive
|
||||
|
||||
ListModel {
|
||||
id: blacklistModel
|
||||
}
|
||||
|
||||
function populateBlacklist() {
|
||||
for (var i = 0; i < localBlacklist.length; i++) {
|
||||
blacklistModel.append({
|
||||
"rule": localBlacklist[i]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
Qt.callLater(populateBlacklist);
|
||||
}
|
||||
|
||||
spacing: Style.marginM
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.tray.drawer-enabled-label")
|
||||
description: I18n.tr("bar.tray.drawer-enabled-description")
|
||||
checked: root.valueDrawerEnabled
|
||||
onToggled: checked => {
|
||||
root.valueDrawerEnabled = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.drawerEnabled
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
label: I18n.tr("bar.tray.chevron-color-label")
|
||||
description: I18n.tr("bar.tray.chevron-color-description")
|
||||
currentKey: root.valueChevronColor
|
||||
onSelected: key => {
|
||||
root.valueChevronColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
visible: root.valueDrawerEnabled
|
||||
defaultValue: widgetMetadata.chevronColor
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.tray.colorize-icons-label")
|
||||
description: I18n.tr("bar.tray.colorize-icons-description")
|
||||
checked: root.valueColorizeIcons
|
||||
onToggled: checked => {
|
||||
root.valueColorizeIcons = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.colorizeIcons
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.tray.hide-passive-label")
|
||||
description: I18n.tr("bar.tray.hide-passive-description")
|
||||
checked: root.valueHidePassive
|
||||
onToggled: checked => {
|
||||
root.valueHidePassive = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.hidePassive
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("panels.bar.tray-blacklist-label")
|
||||
description: I18n.tr("panels.bar.tray-blacklist-description")
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
|
||||
NTextInputButton {
|
||||
id: newRuleInput
|
||||
Layout.fillWidth: true
|
||||
placeholderText: I18n.tr("panels.bar.tray-blacklist-placeholder")
|
||||
buttonIcon: "add"
|
||||
onButtonClicked: {
|
||||
if (newRuleInput.text.length > 0) {
|
||||
var newRule = newRuleInput.text.trim();
|
||||
var exists = false;
|
||||
for (var i = 0; i < blacklistModel.count; i++) {
|
||||
if (blacklistModel.get(i).rule === newRule) {
|
||||
exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!exists) {
|
||||
blacklistModel.append({
|
||||
"rule": newRule
|
||||
});
|
||||
newRuleInput.text = "";
|
||||
saveSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// List of current blacklist items
|
||||
NListView {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 150
|
||||
Layout.topMargin: Style.marginL // Increased top margin
|
||||
gradientColor: Color.mSurface
|
||||
|
||||
model: blacklistModel
|
||||
delegate: Item {
|
||||
width: ListView.width
|
||||
height: 40
|
||||
|
||||
Rectangle {
|
||||
id: itemBackground
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginXS
|
||||
color: "transparent" // Make background transparent
|
||||
border.color: Color.mOutline
|
||||
border.width: Style.borderS
|
||||
radius: Style.radiusS
|
||||
visible: model.rule !== undefined && model.rule !== "" // Only visible if rule exists
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Style.marginS
|
||||
anchors.rightMargin: Style.marginS
|
||||
spacing: Style.marginS
|
||||
|
||||
NText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: model.rule
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
icon: "close"
|
||||
baseSize: 12 * Style.uiScaleRatio
|
||||
colorBg: Color.mSurfaceVariant
|
||||
colorFg: Color.mOnSurfaceVariant
|
||||
colorBgHover: Color.mError
|
||||
colorFgHover: Color.mOnError
|
||||
onClicked: {
|
||||
blacklistModel.remove(index);
|
||||
saveSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This function will be called by the dialog to get the new settings
|
||||
function saveSettings() {
|
||||
var newBlacklist = [];
|
||||
for (var i = 0; i < blacklistModel.count; i++) {
|
||||
newBlacklist.push(blacklistModel.get(i).rule);
|
||||
}
|
||||
|
||||
// Return the updated settings for this widget instance
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.blacklist = newBlacklist;
|
||||
settings.colorizeIcons = root.valueColorizeIcons;
|
||||
settings.chevronColor = root.valueChevronColor;
|
||||
settings.drawerEnabled = root.valueDrawerEnabled;
|
||||
settings.hidePassive = root.valueHidePassive;
|
||||
settingsChanged(settings);
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var screen: null
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode
|
||||
property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor
|
||||
property string valueTextColor: widgetData.textColor !== undefined ? widgetData.textColor : widgetMetadata.textColor
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.displayMode = valueDisplayMode;
|
||||
settings.iconColor = valueIconColor;
|
||||
settings.textColor = valueTextColor;
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("common.display-mode")
|
||||
description: I18n.tr("bar.volume.display-mode-description")
|
||||
minimumWidth: 200
|
||||
model: [
|
||||
{
|
||||
"key": "onhover",
|
||||
"name": I18n.tr("display-modes.on-hover")
|
||||
},
|
||||
{
|
||||
"key": "alwaysShow",
|
||||
"name": I18n.tr("display-modes.always-show")
|
||||
},
|
||||
{
|
||||
"key": "alwaysHide",
|
||||
"name": I18n.tr("display-modes.always-hide")
|
||||
}
|
||||
]
|
||||
currentKey: root.valueDisplayMode
|
||||
onSelected: key => {
|
||||
root.valueDisplayMode = key;
|
||||
saveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
label: I18n.tr("common.select-icon-color")
|
||||
currentKey: valueIconColor
|
||||
onSelected: key => {
|
||||
valueIconColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
currentKey: valueTextColor
|
||||
onSelected: key => {
|
||||
valueTextColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var screen: null
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
// Local state
|
||||
property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode
|
||||
property string valueMiddleClickCommand: widgetData.middleClickCommand !== undefined ? widgetData.middleClickCommand : widgetMetadata.middleClickCommand
|
||||
property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor
|
||||
property string valueTextColor: widgetData.textColor !== undefined ? widgetData.textColor : widgetMetadata.textColor
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.displayMode = valueDisplayMode;
|
||||
settings.middleClickCommand = valueMiddleClickCommand;
|
||||
settings.iconColor = valueIconColor;
|
||||
settings.textColor = valueTextColor;
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("common.display-mode")
|
||||
description: I18n.tr("bar.volume.display-mode-description")
|
||||
minimumWidth: 200
|
||||
model: [
|
||||
{
|
||||
"key": "onhover",
|
||||
"name": I18n.tr("display-modes.on-hover")
|
||||
},
|
||||
{
|
||||
"key": "alwaysShow",
|
||||
"name": I18n.tr("display-modes.always-show")
|
||||
},
|
||||
{
|
||||
"key": "alwaysHide",
|
||||
"name": I18n.tr("display-modes.always-hide")
|
||||
}
|
||||
]
|
||||
currentKey: valueDisplayMode
|
||||
onSelected: key => {
|
||||
valueDisplayMode = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.displayMode
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
label: I18n.tr("common.select-icon-color")
|
||||
currentKey: valueIconColor
|
||||
onSelected: key => {
|
||||
valueIconColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.iconColor
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
currentKey: valueTextColor
|
||||
onSelected: key => {
|
||||
valueTextColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.textColor
|
||||
}
|
||||
|
||||
// Middle click command
|
||||
NTextInput {
|
||||
label: I18n.tr("bar.custom-button.middle-click-label")
|
||||
description: I18n.tr("panels.audio.on-middle-clicked-description")
|
||||
placeholderText: I18n.tr("panels.audio.external-mixer-placeholder")
|
||||
text: valueMiddleClickCommand
|
||||
onTextChanged: {
|
||||
valueMiddleClickCommand = text;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.middleClickCommand
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var screen: null
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
property string valueIconColor: widgetData.iconColor !== undefined ? widgetData.iconColor : widgetMetadata.iconColor
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.iconColor = valueIconColor;
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
label: I18n.tr("common.select-icon-color")
|
||||
currentKey: valueIconColor
|
||||
onSelected: key => {
|
||||
valueIconColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
+323
@@ -0,0 +1,323 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var screen: null
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
property string valueLabelMode: widgetData.labelMode !== undefined ? widgetData.labelMode : widgetMetadata.labelMode
|
||||
property bool valueHideUnoccupied: widgetData.hideUnoccupied !== undefined ? widgetData.hideUnoccupied : widgetMetadata.hideUnoccupied
|
||||
property bool valueFollowFocusedScreen: widgetData.followFocusedScreen !== undefined ? widgetData.followFocusedScreen : widgetMetadata.followFocusedScreen
|
||||
property int valueCharacterCount: widgetData.characterCount !== undefined ? widgetData.characterCount : widgetMetadata.characterCount
|
||||
|
||||
// Grouped mode settings
|
||||
property bool valueShowApplications: widgetData.showApplications !== undefined ? widgetData.showApplications : widgetMetadata.showApplications
|
||||
property bool valueShowApplicationsHover: widgetData.showApplicationsHover !== undefined ? widgetData.showApplicationsHover : widgetMetadata.showApplicationsHover
|
||||
property bool valueShowLabelsOnlyWhenOccupied: widgetData.showLabelsOnlyWhenOccupied !== undefined ? widgetData.showLabelsOnlyWhenOccupied : widgetMetadata.showLabelsOnlyWhenOccupied
|
||||
property bool valueColorizeIcons: widgetData.colorizeIcons !== undefined ? widgetData.colorizeIcons : widgetMetadata.colorizeIcons
|
||||
property real valueUnfocusedIconsOpacity: widgetData.unfocusedIconsOpacity !== undefined ? widgetData.unfocusedIconsOpacity : widgetMetadata.unfocusedIconsOpacity
|
||||
property real valueGroupedBorderOpacity: widgetData.groupedBorderOpacity !== undefined ? widgetData.groupedBorderOpacity : widgetMetadata.groupedBorderOpacity
|
||||
property bool valueEnableScrollWheel: widgetData.enableScrollWheel !== undefined ? widgetData.enableScrollWheel : widgetMetadata.enableScrollWheel
|
||||
property real valueIconScale: widgetData.iconScale !== undefined ? widgetData.iconScale : widgetMetadata.iconScale
|
||||
property string valueFocusedColor: widgetData.focusedColor !== undefined ? widgetData.focusedColor : widgetMetadata.focusedColor
|
||||
property string valueOccupiedColor: widgetData.occupiedColor !== undefined ? widgetData.occupiedColor : widgetMetadata.occupiedColor
|
||||
property string valueEmptyColor: widgetData.emptyColor !== undefined ? widgetData.emptyColor : widgetMetadata.emptyColor
|
||||
property bool valueShowBadge: widgetData.showBadge !== undefined ? widgetData.showBadge : widgetMetadata.showBadge
|
||||
property real valuePillSize: widgetData.pillSize !== undefined ? widgetData.pillSize : widgetMetadata.pillSize
|
||||
property string valueFontWeight: widgetData.fontWeight !== undefined ? widgetData.fontWeight : widgetMetadata.fontWeight
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.labelMode = valueLabelMode;
|
||||
settings.hideUnoccupied = valueHideUnoccupied;
|
||||
settings.characterCount = valueCharacterCount;
|
||||
settings.followFocusedScreen = valueFollowFocusedScreen;
|
||||
settings.showApplications = valueShowApplications;
|
||||
settings.showApplicationsHover = valueShowApplicationsHover;
|
||||
settings.showLabelsOnlyWhenOccupied = valueShowLabelsOnlyWhenOccupied;
|
||||
settings.colorizeIcons = valueColorizeIcons;
|
||||
settings.unfocusedIconsOpacity = valueUnfocusedIconsOpacity;
|
||||
settings.groupedBorderOpacity = valueGroupedBorderOpacity;
|
||||
settings.enableScrollWheel = valueEnableScrollWheel;
|
||||
settings.iconScale = valueIconScale;
|
||||
settings.focusedColor = valueFocusedColor;
|
||||
settings.occupiedColor = valueOccupiedColor;
|
||||
settings.emptyColor = valueEmptyColor;
|
||||
settings.showBadge = valueShowBadge;
|
||||
settings.pillSize = valuePillSize;
|
||||
settings.fontWeight = valueFontWeight;
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
id: labelModeCombo
|
||||
label: I18n.tr("bar.workspace.label-mode-label")
|
||||
description: I18n.tr("bar.workspace.label-mode-description")
|
||||
model: [
|
||||
{
|
||||
"key": "none",
|
||||
"name": I18n.tr("common.none")
|
||||
},
|
||||
{
|
||||
"key": "index",
|
||||
"name": I18n.tr("options.workspace-labels.index")
|
||||
},
|
||||
{
|
||||
"key": "name",
|
||||
"name": I18n.tr("options.workspace-labels.name")
|
||||
},
|
||||
{
|
||||
"key": "index+name",
|
||||
"name": I18n.tr("options.workspace-labels.index-and-name")
|
||||
}
|
||||
]
|
||||
currentKey: widgetData.labelMode || widgetMetadata.labelMode
|
||||
onSelected: key => {
|
||||
valueLabelMode = key;
|
||||
saveSettings();
|
||||
}
|
||||
minimumWidth: 200
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
label: I18n.tr("bar.workspace.character-count-label")
|
||||
description: I18n.tr("bar.workspace.character-count-description")
|
||||
from: 1
|
||||
to: 10
|
||||
value: valueCharacterCount
|
||||
onValueChanged: {
|
||||
valueCharacterCount = value;
|
||||
saveSettings();
|
||||
}
|
||||
visible: valueLabelMode === "name"
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
label: I18n.tr("bar.workspace.pill-size-label")
|
||||
description: I18n.tr("bar.workspace.pill-size-description")
|
||||
from: 0.4
|
||||
to: 1.0
|
||||
stepSize: 0.01
|
||||
value: valuePillSize
|
||||
defaultValue: widgetMetadata.pillSize
|
||||
showReset: true
|
||||
onMoved: value => {
|
||||
valuePillSize = value;
|
||||
saveSettings();
|
||||
}
|
||||
text: Math.round(valuePillSize * 100) + "%"
|
||||
visible: !valueShowApplications
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
id: fontWeightCombo
|
||||
label: I18n.tr("bar.workspace.font-weight-label")
|
||||
description: I18n.tr("bar.workspace.font-weight-description")
|
||||
model: [
|
||||
{
|
||||
"key": "regular",
|
||||
"name": I18n.tr("common.font-weight-regular")
|
||||
},
|
||||
{
|
||||
"key": "medium",
|
||||
"name": I18n.tr("common.font-weight-medium")
|
||||
},
|
||||
{
|
||||
"key": "semibold",
|
||||
"name": I18n.tr("common.font-weight-semibold")
|
||||
},
|
||||
{
|
||||
"key": "bold",
|
||||
"name": I18n.tr("common.font-weight-bold")
|
||||
},
|
||||
]
|
||||
currentKey: widgetData.fontWeight || widgetMetadata.fontWeight
|
||||
onSelected: key => {
|
||||
valueFontWeight = key;
|
||||
saveSettings();
|
||||
}
|
||||
minimumWidth: 200
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.workspace.hide-unoccupied-label")
|
||||
description: I18n.tr("bar.workspace.hide-unoccupied-description")
|
||||
checked: valueHideUnoccupied
|
||||
onToggled: checked => {
|
||||
valueHideUnoccupied = checked;
|
||||
saveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.workspace.show-labels-only-when-occupied-label")
|
||||
description: I18n.tr("bar.workspace.show-labels-only-when-occupied-description")
|
||||
checked: valueShowLabelsOnlyWhenOccupied
|
||||
onToggled: checked => {
|
||||
valueShowLabelsOnlyWhenOccupied = checked;
|
||||
saveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.workspace.follow-focused-screen-label")
|
||||
description: I18n.tr("bar.workspace.follow-focused-screen-description")
|
||||
checked: valueFollowFocusedScreen
|
||||
onToggled: checked => {
|
||||
valueFollowFocusedScreen = checked;
|
||||
saveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.workspace.enable-scrollwheel-label")
|
||||
description: I18n.tr("bar.workspace.enable-scrollwheel-description")
|
||||
checked: valueEnableScrollWheel
|
||||
onToggled: checked => {
|
||||
valueEnableScrollWheel = checked;
|
||||
saveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.workspace.show-applications-label")
|
||||
description: I18n.tr("bar.workspace.show-applications-description")
|
||||
checked: valueShowApplications
|
||||
onToggled: checked => {
|
||||
valueShowApplications = checked;
|
||||
saveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.workspace.show-applications-hover-label")
|
||||
description: I18n.tr("bar.workspace.show-applications-hover-description")
|
||||
checked: valueShowApplicationsHover
|
||||
onToggled: checked => {
|
||||
valueShowApplicationsHover = checked;
|
||||
saveSettings();
|
||||
}
|
||||
visible: valueShowApplications
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.workspace.show-badge-label")
|
||||
description: I18n.tr("bar.workspace.show-badge-description")
|
||||
checked: valueShowBadge
|
||||
onToggled: checked => {
|
||||
valueShowBadge = checked;
|
||||
saveSettings();
|
||||
}
|
||||
visible: valueShowApplications
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.tray.colorize-icons-label")
|
||||
description: I18n.tr("bar.active-window.colorize-icons-description")
|
||||
checked: valueColorizeIcons
|
||||
onToggled: checked => {
|
||||
valueColorizeIcons = checked;
|
||||
saveSettings();
|
||||
}
|
||||
visible: valueShowApplications
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
label: I18n.tr("bar.workspace.unfocused-icons-opacity-label")
|
||||
description: I18n.tr("bar.workspace.unfocused-icons-opacity-description")
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
showReset: true
|
||||
value: valueUnfocusedIconsOpacity
|
||||
defaultValue: widgetMetadata.unfocusedIconsOpacity
|
||||
onMoved: value => {
|
||||
valueUnfocusedIconsOpacity = value;
|
||||
saveSettings();
|
||||
}
|
||||
text: Math.floor(valueUnfocusedIconsOpacity * 100) + "%"
|
||||
visible: valueShowApplications
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
label: I18n.tr("bar.workspace.grouped-border-opacity-label")
|
||||
description: I18n.tr("bar.workspace.grouped-border-opacity-description")
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
showReset: true
|
||||
value: valueGroupedBorderOpacity
|
||||
defaultValue: widgetMetadata.groupedBorderOpacity
|
||||
onMoved: value => {
|
||||
valueGroupedBorderOpacity = value;
|
||||
saveSettings();
|
||||
}
|
||||
text: Math.floor(valueGroupedBorderOpacity * 100) + "%"
|
||||
visible: valueShowApplications
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
label: I18n.tr("bar.taskbar.icon-scale-label")
|
||||
description: I18n.tr("bar.taskbar.icon-scale-description")
|
||||
from: 0.5
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
showReset: true
|
||||
value: valueIconScale
|
||||
defaultValue: widgetMetadata.iconScale
|
||||
onMoved: value => {
|
||||
valueIconScale = value;
|
||||
saveSettings();
|
||||
}
|
||||
text: Math.round(valueIconScale * 100) + "%"
|
||||
visible: valueShowApplications
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
label: I18n.tr("bar.workspace.focused-color-label")
|
||||
description: I18n.tr("bar.workspace.focused-color-description")
|
||||
currentKey: valueFocusedColor
|
||||
onSelected: key => {
|
||||
valueFocusedColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
label: I18n.tr("bar.workspace.occupied-color-label")
|
||||
description: I18n.tr("bar.workspace.occupied-color-description")
|
||||
currentKey: valueOccupiedColor
|
||||
onSelected: key => {
|
||||
valueOccupiedColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
label: I18n.tr("bar.workspace.empty-color-label")
|
||||
description: I18n.tr("bar.workspace.empty-color-description")
|
||||
currentKey: valueEmptyColor
|
||||
onSelected: key => {
|
||||
valueEmptyColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
// Widget Settings Dialog Component
|
||||
Popup {
|
||||
id: root
|
||||
|
||||
property int widgetIndex: -1
|
||||
property var widgetData: null
|
||||
property string widgetId: ""
|
||||
property string sectionId: ""
|
||||
property var screen: null
|
||||
property var settingsCache: ({})
|
||||
|
||||
signal updateWidgetSettings(string section, int index, var settings)
|
||||
|
||||
readonly property real maxHeight: (screen ? screen.height : (parent ? parent.height : 800)) * 0.8
|
||||
readonly property real defaultContentWidth: Math.round(600 * Style.uiScaleRatio)
|
||||
readonly property real settingsContentWidth: {
|
||||
if (settingsLoader.item && settingsLoader.item.implicitWidth > 0) {
|
||||
return settingsLoader.item.implicitWidth;
|
||||
}
|
||||
return defaultContentWidth;
|
||||
}
|
||||
|
||||
readonly property real dialogPadding: Style.marginXL
|
||||
width: Math.max(settingsContentWidth + dialogPadding * 2, 500)
|
||||
height: Math.min(content.implicitHeight + dialogPadding * 2, maxHeight)
|
||||
padding: 0
|
||||
modal: true
|
||||
closePolicy: Popup.NoAutoClose
|
||||
anchors.centerIn: parent
|
||||
|
||||
onOpened: {
|
||||
if (widgetData && widgetId) {
|
||||
loadWidgetSettings();
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Color.mSurface
|
||||
radius: Style.radiusL
|
||||
border.color: Color.mPrimary
|
||||
border.width: Style.borderM
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
id: content
|
||||
anchors.fill: parent
|
||||
anchors.margins: dialogPadding
|
||||
spacing: Style.marginM
|
||||
|
||||
// Title
|
||||
RowLayout {
|
||||
id: titleRow
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: implicitHeight
|
||||
|
||||
NText {
|
||||
text: I18n.tr("system.widget-settings-title", {
|
||||
"widget": root.widgetId
|
||||
})
|
||||
pointSize: Style.fontSizeL
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mPrimary
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
tooltipText: I18n.tr("common.close")
|
||||
onClicked: saveAndClose()
|
||||
}
|
||||
}
|
||||
|
||||
// Separator
|
||||
Rectangle {
|
||||
id: separator
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
color: Color.mOutline
|
||||
}
|
||||
|
||||
// Scrollable settings area
|
||||
NScrollView {
|
||||
id: scrollView
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.minimumHeight: 100
|
||||
gradientColor: Color.mSurface
|
||||
reserveScrollbarSpace: false
|
||||
|
||||
ColumnLayout {
|
||||
width: scrollView.availableWidth
|
||||
spacing: Style.marginM
|
||||
|
||||
Loader {
|
||||
id: settingsLoader
|
||||
Layout.fillWidth: true
|
||||
onItemChanged: {
|
||||
if (item) {
|
||||
// Force width recalculation when content loads
|
||||
Qt.callLater(() => {
|
||||
root.width = Math.max(root.settingsContentWidth + root.dialogPadding * 2, 500);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: saveTimer
|
||||
running: false
|
||||
interval: 150
|
||||
onTriggered: {
|
||||
root.updateWidgetSettings(root.sectionId, root.widgetIndex, root.settingsCache);
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: settingsLoader.item
|
||||
ignoreUnknownSignals: true
|
||||
function onSettingsChanged(newSettings) {
|
||||
if (newSettings) {
|
||||
root.settingsCache = newSettings;
|
||||
saveTimer.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function saveAndClose() {
|
||||
if (settingsLoader.item && typeof settingsLoader.item.saveSettings === 'function') {
|
||||
var newSettings = settingsLoader.item.saveSettings();
|
||||
if (newSettings) {
|
||||
root.updateWidgetSettings(root.sectionId, root.widgetIndex, newSettings);
|
||||
}
|
||||
}
|
||||
root.close();
|
||||
}
|
||||
|
||||
function loadWidgetSettings() {
|
||||
const widgetSettingsMap = {
|
||||
"CustomButton": "WidgetSettings/CustomButtonSettings.qml"
|
||||
};
|
||||
|
||||
const source = widgetSettingsMap[widgetId];
|
||||
if (source) {
|
||||
settingsLoader.setSource(source, {
|
||||
"widgetData": widgetData,
|
||||
"widgetMetadata": ControlCenterWidgetRegistry.widgetMetadata[widgetId]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+303
@@ -0,0 +1,303 @@
|
||||
import QtQml.Models
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
Layout.preferredWidth: Math.round(600 * Style.uiScaleRatio)
|
||||
implicitWidth: Layout.preferredWidth
|
||||
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
property var rootSettings: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
QtObject {
|
||||
id: _settings
|
||||
|
||||
property string icon: widgetData.icon !== undefined ? widgetData.icon : widgetMetadata.icon
|
||||
property string onClicked: widgetData.onClicked !== undefined ? widgetData.onClicked : widgetMetadata.onClicked
|
||||
property string onRightClicked: widgetData.onRightClicked !== undefined ? widgetData.onRightClicked : widgetMetadata.onRightClicked
|
||||
property string onMiddleClicked: widgetData.onMiddleClicked !== undefined ? widgetData.onMiddleClicked : widgetMetadata.onMiddleClicked
|
||||
property ListModel _stateChecksListModel: ListModel {}
|
||||
property string stateChecksJson: "[]"
|
||||
property string generalTooltipText: widgetData.generalTooltipText !== undefined ? widgetData.generalTooltipText : widgetMetadata.generalTooltipText
|
||||
property bool enableOnStateLogic: widgetData.enableOnStateLogic !== undefined ? widgetData.enableOnStateLogic : widgetMetadata.enableOnStateLogic
|
||||
property bool showExecTooltip: widgetData.showExecTooltip !== undefined ? widgetData.showExecTooltip : widgetMetadata.showExecTooltip
|
||||
|
||||
function populateStateChecks() {
|
||||
try {
|
||||
var initialChecks = JSON.parse(stateChecksJson);
|
||||
if (initialChecks && Array.isArray(initialChecks)) {
|
||||
for (var i = 0; i < initialChecks.length; i++) {
|
||||
var item = initialChecks[i];
|
||||
if (item && typeof item === "object") {
|
||||
_settings._stateChecksListModel.append({
|
||||
"command": item.command || "",
|
||||
"icon": item.icon || ""
|
||||
});
|
||||
} else {
|
||||
Logger.w("CustomButtonSettings", "Invalid stateChecks entry at index " + i + ":", item);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
Logger.e("CustomButtonSettings", "Failed to parse stateChecksJson:", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
root.rootSettings = _settings;
|
||||
stateChecksJson = (widgetData && widgetData.stateChecksJson !== undefined) ? widgetData.stateChecksJson : (widgetMetadata && widgetMetadata.stateChecksJson ? widgetMetadata.stateChecksJson : "[]");
|
||||
Qt.callLater(populateStateChecks);
|
||||
}
|
||||
|
||||
function saveSettings() {
|
||||
var savedStateChecksArray = [];
|
||||
for (var i = 0; i < _settings._stateChecksListModel.count; i++) {
|
||||
savedStateChecksArray.push(_settings._stateChecksListModel.get(i));
|
||||
}
|
||||
_settings.stateChecksJson = JSON.stringify(savedStateChecksArray);
|
||||
|
||||
return {
|
||||
"id": widgetData.id,
|
||||
"icon": _settings.icon,
|
||||
"onClicked": _settings.onClicked,
|
||||
"onRightClicked": _settings.onRightClicked,
|
||||
"onMiddleClicked": _settings.onMiddleClicked,
|
||||
"stateChecksJson": _settings.stateChecksJson,
|
||||
"generalTooltipText": _settings.generalTooltipText,
|
||||
"enableOnStateLogic": _settings.enableOnStateLogic,
|
||||
"showExecTooltip": _settings.showExecTooltip
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function saveSettings() {
|
||||
return _settings.saveSettings();
|
||||
}
|
||||
|
||||
function updateStateCheck(index, command, icon) {
|
||||
_settings._stateChecksListModel.set(index, {
|
||||
"command": command,
|
||||
"icon": icon
|
||||
});
|
||||
_settings.saveSettings();
|
||||
}
|
||||
|
||||
function removeStateCheck(index) {
|
||||
_settings._stateChecksListModel.remove(index);
|
||||
_settings.saveSettings();
|
||||
}
|
||||
|
||||
function addStateCheck() {
|
||||
_settings._stateChecksListModel.append({
|
||||
"command": "",
|
||||
"icon": ""
|
||||
});
|
||||
_settings.saveSettings();
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style?.marginM ?? 8
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("common.icon")
|
||||
description: I18n.tr("panels.control-center.shortcuts-custom-button-icon-description")
|
||||
}
|
||||
|
||||
NIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
icon: _settings.icon || (widgetMetadata && widgetMetadata.icon ? widgetMetadata.icon : "")
|
||||
pointSize: Style?.fontSizeXL ?? 24
|
||||
visible: (_settings.icon || (widgetMetadata && widgetMetadata.icon ? widgetMetadata.icon : "")) !== ""
|
||||
}
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("common.browse")
|
||||
onClicked: iconPicker.open()
|
||||
}
|
||||
}
|
||||
|
||||
NIconPicker {
|
||||
id: iconPicker
|
||||
initialIcon: _settings.icon
|
||||
onIconSelected: function (iconName) {
|
||||
_settings.icon = iconName;
|
||||
saveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.custom-button.general-tooltip-text-label")
|
||||
description: I18n.tr("bar.custom-button.general-tooltip-text-description")
|
||||
placeholderText: I18n.tr("placeholders.enter-tooltip")
|
||||
text: _settings.generalTooltipText
|
||||
onTextChanged: {
|
||||
_settings.generalTooltipText = text;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.generalTooltipText
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.custom-button.show-exec-tooltip-label")
|
||||
description: I18n.tr("bar.custom-button.show-exec-tooltip-description")
|
||||
checked: _settings.showExecTooltip
|
||||
onToggled: checked => {
|
||||
_settings.showExecTooltip = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showExecTooltip
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.custom-button.left-click-label")
|
||||
description: I18n.tr("bar.custom-button.left-click-description")
|
||||
placeholderText: I18n.tr("placeholders.enter-command")
|
||||
text: _settings.onClicked
|
||||
onTextChanged: {
|
||||
_settings.onClicked = text;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.onClicked
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.custom-button.right-click-label")
|
||||
description: I18n.tr("bar.custom-button.right-click-description")
|
||||
placeholderText: I18n.tr("placeholders.enter-command")
|
||||
text: _settings.onRightClicked
|
||||
onTextChanged: {
|
||||
_settings.onRightClicked = text;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.onRightClicked
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.custom-button.middle-click-label")
|
||||
description: I18n.tr("bar.custom-button.middle-click-description")
|
||||
placeholderText: I18n.tr("placeholders.enter-command")
|
||||
text: _settings.onMiddleClicked
|
||||
onTextChanged: {
|
||||
_settings.onMiddleClicked = text;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.onMiddleClicked
|
||||
}
|
||||
|
||||
NDivider {}
|
||||
|
||||
NToggle {
|
||||
id: enableOnStateLogicToggle
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.control-center.shortcuts-custom-button-enable-on-state-logic-label")
|
||||
description: I18n.tr("panels.control-center.shortcuts-custom-button-enable-on-state-logic-description")
|
||||
checked: _settings.enableOnStateLogic
|
||||
onToggled: checked => {
|
||||
_settings.enableOnStateLogic = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.enableOnStateLogic
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
visible: root.rootSettings && root.rootSettings.enableOnStateLogic
|
||||
spacing: Style?.marginM ?? 8
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("panels.control-center.shortcuts-custom-button-state-checks-label")
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.rootSettings ? root.rootSettings._stateChecksListModel : null
|
||||
delegate: Item {
|
||||
property int currentIndex: index
|
||||
|
||||
implicitHeight: contentRow.implicitHeight
|
||||
Layout.fillWidth: true
|
||||
|
||||
RowLayout {
|
||||
id: contentRow
|
||||
anchors.fill: parent
|
||||
spacing: Style?.marginM ?? 8
|
||||
|
||||
NTextInput {
|
||||
Layout.fillWidth: true
|
||||
placeholderText: I18n.tr("panels.control-center.shortcuts-custom-button-state-checks-command")
|
||||
text: model.command
|
||||
onTextChanged: {
|
||||
updateStateCheck(currentIndex, text, model.icon);
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
spacing: Style?.marginS ?? 4
|
||||
|
||||
NIcon {
|
||||
icon: model.icon
|
||||
pointSize: Style?.fontSizeL ?? 20
|
||||
visible: model.icon !== undefined && model.icon !== ""
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "folder"
|
||||
tooltipText: I18n.tr("common.browse")
|
||||
baseSize: Style?.buttonSizeS ?? 24
|
||||
onClicked: iconPickerDelegate.open()
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
tooltipText: I18n.tr("panels.control-center.shortcuts-custom-button-state-checks-remove")
|
||||
baseSize: Style?.buttonSizeS ?? 24
|
||||
colorBorder: Qt.alpha(Color.mOutline, Style.opacityLight)
|
||||
colorBg: Color.mError
|
||||
colorFg: Color.mOnError
|
||||
colorBgHover: Qt.alpha(Color.mError, Style.opacityMedium)
|
||||
colorFgHover: Color.mOnError
|
||||
onClicked: {
|
||||
removeStateCheck(currentIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NIconPicker {
|
||||
id: iconPickerDelegate
|
||||
initialIcon: model.icon
|
||||
onIconSelected: function (iconName) {
|
||||
updateStateCheck(currentIndex, model.command, iconName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style?.marginM ?? 8
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("panels.control-center.shortcuts-custom-button-state-checks-add")
|
||||
onClicked: addStateCheck()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+276
@@ -0,0 +1,276 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.Noctalia
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
Popup {
|
||||
id: root
|
||||
|
||||
property int widgetIndex: -1
|
||||
property var widgetData: null
|
||||
property string widgetId: ""
|
||||
property string sectionId: "" // Not used for desktop widgets, but required by NSectionEditor
|
||||
property var screen: null
|
||||
property var settingsCache: ({})
|
||||
|
||||
readonly property real maxHeight: (screen ? screen.height : (parent ? parent.height : 800)) * 0.8
|
||||
|
||||
signal updateWidgetSettings(string section, int index, var settings)
|
||||
|
||||
width: Math.max(content.implicitWidth + padding * 2, 500)
|
||||
height: Math.min(content.implicitHeight + padding * 2, maxHeight)
|
||||
padding: Style.marginXL
|
||||
modal: true
|
||||
closePolicy: Popup.NoAutoClose
|
||||
dim: false
|
||||
|
||||
// Center in parent
|
||||
x: Math.round((parent.width - width) / 2)
|
||||
y: Math.round((parent.height - height) / 2)
|
||||
|
||||
onOpened: {
|
||||
if (widgetData && widgetId) {
|
||||
loadWidgetSettings();
|
||||
}
|
||||
forceActiveFocus();
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
id: bgRect
|
||||
color: Color.mSurface
|
||||
radius: Style.radiusL
|
||||
border.color: Color.mPrimary
|
||||
border.width: Style.borderM
|
||||
}
|
||||
|
||||
contentItem: FocusScope {
|
||||
id: focusScope
|
||||
focus: true
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
anchors.fill: parent
|
||||
spacing: Style.marginM
|
||||
|
||||
RowLayout {
|
||||
id: titleRow
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: implicitHeight
|
||||
|
||||
NText {
|
||||
text: I18n.tr("system.widget-settings-title", {
|
||||
"widget": DesktopWidgetRegistry.getWidgetDisplayName(root.widgetId)
|
||||
})
|
||||
pointSize: Style.fontSizeL
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mPrimary
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
tooltipText: I18n.tr("common.close")
|
||||
onClicked: saveAndClose()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: separator
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
color: Color.mOutline
|
||||
}
|
||||
|
||||
// Scrollable settings area
|
||||
NScrollView {
|
||||
id: scrollView
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.minimumHeight: 100
|
||||
gradientColor: Color.mSurface
|
||||
|
||||
ColumnLayout {
|
||||
width: scrollView.availableWidth
|
||||
spacing: Style.marginM
|
||||
|
||||
Loader {
|
||||
id: settingsLoader
|
||||
Layout.fillWidth: true
|
||||
onLoaded: {
|
||||
if (item) {
|
||||
Qt.callLater(() => {
|
||||
var firstInput = findFirstFocusable(item);
|
||||
if (firstInput) {
|
||||
firstInput.forceActiveFocus();
|
||||
} else {
|
||||
focusScope.forceActiveFocus();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function findFirstFocusable(item) {
|
||||
if (!item)
|
||||
return null;
|
||||
if (item.focus !== undefined && item.focus === true)
|
||||
return item;
|
||||
if (item.children) {
|
||||
for (var i = 0; i < item.children.length; i++) {
|
||||
var child = item.children[i];
|
||||
if (child && child.focus !== undefined && child.focus === true)
|
||||
return child;
|
||||
var found = findFirstFocusable(child);
|
||||
if (found)
|
||||
return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mouse area for a draggable popup
|
||||
MouseArea {
|
||||
x: titleRow.x
|
||||
y: titleRow.y
|
||||
width: titleRow.width
|
||||
height: titleRow.height
|
||||
z: -1
|
||||
|
||||
cursorShape: Qt.OpenHandCursor
|
||||
|
||||
property real pressX: 0
|
||||
property real pressY: 0
|
||||
|
||||
onPressed: mouse => {
|
||||
pressX = mouse.x;
|
||||
pressY = mouse.y;
|
||||
cursorShape = Qt.ClosedHandCursor;
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
cursorShape = Qt.OpenHandCursor;
|
||||
}
|
||||
|
||||
onPositionChanged: mouse => {
|
||||
if (pressed) {
|
||||
var deltaX = mouse.x - pressX;
|
||||
var deltaY = mouse.y - pressY;
|
||||
|
||||
root.x += deltaX;
|
||||
root.y += deltaY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: saveTimer
|
||||
running: false
|
||||
interval: 150
|
||||
onTriggered: {
|
||||
root.updateWidgetSettings(root.sectionId, root.widgetIndex, root.settingsCache);
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: settingsLoader.item
|
||||
ignoreUnknownSignals: true
|
||||
function onSettingsChanged(newSettings) {
|
||||
if (newSettings) {
|
||||
root.settingsCache = newSettings;
|
||||
saveTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
function onSettingsSaved(newSettings) {
|
||||
if (newSettings) {
|
||||
root.updateWidgetSettings(root.sectionId, root.widgetIndex, newSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function saveAndClose() {
|
||||
if (settingsLoader.item && typeof settingsLoader.item.saveSettings === 'function') {
|
||||
var newSettings = settingsLoader.item.saveSettings();
|
||||
if (newSettings) {
|
||||
root.updateWidgetSettings(root.sectionId, root.widgetIndex, newSettings);
|
||||
}
|
||||
}
|
||||
root.close();
|
||||
}
|
||||
|
||||
function loadWidgetSettings() {
|
||||
// Handle plugin widgets
|
||||
if (DesktopWidgetRegistry.isPluginWidget(widgetId)) {
|
||||
var pluginId = widgetId.replace("plugin:", "");
|
||||
var manifest = PluginRegistry.getPluginManifest(pluginId);
|
||||
|
||||
var pluginDir = PluginRegistry.getPluginDir(pluginId);
|
||||
var loadVersion = PluginRegistry.pluginLoadVersions[pluginId] || 0;
|
||||
var api = PluginService.getPluginAPI(pluginId);
|
||||
|
||||
var settingsPath;
|
||||
if (manifest && manifest.entryPoints && manifest.entryPoints.desktopWidgetSettings) {
|
||||
settingsPath = "file://" + pluginDir + "/" + manifest.entryPoints.desktopWidgetSettings;
|
||||
|
||||
var widgetSettings = {};
|
||||
widgetSettings.data = widgetData || {};
|
||||
widgetSettings.metadata = DesktopWidgetRegistry.widgetMetadata[widgetId] || {};
|
||||
widgetSettings.save = function () {
|
||||
var newSettings = Object.assign({}, widgetSettings.data);
|
||||
root.settingsCache = newSettings;
|
||||
saveTimer.start();
|
||||
};
|
||||
|
||||
settingsLoader.setSource(settingsPath + "?v=" + loadVersion, {
|
||||
"pluginApi": api,
|
||||
"widgetSettings": widgetSettings
|
||||
});
|
||||
} else {
|
||||
Logger.w("DesktopWidgetSettingsDialog", "Plugin does not have desktop widget settings:", pluginId);
|
||||
|
||||
// Fallback to the plugin settings
|
||||
if (manifest && manifest.entryPoints && manifest.entryPoints.settings) {
|
||||
settingsPath = "file://" + pluginDir + "/" + manifest.entryPoints.settings;
|
||||
|
||||
settingsLoader.setSource(settingsPath + "?v=" + loadVersion, {
|
||||
"pluginApi": api
|
||||
});
|
||||
} else {
|
||||
Logger.w("DesktopWidgetSettingsDialog", "Plugin does not have settings:", pluginId);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle core widgets
|
||||
const source = DesktopWidgetRegistry.widgetSettingsMap[widgetId];
|
||||
if (source) {
|
||||
var currentWidgetData = widgetData;
|
||||
var monitorWidgets = Settings.data.desktopWidgets.monitorWidgets || [];
|
||||
for (var i = 0; i < monitorWidgets.length; i++) {
|
||||
if (monitorWidgets[i].name === sectionId) {
|
||||
var widgets = monitorWidgets[i].widgets || [];
|
||||
if (widgetIndex >= 0 && widgetIndex < widgets.length) {
|
||||
currentWidgetData = widgets[widgetIndex];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
var fullPath = Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/DesktopWidgets/" + source);
|
||||
settingsLoader.setSource(fullPath, {
|
||||
"widgetData": currentWidgetData,
|
||||
"widgetMetadata": DesktopWidgetRegistry.widgetMetadata[widgetId]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
property int valueWidth: widgetData.width !== undefined ? widgetData.width : widgetMetadata.width
|
||||
property int valueHeight: widgetData.height !== undefined ? widgetData.height : widgetMetadata.height
|
||||
property string valueVisualizerType: widgetData.visualizerType !== undefined ? widgetData.visualizerType : widgetMetadata.visualizerType
|
||||
property string valueColorName: widgetData.colorName !== undefined ? widgetData.colorName : widgetMetadata.colorName
|
||||
property bool valueHideWhenIdle: widgetData.hideWhenIdle !== undefined ? widgetData.hideWhenIdle : widgetMetadata.hideWhenIdle
|
||||
property bool valueShowBackground: widgetData.showBackground !== undefined ? widgetData.showBackground : widgetMetadata.showBackground
|
||||
property bool valueRoundedCorners: widgetData.roundedCorners !== undefined ? widgetData.roundedCorners : widgetMetadata.roundedCorners
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.width = valueWidth;
|
||||
settings.height = valueHeight;
|
||||
settings.visualizerType = valueVisualizerType;
|
||||
settings.colorName = valueColorName;
|
||||
settings.hideWhenIdle = valueHideWhenIdle;
|
||||
settings.showBackground = valueShowBackground;
|
||||
settings.roundedCorners = valueRoundedCorners;
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
id: widthInput
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("common.width")
|
||||
description: I18n.tr("bar.audio-visualizer.width-description")
|
||||
text: String(valueWidth)
|
||||
placeholderText: I18n.tr("placeholders.enter-width-pixels")
|
||||
inputMethodHints: Qt.ImhDigitsOnly
|
||||
onEditingFinished: {
|
||||
const parsed = parseInt(text);
|
||||
if (!isNaN(parsed) && parsed > 0) {
|
||||
valueWidth = parsed;
|
||||
saveSettings();
|
||||
} else {
|
||||
text = String(valueWidth);
|
||||
}
|
||||
}
|
||||
defaultValue: String(widgetMetadata.width)
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
id: heightInput
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("common.height")
|
||||
description: I18n.tr("bar.audio-visualizer.height-description")
|
||||
text: String(valueHeight)
|
||||
placeholderText: I18n.tr("placeholders.enter-width-pixels")
|
||||
inputMethodHints: Qt.ImhDigitsOnly
|
||||
onEditingFinished: {
|
||||
const parsed = parseInt(text);
|
||||
if (!isNaN(parsed) && parsed > 0) {
|
||||
valueHeight = parsed;
|
||||
saveSettings();
|
||||
} else {
|
||||
text = String(valueHeight);
|
||||
}
|
||||
}
|
||||
defaultValue: String(widgetMetadata.height)
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.audio.visualizer-type-label")
|
||||
description: I18n.tr("panels.desktop-widgets.media-player-visualizer-type-description")
|
||||
model: [
|
||||
{
|
||||
"key": "linear",
|
||||
"name": I18n.tr("options.visualizer-types.linear")
|
||||
},
|
||||
{
|
||||
"key": "mirrored",
|
||||
"name": I18n.tr("options.visualizer-types.mirrored")
|
||||
},
|
||||
{
|
||||
"key": "wave",
|
||||
"name": I18n.tr("options.visualizer-types.wave")
|
||||
}
|
||||
]
|
||||
currentKey: valueVisualizerType
|
||||
onSelected: key => {
|
||||
valueVisualizerType = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.visualizerType
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.audio-visualizer.color-name-label")
|
||||
description: I18n.tr("bar.audio-visualizer.color-name-description")
|
||||
currentKey: valueColorName
|
||||
onSelected: key => {
|
||||
valueColorName = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.colorName
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.audio-visualizer.hide-when-idle-label")
|
||||
description: I18n.tr("bar.audio-visualizer.hide-when-idle-description")
|
||||
checked: valueHideWhenIdle
|
||||
onToggled: checked => {
|
||||
valueHideWhenIdle = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.hideWhenIdle
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.desktop-widgets.clock-show-background-label")
|
||||
description: I18n.tr("panels.desktop-widgets.media-player-show-background-description")
|
||||
checked: valueShowBackground
|
||||
onToggled: checked => {
|
||||
valueShowBackground = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showBackground
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
visible: valueShowBackground
|
||||
label: I18n.tr("panels.desktop-widgets.clock-rounded-corners-label")
|
||||
description: I18n.tr("panels.desktop-widgets.media-player-rounded-corners-description")
|
||||
checked: valueRoundedCorners
|
||||
onToggled: checked => {
|
||||
valueRoundedCorners = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.roundedCorners
|
||||
}
|
||||
}
|
||||
+293
@@ -0,0 +1,293 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.System
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
width: 700
|
||||
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
property bool valueShowBackground: widgetData.showBackground !== undefined ? widgetData.showBackground : widgetMetadata.showBackground
|
||||
property bool valueRoundedCorners: widgetData.roundedCorners !== undefined ? widgetData.roundedCorners : widgetMetadata.roundedCorners
|
||||
property string valueClockStyle: widgetData.clockStyle !== undefined ? widgetData.clockStyle : widgetMetadata.clockStyle
|
||||
property string valueClockColor: widgetData.clockColor !== undefined ? widgetData.clockColor : widgetMetadata.clockColor
|
||||
property bool valueUseCustomFont: widgetData.useCustomFont !== undefined ? widgetData.useCustomFont : widgetMetadata.useCustomFont
|
||||
property string valueCustomFont: widgetData.customFont !== undefined ? widgetData.customFont : widgetMetadata.custonFont
|
||||
property string valueFormat: widgetData.format !== undefined ? widgetData.format : widgetMetadata.format
|
||||
|
||||
// Track the currently focused input field
|
||||
property var focusedInput: null
|
||||
|
||||
readonly property bool isMinimalMode: valueClockStyle === "minimal"
|
||||
readonly property var now: Time.now
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.showBackground = valueShowBackground;
|
||||
settings.roundedCorners = valueRoundedCorners;
|
||||
settings.clockStyle = valueClockStyle;
|
||||
settings.clockColor = valueClockColor;
|
||||
settings.useCustomFont = valueUseCustomFont;
|
||||
settings.customFont = valueCustomFont;
|
||||
settings.format = valueFormat.trim();
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
// Function to insert token at cursor position in the focused input
|
||||
function insertToken(token) {
|
||||
if (!focusedInput || !focusedInput.inputItem) {
|
||||
// If no input is focused, default to format input
|
||||
if (formatInput.inputItem) {
|
||||
formatInput.inputItem.focus = true;
|
||||
focusedInput = formatInput;
|
||||
}
|
||||
}
|
||||
|
||||
if (focusedInput && focusedInput.inputItem) {
|
||||
var input = focusedInput.inputItem;
|
||||
var cursorPos = input.cursorPosition;
|
||||
var currentText = input.text;
|
||||
|
||||
// Insert token at cursor position
|
||||
var newText = currentText.substring(0, cursorPos) + token + currentText.substring(cursorPos);
|
||||
input.text = newText + " ";
|
||||
|
||||
// Move cursor after the inserted token
|
||||
input.cursorPosition = cursorPos + token.length + 1;
|
||||
|
||||
// Ensure the input keeps focus
|
||||
input.focus = true;
|
||||
saveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.desktop-widgets.clock-style-label")
|
||||
description: I18n.tr("panels.desktop-widgets.clock-style-description")
|
||||
currentKey: valueClockStyle
|
||||
minimumWidth: 260 * Style.uiScaleRatio
|
||||
model: [
|
||||
{
|
||||
"key": "minimal",
|
||||
"name": I18n.tr("panels.desktop-widgets.clock-style-minimal")
|
||||
},
|
||||
{
|
||||
"key": "digital",
|
||||
"name": I18n.tr("panels.desktop-widgets.clock-style-digital")
|
||||
},
|
||||
{
|
||||
"key": "analog",
|
||||
"name": I18n.tr("panels.desktop-widgets.clock-style-analog")
|
||||
},
|
||||
{
|
||||
"key": "binary",
|
||||
"name": I18n.tr("panels.desktop-widgets.clock-style-binary")
|
||||
}
|
||||
]
|
||||
onSelected: key => {
|
||||
valueClockStyle = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.clockStyle
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
label: I18n.tr("common.select-color")
|
||||
description: I18n.tr("common.select-color-description")
|
||||
currentKey: valueClockColor
|
||||
onSelected: key => {
|
||||
valueClockColor = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.clockColor
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.clock.use-custom-font-label")
|
||||
description: I18n.tr("bar.clock.use-custom-font-description")
|
||||
checked: valueUseCustomFont
|
||||
onToggled: checked => {
|
||||
valueUseCustomFont = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.useCustomFont
|
||||
}
|
||||
|
||||
NSearchableComboBox {
|
||||
Layout.fillWidth: true
|
||||
visible: valueUseCustomFont
|
||||
label: I18n.tr("bar.clock.custom-font-label")
|
||||
description: I18n.tr("bar.clock.custom-font-description")
|
||||
model: FontService.availableFonts
|
||||
currentKey: valueCustomFont
|
||||
placeholder: I18n.tr("bar.clock.custom-font-placeholder")
|
||||
searchPlaceholder: I18n.tr("bar.clock.custom-font-search-placeholder")
|
||||
popupHeight: 420
|
||||
minimumWidth: 300
|
||||
onSelected: function (key) {
|
||||
valueCustomFont = key;
|
||||
saveSettings();
|
||||
}
|
||||
enabled: valueClockStyle === "minimal"
|
||||
defaultValue: Settings.data.ui.fontDefault
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
visible: isMinimalMode
|
||||
}
|
||||
|
||||
NHeader {
|
||||
visible: isMinimalMode
|
||||
label: I18n.tr("bar.clock.clock-display-label")
|
||||
description: I18n.tr("bar.clock.clock-display-description")
|
||||
}
|
||||
|
||||
// Format editor - only visible in minimal mode
|
||||
RowLayout {
|
||||
id: main
|
||||
visible: isMinimalMode
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: 1
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
|
||||
|
||||
NTextInput {
|
||||
id: formatInput
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.desktop-widgets.clock-format-label")
|
||||
description: I18n.tr("bar.clock.horizontal-bar-description")
|
||||
placeholderText: "HH:mm\\nd MMMM yyyy"
|
||||
text: valueFormat
|
||||
onTextChanged: {
|
||||
valueFormat = text;
|
||||
settingsChanged(saveSettings());
|
||||
}
|
||||
Component.onCompleted: {
|
||||
if (inputItem) {
|
||||
inputItem.onActiveFocusChanged.connect(function () {
|
||||
if (inputItem.activeFocus) {
|
||||
root.focusedInput = formatInput;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
defaultValue: widgetMetadata.format
|
||||
}
|
||||
}
|
||||
|
||||
// Preview
|
||||
ColumnLayout {
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
|
||||
Layout.fillWidth: false
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("bar.clock.preview")
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 320
|
||||
Layout.preferredHeight: 160
|
||||
color: Color.mSurfaceVariant
|
||||
radius: Style.radiusM
|
||||
border.color: Color.mSecondary
|
||||
border.width: Style.borderS
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginM
|
||||
anchors.centerIn: parent
|
||||
|
||||
ColumnLayout {
|
||||
spacing: -2
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
Repeater {
|
||||
Layout.topMargin: Style.marginM
|
||||
model: I18n.locale.toString(now, valueFormat.trim()).split("\\n")
|
||||
delegate: NText {
|
||||
visible: text !== ""
|
||||
text: modelData
|
||||
family: valueUseCustomFont && valueCustomFont ? valueCustomFont : Settings.data.ui.fontDefault
|
||||
pointSize: Style.fontSizeM
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.resolveColorKey(valueClockColor)
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.topMargin: Style.marginM
|
||||
Layout.bottomMargin: Style.marginM
|
||||
visible: isMinimalMode
|
||||
}
|
||||
|
||||
NDateTimeTokens {
|
||||
Layout.fillWidth: true
|
||||
height: 200
|
||||
visible: isMinimalMode
|
||||
onTokenClicked: token => root.insertToken(token)
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.desktop-widgets.clock-show-background-label")
|
||||
description: I18n.tr("panels.desktop-widgets.clock-show-background-description")
|
||||
checked: valueShowBackground
|
||||
onToggled: checked => {
|
||||
valueShowBackground = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showBackground
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
visible: valueShowBackground
|
||||
label: I18n.tr("panels.desktop-widgets.clock-rounded-corners-label")
|
||||
description: I18n.tr("panels.desktop-widgets.clock-rounded-corners-description")
|
||||
checked: valueRoundedCorners
|
||||
onToggled: checked => {
|
||||
valueRoundedCorners = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.roundedCorners
|
||||
}
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
property bool valueShowBackground: widgetData.showBackground !== undefined ? widgetData.showBackground : widgetMetadata.showBackground
|
||||
property string valueVisualizerType: widgetData.visualizerType ? widgetData.visualizerType : widgetMetadata.visualizerType
|
||||
property string valueHideMode: widgetData.hideMode !== undefined ? widgetData.hideMode : widgetMetadata.hideMode
|
||||
property bool valueShowButtons: widgetData.showButtons !== undefined ? widgetData.showButtons : widgetMetadata.showButtons
|
||||
property bool valueShowAlbumArt: widgetData.showAlbumArt !== undefined ? widgetData.showAlbumArt : widgetMetadata.showAlbumArt
|
||||
property bool valueShowVisualizer: widgetData.showVisualizer !== undefined ? widgetData.showVisualizer : widgetMetadata.showVisualizer
|
||||
property bool valueRoundedCorners: widgetData.roundedCorners !== undefined ? widgetData.roundedCorners : widgetMetadata.roundedCorners
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.showBackground = valueShowBackground;
|
||||
settings.visualizerType = valueVisualizerType;
|
||||
settings.hideMode = valueHideMode;
|
||||
settings.showButtons = valueShowButtons;
|
||||
settings.showAlbumArt = valueShowAlbumArt;
|
||||
settings.showVisualizer = valueShowVisualizer;
|
||||
settings.roundedCorners = valueRoundedCorners;
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.desktop-widgets.clock-show-background-label")
|
||||
description: I18n.tr("panels.desktop-widgets.media-player-show-background-description")
|
||||
checked: valueShowBackground
|
||||
onToggled: checked => {
|
||||
valueShowBackground = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showBackground
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.desktop-widgets.clock-rounded-corners-label")
|
||||
description: I18n.tr("panels.desktop-widgets.media-player-rounded-corners-description")
|
||||
checked: valueRoundedCorners
|
||||
onToggled: checked => {
|
||||
valueRoundedCorners = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.roundedCorners
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.desktop-widgets.media-player-show-album-art-label")
|
||||
description: I18n.tr("panels.desktop-widgets.media-player-show-album-art-description")
|
||||
checked: valueShowAlbumArt
|
||||
onToggled: checked => {
|
||||
valueShowAlbumArt = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showAlbumArt
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.media-mini.show-visualizer-label")
|
||||
description: I18n.tr("panels.desktop-widgets.media-player-show-visualizer-description")
|
||||
checked: valueShowVisualizer
|
||||
onToggled: checked => {
|
||||
valueShowVisualizer = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showVisualizer
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.desktop-widgets.media-player-show-buttons-label")
|
||||
description: I18n.tr("panels.desktop-widgets.media-player-show-buttons-description")
|
||||
checked: valueShowButtons
|
||||
onToggled: checked => {
|
||||
valueShowButtons = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showButtons
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.audio.visualizer-type-label")
|
||||
description: I18n.tr("panels.desktop-widgets.media-player-visualizer-type-description")
|
||||
enabled: valueShowVisualizer
|
||||
model: [
|
||||
{
|
||||
"key": "linear",
|
||||
"name": I18n.tr("options.visualizer-types.linear")
|
||||
},
|
||||
{
|
||||
"key": "mirrored",
|
||||
"name": I18n.tr("options.visualizer-types.mirrored")
|
||||
},
|
||||
{
|
||||
"key": "wave",
|
||||
"name": I18n.tr("options.visualizer-types.wave")
|
||||
}
|
||||
]
|
||||
currentKey: valueVisualizerType
|
||||
onSelected: key => {
|
||||
valueVisualizerType = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.visualizerType
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.taskbar.hide-mode-label")
|
||||
description: I18n.tr("bar.media-mini.hide-mode-description")
|
||||
model: [
|
||||
{
|
||||
"key": "hidden",
|
||||
"name": I18n.tr("hide-modes.hidden")
|
||||
},
|
||||
{
|
||||
"key": "idle",
|
||||
"name": I18n.tr("hide-modes.idle")
|
||||
},
|
||||
{
|
||||
"key": "visible",
|
||||
"name": I18n.tr("hide-modes.visible")
|
||||
}
|
||||
]
|
||||
currentKey: valueHideMode
|
||||
onSelected: key => {
|
||||
valueHideMode = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.hideMode
|
||||
}
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.System
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
width: 700
|
||||
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
property string valueStatType: widgetData.statType !== undefined ? widgetData.statType : widgetMetadata.statType
|
||||
property string valueDiskPath: widgetData.diskPath !== undefined ? widgetData.diskPath : widgetMetadata.diskPath
|
||||
property bool valueShowBackground: widgetData.showBackground !== undefined ? widgetData.showBackground : widgetMetadata.showBackground
|
||||
property bool valueRoundedCorners: widgetData.roundedCorners !== undefined ? widgetData.roundedCorners : widgetMetadata.roundedCorners
|
||||
property string valueLayout: widgetData.layout !== undefined ? widgetData.layout : widgetMetadata.layout
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.statType = valueStatType;
|
||||
settings.diskPath = valueDiskPath;
|
||||
settings.showBackground = valueShowBackground;
|
||||
settings.roundedCorners = valueRoundedCorners;
|
||||
settings.layout = valueLayout;
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.desktop-widgets.system-stat-stat-type-label")
|
||||
description: I18n.tr("panels.desktop-widgets.system-stat-stat-type-description")
|
||||
currentKey: valueStatType
|
||||
minimumWidth: 260 * Style.uiScaleRatio
|
||||
model: {
|
||||
let items = [
|
||||
{
|
||||
"key": "CPU",
|
||||
"name": I18n.tr("system-monitor.cpu-usage")
|
||||
},
|
||||
{
|
||||
"key": "Memory",
|
||||
"name": I18n.tr("common.memory")
|
||||
},
|
||||
{
|
||||
"key": "Network",
|
||||
"name": I18n.tr("bar.system-monitor.network-traffic-label")
|
||||
},
|
||||
{
|
||||
"key": "Disk",
|
||||
"name": I18n.tr("system-monitor.disk")
|
||||
}
|
||||
];
|
||||
if (Settings.data.systemMonitor.enableDgpuMonitoring)
|
||||
items.push({
|
||||
"key": "GPU",
|
||||
"name": I18n.tr("panels.system-monitor.gpu-section-label")
|
||||
});
|
||||
return items;
|
||||
}
|
||||
onSelected: key => {
|
||||
valueStatType = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.statType
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
visible: valueStatType === "Disk"
|
||||
label: I18n.tr("bar.system-monitor.disk-path-label")
|
||||
description: I18n.tr("bar.system-monitor.disk-path-description")
|
||||
model: {
|
||||
const paths = Object.keys(SystemStatService.diskPercents).sort();
|
||||
return paths.map(path => ({
|
||||
key: path,
|
||||
name: path
|
||||
}));
|
||||
}
|
||||
currentKey: valueDiskPath
|
||||
onSelected: key => {
|
||||
valueDiskPath = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.diskPath
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.desktop-widgets.system-stat-show-background-label")
|
||||
description: I18n.tr("panels.desktop-widgets.system-stat-show-background-description")
|
||||
checked: valueShowBackground
|
||||
onToggled: checked => {
|
||||
valueShowBackground = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showBackground
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
visible: valueShowBackground
|
||||
label: I18n.tr("panels.desktop-widgets.system-stat-rounded-corners-label")
|
||||
description: I18n.tr("panels.desktop-widgets.system-stat-rounded-corners-description")
|
||||
checked: valueRoundedCorners
|
||||
onToggled: checked => {
|
||||
valueRoundedCorners = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.roundedCorners
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.desktop-widgets.system-stat-layout-label")
|
||||
description: I18n.tr("panels.desktop-widgets.system-stat-layout-description")
|
||||
currentKey: valueLayout
|
||||
minimumWidth: 260 * Style.uiScaleRatio
|
||||
model: [
|
||||
{
|
||||
"key": "side",
|
||||
"name": I18n.tr("panels.desktop-widgets.system-stat-layout-side")
|
||||
},
|
||||
{
|
||||
"key": "bottom",
|
||||
"name": I18n.tr("panels.desktop-widgets.system-stat-layout-bottom")
|
||||
}
|
||||
]
|
||||
onSelected: key => {
|
||||
valueLayout = key;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.layout
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
signal settingsChanged(var settings)
|
||||
|
||||
property bool valueShowBackground: widgetData.showBackground !== undefined ? widgetData.showBackground : widgetMetadata.showBackground
|
||||
property bool valueRoundedCorners: widgetData.roundedCorners !== undefined ? widgetData.roundedCorners : widgetMetadata.roundedCorners
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.showBackground = valueShowBackground;
|
||||
settings.roundedCorners = valueRoundedCorners;
|
||||
settingsChanged(settings);
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.desktop-widgets.clock-show-background-label")
|
||||
description: I18n.tr("panels.desktop-widgets.weather-show-background-description")
|
||||
checked: valueShowBackground
|
||||
onToggled: checked => {
|
||||
valueShowBackground = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.showBackground
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
visible: valueShowBackground
|
||||
label: I18n.tr("panels.desktop-widgets.clock-rounded-corners-label")
|
||||
description: I18n.tr("panels.desktop-widgets.clock-rounded-corners-description")
|
||||
checked: valueRoundedCorners
|
||||
onToggled: checked => {
|
||||
valueRoundedCorners = checked;
|
||||
saveSettings();
|
||||
}
|
||||
defaultValue: widgetMetadata.roundedCorners
|
||||
}
|
||||
}
|
||||
+1331
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,290 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Modules.MainScreen
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
SmartPanel {
|
||||
id: root
|
||||
|
||||
preferredWidth: Math.round(840 * Style.uiScaleRatio)
|
||||
preferredHeight: Math.round(910 * Style.uiScaleRatio)
|
||||
|
||||
// Settings panel mode: "centered", "attached", "window"
|
||||
readonly property string settingsPanelMode: Settings.data.ui.settingsPanelMode
|
||||
readonly property bool isWindowMode: settingsPanelMode === "window"
|
||||
readonly property bool attachToBar: settingsPanelMode === "attached"
|
||||
|
||||
readonly property string barDensity: Settings.data.bar.density
|
||||
readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name)
|
||||
readonly property bool barFloating: Settings.data.bar.barType === "floating"
|
||||
readonly property real barMarginH: barFloating ? Math.ceil(Settings.data.bar.marginHorizontal) : 0
|
||||
readonly property real barMarginV: barFloating ? Math.ceil(Settings.data.bar.marginVertical) : 0
|
||||
|
||||
forceAttachToBar: attachToBar
|
||||
panelAnchorHorizontalCenter: !root.useButtonPosition && (attachToBar ? (barPosition === "top" || barPosition === "bottom") : true)
|
||||
panelAnchorVerticalCenter: !root.useButtonPosition && (attachToBar ? (barPosition === "left" || barPosition === "right") : true)
|
||||
panelAnchorTop: !root.useButtonPosition && attachToBar && barPosition === "top"
|
||||
panelAnchorBottom: !root.useButtonPosition && attachToBar && barPosition === "bottom"
|
||||
panelAnchorLeft: !root.useButtonPosition && attachToBar && barPosition === "left"
|
||||
panelAnchorRight: !root.useButtonPosition && attachToBar && barPosition === "right"
|
||||
|
||||
onAttachToBarChanged: {
|
||||
if (isPanelOpen) {
|
||||
Qt.callLater(root.setPosition);
|
||||
}
|
||||
}
|
||||
|
||||
onBarPositionChanged: {
|
||||
if (isPanelOpen) {
|
||||
Qt.callLater(root.setPosition);
|
||||
}
|
||||
}
|
||||
|
||||
onBarDensityChanged: {
|
||||
if (isPanelOpen) {
|
||||
Qt.callLater(root.setPosition);
|
||||
}
|
||||
}
|
||||
|
||||
onBarFloatingChanged: {
|
||||
if (isPanelOpen) {
|
||||
Qt.callLater(root.setPosition);
|
||||
}
|
||||
}
|
||||
|
||||
onBarMarginHChanged: {
|
||||
if (isPanelOpen) {
|
||||
Qt.callLater(root.setPosition);
|
||||
}
|
||||
}
|
||||
|
||||
onBarMarginVChanged: {
|
||||
if (isPanelOpen) {
|
||||
Qt.callLater(root.setPosition);
|
||||
}
|
||||
}
|
||||
|
||||
// Tabs enumeration, order is NOT relevant
|
||||
enum Tab {
|
||||
About,
|
||||
Audio,
|
||||
Bar,
|
||||
ColorScheme,
|
||||
LockScreen,
|
||||
ControlCenter,
|
||||
DesktopWidgets,
|
||||
OSD,
|
||||
Display,
|
||||
Dock,
|
||||
General,
|
||||
Hooks,
|
||||
Idle,
|
||||
Launcher,
|
||||
Location,
|
||||
Connections,
|
||||
Notifications,
|
||||
Plugins,
|
||||
SessionMenu,
|
||||
System,
|
||||
UserInterface,
|
||||
Wallpaper
|
||||
}
|
||||
|
||||
property int requestedTab: SettingsPanel.Tab.General
|
||||
property int requestedSubTab: -1
|
||||
property var requestedEntry: null
|
||||
|
||||
// Content state - these are synced with SettingsContent when panel opens
|
||||
property int currentTabIndex: 0
|
||||
property var tabsModel: []
|
||||
property var activeScrollView: null
|
||||
|
||||
// Internal reference to the content (set when panel content loads)
|
||||
property var _settingsContent: null
|
||||
|
||||
// Override toggle to handle window mode
|
||||
function toggle(buttonItem, buttonName) {
|
||||
if (isWindowMode) {
|
||||
SettingsPanelService.toggleWindow(requestedTab);
|
||||
return;
|
||||
}
|
||||
// Call parent toggle
|
||||
if (isPanelOpen) {
|
||||
close();
|
||||
} else {
|
||||
open(buttonItem, buttonName);
|
||||
}
|
||||
}
|
||||
|
||||
// Override open to handle window mode
|
||||
function open(buttonItem, buttonName) {
|
||||
if (isWindowMode) {
|
||||
SettingsPanelService.openWindow(requestedTab);
|
||||
return;
|
||||
}
|
||||
|
||||
// Panel mode: replicate SmartPanel.open() logic
|
||||
if (!buttonItem && buttonName) {
|
||||
if (typeof buttonName === "object" && buttonName.x !== undefined && buttonName.y !== undefined) {
|
||||
root.buttonItem = null;
|
||||
root.buttonPosition = buttonName;
|
||||
root.buttonWidth = 0;
|
||||
root.buttonHeight = 0;
|
||||
root.useButtonPosition = true;
|
||||
} else {
|
||||
buttonItem = BarService.lookupWidget(buttonName, screen.name);
|
||||
}
|
||||
}
|
||||
|
||||
if (buttonItem) {
|
||||
root.buttonItem = buttonItem;
|
||||
var buttonPos = buttonItem.mapToItem(null, 0, 0);
|
||||
root.buttonPosition = Qt.point(buttonPos.x, buttonPos.y);
|
||||
root.buttonWidth = buttonItem.width;
|
||||
root.buttonHeight = buttonItem.height;
|
||||
root.useButtonPosition = true;
|
||||
} else if (!(buttonName && typeof buttonName === "object" && buttonName.x !== undefined && buttonName.y !== undefined)) {
|
||||
root.buttonItem = null;
|
||||
root.useButtonPosition = false;
|
||||
}
|
||||
|
||||
isPanelOpen = true;
|
||||
PanelService.willOpenPanel(root);
|
||||
}
|
||||
|
||||
// Open to a specific tab and optionally a subtab
|
||||
function openToTab(tab, subTab, buttonItem, buttonName) {
|
||||
requestedTab = tab !== undefined ? tab : SettingsPanel.Tab.General;
|
||||
requestedSubTab = subTab !== undefined ? subTab : -1;
|
||||
open(buttonItem, buttonName);
|
||||
}
|
||||
|
||||
// When the panel opens, initialize content
|
||||
onOpened: {
|
||||
if (_settingsContent) {
|
||||
if (requestedEntry) {
|
||||
_settingsContent.requestedTab = requestedEntry.tab;
|
||||
_settingsContent.initialize();
|
||||
const entry = requestedEntry;
|
||||
requestedEntry = null;
|
||||
Qt.callLater(() => _settingsContent.navigateToResult(entry));
|
||||
} else {
|
||||
_settingsContent.requestedTab = requestedTab;
|
||||
if (requestedSubTab >= 0) {
|
||||
_settingsContent._pendingSubTab = requestedSubTab;
|
||||
requestedSubTab = -1;
|
||||
}
|
||||
_settingsContent.initialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scroll functions - delegate to content
|
||||
function scrollDown() {
|
||||
if (_settingsContent)
|
||||
_settingsContent.scrollDown();
|
||||
}
|
||||
|
||||
function scrollUp() {
|
||||
if (_settingsContent)
|
||||
_settingsContent.scrollUp();
|
||||
}
|
||||
|
||||
function scrollPageDown() {
|
||||
if (_settingsContent)
|
||||
_settingsContent.scrollPageDown();
|
||||
}
|
||||
|
||||
function scrollPageUp() {
|
||||
if (_settingsContent)
|
||||
_settingsContent.scrollPageUp();
|
||||
}
|
||||
|
||||
// Navigation functions - delegate to content
|
||||
function selectNextTab() {
|
||||
if (_settingsContent)
|
||||
_settingsContent.selectNextTab();
|
||||
}
|
||||
|
||||
function selectPreviousTab() {
|
||||
if (_settingsContent)
|
||||
_settingsContent.selectPreviousTab();
|
||||
}
|
||||
|
||||
// Override keyboard handlers from SmartPanel
|
||||
function onTabPressed() {
|
||||
selectNextTab();
|
||||
}
|
||||
|
||||
function onBackTabPressed() {
|
||||
selectPreviousTab();
|
||||
}
|
||||
|
||||
function onUpPressed() {
|
||||
if (_settingsContent && _settingsContent.searchText.trim() !== "") {
|
||||
_settingsContent.searchSelectPrevious();
|
||||
} else {
|
||||
scrollUp();
|
||||
}
|
||||
}
|
||||
|
||||
function onDownPressed() {
|
||||
if (_settingsContent && _settingsContent.searchText.trim() !== "") {
|
||||
_settingsContent.searchSelectNext();
|
||||
} else {
|
||||
scrollDown();
|
||||
}
|
||||
}
|
||||
|
||||
function onPageUpPressed() {
|
||||
scrollPageUp();
|
||||
}
|
||||
|
||||
function onPageDownPressed() {
|
||||
scrollPageDown();
|
||||
}
|
||||
|
||||
function onCtrlJPressed() {
|
||||
if (_settingsContent && _settingsContent.searchText.trim() !== "") {
|
||||
_settingsContent.searchSelectNext();
|
||||
} else {
|
||||
scrollDown();
|
||||
}
|
||||
}
|
||||
|
||||
function onCtrlKPressed() {
|
||||
if (_settingsContent && _settingsContent.searchText.trim() !== "") {
|
||||
_settingsContent.searchSelectPrevious();
|
||||
} else {
|
||||
scrollUp();
|
||||
}
|
||||
}
|
||||
|
||||
panelContent: Rectangle {
|
||||
id: panelContent
|
||||
color: "transparent"
|
||||
|
||||
SettingsContent {
|
||||
id: settingsContent
|
||||
anchors.fill: parent
|
||||
screen: root.screen
|
||||
onCloseRequested: root.close()
|
||||
Component.onCompleted: {
|
||||
root._settingsContent = settingsContent;
|
||||
root.tabsModel = Qt.binding(function () {
|
||||
return settingsContent.tabsModel;
|
||||
});
|
||||
root.currentTabIndex = Qt.binding(function () {
|
||||
return settingsContent.currentTabIndex;
|
||||
});
|
||||
root.activeScrollView = Qt.binding(function () {
|
||||
return settingsContent.activeScrollView;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
FloatingWindow {
|
||||
id: root
|
||||
|
||||
title: "Noctalia"
|
||||
minimumSize: Qt.size(840 * Style.uiScaleRatio, 910 * Style.uiScaleRatio)
|
||||
implicitWidth: Math.round(840 * Style.uiScaleRatio)
|
||||
implicitHeight: Math.round(910 * Style.uiScaleRatio)
|
||||
color: "transparent"
|
||||
|
||||
visible: false
|
||||
|
||||
// Register with SettingsPanelService
|
||||
Component.onCompleted: {
|
||||
SettingsPanelService.settingsWindow = root;
|
||||
}
|
||||
|
||||
property bool isInitialized: false
|
||||
|
||||
// Navigate to a specific tab and optional subtab.
|
||||
// Works whether the window is already visible or just becoming visible.
|
||||
function navigateTo(tab, subTab) {
|
||||
const tabId = tab !== undefined ? tab : 0;
|
||||
const subTabId = (subTab !== undefined && subTab !== null && subTab >= 0) ? subTab : -1;
|
||||
if (isInitialized) {
|
||||
settingsContent.navigateToTab(tabId, subTabId);
|
||||
} else {
|
||||
settingsContent.requestedTab = tabId;
|
||||
if (subTabId >= 0)
|
||||
settingsContent._pendingSubTab = subTabId;
|
||||
settingsContent.initialize();
|
||||
isInitialized = true;
|
||||
// Tab content persists in window mode; if no subtab specified and the
|
||||
// tab content is still loaded (same tab), reset to first subtab
|
||||
if (subTabId < 0 && settingsContent.activeTabContent)
|
||||
settingsContent.setSubTabIndex(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Navigate to a search result entry.
|
||||
// Works whether the window is already visible or just becoming visible.
|
||||
function navigateToEntry(entry) {
|
||||
if (isInitialized) {
|
||||
Qt.callLater(() => settingsContent.navigateToResult(entry));
|
||||
} else {
|
||||
settingsContent.requestedTab = entry.tab;
|
||||
settingsContent.initialize();
|
||||
Qt.callLater(() => settingsContent.navigateToResult(entry));
|
||||
isInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Sync visibility with service
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
SettingsPanelService.isWindowOpen = true;
|
||||
} else {
|
||||
isInitialized = false;
|
||||
SettingsPanelService.isWindowOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Keyboard shortcuts
|
||||
Shortcut {
|
||||
sequence: "Escape"
|
||||
enabled: !PanelService.isKeybindRecording
|
||||
onActivated: SettingsPanelService.closeWindow()
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Tab"
|
||||
enabled: !PanelService.isKeybindRecording
|
||||
onActivated: settingsContent.selectNextTab()
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Backtab"
|
||||
enabled: !PanelService.isKeybindRecording
|
||||
onActivated: settingsContent.selectPreviousTab()
|
||||
}
|
||||
|
||||
Instantiator {
|
||||
model: Settings.data.general.keybinds.keyUp || []
|
||||
Shortcut {
|
||||
sequence: modelData
|
||||
enabled: !PanelService.isKeybindRecording
|
||||
onActivated: {
|
||||
if (settingsContent.searchText.trim() !== "")
|
||||
settingsContent.searchSelectPrevious();
|
||||
else
|
||||
settingsContent.scrollUp();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Instantiator {
|
||||
model: Settings.data.general.keybinds.keyDown || []
|
||||
Shortcut {
|
||||
sequence: modelData
|
||||
enabled: !PanelService.isKeybindRecording
|
||||
onActivated: {
|
||||
if (settingsContent.searchText.trim() !== "")
|
||||
settingsContent.searchSelectNext();
|
||||
else
|
||||
settingsContent.scrollDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Main content
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Qt.alpha(Color.mSurface, Settings.data.ui.panelBackgroundOpacity)
|
||||
radius: Style.radiusL
|
||||
|
||||
SettingsContent {
|
||||
id: settingsContent
|
||||
anchors.fill: parent
|
||||
onCloseRequested: SettingsPanelService.closeWindow()
|
||||
}
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 0
|
||||
|
||||
NTabBar {
|
||||
id: subTabBar
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: Style.marginM
|
||||
distributeEvenly: true
|
||||
currentIndex: tabView.currentIndex
|
||||
|
||||
NTabButton {
|
||||
text: I18n.tr("common.info")
|
||||
tabIndex: 0
|
||||
checked: subTabBar.currentIndex === 0
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("common.contributors")
|
||||
tabIndex: 1
|
||||
checked: subTabBar.currentIndex === 1
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("common.supporters")
|
||||
tabIndex: 2
|
||||
checked: subTabBar.currentIndex === 2
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.marginL
|
||||
}
|
||||
|
||||
NTabView {
|
||||
id: tabView
|
||||
currentIndex: subTabBar.currentIndex
|
||||
|
||||
VersionSubTab {}
|
||||
ContributorsSubTab {}
|
||||
SupportersSubTab {}
|
||||
}
|
||||
}
|
||||
+253
@@ -0,0 +1,253 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.Noctalia
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
property var contributors: GitHubService.contributors
|
||||
property int avatarCacheVersion: 0
|
||||
|
||||
readonly property int topContributorsCount: 20
|
||||
|
||||
Connections {
|
||||
target: GitHubService
|
||||
function onCachedAvatarsChanged() {
|
||||
root.avatarCacheVersion++;
|
||||
}
|
||||
}
|
||||
|
||||
spacing: Style.marginL
|
||||
|
||||
NHeader {
|
||||
description: I18n.trp("panels.about.contributors-description", root.contributors.length)
|
||||
enableDescriptionRichText: true
|
||||
}
|
||||
|
||||
// Top 20 contributors with full cards (avoids GridView shader crashes on Qt 6.8)
|
||||
Flow {
|
||||
id: topContributorsFlow
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
Repeater {
|
||||
model: Math.min(root.contributors.length, root.topContributorsCount)
|
||||
|
||||
delegate: Rectangle {
|
||||
width: Math.max(Math.round(topContributorsFlow.width / 2 - Style.marginM - 1), Math.round(Style.baseWidgetSize * 4))
|
||||
height: Math.round(Style.baseWidgetSize * 2.3)
|
||||
radius: Style.radiusM
|
||||
color: contributorArea.containsMouse ? Color.mHover : "transparent"
|
||||
border.width: 1
|
||||
border.color: contributorArea.containsMouse ? Color.mPrimary : Color.mOutline
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM
|
||||
spacing: Style.marginM
|
||||
|
||||
// Avatar container with rectangular design (modern, no shader issues)
|
||||
Item {
|
||||
id: wrapper
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredWidth: Style.baseWidgetSize * 1.8
|
||||
Layout.preferredHeight: Style.baseWidgetSize * 1.8
|
||||
|
||||
property bool isRounded: false
|
||||
|
||||
// Background and image container
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
// Simple circular image (pre-rendered, no shaders)
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
source: {
|
||||
// Depend on avatarCacheVersion to trigger re-evaluation
|
||||
var _ = root.avatarCacheVersion;
|
||||
// Try cached circular version first
|
||||
var username = root.contributors[index].login;
|
||||
var cached = GitHubService.getAvatarPath(username);
|
||||
if (cached) {
|
||||
wrapper.isRounded = true;
|
||||
return cached;
|
||||
}
|
||||
|
||||
// Fall back to original avatar URL
|
||||
return root.contributors[index].avatar_url || "";
|
||||
}
|
||||
fillMode: Image.PreserveAspectFit // Fit since image is already circular with transparency
|
||||
mipmap: true
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
visible: root.contributors[index].avatar_url !== undefined && root.contributors[index].avatar_url !== ""
|
||||
opacity: status === Image.Ready ? 1.0 : 0.0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback icon
|
||||
NIcon {
|
||||
anchors.centerIn: parent
|
||||
visible: !root.contributors[index].avatar_url || root.contributors[index].avatar_url === ""
|
||||
icon: "person"
|
||||
pointSize: Style.fontSizeL
|
||||
color: Color.mPrimary
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: wrapper.isRounded
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
radius: width * 0.5
|
||||
border.width: Style.borderM
|
||||
border.color: Color.mPrimary
|
||||
}
|
||||
}
|
||||
|
||||
// Info column
|
||||
ColumnLayout {
|
||||
spacing: 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: root.contributors[index].login || "Unknown"
|
||||
font.weight: Style.fontWeightBold
|
||||
color: contributorArea.containsMouse ? Color.mOnHover : Color.mOnSurface
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
pointSize: Style.fontSizeS
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NIcon {
|
||||
icon: "git-commit"
|
||||
pointSize: Style.fontSizeXS
|
||||
color: contributorArea.containsMouse ? Color.mOnHover : Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NText {
|
||||
text: `${(root.contributors[index].contributions || 0).toString()} commits`
|
||||
pointSize: Style.fontSizeXS
|
||||
color: contributorArea.containsMouse ? Color.mOnHover : Color.mOnSurfaceVariant
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hover indicator
|
||||
NIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
icon: "arrow-right"
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mPrimary
|
||||
opacity: contributorArea.containsMouse ? 1.0 : 0.0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: contributorArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (root.contributors[index].html_url)
|
||||
Quickshell.execDetached(["xdg-open", root.contributors[index].html_url]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remaining contributors (simple text links)
|
||||
Flow {
|
||||
id: remainingContributorsFlow
|
||||
visible: root.contributors.length > root.topContributorsCount
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
spacing: Style.marginS
|
||||
|
||||
Repeater {
|
||||
model: Math.max(0, root.contributors.length - root.topContributorsCount)
|
||||
|
||||
delegate: Rectangle {
|
||||
width: nameText.implicitWidth + Style.margin2M
|
||||
height: nameText.implicitHeight + Style.margin2S
|
||||
radius: Style.radiusS
|
||||
color: nameArea.containsMouse ? Color.mHover : "transparent"
|
||||
border.width: Style.borderS
|
||||
border.color: nameArea.containsMouse ? Color.mPrimary : Color.mOutline
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
id: nameText
|
||||
anchors.centerIn: parent
|
||||
text: root.contributors[index + root.topContributorsCount].login || "Unknown"
|
||||
pointSize: Style.fontSizeXS
|
||||
color: nameArea.containsMouse ? Color.mOnHover : Color.mOnSurface
|
||||
font.weight: Style.fontWeightMedium
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: nameArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (root.contributors[index + root.topContributorsCount].html_url)
|
||||
Quickshell.execDetached(["xdg-open", root.contributors[index + root.topContributorsCount].html_url]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.Noctalia
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
property var supporters: SupporterService.supporters
|
||||
property int avatarCacheVersion: 0
|
||||
|
||||
Connections {
|
||||
target: SupporterService
|
||||
function onCachedAvatarsChanged() {
|
||||
root.avatarCacheVersion++;
|
||||
}
|
||||
}
|
||||
|
||||
spacing: Style.marginL
|
||||
|
||||
NHeader {
|
||||
description: root.supporters.length === 0 ? I18n.tr("panels.about.supporters-loading") : I18n.trp("panels.about.supporters-desc", root.supporters.length)
|
||||
enableDescriptionRichText: true
|
||||
}
|
||||
|
||||
NButton {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
icon: "heart"
|
||||
text: I18n.tr("panels.about.become-supporter")
|
||||
outlined: true
|
||||
onClicked: {
|
||||
Quickshell.execDetached(["xdg-open", "https://buymeacoffee.com/noctalia"]);
|
||||
ToastService.showNotice(I18n.tr("panels.about.support"), I18n.tr("toast.donation-opened"));
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
// Supporter cards
|
||||
Flow {
|
||||
id: supportersFlow
|
||||
visible: root.supporters.length > 0
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginL
|
||||
|
||||
Repeater {
|
||||
model: root.supporters.length
|
||||
|
||||
delegate: Rectangle {
|
||||
id: supporterCard
|
||||
|
||||
property bool hasGithub: !!root.supporters[index].github_username
|
||||
|
||||
width: Math.max(Math.round(supportersFlow.width / 2 - Style.marginL - 1), Math.round(Style.baseWidgetSize * 4.5))
|
||||
height: Math.round(Style.baseWidgetSize * 2.6)
|
||||
radius: Style.radiusM
|
||||
color: supporterArea.containsMouse && hasGithub ? Color.mHover : Qt.alpha(Color.mPrimary, 0.05)
|
||||
border.width: Style.borderM
|
||||
border.color: supporterArea.containsMouse && hasGithub ? Color.mPrimary : Qt.alpha(Color.mPrimary, 0.5)
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM
|
||||
spacing: Style.marginM
|
||||
|
||||
// Avatar with heart badge
|
||||
Item {
|
||||
id: avatarWrapper
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredWidth: Style.baseWidgetSize * 2.0
|
||||
Layout.preferredHeight: Style.baseWidgetSize * 2.0
|
||||
|
||||
property bool isRounded: false
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
visible: supporterCard.hasGithub
|
||||
source: {
|
||||
if (!supporterCard.hasGithub)
|
||||
return "";
|
||||
var _ = root.avatarCacheVersion;
|
||||
var username = root.supporters[index].github_username;
|
||||
var cached = SupporterService.getAvatarPath(username);
|
||||
if (cached) {
|
||||
avatarWrapper.isRounded = true;
|
||||
return cached;
|
||||
}
|
||||
return "https://github.com/" + username + ".png?size=256";
|
||||
}
|
||||
fillMode: Image.PreserveAspectFit
|
||||
mipmap: true
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
opacity: status === Image.Ready ? 1.0 : 0.0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback logo for supporters without GitHub
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: !supporterCard.hasGithub
|
||||
radius: width * 0.5
|
||||
color: Qt.alpha(Color.mPrimary, 0.15)
|
||||
|
||||
Image {
|
||||
anchors.centerIn: parent
|
||||
source: "../../../../../Assets/noctalia.svg"
|
||||
width: parent.width * 0.75
|
||||
height: width
|
||||
fillMode: Image.PreserveAspectFit
|
||||
mipmap: true
|
||||
smooth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: avatarWrapper.isRounded || !supporterCard.hasGithub
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
radius: width * 0.5
|
||||
border.width: Style.borderM
|
||||
border.color: Color.mPrimary
|
||||
}
|
||||
|
||||
// Heart badge
|
||||
Rectangle {
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.rightMargin: -2
|
||||
anchors.bottomMargin: -2
|
||||
width: Style.fontSizeM + Style.marginS
|
||||
height: width
|
||||
radius: width * 0.5
|
||||
color: Color.mPrimary
|
||||
|
||||
NIcon {
|
||||
anchors.centerIn: parent
|
||||
icon: "heart-filled"
|
||||
pointSize: Style.fontSizeXS
|
||||
color: Color.mOnPrimary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Info column
|
||||
ColumnLayout {
|
||||
spacing: 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: root.supporters[index].name || root.supporters[index].github_username || "Unknown"
|
||||
font.weight: Style.fontWeightBold
|
||||
color: supporterArea.containsMouse && supporterCard.hasGithub ? Color.mOnHover : Color.mOnSurface
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
pointSize: Style.fontSizeS
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("panels.about.supporter-badge")
|
||||
pointSize: Style.fontSizeXS
|
||||
color: Color.mPrimary
|
||||
font.weight: Style.fontWeightMedium
|
||||
}
|
||||
}
|
||||
|
||||
// Hover indicator (only for supporters with GitHub)
|
||||
NIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
icon: "arrow-right"
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mPrimary
|
||||
visible: supporterCard.hasGithub
|
||||
opacity: supporterArea.containsMouse ? 1.0 : 0.0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: supporterArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: supporterCard.hasGithub ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: {
|
||||
var username = root.supporters[index].github_username;
|
||||
if (username) {
|
||||
Quickshell.execDetached(["xdg-open", "https://github.com/" + username]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+985
@@ -0,0 +1,985 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
import qs.Services.Compositor
|
||||
import qs.Services.Noctalia
|
||||
import qs.Services.System
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
opacity: 0
|
||||
|
||||
onSystemInfoLoadingChanged: {
|
||||
if (!systemInfoLoading)
|
||||
tabAppearAnim.start();
|
||||
}
|
||||
|
||||
NumberAnimation on opacity {
|
||||
id: tabAppearAnim
|
||||
from: 0
|
||||
to: 1
|
||||
duration: Style.animationSlowest
|
||||
easing.type: Easing.OutCubic
|
||||
running: false
|
||||
}
|
||||
|
||||
property string latestVersion: GitHubService.latestVersion
|
||||
property string currentVersion: UpdateService.currentVersion
|
||||
property string commitInfo: ""
|
||||
property string qsVersion: ""
|
||||
property string qsRevision: ""
|
||||
|
||||
readonly property bool isGitVersion: root.currentVersion.endsWith("-git")
|
||||
readonly property int gigaB: (1024 * 1024 * 1024)
|
||||
readonly property int gigaD: (1000 * 1000 * 1000)
|
||||
|
||||
// Update status: compare versions
|
||||
readonly property bool updateAvailable: {
|
||||
if (!root.latestVersion || !root.currentVersion || root.latestVersion === I18n.tr("common.unknown"))
|
||||
return false;
|
||||
return UpdateService.compareVersions(root.latestVersion, root.currentVersion) > 0 && !root.isGitVersion;
|
||||
}
|
||||
readonly property bool isUpToDate: {
|
||||
if (!root.latestVersion || !root.currentVersion || root.latestVersion === I18n.tr("common.unknown"))
|
||||
return false;
|
||||
return UpdateService.compareVersions(root.latestVersion, root.currentVersion) <= 0;
|
||||
}
|
||||
|
||||
readonly property bool qsUpdateAvailable: {
|
||||
if (!GitHubService.latestQSVersion || !root.qsVersion || GitHubService.latestQSVersion === I18n.tr("common.unknown"))
|
||||
return false;
|
||||
return UpdateService.compareVersions(GitHubService.latestQSVersion, root.qsVersion) > 0;
|
||||
}
|
||||
|
||||
readonly property bool qsIsUpToDate: {
|
||||
if (!GitHubService.latestQSVersion || !root.qsVersion || GitHubService.latestQSVersion === I18n.tr("common.unknown"))
|
||||
return false;
|
||||
return UpdateService.compareVersions(GitHubService.latestQSVersion, root.qsVersion) <= 0;
|
||||
}
|
||||
|
||||
// System info properties
|
||||
property var systemInfo: null
|
||||
property bool systemInfoLoading: true
|
||||
property bool systemInfoAvailable: true
|
||||
|
||||
spacing: Style.marginL
|
||||
|
||||
function getModule(type) {
|
||||
if (!root.systemInfo)
|
||||
return null;
|
||||
return root.systemInfo.find(m => m.type === type);
|
||||
}
|
||||
|
||||
function getMonitorsText(separator) {
|
||||
const sep = separator || "\n";
|
||||
const screens = Quickshell.screens || [];
|
||||
const scales = CompositorService.displayScales || {};
|
||||
let lines = [];
|
||||
for (let i = 0; i < screens.length; i++) {
|
||||
const screen = screens[i];
|
||||
const name = screen.name || "Unknown";
|
||||
const scaleData = scales[name];
|
||||
const scaleValue = (typeof scaleData === "object" && scaleData !== null) ? (scaleData.scale || 1.0) : (scaleData || 1.0);
|
||||
lines.push(name + ": " + screen.width + "x" + screen.height + " @ " + scaleValue + "x");
|
||||
}
|
||||
return lines.join(sep);
|
||||
}
|
||||
|
||||
function getTelemetryPayload() {
|
||||
const screens = Quickshell.screens || [];
|
||||
const scales = CompositorService.displayScales || {};
|
||||
const monitors = [];
|
||||
for (let i = 0; i < screens.length; i++) {
|
||||
const screen = screens[i];
|
||||
const name = screen.name || "Unknown";
|
||||
const scaleData = scales[name];
|
||||
const scaleValue = (typeof scaleData === "object" && scaleData !== null) ? (scaleData.scale || 1.0) : (scaleData || 1.0);
|
||||
monitors.push({
|
||||
width: screen.width || 0,
|
||||
height: screen.height || 0,
|
||||
scale: scaleValue
|
||||
});
|
||||
}
|
||||
return {
|
||||
instanceId: TelemetryService.getInstanceId(),
|
||||
version: UpdateService.currentVersion,
|
||||
compositor: TelemetryService.getCompositorType(),
|
||||
os: HostService.osPretty || "Unknown",
|
||||
ramGb: Math.round((root.getModule("Memory")?.result?.total || 0) / root.gigaB),
|
||||
monitors: monitors,
|
||||
ui: {
|
||||
scaleRatio: Settings.data.general.scaleRatio,
|
||||
fontDefaultScale: Settings.data.ui.fontDefaultScale,
|
||||
fontFixedScale: Settings.data.ui.fontFixedScale
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function copyTelemetryData() {
|
||||
const payload = getTelemetryPayload();
|
||||
const json = JSON.stringify(payload, null, 2);
|
||||
Quickshell.execDetached(["wl-copy", json]);
|
||||
ToastService.showNotice(I18n.tr("panels.about.telemetry-title"), I18n.tr("panels.about.telemetry-data-copied"));
|
||||
}
|
||||
|
||||
function copyInfoToClipboard() {
|
||||
let info = "Noctalia Shell: " + root.currentVersion;
|
||||
if (root.isGitVersion && root.commitInfo) {
|
||||
info += " (" + root.commitInfo + ")";
|
||||
}
|
||||
info += "\n";
|
||||
|
||||
if (root.qsVersion) {
|
||||
let qsV = root.qsVersion.startsWith("v") ? root.qsVersion : "v" + root.qsVersion;
|
||||
info += "Noctalia QS: " + qsV;
|
||||
if (root.qsRevision) {
|
||||
info += " (" + root.qsRevision + ")";
|
||||
}
|
||||
info += "\n";
|
||||
}
|
||||
|
||||
info += "\nSystem Information\n";
|
||||
info += "==================\n";
|
||||
if (root.systemInfo) {
|
||||
const os = root.getModule("OS");
|
||||
const kernel = root.getModule("Kernel");
|
||||
const title = root.getModule("Title");
|
||||
const product = root.getModule("Host");
|
||||
const board = root.getModule("Board");
|
||||
const cpu = root.getModule("CPU");
|
||||
const gpu = root.getModule("GPU");
|
||||
const mem = root.getModule("Memory");
|
||||
const wm = root.getModule("WM");
|
||||
info += "OS: " + (os?.result?.prettyName || "N/A") + "\n";
|
||||
info += "Kernel: " + (kernel?.result?.release || "N/A") + "\n";
|
||||
info += "Host: " + (title?.result?.hostName || "N/A") + "\n";
|
||||
info += "Product: " + (product?.result?.name || "N/A") + "\n";
|
||||
info += "Board: " + (board?.result?.name || "N/A") + "\n";
|
||||
info += "CPU: " + (cpu?.result?.cpu || "N/A") + "\n";
|
||||
if (gpu?.result && Array.isArray(gpu.result) && gpu.result.length > 0) {
|
||||
info += "GPU: " + gpu.result.map(g => g.name || "Unknown").join(", ") + "\n";
|
||||
}
|
||||
if (mem?.result) {
|
||||
info += "Memory: " + (mem.result.total / root.gigaB).toFixed(1) + " GB \n";
|
||||
}
|
||||
if (wm?.result) {
|
||||
info += "WM: " + (wm.result.prettyName || wm.result.processName || "N/A") + "\n";
|
||||
}
|
||||
}
|
||||
const monitors = getMonitorsText("\n").split("\n");
|
||||
for (const mon of monitors) {
|
||||
info += "Monitor: " + mon + "\n";
|
||||
}
|
||||
info += "\nSettings\n";
|
||||
info += "========\n";
|
||||
info += "UI Scale: " + Settings.data.general.scaleRatio + "\n";
|
||||
info += "Default Font: " + (Settings.data.ui.fontDefault || "default") + " @ " + Settings.data.ui.fontDefaultScale + "x\n";
|
||||
info += "Fixed Font: " + (Settings.data.ui.fontFixed || "default") + " @ " + Settings.data.ui.fontFixedScale + "x\n";
|
||||
Quickshell.execDetached(["wl-copy", info]);
|
||||
ToastService.showNotice(I18n.tr("panels.about.title"), I18n.tr("panels.about.info-copied"));
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
// Check if fastfetch is available before trying to run it
|
||||
checkFastfetchProcess.running = true;
|
||||
qsVersionProcess.running = true;
|
||||
|
||||
Logger.d("VersionSubTab", "Current version:", root.currentVersion);
|
||||
Logger.d("VersionSubTab", "Is git version:", root.isGitVersion);
|
||||
// Only fetch commit info for -git versions
|
||||
if (root.isGitVersion) {
|
||||
// On NixOS, extract commit hash from the store path first
|
||||
if (HostService.isNixOS) {
|
||||
var shellDir = Quickshell.shellDir || "";
|
||||
Logger.d("VersionSubTab", "Component.onCompleted - NixOS detected, shellDir:", shellDir);
|
||||
if (shellDir) {
|
||||
// Extract commit hash from path like: /nix/store/...-noctalia-shell-2025-11-30_225e6d3/share/noctalia-shell
|
||||
// Pattern matches: noctalia-shell-YYYY-MM-DD_<commit_hash>
|
||||
var match = shellDir.match(/noctalia-shell-\d{4}-\d{2}-\d{2}_([0-9a-f]{7,})/i);
|
||||
if (match && match[1]) {
|
||||
// Use first 7 characters of the commit hash
|
||||
root.commitInfo = match[1].substring(0, 7);
|
||||
Logger.d("VersionSubTab", "Component.onCompleted - Extracted commit from NixOS path:", root.commitInfo);
|
||||
return;
|
||||
} else {
|
||||
Logger.d("VersionSubTab", "Component.onCompleted - Could not extract commit from NixOS path, trying fallback");
|
||||
}
|
||||
}
|
||||
}
|
||||
fetchGitCommit();
|
||||
}
|
||||
}
|
||||
|
||||
function fetchGitCommit() {
|
||||
var shellDir = Quickshell.shellDir || "";
|
||||
Logger.d("VersionSubTab", "fetchGitCommit - shellDir:", shellDir);
|
||||
if (!shellDir) {
|
||||
Logger.d("VersionSubTab", "fetchGitCommit - Cannot determine shell directory, skipping git commit fetch");
|
||||
return;
|
||||
}
|
||||
|
||||
gitProcess.workingDirectory = shellDir;
|
||||
gitProcess.running = true;
|
||||
}
|
||||
|
||||
Process {
|
||||
id: gitProcess
|
||||
command: ["git", "rev-parse", "--short", "HEAD"]
|
||||
running: false
|
||||
|
||||
onExited: function (exitCode) {
|
||||
Logger.d("VersionSubTab", "gitProcess - Process exited with code:", exitCode);
|
||||
if (exitCode === 0) {
|
||||
var gitOutput = stdout.text.trim();
|
||||
Logger.d("VersionSubTab", "gitProcess - gitOutput:", gitOutput);
|
||||
if (gitOutput) {
|
||||
root.commitInfo = gitOutput;
|
||||
Logger.d("VersionSubTab", "gitProcess - Set commitInfo to:", root.commitInfo);
|
||||
}
|
||||
} else {
|
||||
Logger.d("VersionSubTab", "gitProcess - Git command failed. Exit code:", exitCode);
|
||||
}
|
||||
}
|
||||
|
||||
stdout: StdioCollector {}
|
||||
stderr: StdioCollector {}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: qsVersionProcess
|
||||
command: ["qs", "--version"]
|
||||
running: false
|
||||
|
||||
onExited: function (exitCode) {
|
||||
if (exitCode === 0) {
|
||||
var output = stdout.text.trim();
|
||||
// Format (old): "noctalia-qs 0.3.0, revision abc12345, distributed by: ..."
|
||||
// Format (new): "noctalia-qs 0.0.9 (revision b602b69c81d96a1d7c645328feb7b1e1d4b7b7a4, distributed by Unset)"
|
||||
// Only set if this is actually noctalia-qs; leave empty for upstream quickshell
|
||||
var match = output.match(/noctalia-qs\s+(\S+?)[\s,(]+revision\s*([0-9a-f]*)/i);
|
||||
if (match) {
|
||||
root.qsVersion = match[1];
|
||||
root.qsRevision = match[2] ? match[2].substring(0, 9) : "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stdout: StdioCollector {}
|
||||
stderr: StdioCollector {}
|
||||
}
|
||||
|
||||
// Check if fastfetch is available before attempting to run it
|
||||
Process {
|
||||
id: checkFastfetchProcess
|
||||
command: ["sh", "-c", "command -v fastfetch"]
|
||||
running: false
|
||||
|
||||
onExited: function (exitCode) {
|
||||
if (exitCode === 0) {
|
||||
// fastfetch is available, run it
|
||||
Logger.d("VersionSubTab", "fastfetch found, running it");
|
||||
fastfetchProcess.running = true;
|
||||
} else {
|
||||
// fastfetch not found, show error state immediately
|
||||
Logger.w("VersionSubTab", "fastfetch not found");
|
||||
root.systemInfoLoading = false;
|
||||
root.systemInfoAvailable = false;
|
||||
}
|
||||
}
|
||||
|
||||
stdout: StdioCollector {}
|
||||
stderr: StdioCollector {}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: fastfetchProcess
|
||||
command: ["fastfetch", "--format", "json", "--config", Quickshell.shellDir + "/Assets/Services/fastfetch/system-info.jsonc"]
|
||||
running: false
|
||||
|
||||
onExited: function (exitCode) {
|
||||
root.systemInfoLoading = false;
|
||||
if (exitCode === 0) {
|
||||
try {
|
||||
root.systemInfo = JSON.parse(stdout.text);
|
||||
root.systemInfoAvailable = true;
|
||||
} catch (e) {
|
||||
Logger.w("VersionSubTab", "Failed to parse fastfetch JSON: " + e);
|
||||
root.systemInfoAvailable = false;
|
||||
}
|
||||
} else {
|
||||
root.systemInfoAvailable = false;
|
||||
}
|
||||
}
|
||||
|
||||
stdout: StdioCollector {}
|
||||
stderr: StdioCollector {}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
spacing: Style.marginXL
|
||||
|
||||
// Noctalia logo
|
||||
Image {
|
||||
source: "../../../../../Assets/noctalia.svg"
|
||||
width: 96 * Style.uiScaleRatio
|
||||
height: width
|
||||
fillMode: Image.PreserveAspectFit
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
mipmap: true
|
||||
smooth: true
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
rotation: Settings.isDebug ? 180 : 0
|
||||
|
||||
Behavior on rotation {
|
||||
NumberAnimation {
|
||||
duration: Style.animationSlowest
|
||||
easing.type: Easing.OutBack
|
||||
}
|
||||
}
|
||||
|
||||
property int debugTapCount: 0
|
||||
|
||||
Timer {
|
||||
id: debugTapTimer
|
||||
interval: 5000
|
||||
onTriggered: parent.debugTapCount = 0
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
if (parent.debugTapCount === 0) {
|
||||
debugTapTimer.restart();
|
||||
}
|
||||
parent.debugTapCount++;
|
||||
if (parent.debugTapCount >= 8) {
|
||||
parent.debugTapCount = 0;
|
||||
debugTapTimer.stop();
|
||||
Settings.isDebug = !Settings.isDebug;
|
||||
if (Settings.isDebug) {
|
||||
ToastService.showNotice("Debug", I18n.tr("panels.about.debug-enabled"));
|
||||
} else {
|
||||
ToastService.showNotice("Debug", I18n.tr("panels.about.debug-disabled"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
NHeader {
|
||||
label: "Noctalia Shell"
|
||||
}
|
||||
|
||||
// Versions
|
||||
GridLayout {
|
||||
columns: 2
|
||||
rowSpacing: Style.marginXS
|
||||
columnSpacing: Style.marginM
|
||||
|
||||
// Installed Version (Shell)
|
||||
NText {
|
||||
text: "Noctalia Shell:"
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginS
|
||||
|
||||
NText {
|
||||
text: root.currentVersion
|
||||
color: Color.mOnSurface
|
||||
font.weight: Style.fontWeightBold
|
||||
}
|
||||
|
||||
// Git commit in parentheses
|
||||
NText {
|
||||
id: commitText
|
||||
visible: root.isGitVersion
|
||||
text: "(" + (root.commitInfo || I18n.tr("common.loading")) + ")"
|
||||
color: commitMouseArea.containsMouse ? Color.mPrimary : Color.mOnSurfaceVariant
|
||||
pointSize: Style.fontSizeXS
|
||||
font.underline: commitMouseArea.containsMouse && root.commitInfo
|
||||
|
||||
MouseArea {
|
||||
id: commitMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: root.commitInfo ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onEntered: {
|
||||
if (root.commitInfo) {
|
||||
TooltipService.show(commitText, I18n.tr("panels.about.view-commit"));
|
||||
}
|
||||
}
|
||||
onExited: TooltipService.hide()
|
||||
onClicked: {
|
||||
if (root.commitInfo) {
|
||||
Quickshell.execDetached(["xdg-open", "https://github.com/noctalia-dev/noctalia-shell/commit/" + root.commitInfo]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update status indicator
|
||||
NIcon {
|
||||
id: upToDateIcon
|
||||
visible: root.isUpToDate
|
||||
icon: "circle-check"
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mPrimary
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: TooltipService.show(upToDateIcon, I18n.tr("panels.about.up-to-date"))
|
||||
onExited: TooltipService.hide()
|
||||
}
|
||||
}
|
||||
|
||||
NIcon {
|
||||
id: updateAvailableIcon
|
||||
visible: root.updateAvailable
|
||||
icon: "arrow-up-circle"
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mPrimary
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: TooltipService.show(updateAvailableIcon, I18n.tr("panels.about.update-available"))
|
||||
onExited: TooltipService.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Latest Version (Shell)
|
||||
NText {
|
||||
visible: root.updateAvailable
|
||||
text: I18n.tr("panels.about.noctalia-available")
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
visible: root.updateAvailable
|
||||
text: root.latestVersion
|
||||
color: Color.mOnSurface
|
||||
font.weight: Style.fontWeightBold
|
||||
}
|
||||
|
||||
// Divider-like spacing
|
||||
Item {
|
||||
visible: root.qsUpdateAvailable || root.updateAvailable
|
||||
Layout.columnSpan: 2
|
||||
Layout.preferredHeight: Style.marginXS
|
||||
}
|
||||
|
||||
// Quickshell Version
|
||||
NText {
|
||||
visible: root.qsVersion !== ""
|
||||
text: "Noctalia QS:"
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: root.qsVersion !== ""
|
||||
spacing: Style.marginS
|
||||
|
||||
NText {
|
||||
text: root.qsVersion.startsWith("v") ? root.qsVersion : "v" + root.qsVersion
|
||||
color: Color.mOnSurface
|
||||
font.weight: Style.fontWeightBold
|
||||
}
|
||||
|
||||
// Git revision in parentheses
|
||||
NText {
|
||||
id: qsRevisionText
|
||||
visible: root.qsRevision !== ""
|
||||
text: "(" + root.qsRevision + ")"
|
||||
color: qsRevisionMouseArea.containsMouse ? Color.mPrimary : Color.mOnSurfaceVariant
|
||||
pointSize: Style.fontSizeXS
|
||||
font.underline: qsRevisionMouseArea.containsMouse
|
||||
|
||||
MouseArea {
|
||||
id: qsRevisionMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onEntered: TooltipService.show(qsRevisionText, I18n.tr("panels.about.view-commit"))
|
||||
onExited: TooltipService.hide()
|
||||
onClicked: {
|
||||
Quickshell.execDetached(["xdg-open", "https://github.com/noctalia-dev/noctalia-qs/commit/" + root.qsRevision]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update status indicator
|
||||
NIcon {
|
||||
id: qsUpToDateIcon
|
||||
visible: root.qsIsUpToDate
|
||||
icon: "circle-check"
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mPrimary
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: TooltipService.show(qsUpToDateIcon, I18n.tr("panels.about.up-to-date"))
|
||||
onExited: TooltipService.hide()
|
||||
}
|
||||
}
|
||||
|
||||
NIcon {
|
||||
id: qsUpdateAvailableIcon
|
||||
visible: root.qsUpdateAvailable
|
||||
icon: "arrow-up-circle"
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mPrimary
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: TooltipService.show(qsUpdateAvailableIcon, I18n.tr("panels.about.update-available"))
|
||||
onExited: TooltipService.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Latest Quickshell Version
|
||||
NText {
|
||||
visible: root.qsUpdateAvailable
|
||||
text: I18n.tr("panels.about.noctalia-available")
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
visible: root.qsUpdateAvailable
|
||||
text: GitHubService.latestQSVersion
|
||||
color: Color.mOnSurface
|
||||
font.weight: Style.fontWeightBold
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
id: actionsGrid
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: Style.marginM
|
||||
Layout.bottomMargin: Style.marginM
|
||||
rowSpacing: Style.marginM
|
||||
columnSpacing: Style.marginM
|
||||
|
||||
columns: (changelogBtn.implicitWidth + copyBtn.implicitWidth + supportBtn.implicitWidth + 2 * columnSpacing) < root.width ? 3 : 1
|
||||
|
||||
NButton {
|
||||
id: changelogBtn
|
||||
icon: "sparkles"
|
||||
text: I18n.tr("panels.about.changelog")
|
||||
outlined: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
onClicked: {
|
||||
var screen = PanelService.openedPanel?.screen || SettingsPanelService.settingsWindow?.screen || PanelService.findScreenForPanels();
|
||||
SettingsPanelService.close(screen);
|
||||
UpdateService.viewChangelog(screen);
|
||||
}
|
||||
}
|
||||
|
||||
NButton {
|
||||
id: copyBtn
|
||||
icon: "copy"
|
||||
text: I18n.tr("panels.about.copy-info")
|
||||
outlined: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
onClicked: root.copyInfoToClipboard()
|
||||
}
|
||||
|
||||
NButton {
|
||||
id: supportBtn
|
||||
icon: "heart"
|
||||
text: I18n.tr("panels.about.support")
|
||||
outlined: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
onClicked: {
|
||||
Quickshell.execDetached(["xdg-open", "https://buymeacoffee.com/noctalia"]);
|
||||
ToastService.showNotice(I18n.tr("panels.about.support"), I18n.tr("toast.donation-opened"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.about.changelog-on-startup")
|
||||
description: I18n.tr("panels.about.changelog-on-startup-desc")
|
||||
checked: Settings.data.general.showChangelogOnStartup
|
||||
onToggled: checked => Settings.data.general.showChangelogOnStartup = checked
|
||||
}
|
||||
|
||||
// System Information Section
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("panels.about.system-title")
|
||||
}
|
||||
|
||||
// Error state (fastfetch not installed)
|
||||
ColumnLayout {
|
||||
visible: !root.systemInfoAvailable
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
|
||||
NText {
|
||||
text: I18n.tr("panels.about.system-not-installed")
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("panels.about.system-install-hint")
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: Style.fontSizeXS
|
||||
}
|
||||
}
|
||||
|
||||
// System info grid
|
||||
GridLayout {
|
||||
id: sysInfo
|
||||
readonly property real textSize: Style.fontSizeS
|
||||
|
||||
visible: root.systemInfoAvailable && root.systemInfo
|
||||
Layout.fillWidth: true
|
||||
columns: 2
|
||||
rowSpacing: Style.marginXS
|
||||
columnSpacing: Style.marginM
|
||||
|
||||
// OS
|
||||
NText {
|
||||
text: I18n.tr("panels.about.system-os")
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: sysInfo.textSize
|
||||
}
|
||||
NText {
|
||||
text: {
|
||||
const os = root.getModule("OS");
|
||||
return os?.result?.prettyName || "N/A";
|
||||
}
|
||||
color: Color.mOnSurface
|
||||
pointSize: sysInfo.textSize
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
// Kernel
|
||||
NText {
|
||||
text: I18n.tr("panels.about.system-kernel")
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: sysInfo.textSize
|
||||
}
|
||||
NText {
|
||||
text: {
|
||||
const kernel = root.getModule("Kernel");
|
||||
return kernel?.result?.release || "N/A";
|
||||
}
|
||||
color: Color.mOnSurface
|
||||
pointSize: sysInfo.textSize
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
// Host
|
||||
NText {
|
||||
text: I18n.tr("panels.about.system-host")
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: sysInfo.textSize
|
||||
}
|
||||
NText {
|
||||
text: {
|
||||
const title = root.getModule("Title");
|
||||
return title?.result?.hostName || "N/A";
|
||||
}
|
||||
color: Color.mOnSurface
|
||||
pointSize: sysInfo.textSize
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
// Product name
|
||||
NText {
|
||||
text: I18n.tr("panels.about.system-product")
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: sysInfo.textSize
|
||||
}
|
||||
NText {
|
||||
text: {
|
||||
const title = root.getModule("Host");
|
||||
return title?.result?.name || "N/A";
|
||||
}
|
||||
color: Color.mOnSurface
|
||||
pointSize: sysInfo.textSize
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
// Board name
|
||||
NText {
|
||||
text: I18n.tr("panels.about.system-board")
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: sysInfo.textSize
|
||||
}
|
||||
NText {
|
||||
text: {
|
||||
const title = root.getModule("Board");
|
||||
return title?.result?.name || "N/A";
|
||||
}
|
||||
color: Color.mOnSurface
|
||||
pointSize: sysInfo.textSize
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
// Uptime
|
||||
NText {
|
||||
text: I18n.tr("panels.about.system-uptime")
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: sysInfo.textSize
|
||||
}
|
||||
NText {
|
||||
text: {
|
||||
const value = root.getModule("Uptime")?.result?.uptime;
|
||||
return value ? Time.formatVagueHumanReadableDuration(value / 1000) : "-";
|
||||
}
|
||||
color: Color.mOnSurface
|
||||
pointSize: sysInfo.textSize
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
// CPU
|
||||
NText {
|
||||
text: I18n.tr("panels.about.system-cpu")
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: sysInfo.textSize
|
||||
}
|
||||
NText {
|
||||
text: {
|
||||
const cpu = root.getModule("CPU");
|
||||
if (!cpu?.result)
|
||||
return "N/A";
|
||||
let cpuText = cpu.result.cpu || "N/A";
|
||||
const cores = cpu.result.cores;
|
||||
if (cores?.logical) {
|
||||
cpuText += " (" + cores.logical + " threads)";
|
||||
}
|
||||
return cpuText;
|
||||
}
|
||||
color: Color.mOnSurface
|
||||
pointSize: sysInfo.textSize
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
// GPU
|
||||
NText {
|
||||
text: I18n.tr("panels.about.system-gpu")
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: sysInfo.textSize
|
||||
}
|
||||
NText {
|
||||
text: {
|
||||
const gpu = root.getModule("GPU");
|
||||
if (!gpu?.result || !Array.isArray(gpu.result) || gpu.result.length === 0)
|
||||
return "N/A";
|
||||
return gpu.result.map(g => g.name || "Unknown").join(", ");
|
||||
}
|
||||
color: Color.mOnSurface
|
||||
pointSize: sysInfo.textSize
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
// Memory
|
||||
NText {
|
||||
text: I18n.tr("panels.about.system-memory")
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: sysInfo.textSize
|
||||
}
|
||||
NText {
|
||||
text: {
|
||||
const mem = root.getModule("Memory");
|
||||
if (!mem?.result)
|
||||
return "N/A";
|
||||
const used = (mem.result.used / root.gigaB).toFixed(1);
|
||||
const total = (mem.result.total / root.gigaB).toFixed(1);
|
||||
return used + " GiB / " + total + " GiB";
|
||||
}
|
||||
color: Color.mOnSurface
|
||||
pointSize: sysInfo.textSize
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
// Disk
|
||||
NText {
|
||||
text: I18n.tr("panels.about.system-disk")
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: sysInfo.textSize
|
||||
}
|
||||
NText {
|
||||
text: {
|
||||
const disk = root.getModule("Disk");
|
||||
if (!disk?.result || !Array.isArray(disk.result) || disk.result.length === 0)
|
||||
return "N/A";
|
||||
const rootDisk = disk.result.find(d => d.mountpoint === "/");
|
||||
if (!rootDisk?.bytes)
|
||||
return "N/A";
|
||||
const used = (rootDisk.bytes.used / root.gigaD).toFixed(1);
|
||||
const total = (rootDisk.bytes.total / root.gigaD).toFixed(1);
|
||||
return used + " GB / " + total + " GB" + " (" + rootDisk.filesystem + ")";
|
||||
}
|
||||
color: Color.mOnSurface
|
||||
pointSize: sysInfo.textSize
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
// WM
|
||||
NText {
|
||||
text: I18n.tr("panels.about.system-wm")
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: sysInfo.textSize
|
||||
}
|
||||
NText {
|
||||
text: {
|
||||
const wm = root.getModule("WM");
|
||||
if (!wm?.result)
|
||||
return "N/A";
|
||||
let wmText = wm.result.prettyName || wm.result.processName || "N/A";
|
||||
if (wm.result.protocolName) {
|
||||
wmText += " (" + wm.result.protocolName + ")";
|
||||
}
|
||||
return wmText;
|
||||
}
|
||||
color: Color.mOnSurface
|
||||
pointSize: sysInfo.textSize
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
// Packages
|
||||
NText {
|
||||
text: I18n.tr("panels.about.system-packages")
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: sysInfo.textSize
|
||||
}
|
||||
NText {
|
||||
text: {
|
||||
const pkg = root.getModule("Packages");
|
||||
if (!pkg?.result)
|
||||
return "N/A";
|
||||
const result = pkg.result;
|
||||
if (result.all) {
|
||||
const managers = [];
|
||||
if (result.rpm > 0)
|
||||
managers.push("rpm: " + result.rpm);
|
||||
if (result.pacman > 0)
|
||||
managers.push("pacman: " + result.pacman);
|
||||
if (result.dpkg > 0)
|
||||
managers.push("dpkg: " + result.dpkg);
|
||||
if (result.flatpakSystem > 0 || result.flatpakUser > 0) {
|
||||
const flatpak = (result.flatpakSystem || 0) + (result.flatpakUser || 0);
|
||||
managers.push("flatpak: " + flatpak);
|
||||
}
|
||||
if (result.snap > 0)
|
||||
managers.push("snap: " + result.snap);
|
||||
if (result.nixSystem > 0 || result.nixUser > 0 || result.nixDefault > 0) {
|
||||
const nix = (result.nixSystem || 0) + (result.nixUser || 0) + (result.nixDefault || 0);
|
||||
managers.push("nix: " + nix);
|
||||
}
|
||||
if (result.brew > 0)
|
||||
managers.push("brew: " + result.brew);
|
||||
if (managers.length > 0) {
|
||||
return result.all + " (" + managers.join(", ") + ")";
|
||||
}
|
||||
return result.all.toString();
|
||||
}
|
||||
return "N/A";
|
||||
}
|
||||
color: Color.mOnSurface
|
||||
pointSize: sysInfo.textSize
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
// Monitors (2 items per screen: label + value)
|
||||
Repeater {
|
||||
model: Quickshell.screens.length * 2
|
||||
|
||||
NText {
|
||||
readonly property int screenIndex: Math.floor(index / 2)
|
||||
readonly property bool isLabel: index % 2 === 0
|
||||
readonly property var screen: Quickshell.screens[screenIndex]
|
||||
|
||||
text: {
|
||||
if (isLabel)
|
||||
return I18n.tr("panels.about.system-monitor");
|
||||
const name = screen?.name || "Unknown";
|
||||
const scales = CompositorService.displayScales || {};
|
||||
const scaleData = scales[name];
|
||||
const scaleValue = (typeof scaleData === "object" && scaleData !== null) ? (scaleData.scale || 1.0) : (scaleData || 1.0);
|
||||
return name + ": " + (screen?.width || 0) + "x" + (screen?.height || 0) + " @ " + scaleValue + "x";
|
||||
}
|
||||
color: isLabel ? Color.mOnSurfaceVariant : Color.mOnSurface
|
||||
pointSize: sysInfo.textSize
|
||||
Layout.fillWidth: !isLabel
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Telemetry Section
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginL
|
||||
}
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("panels.about.telemetry-title")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.about.telemetry-enabled")
|
||||
description: I18n.tr("panels.about.telemetry-desc")
|
||||
checked: Settings.data.general.telemetryEnabled
|
||||
onToggled: checked => Settings.data.general.telemetryEnabled = checked
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginM
|
||||
|
||||
NButton {
|
||||
icon: "eye"
|
||||
text: I18n.tr("panels.about.telemetry-show-data")
|
||||
outlined: true
|
||||
onClicked: root.copyTelemetryData()
|
||||
}
|
||||
|
||||
NButton {
|
||||
icon: "shield-lock"
|
||||
text: I18n.tr("panels.about.privacy-policy")
|
||||
outlined: true
|
||||
onClicked: Quickshell.execDetached(["xdg-open", "https://noctalia.dev/privacy"])
|
||||
}
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 0
|
||||
|
||||
NTabBar {
|
||||
id: subTabBar
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: Style.marginM
|
||||
distributeEvenly: true
|
||||
currentIndex: tabView.currentIndex
|
||||
|
||||
NTabButton {
|
||||
text: I18n.tr("common.volumes")
|
||||
tabIndex: 0
|
||||
checked: subTabBar.currentIndex === 0
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("common.devices")
|
||||
tabIndex: 1
|
||||
checked: subTabBar.currentIndex === 1
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("common.media")
|
||||
tabIndex: 2
|
||||
checked: subTabBar.currentIndex === 2
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("common.visualizer")
|
||||
tabIndex: 3
|
||||
checked: subTabBar.currentIndex === 3
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.marginL
|
||||
}
|
||||
|
||||
NTabView {
|
||||
id: tabView
|
||||
currentIndex: subTabBar.currentIndex
|
||||
|
||||
VolumesSubTab {}
|
||||
DevicesSubTab {}
|
||||
MediaSubTab {}
|
||||
VisualizerSubTab {}
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Services.Pipewire
|
||||
import qs.Commons
|
||||
import qs.Services.Media
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
// Output Devices
|
||||
ButtonGroup {
|
||||
id: sinks
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXS
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: Style.marginL
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("panels.audio.devices-output-device-label")
|
||||
description: I18n.tr("panels.audio.devices-output-device-description")
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: AudioService.sinks
|
||||
NRadioButton {
|
||||
ButtonGroup.group: sinks
|
||||
required property PwNode modelData
|
||||
text: modelData.description
|
||||
checked: AudioService.sink?.id === modelData.id
|
||||
onClicked: {
|
||||
AudioService.setAudioSink(modelData);
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Input Devices
|
||||
ButtonGroup {
|
||||
id: sources
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("panels.audio.devices-input-device-label")
|
||||
description: I18n.tr("panels.audio.devices-input-device-description")
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: AudioService.sources
|
||||
NRadioButton {
|
||||
ButtonGroup.group: sources
|
||||
required property PwNode modelData
|
||||
text: modelData.description
|
||||
checked: AudioService.source?.id === modelData.id
|
||||
onClicked: AudioService.setAudioSource(modelData)
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.Media
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
// Preferred player
|
||||
NTextInput {
|
||||
label: I18n.tr("panels.audio.media-primary-player-label")
|
||||
description: I18n.tr("panels.audio.media-primary-player-description")
|
||||
placeholderText: I18n.tr("panels.audio.media-primary-player-placeholder")
|
||||
text: Settings.data.audio.preferredPlayer
|
||||
defaultValue: Settings.getDefaultValue("audio.preferredPlayer")
|
||||
onTextChanged: {
|
||||
Settings.data.audio.preferredPlayer = text;
|
||||
MediaService.updateCurrentPlayer();
|
||||
}
|
||||
}
|
||||
|
||||
// Blacklist editor
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NTextInputButton {
|
||||
id: blacklistInput
|
||||
label: I18n.tr("panels.audio.media-excluded-player-label")
|
||||
description: I18n.tr("panels.audio.media-excluded-player-description")
|
||||
placeholderText: I18n.tr("panels.audio.media-excluded-player-placeholder")
|
||||
buttonIcon: "add"
|
||||
Layout.fillWidth: true
|
||||
onButtonClicked: {
|
||||
const val = (blacklistInput.text || "").trim();
|
||||
if (val !== "") {
|
||||
const arr = (Settings.data.audio.mprisBlacklist || []);
|
||||
if (!arr.find(x => String(x).toLowerCase() === val.toLowerCase())) {
|
||||
Settings.data.audio.mprisBlacklist = [...arr, val];
|
||||
blacklistInput.text = "";
|
||||
MediaService.updateCurrentPlayer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Current blacklist entries
|
||||
Flow {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Style.marginS
|
||||
spacing: Style.marginS
|
||||
|
||||
Repeater {
|
||||
model: Settings.data.audio.mprisBlacklist
|
||||
delegate: Rectangle {
|
||||
required property string modelData
|
||||
property real pad: Style.marginS
|
||||
color: Qt.alpha(Color.mOnSurface, 0.125)
|
||||
border.color: Qt.alpha(Color.mOnSurface, Style.opacityLight)
|
||||
border.width: Style.borderS
|
||||
|
||||
RowLayout {
|
||||
id: chipRow
|
||||
spacing: Style.marginXS
|
||||
anchors.fill: parent
|
||||
anchors.margins: pad
|
||||
|
||||
NText {
|
||||
text: modelData
|
||||
color: Color.mOnSurface
|
||||
pointSize: Style.fontSizeS
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.leftMargin: Style.marginS
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.rightMargin: Style.marginXS
|
||||
onClicked: {
|
||||
const arr = (Settings.data.audio.mprisBlacklist || []);
|
||||
const idx = arr.findIndex(x => String(x) === modelData);
|
||||
if (idx >= 0) {
|
||||
arr.splice(idx, 1);
|
||||
Settings.data.audio.mprisBlacklist = arr;
|
||||
MediaService.updateCurrentPlayer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
implicitWidth: chipRow.implicitWidth + pad * 2
|
||||
implicitHeight: Math.max(chipRow.implicitHeight + pad * 2, Style.baseWidgetSize * 0.8)
|
||||
radius: Style.radiusM
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("panels.audio.visualizer-type-label")
|
||||
description: I18n.tr("panels.audio.visualizer-type-description")
|
||||
model: [
|
||||
{
|
||||
"key": "none",
|
||||
"name": I18n.tr("common.none")
|
||||
},
|
||||
{
|
||||
"key": "linear",
|
||||
"name": I18n.tr("options.visualizer-types.linear")
|
||||
},
|
||||
{
|
||||
"key": "mirrored",
|
||||
"name": I18n.tr("options.visualizer-types.mirrored")
|
||||
},
|
||||
{
|
||||
"key": "wave",
|
||||
"name": I18n.tr("options.visualizer-types.wave")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.audio.visualizerType
|
||||
defaultValue: Settings.getDefaultValue("audio.visualizerType")
|
||||
onSelected: key => Settings.data.audio.visualizerType = key
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.audio.spectrum-mirrored-label")
|
||||
description: I18n.tr("panels.audio.spectrum-mirrored-description")
|
||||
checked: Settings.data.audio.spectrumMirrored
|
||||
defaultValue: Settings.getDefaultValue("audio.spectrumMirrored")
|
||||
onToggled: Settings.data.audio.spectrumMirrored = checked
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("panels.audio.media-frame-rate-label")
|
||||
description: I18n.tr("panels.audio.media-frame-rate-description")
|
||||
model: [
|
||||
{
|
||||
"key": "30",
|
||||
"name": I18n.tr("options.frame-rates-fps", {
|
||||
"fps": "30"
|
||||
})
|
||||
},
|
||||
{
|
||||
"key": "60",
|
||||
"name": I18n.tr("options.frame-rates-fps", {
|
||||
"fps": "60"
|
||||
})
|
||||
},
|
||||
{
|
||||
"key": "100",
|
||||
"name": I18n.tr("options.frame-rates-fps", {
|
||||
"fps": "100"
|
||||
})
|
||||
},
|
||||
{
|
||||
"key": "120",
|
||||
"name": I18n.tr("options.frame-rates-fps", {
|
||||
"fps": "120"
|
||||
})
|
||||
},
|
||||
{
|
||||
"key": "144",
|
||||
"name": I18n.tr("options.frame-rates-fps", {
|
||||
"fps": "144"
|
||||
})
|
||||
},
|
||||
{
|
||||
"key": "165",
|
||||
"name": I18n.tr("options.frame-rates-fps", {
|
||||
"fps": "165"
|
||||
})
|
||||
},
|
||||
{
|
||||
"key": "240",
|
||||
"name": I18n.tr("options.frame-rates-fps", {
|
||||
"fps": "240"
|
||||
})
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.audio.spectrumFrameRate
|
||||
defaultValue: Settings.getDefaultValue("audio.spectrumFrameRate")
|
||||
onSelected: key => Settings.data.audio.spectrumFrameRate = key
|
||||
}
|
||||
}
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.Media
|
||||
import qs.Services.System
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
property real localVolume: AudioService.volume
|
||||
|
||||
Connections {
|
||||
target: AudioService
|
||||
function onSinkChanged() {
|
||||
localVolume = AudioService.volume;
|
||||
}
|
||||
function onVolumeChanged() {
|
||||
localVolume = AudioService.volume;
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: AudioService.sink?.audio ? AudioService.sink?.audio : null
|
||||
function onVolumeChanged() {
|
||||
localVolume = AudioService.volume;
|
||||
}
|
||||
}
|
||||
|
||||
// Output Volume
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.osd.types-volume-label")
|
||||
description: I18n.tr("panels.audio.volumes-output-volume-description")
|
||||
from: 0
|
||||
to: Settings.data.audio.volumeOverdrive ? 1.5 : 1.0
|
||||
value: localVolume
|
||||
stepSize: 0.01
|
||||
text: Math.round(AudioService.volume * 100) + "%"
|
||||
onMoved: value => localVolume = value
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 100
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
if (!AudioService.isSwitchingSink && Math.abs(localVolume - AudioService.volume) >= 0.01) {
|
||||
AudioService.setVolume(localVolume);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mute Toggle
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.audio.volumes-mute-output-label")
|
||||
description: I18n.tr("panels.audio.volumes-mute-output-description")
|
||||
checked: AudioService.muted
|
||||
onToggled: checked => AudioService.setOutputMuted(checked)
|
||||
}
|
||||
}
|
||||
|
||||
// Volume Feedback sound Toggle
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.audio.volumes-volume-feedback-label")
|
||||
description: I18n.tr("panels.audio.volumes-volume-feedback-description")
|
||||
checked: Settings.data.audio.volumeFeedback
|
||||
defaultValue: Settings.getDefaultValue("audio.volumeFeedback")
|
||||
onToggled: checked => Settings.data.audio.volumeFeedback = checked
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
enabled: SoundService.multimediaAvailable && Settings.data.audio.volumeFeedback
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("panels.audio.volumes-feedback-sound-file-label")
|
||||
description: I18n.tr("panels.audio.volumes-feedback-sound-file-description")
|
||||
}
|
||||
|
||||
NTextInputButton {
|
||||
enabled: parent.enabled
|
||||
Layout.fillWidth: true
|
||||
placeholderText: I18n.tr("panels.notifications.sounds-files-placeholder")
|
||||
text: Settings.data.audio.volumeFeedbackSoundFile ?? ""
|
||||
buttonIcon: "folder-open"
|
||||
buttonTooltip: I18n.tr("panels.notifications.sounds-files-select-file")
|
||||
onInputTextChanged: text => Settings.data.audio.volumeFeedbackSoundFile = text
|
||||
onButtonClicked: volumeFeedbackFilePicker.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Input Volume
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.osd.types-input-volume-label")
|
||||
description: I18n.tr("panels.audio.volumes-input-volume-description")
|
||||
from: 0
|
||||
to: Settings.data.audio.volumeOverdrive ? 1.5 : 1.0
|
||||
value: AudioService.inputVolume
|
||||
stepSize: 0.01
|
||||
text: Math.round(AudioService.inputVolume * 100) + "%"
|
||||
onMoved: value => AudioService.setInputVolume(value)
|
||||
}
|
||||
}
|
||||
|
||||
// Input Mute Toggle
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.audio.volumes-mute-input-label")
|
||||
description: I18n.tr("panels.audio.volumes-mute-input-description")
|
||||
checked: AudioService.inputMuted
|
||||
onToggled: checked => AudioService.setInputMuted(checked)
|
||||
}
|
||||
}
|
||||
|
||||
// Volume Step Size
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NSpinBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.audio.volumes-step-size-label")
|
||||
description: I18n.tr("panels.audio.volumes-step-size-description")
|
||||
minimum: 1
|
||||
maximum: 25
|
||||
value: Settings.data.audio.volumeStep
|
||||
stepSize: 1
|
||||
suffix: "%"
|
||||
defaultValue: Settings.getDefaultValue("audio.volumeStep")
|
||||
onValueChanged: Settings.data.audio.volumeStep = value
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Raise maximum volume above 100%
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.audio.volumes-volume-overdrive-label")
|
||||
description: I18n.tr("panels.audio.volumes-volume-overdrive-description")
|
||||
checked: Settings.data.audio.volumeOverdrive
|
||||
defaultValue: Settings.getDefaultValue("audio.volumeOverdrive")
|
||||
onToggled: checked => Settings.data.audio.volumeOverdrive = checked
|
||||
}
|
||||
}
|
||||
|
||||
NFilePicker {
|
||||
id: volumeFeedbackFilePicker
|
||||
title: I18n.tr("panels.audio.volumes-feedback-sound-file-select-title")
|
||||
selectionMode: "files"
|
||||
initialPath: Quickshell.env("HOME")
|
||||
nameFilters: ["*.wav", "*.mp3", "*.ogg", "*.flac", "*.m4a", "*.aac"]
|
||||
onAccepted: paths => {
|
||||
if (paths.length > 0) {
|
||||
Settings.data.audio.volumeFeedbackSoundFile = paths[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+379
@@ -0,0 +1,379 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.Compositor
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.bar.appearance-position-label")
|
||||
description: I18n.tr("panels.bar.appearance-position-description")
|
||||
model: [
|
||||
{
|
||||
"key": "top",
|
||||
"name": I18n.tr("positions.top")
|
||||
},
|
||||
{
|
||||
"key": "bottom",
|
||||
"name": I18n.tr("positions.bottom")
|
||||
},
|
||||
{
|
||||
"key": "left",
|
||||
"name": I18n.tr("positions.left")
|
||||
},
|
||||
{
|
||||
"key": "right",
|
||||
"name": I18n.tr("positions.right")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.bar.position
|
||||
defaultValue: Settings.getDefaultValue("bar.position")
|
||||
onSelected: key => Settings.data.bar.position = key
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.bar.appearance-density-label")
|
||||
description: I18n.tr("panels.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")
|
||||
},
|
||||
{
|
||||
"key": "spacious",
|
||||
"name": I18n.tr("options.bar.density-spacious")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.bar.density
|
||||
defaultValue: Settings.getDefaultValue("bar.density")
|
||||
onSelected: key => Settings.data.bar.density = key
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.bar.appearance-type-label")
|
||||
description: I18n.tr("panels.bar.appearance-type-description")
|
||||
model: [
|
||||
{
|
||||
"key": "simple",
|
||||
"name": I18n.tr("options.bar.type-simple")
|
||||
},
|
||||
{
|
||||
"key": "floating",
|
||||
"name": I18n.tr("options.bar.type-floating")
|
||||
},
|
||||
{
|
||||
"key": "framed",
|
||||
"name": I18n.tr("options.bar.type-framed")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.bar.barType
|
||||
defaultValue: Settings.getDefaultValue("bar.barType")
|
||||
onSelected: key => {
|
||||
Settings.data.bar.barType = key;
|
||||
}
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("common.display-mode")
|
||||
description: I18n.tr("panels.bar.appearance-display-mode-description")
|
||||
model: [
|
||||
{
|
||||
"key": "always_visible",
|
||||
"name": I18n.tr("hide-modes.visible")
|
||||
},
|
||||
{
|
||||
"key": "non_exclusive",
|
||||
"name": I18n.tr("hide-modes.non-exclusive")
|
||||
},
|
||||
{
|
||||
"key": "auto_hide",
|
||||
"name": I18n.tr("hide-modes.auto-hide")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.bar.displayMode
|
||||
defaultValue: Settings.getDefaultValue("bar.displayMode")
|
||||
onSelected: key => Settings.data.bar.displayMode = key
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.bar.appearance-use-separate-opacity-label")
|
||||
description: I18n.tr("panels.bar.appearance-use-separate-opacity-description")
|
||||
checked: Settings.data.bar.useSeparateOpacity
|
||||
defaultValue: Settings.getDefaultValue("bar.useSeparateOpacity")
|
||||
onToggled: checked => Settings.data.bar.useSeparateOpacity = checked
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
visible: Settings.data.bar.useSeparateOpacity
|
||||
label: I18n.tr("panels.bar.appearance-background-opacity-label")
|
||||
description: I18n.tr("panels.bar.appearance-background-opacity-description")
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
showReset: true
|
||||
value: Settings.data.bar.backgroundOpacity
|
||||
defaultValue: Settings.getDefaultValue("bar.backgroundOpacity")
|
||||
onMoved: value => Settings.data.bar.backgroundOpacity = value
|
||||
text: Math.floor(Settings.data.bar.backgroundOpacity * 100) + "%"
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.bar.appearance-font-scale-label")
|
||||
description: I18n.tr("panels.bar.appearance-font-scale-description")
|
||||
from: 0.5
|
||||
to: 2.0
|
||||
stepSize: 0.01
|
||||
showReset: true
|
||||
value: Settings.data.bar.fontScale
|
||||
defaultValue: Settings.getDefaultValue("bar.fontScale")
|
||||
onMoved: value => Settings.data.bar.fontScale = value
|
||||
text: Math.floor(Settings.data.bar.fontScale * 100) + "%"
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.bar.appearance-widget-spacing-label")
|
||||
description: I18n.tr("panels.bar.appearance-widget-spacing-description")
|
||||
from: 0
|
||||
to: 30
|
||||
stepSize: 1
|
||||
showReset: true
|
||||
value: Settings.data.bar.widgetSpacing
|
||||
defaultValue: Settings.getDefaultValue("bar.widgetSpacing")
|
||||
onMoved: value => Settings.data.bar.widgetSpacing = value
|
||||
text: Settings.data.bar.widgetSpacing + "px"
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.bar.appearance-content-padding-label")
|
||||
description: I18n.tr("panels.bar.appearance-content-padding-description")
|
||||
from: 0
|
||||
to: 30
|
||||
stepSize: 1
|
||||
showReset: true
|
||||
value: Settings.data.bar.contentPadding
|
||||
defaultValue: Settings.getDefaultValue("bar.contentPadding")
|
||||
onMoved: value => Settings.data.bar.contentPadding = value
|
||||
text: Settings.data.bar.contentPadding + "px"
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.bar.appearance-show-outline-label")
|
||||
description: I18n.tr("panels.bar.appearance-show-outline-description")
|
||||
checked: Settings.data.bar.showOutline
|
||||
defaultValue: Settings.getDefaultValue("bar.showOutline")
|
||||
onToggled: checked => Settings.data.bar.showOutline = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.bar.appearance-show-capsule-label")
|
||||
description: I18n.tr("panels.bar.appearance-show-capsule-description")
|
||||
checked: Settings.data.bar.showCapsule
|
||||
defaultValue: Settings.getDefaultValue("bar.showCapsule")
|
||||
onToggled: checked => Settings.data.bar.showCapsule = checked
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
Layout.fillWidth: true
|
||||
visible: Settings.data.bar.showCapsule
|
||||
label: I18n.tr("panels.bar.appearance-capsule-color-label")
|
||||
description: I18n.tr("panels.bar.appearance-capsule-color-description")
|
||||
noneColor: Color.mSurfaceVariant
|
||||
noneOnColor: Color.mOnSurfaceVariant
|
||||
currentKey: Settings.data.bar.capsuleColorKey
|
||||
onSelected: key => Settings.data.bar.capsuleColorKey = key
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
visible: Settings.data.bar.showCapsule
|
||||
label: I18n.tr("panels.bar.appearance-capsule-opacity-label")
|
||||
description: I18n.tr("panels.bar.appearance-capsule-opacity-description")
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
showReset: true
|
||||
value: Settings.data.bar.capsuleOpacity
|
||||
defaultValue: Settings.getDefaultValue("bar.capsuleOpacity")
|
||||
onMoved: value => Settings.data.bar.capsuleOpacity = value
|
||||
text: Math.floor(Settings.data.bar.capsuleOpacity * 100) + "%"
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.bar.appearance-enable-exclusion-zone-inset-label")
|
||||
description: I18n.tr("panels.bar.appearance-enable-exclusion-zone-inset-description")
|
||||
checked: Settings.data.bar.enableExclusionZoneInset
|
||||
defaultValue: Settings.getDefaultValue("bar.enableExclusionZoneInset")
|
||||
onToggled: checked => Settings.data.bar.enableExclusionZoneInset = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
visible: CompositorService.isNiri
|
||||
label: I18n.tr("panels.bar.appearance-hide-on-overview-label")
|
||||
description: I18n.tr("panels.bar.appearance-hide-on-overview-description")
|
||||
checked: Settings.data.bar.hideOnOverview
|
||||
defaultValue: Settings.getDefaultValue("bar.hideOnOverview")
|
||||
onToggled: checked => Settings.data.bar.hideOnOverview = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.bar.appearance-outer-corners-label")
|
||||
description: I18n.tr("panels.bar.appearance-outer-corners-description")
|
||||
checked: Settings.data.bar.outerCorners
|
||||
visible: Settings.data.bar.barType === "simple"
|
||||
defaultValue: Settings.getDefaultValue("bar.outerCorners")
|
||||
onToggled: checked => Settings.data.bar.outerCorners = checked
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
visible: Settings.data.bar.barType === "framed"
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("panels.bar.appearance-frame-settings-label")
|
||||
description: I18n.tr("panels.bar.appearance-frame-settings-description")
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginL
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.bar.appearance-frame-thickness")
|
||||
from: 4
|
||||
to: 24
|
||||
stepSize: 1
|
||||
showReset: true
|
||||
value: Settings.data.bar.frameThickness
|
||||
defaultValue: Settings.getDefaultValue("bar.frameThickness")
|
||||
onMoved: value => Settings.data.bar.frameThickness = value
|
||||
text: Settings.data.bar.frameThickness + "px"
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.bar.appearance-frame-radius")
|
||||
from: 4
|
||||
to: 24
|
||||
stepSize: 1
|
||||
showReset: true
|
||||
value: Settings.data.bar.frameRadius
|
||||
defaultValue: Settings.getDefaultValue("bar.frameRadius")
|
||||
onMoved: value => Settings.data.bar.frameRadius = value
|
||||
text: Settings.data.bar.frameRadius + "px"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
visible: Settings.data.bar.barType === "floating"
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
label: I18n.tr("panels.bar.appearance-margins-vertical")
|
||||
description: I18n.tr("panels.bar.appearance-margins-description")
|
||||
from: 0
|
||||
to: 500
|
||||
suffix: "px"
|
||||
value: Settings.data.bar.marginVertical
|
||||
defaultValue: Settings.getDefaultValue("bar.marginVertical")
|
||||
onValueChanged: Settings.data.bar.marginVertical = value
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
label: I18n.tr("panels.bar.appearance-margins-horizontal")
|
||||
description: I18n.tr("panels.bar.appearance-margins-description")
|
||||
from: 0
|
||||
to: 500
|
||||
suffix: "px"
|
||||
value: Settings.data.bar.marginHorizontal
|
||||
defaultValue: Settings.getDefaultValue("bar.marginHorizontal")
|
||||
onValueChanged: Settings.data.bar.marginHorizontal = value
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginS
|
||||
visible: Settings.data.bar.displayMode === "auto_hide"
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
visible: Settings.data.bar.displayMode === "auto_hide"
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.bar.appearance-auto-hide-delay-label")
|
||||
description: I18n.tr("panels.bar.appearance-auto-hide-delay-description")
|
||||
from: 100
|
||||
to: 2000
|
||||
stepSize: 100
|
||||
showReset: true
|
||||
value: Settings.data.bar.autoHideDelay
|
||||
defaultValue: Settings.getDefaultValue("bar.autoHideDelay")
|
||||
onMoved: value => Settings.data.bar.autoHideDelay = value
|
||||
text: Settings.data.bar.autoHideDelay + "ms"
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.bar.appearance-auto-show-delay-label")
|
||||
description: I18n.tr("panels.bar.appearance-auto-show-delay-description")
|
||||
from: 0
|
||||
to: 500
|
||||
stepSize: 50
|
||||
showReset: true
|
||||
value: Settings.data.bar.autoShowDelay
|
||||
defaultValue: Settings.getDefaultValue("bar.autoShowDelay")
|
||||
onMoved: value => Settings.data.bar.autoShowDelay = value
|
||||
text: Settings.data.bar.autoShowDelay + "ms"
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.bar.appearance-show-on-workspace-switch-label")
|
||||
description: I18n.tr("panels.bar.appearance-show-on-workspace-switch-description")
|
||||
checked: Settings.data.bar.showOnWorkspaceSwitch
|
||||
defaultValue: Settings.getDefaultValue("bar.showOnWorkspaceSwitch")
|
||||
onToggled: checked => Settings.data.bar.showOnWorkspaceSwitch = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.Noctalia
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 0
|
||||
|
||||
// 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;
|
||||
});
|
||||
}
|
||||
|
||||
// Signal functions for widgets sub-tab (global widgets only).
|
||||
// These intentionally edit Settings.data.bar.widgets (global defaults),
|
||||
// not per-screen overrides. Per-screen editing is handled by MonitorWidgetsConfig.qml.
|
||||
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) {
|
||||
newWidget[key] = metadata[key];
|
||||
});
|
||||
}
|
||||
}
|
||||
Settings.data.bar.widgets[section].push(newWidget);
|
||||
BarService.widgetsRevision++;
|
||||
}
|
||||
|
||||
function _removeWidgetFromSection(section, index) {
|
||||
var widgets = Settings.data.bar.widgets;
|
||||
if (index >= 0 && index < widgets[section].length) {
|
||||
var newArray = widgets[section].slice();
|
||||
var removedWidgets = newArray.splice(index, 1);
|
||||
widgets[section] = newArray;
|
||||
BarService.widgetsRevision++;
|
||||
|
||||
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"), 6000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _reorderWidgetInSection(section, fromIndex, toIndex) {
|
||||
var widgets = Settings.data.bar.widgets;
|
||||
if (fromIndex >= 0 && fromIndex < widgets[section].length && toIndex >= 0 && toIndex < widgets[section].length) {
|
||||
var newArray = widgets[section].slice();
|
||||
var item = newArray[fromIndex];
|
||||
newArray.splice(fromIndex, 1);
|
||||
newArray.splice(toIndex, 0, item);
|
||||
widgets[section] = newArray;
|
||||
BarService.widgetsRevision++;
|
||||
}
|
||||
}
|
||||
|
||||
// Note: _updateWidgetSettingsInSection does NOT increment revision
|
||||
// because it only changes settings, not widget structure
|
||||
function _updateWidgetSettingsInSection(section, index, settings) {
|
||||
Settings.data.bar.widgets[section][index] = settings;
|
||||
}
|
||||
|
||||
function _moveWidgetBetweenSections(fromSection, index, toSection) {
|
||||
var widgets = Settings.data.bar.widgets;
|
||||
if (index >= 0 && index < widgets[fromSection].length) {
|
||||
var widget = widgets[fromSection][index];
|
||||
var sourceArray = widgets[fromSection].slice();
|
||||
sourceArray.splice(index, 1);
|
||||
widgets[fromSection] = sourceArray;
|
||||
var targetArray = widgets[toSection].slice();
|
||||
targetArray.push(widget);
|
||||
widgets[toSection] = targetArray;
|
||||
BarService.widgetsRevision++;
|
||||
Logger.d("BarTab", "_moveWidgetBetweenSections: revision now", BarService.widgetsRevision);
|
||||
}
|
||||
}
|
||||
|
||||
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["arrow-bar-to-left"] = true;
|
||||
else if (section === "center")
|
||||
locations["layout-columns"] = true;
|
||||
else if (section === "right")
|
||||
locations["arrow-bar-to-right"] = true;
|
||||
}
|
||||
}
|
||||
return Object.keys(locations);
|
||||
}
|
||||
|
||||
function createBadges(isPlugin, locations) {
|
||||
const badges = [];
|
||||
if (isPlugin) {
|
||||
badges.push({
|
||||
"icon": "plugin",
|
||||
"color": Color.mSecondary
|
||||
});
|
||||
}
|
||||
locations.forEach(function (location) {
|
||||
badges.push({
|
||||
"icon": location,
|
||||
"color": Color.mOnSurfaceVariant
|
||||
});
|
||||
});
|
||||
return badges;
|
||||
}
|
||||
|
||||
function updateAvailableWidgetsModel() {
|
||||
availableWidgets.clear();
|
||||
const widgets = BarWidgetRegistry.getAvailableWidgets();
|
||||
widgets.forEach(entry => {
|
||||
const isPlugin = BarWidgetRegistry.isPluginWidget(entry);
|
||||
let displayName = entry;
|
||||
if (isPlugin) {
|
||||
const pluginId = entry.replace("plugin:", "");
|
||||
const manifest = PluginRegistry.getPluginManifest(pluginId);
|
||||
if (manifest && manifest.name) {
|
||||
displayName = manifest.name;
|
||||
} else {
|
||||
displayName = pluginId;
|
||||
}
|
||||
}
|
||||
availableWidgets.append({
|
||||
"key": entry,
|
||||
"name": displayName,
|
||||
"badges": createBadges(isPlugin, getWidgetLocations(entry))
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: availableWidgets
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
Qt.callLater(updateAvailableWidgetsModel);
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: BarService
|
||||
function onActiveWidgetsChanged() {
|
||||
updateAvailableWidgetsModel();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: BarWidgetRegistry
|
||||
function onPluginWidgetRegistryUpdated() {
|
||||
updateAvailableWidgetsModel();
|
||||
}
|
||||
}
|
||||
|
||||
NTabBar {
|
||||
id: subTabBar
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: Style.marginM
|
||||
distributeEvenly: true
|
||||
currentIndex: tabView.currentIndex
|
||||
|
||||
NTabButton {
|
||||
text: I18n.tr("common.appearance")
|
||||
tabIndex: 0
|
||||
checked: subTabBar.currentIndex === 0
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("common.widgets")
|
||||
tabIndex: 1
|
||||
checked: subTabBar.currentIndex === 1
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("common.behavior")
|
||||
tabIndex: 2
|
||||
checked: subTabBar.currentIndex === 2
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("common.monitors")
|
||||
tabIndex: 3
|
||||
checked: subTabBar.currentIndex === 3
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.marginS
|
||||
}
|
||||
|
||||
NTabView {
|
||||
id: tabView
|
||||
currentIndex: subTabBar.currentIndex
|
||||
|
||||
AppearanceSubTab {}
|
||||
WidgetsSubTab {
|
||||
availableWidgets: availableWidgets
|
||||
addWidgetToSection: root._addWidgetToSection
|
||||
removeWidgetFromSection: root._removeWidgetFromSection
|
||||
reorderWidgetInSection: root._reorderWidgetInSection
|
||||
updateWidgetSettingsInSection: root._updateWidgetSettingsInSection
|
||||
moveWidgetBetweenSections: root._moveWidgetBetweenSections
|
||||
onOpenPluginSettings: manifest => pluginSettingsDialog.openPluginSettings(manifest)
|
||||
}
|
||||
BehaviorSubTab {}
|
||||
MonitorsSubTab {
|
||||
addMonitor: root.addMonitor
|
||||
removeMonitor: root.removeMonitor
|
||||
}
|
||||
}
|
||||
|
||||
NPluginSettingsPopup {
|
||||
id: pluginSettingsDialog
|
||||
parent: Overlay.overlay
|
||||
showToastOnSave: false
|
||||
}
|
||||
}
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.Compositor
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
readonly property string effectiveWheelAction: Settings.data.bar.mouseWheelAction || "none"
|
||||
readonly property string effectiveMiddleClickAction: Settings.data.bar.middleClickAction || "none"
|
||||
readonly property string effectiveRightClickAction: Settings.data.bar.rightClickAction || "controlCenter"
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.bar.behavior-workspace-scroll-label")
|
||||
description: I18n.tr("panels.bar.behavior-workspace-scroll-description")
|
||||
model: {
|
||||
var items = [
|
||||
{
|
||||
"key": "none",
|
||||
"name": I18n.tr("common.none")
|
||||
},
|
||||
{
|
||||
"key": "volume",
|
||||
"name": I18n.tr("common.volume")
|
||||
},
|
||||
{
|
||||
"key": "workspace",
|
||||
"name": I18n.tr("panels.bar.behavior-workspace-scroll-option-workspace")
|
||||
}
|
||||
];
|
||||
if (CompositorService.isNiri) {
|
||||
items.push({
|
||||
"key": "content",
|
||||
"name": I18n.tr("panels.bar.behavior-workspace-scroll-option-content")
|
||||
});
|
||||
}
|
||||
return items;
|
||||
}
|
||||
currentKey: root.effectiveWheelAction
|
||||
defaultValue: Settings.getDefaultValue("bar.mouseWheelAction")
|
||||
onSelected: key => Settings.data.bar.mouseWheelAction = key
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.general.reverse-scrolling-label")
|
||||
description: I18n.tr("panels.general.reverse-scrolling-description")
|
||||
checked: Settings.data.bar.reverseScroll
|
||||
defaultValue: Settings.getDefaultValue("bar.reverseScroll")
|
||||
onToggled: checked => Settings.data.bar.reverseScroll = checked
|
||||
visible: Settings.data.bar.mouseWheelAction !== "none"
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.bar.behavior-wheel-wrap-label")
|
||||
description: I18n.tr("panels.bar.behavior-wheel-wrap-description")
|
||||
checked: Settings.data.bar.mouseWheelWrap
|
||||
defaultValue: Settings.getDefaultValue("bar.mouseWheelWrap")
|
||||
onToggled: checked => Settings.data.bar.mouseWheelWrap = checked
|
||||
visible: Settings.data.bar.mouseWheelAction === "workspace"
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.bar.behavior-middle-click-label")
|
||||
description: I18n.tr("panels.bar.behavior-middle-click-description")
|
||||
model: [
|
||||
{
|
||||
"key": "none",
|
||||
"name": I18n.tr("common.none")
|
||||
},
|
||||
{
|
||||
"key": "controlCenter",
|
||||
"name": I18n.tr("tooltips.open-control-center")
|
||||
},
|
||||
{
|
||||
"key": "settings",
|
||||
"name": I18n.tr("tooltips.open-settings")
|
||||
},
|
||||
{
|
||||
"key": "launcherPanel",
|
||||
"name": I18n.tr("actions.open-launcher")
|
||||
},
|
||||
{
|
||||
"key": "command",
|
||||
"name": I18n.tr("actions.run-custom-command")
|
||||
}
|
||||
]
|
||||
currentKey: root.effectiveMiddleClickAction
|
||||
defaultValue: Settings.getDefaultValue("bar.middleClickAction")
|
||||
onSelected: key => Settings.data.bar.middleClickAction = key
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.bar.behavior-middle-click-command-label")
|
||||
description: I18n.tr("panels.bar.behavior-middle-click-command-description")
|
||||
placeholderText: I18n.tr("panels.bar.behavior-middle-click-command-placeholder")
|
||||
text: Settings.data.bar.middleClickCommand
|
||||
fontFamily: Settings.data.ui.fontFixed
|
||||
onTextChanged: Settings.data.bar.middleClickCommand = text
|
||||
visible: Settings.data.bar.middleClickAction === "command"
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.bar.behavior-middle-click-follow-mouse-label")
|
||||
description: I18n.tr("panels.bar.behavior-middle-click-follow-mouse-description")
|
||||
checked: Settings.data.bar.middleClickFollowMouse
|
||||
defaultValue: Settings.getDefaultValue("bar.middleClickFollowMouse")
|
||||
onToggled: checked => Settings.data.bar.middleClickFollowMouse = checked
|
||||
visible: Settings.data.bar.middleClickAction !== "none" && Settings.data.bar.middleClickAction !== "command" && !(Settings.data.bar.middleClickAction === "settings" && Settings.data.ui.settingsPanelMode === "window")
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.bar.behavior-right-click-label")
|
||||
description: I18n.tr("panels.bar.behavior-right-click-description")
|
||||
model: [
|
||||
{
|
||||
"key": "none",
|
||||
"name": I18n.tr("common.none")
|
||||
},
|
||||
{
|
||||
"key": "controlCenter",
|
||||
"name": I18n.tr("tooltips.open-control-center")
|
||||
},
|
||||
{
|
||||
"key": "settings",
|
||||
"name": I18n.tr("tooltips.open-settings")
|
||||
},
|
||||
{
|
||||
"key": "launcherPanel",
|
||||
"name": I18n.tr("actions.open-launcher")
|
||||
},
|
||||
{
|
||||
"key": "command",
|
||||
"name": I18n.tr("actions.run-custom-command")
|
||||
}
|
||||
]
|
||||
currentKey: root.effectiveRightClickAction
|
||||
defaultValue: Settings.getDefaultValue("bar.rightClickAction")
|
||||
onSelected: key => Settings.data.bar.rightClickAction = key
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.bar.behavior-right-click-command-label")
|
||||
description: I18n.tr("panels.bar.behavior-right-click-command-description")
|
||||
placeholderText: I18n.tr("panels.bar.behavior-right-click-command-placeholder")
|
||||
text: Settings.data.bar.rightClickCommand
|
||||
fontFamily: Settings.data.ui.fontFixed
|
||||
onTextChanged: Settings.data.bar.rightClickCommand = text
|
||||
visible: Settings.data.bar.rightClickAction === "command"
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.bar.behavior-right-click-follow-mouse-label")
|
||||
description: I18n.tr("panels.bar.behavior-right-click-follow-mouse-description")
|
||||
checked: Settings.data.bar.rightClickFollowMouse
|
||||
defaultValue: Settings.getDefaultValue("bar.rightClickFollowMouse")
|
||||
onToggled: checked => Settings.data.bar.rightClickFollowMouse = checked
|
||||
visible: Settings.data.bar.rightClickAction !== "none" && Settings.data.bar.rightClickAction !== "command" && !(Settings.data.bar.rightClickAction === "settings" && Settings.data.ui.settingsPanelMode === "window")
|
||||
}
|
||||
}
|
||||
+276
@@ -0,0 +1,276 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import "../../Bar" as BarSettings
|
||||
import qs.Commons
|
||||
import qs.Services.Compositor
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
property var addMonitor
|
||||
property var removeMonitor
|
||||
|
||||
NText {
|
||||
text: I18n.tr("panels.bar.monitors-desc-new")
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Monitor cards
|
||||
Repeater {
|
||||
model: Quickshell.screens || []
|
||||
delegate: NBox {
|
||||
id: monitorCard
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: cardContent.implicitHeight + Style.margin2L
|
||||
color: Color.mSurface
|
||||
|
||||
required property var modelData
|
||||
readonly property string screenName: modelData.name || "Unknown"
|
||||
readonly property real compositorScale: {
|
||||
const info = CompositorService.displayScales[screenName];
|
||||
return (info && info.scale) ? info.scale : 1.0;
|
||||
}
|
||||
readonly property bool barEnabled: (Settings.data.bar.monitors || []).indexOf(screenName) !== -1
|
||||
readonly property bool hasOverride: Settings.hasScreenOverride(screenName)
|
||||
|
||||
// Track if override is enabled (controls both visibility AND whether overrides are applied)
|
||||
readonly property bool overrideEnabled: Settings.isScreenOverrideEnabled(screenName)
|
||||
|
||||
// Get effective values for this screen
|
||||
readonly property string effectivePosition: Settings.getBarPositionForScreen(screenName)
|
||||
readonly property string effectiveDensity: Settings.getBarDensityForScreen(screenName)
|
||||
|
||||
ColumnLayout {
|
||||
id: cardContent
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginL
|
||||
spacing: Style.marginM
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
// Header: Monitor name and specs
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginXXS
|
||||
|
||||
NText {
|
||||
Layout.fillWidth: true
|
||||
text: monitorCard.screenName
|
||||
pointSize: Style.fontSizeM
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
text: {
|
||||
return I18n.tr("system.monitor-description", {
|
||||
"model": monitorCard.modelData.model || I18n.tr("common.unknown"),
|
||||
"width": Math.round(monitorCard.modelData.width * monitorCard.compositorScale),
|
||||
"height": Math.round(monitorCard.modelData.height * monitorCard.compositorScale),
|
||||
"scale": monitorCard.compositorScale
|
||||
});
|
||||
}
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
}
|
||||
|
||||
// Enable bar toggle
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
checked: monitorCard.barEnabled
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
Settings.data.bar.monitors = root.addMonitor(Settings.data.bar.monitors, monitorCard.screenName);
|
||||
} else {
|
||||
Settings.data.bar.monitors = root.removeMonitor(Settings.data.bar.monitors, monitorCard.screenName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
visible: Settings.data.bar.monitors.includes(monitorCard.screenName)
|
||||
}
|
||||
|
||||
// Override section (only visible when bar is enabled)
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
visible: monitorCard.barEnabled
|
||||
|
||||
// Override toggle
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.bar.monitor-override-settings")
|
||||
description: I18n.tr("panels.bar.monitor-override-settings-description")
|
||||
checked: monitorCard.overrideEnabled
|
||||
onToggled: checked => {
|
||||
Settings.setScreenOverride(monitorCard.screenName, "enabled", checked);
|
||||
BarService.widgetsRevision++;
|
||||
}
|
||||
}
|
||||
|
||||
// Override controls (only visible when override toggle is on)
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
visible: monitorCard.overrideEnabled
|
||||
|
||||
// Position override
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.bar.appearance-position-label")
|
||||
description: I18n.tr("panels.bar.appearance-position-description")
|
||||
model: [
|
||||
{
|
||||
"key": "top",
|
||||
"name": I18n.tr("positions.top")
|
||||
},
|
||||
{
|
||||
"key": "bottom",
|
||||
"name": I18n.tr("positions.bottom")
|
||||
},
|
||||
{
|
||||
"key": "left",
|
||||
"name": I18n.tr("positions.left")
|
||||
},
|
||||
{
|
||||
"key": "right",
|
||||
"name": I18n.tr("positions.right")
|
||||
}
|
||||
]
|
||||
currentKey: monitorCard.effectivePosition
|
||||
onSelected: key => Settings.setScreenOverride(monitorCard.screenName, "position", key)
|
||||
}
|
||||
}
|
||||
|
||||
// Density override
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.bar.appearance-density-label")
|
||||
description: I18n.tr("panels.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")
|
||||
},
|
||||
{
|
||||
"key": "spacious",
|
||||
"name": I18n.tr("options.bar.density-spacious")
|
||||
}
|
||||
]
|
||||
currentKey: monitorCard.effectiveDensity
|
||||
onSelected: key => Settings.setScreenOverride(monitorCard.screenName, "density", key)
|
||||
}
|
||||
}
|
||||
|
||||
// DisplayMode override
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("common.display-mode")
|
||||
description: I18n.tr("panels.bar.appearance-display-mode-description")
|
||||
model: [
|
||||
{
|
||||
"key": "always_visible",
|
||||
"name": I18n.tr("hide-modes.visible")
|
||||
},
|
||||
{
|
||||
"key": "non_exclusive",
|
||||
"name": I18n.tr("hide-modes.non-exclusive")
|
||||
},
|
||||
{
|
||||
"key": "auto_hide",
|
||||
"name": I18n.tr("hide-modes.auto-hide")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.getBarDisplayModeForScreen(monitorCard.screenName)
|
||||
onSelected: key => Settings.setScreenOverride(monitorCard.screenName, "displayMode", key)
|
||||
}
|
||||
}
|
||||
|
||||
// Widgets configuration button and Reset all
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
|
||||
NButton {
|
||||
id: widgetConfigButton
|
||||
property bool expanded: false
|
||||
Layout.fillWidth: true
|
||||
fontSize: Style.fontSizeS
|
||||
text: I18n.tr("panels.bar.monitor-configure-widgets")
|
||||
icon: expanded ? "chevron-up" : "layout-grid"
|
||||
onClicked: expanded = !expanded
|
||||
}
|
||||
|
||||
NButton {
|
||||
visible: Settings.hasScreenOverride(monitorCard.screenName, "widgets")
|
||||
Layout.fillWidth: true
|
||||
fontSize: Style.fontSizeS
|
||||
text: I18n.tr("panels.bar.use-global-widgets")
|
||||
icon: "refresh"
|
||||
onClicked: {
|
||||
Settings.clearScreenOverride(monitorCard.screenName, "widgets");
|
||||
BarService.widgetsRevision++;
|
||||
}
|
||||
}
|
||||
|
||||
NButton {
|
||||
Layout.fillWidth: true
|
||||
fontSize: Style.fontSizeS
|
||||
text: I18n.tr("panels.bar.monitor-reset-all")
|
||||
icon: "restore"
|
||||
onClicked: {
|
||||
Settings.clearScreenOverride(monitorCard.screenName);
|
||||
BarService.widgetsRevision++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Inline widget configuration
|
||||
BarSettings.MonitorWidgetsConfig {
|
||||
visible: widgetConfigButton.expanded
|
||||
screen: monitorCard.modelData
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginS
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
property var availableWidgets
|
||||
property var addWidgetToSection
|
||||
property var removeWidgetFromSection
|
||||
property var reorderWidgetInSection
|
||||
property var updateWidgetSettingsInSection
|
||||
property var moveWidgetBetweenSections
|
||||
|
||||
signal openPluginSettings(var manifest)
|
||||
|
||||
// This sub-tab edits the global default widget configuration (Settings.data.bar.widgets).
|
||||
// Per-screen widget overrides are edited in MonitorWidgetsConfig.qml (Monitors sub-tab).
|
||||
|
||||
// determine bar orientation
|
||||
readonly property string barPosition: Settings.data.bar.position
|
||||
readonly property bool barIsVertical: barPosition === "left" || barPosition === "right"
|
||||
|
||||
function getSectionIcons() {
|
||||
return {
|
||||
"left": "arrow-bar-to-up",
|
||||
"center": "layout-distribute-horizontal",
|
||||
"right": "arrow-bar-to-down"
|
||||
};
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("panels.bar.widgets-desc")
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Left Section
|
||||
NSectionEditor {
|
||||
sectionName: root.barIsVertical ? I18n.tr("positions.top") : I18n.tr("positions.left")
|
||||
sectionId: "left"
|
||||
barIsVertical: root.barIsVertical
|
||||
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml")
|
||||
widgetRegistry: BarWidgetRegistry
|
||||
widgetModel: Settings.data.bar.widgets.left
|
||||
sectionIcons: root.getSectionIcons()
|
||||
availableWidgets: root.availableWidgets
|
||||
onAddWidget: (widgetId, section) => root.addWidgetToSection(widgetId, section)
|
||||
onRemoveWidget: (section, index) => root.removeWidgetFromSection(section, index)
|
||||
onReorderWidget: (section, fromIndex, toIndex) => root.reorderWidgetInSection(section, fromIndex, toIndex)
|
||||
onUpdateWidgetSettings: (section, index, settings) => root.updateWidgetSettingsInSection(section, index, settings)
|
||||
onMoveWidget: (fromSection, index, toSection) => root.moveWidgetBetweenSections(fromSection, index, toSection)
|
||||
onOpenPluginSettingsRequested: manifest => root.openPluginSettings(manifest)
|
||||
}
|
||||
|
||||
// Center Section
|
||||
NSectionEditor {
|
||||
sectionName: I18n.tr("positions.center")
|
||||
sectionId: "center"
|
||||
barIsVertical: root.barIsVertical
|
||||
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml")
|
||||
widgetRegistry: BarWidgetRegistry
|
||||
widgetModel: Settings.data.bar.widgets.center
|
||||
sectionIcons: root.getSectionIcons()
|
||||
availableWidgets: root.availableWidgets
|
||||
onAddWidget: (widgetId, section) => root.addWidgetToSection(widgetId, section)
|
||||
onRemoveWidget: (section, index) => root.removeWidgetFromSection(section, index)
|
||||
onReorderWidget: (section, fromIndex, toIndex) => root.reorderWidgetInSection(section, fromIndex, toIndex)
|
||||
onUpdateWidgetSettings: (section, index, settings) => root.updateWidgetSettingsInSection(section, index, settings)
|
||||
onMoveWidget: (fromSection, index, toSection) => root.moveWidgetBetweenSections(fromSection, index, toSection)
|
||||
onOpenPluginSettingsRequested: manifest => root.openPluginSettings(manifest)
|
||||
}
|
||||
|
||||
// Right Section
|
||||
NSectionEditor {
|
||||
sectionName: root.barIsVertical ? I18n.tr("positions.bottom") : I18n.tr("positions.right")
|
||||
sectionId: "right"
|
||||
barIsVertical: root.barIsVertical
|
||||
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml")
|
||||
widgetRegistry: BarWidgetRegistry
|
||||
widgetModel: Settings.data.bar.widgets.right
|
||||
sectionIcons: root.getSectionIcons()
|
||||
availableWidgets: root.availableWidgets
|
||||
onAddWidget: (widgetId, section) => root.addWidgetToSection(widgetId, section)
|
||||
onRemoveWidget: (section, index) => root.removeWidgetFromSection(section, index)
|
||||
onReorderWidget: (section, fromIndex, toIndex) => root.reorderWidgetInSection(section, fromIndex, toIndex)
|
||||
onUpdateWidgetSettings: (section, index, settings) => root.updateWidgetSettingsInSection(section, index, settings)
|
||||
onMoveWidget: (fromSection, index, toSection) => root.moveWidgetBetweenSections(fromSection, index, toSection)
|
||||
onOpenPluginSettingsRequested: manifest => root.openPluginSettings(manifest)
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import "."
|
||||
import qs.Commons
|
||||
import qs.Services.System
|
||||
import qs.Services.Theming
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 0
|
||||
|
||||
// Time dropdown options (00:00 .. 23:30)
|
||||
ListModel {
|
||||
id: timeOptions
|
||||
}
|
||||
|
||||
property var screen
|
||||
|
||||
function populateTimeOptions() {
|
||||
for (var h = 0; h < 24; h++) {
|
||||
for (var m = 0; m < 60; m += 30) {
|
||||
var hh = ("0" + h).slice(-2);
|
||||
var mm = ("0" + m).slice(-2);
|
||||
var key = hh + ":" + mm;
|
||||
timeOptions.append({
|
||||
"key": key,
|
||||
"name": key
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
Qt.callLater(populateTimeOptions);
|
||||
}
|
||||
|
||||
// Download popup
|
||||
Loader {
|
||||
id: downloadPopupLoader
|
||||
active: false
|
||||
sourceComponent: SchemeDownloader {
|
||||
parent: Overlay.overlay
|
||||
}
|
||||
|
||||
property bool pendingOpen: false
|
||||
|
||||
function open() {
|
||||
pendingOpen = true;
|
||||
active = true;
|
||||
if (item) {
|
||||
item.open();
|
||||
pendingOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
onItemChanged: {
|
||||
if (item && pendingOpen) {
|
||||
item.open();
|
||||
pendingOpen = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NTabBar {
|
||||
id: subTabBar
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: Style.marginM
|
||||
distributeEvenly: true
|
||||
currentIndex: tabView.currentIndex
|
||||
|
||||
NTabButton {
|
||||
text: I18n.tr("common.colors")
|
||||
tabIndex: 0
|
||||
checked: subTabBar.currentIndex === 0
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("common.templates")
|
||||
tabIndex: 1
|
||||
checked: subTabBar.currentIndex === 1
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.marginL
|
||||
}
|
||||
|
||||
NTabView {
|
||||
id: tabView
|
||||
currentIndex: subTabBar.currentIndex
|
||||
|
||||
ColorsSubTab {
|
||||
screen: root.screen
|
||||
timeOptions: timeOptions
|
||||
onOpenDownloadPopup: downloadPopupLoader.open()
|
||||
}
|
||||
TemplatesSubTab {}
|
||||
}
|
||||
}
|
||||
+458
@@ -0,0 +1,458 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
import qs.Services.System
|
||||
import qs.Services.Theming
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
property var timeOptions
|
||||
property var schemeColorsCache: ({})
|
||||
property int cacheVersion: 0
|
||||
property var screen
|
||||
|
||||
signal openDownloadPopup
|
||||
|
||||
function extractSchemeName(schemePath) {
|
||||
var pathParts = schemePath.split("/");
|
||||
var filename = pathParts[pathParts.length - 1];
|
||||
var schemeName = filename.replace(".json", "");
|
||||
|
||||
if (schemeName === "Noctalia-default") {
|
||||
schemeName = "Noctalia (default)";
|
||||
} else if (schemeName === "Noctalia-legacy") {
|
||||
schemeName = "Noctalia (legacy)";
|
||||
} else if (schemeName === "Tokyo-Night") {
|
||||
schemeName = "Tokyo Night";
|
||||
} else if (schemeName === "Rosepine") {
|
||||
schemeName = "Rose Pine";
|
||||
}
|
||||
|
||||
return schemeName;
|
||||
}
|
||||
|
||||
function getSchemeColor(schemeName, colorKey) {
|
||||
var _ = cacheVersion;
|
||||
|
||||
if (schemeColorsCache[schemeName]) {
|
||||
var entry = schemeColorsCache[schemeName];
|
||||
var variant = entry;
|
||||
|
||||
if (entry.dark || entry.light) {
|
||||
variant = Settings.data.colorSchemes.darkMode ? (entry.dark || entry.light) : (entry.light || entry.dark);
|
||||
}
|
||||
|
||||
if (variant && variant[colorKey]) {
|
||||
return variant[colorKey];
|
||||
}
|
||||
}
|
||||
|
||||
if (colorKey === "mSurface")
|
||||
return Color.mSurfaceVariant;
|
||||
if (colorKey === "mPrimary")
|
||||
return Color.mPrimary;
|
||||
if (colorKey === "mSecondary")
|
||||
return Color.mSecondary;
|
||||
if (colorKey === "mTertiary")
|
||||
return Color.mTertiary;
|
||||
if (colorKey === "mError")
|
||||
return Color.mError;
|
||||
return Color.mOnSurfaceVariant;
|
||||
}
|
||||
|
||||
function schemeLoaded(schemeName, jsonData) {
|
||||
var value = jsonData || {};
|
||||
schemeColorsCache[schemeName] = value;
|
||||
cacheVersion++;
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: ColorSchemeService
|
||||
function onSchemesChanged() {
|
||||
root.schemeColorsCache = {};
|
||||
root.cacheVersion++;
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: fileLoaders
|
||||
visible: false
|
||||
|
||||
Repeater {
|
||||
model: ColorSchemeService.schemes
|
||||
delegate: Item {
|
||||
FileView {
|
||||
path: modelData
|
||||
blockLoading: false
|
||||
onLoaded: {
|
||||
var schemeName = root.extractSchemeName(path);
|
||||
|
||||
try {
|
||||
var jsonData = JSON.parse(text());
|
||||
root.schemeLoaded(schemeName, jsonData);
|
||||
} catch (e) {
|
||||
Logger.w("ColorSchemeTab", "Failed to parse JSON for scheme:", schemeName, e);
|
||||
root.schemeLoaded(schemeName, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("tooltips.switch-to-dark-mode")
|
||||
description: I18n.tr("panels.color-scheme.dark-mode-switch-description")
|
||||
checked: Settings.data.colorSchemes.darkMode
|
||||
onToggled: checked => {
|
||||
Settings.data.colorSchemes.darkMode = checked;
|
||||
root.cacheVersion++;
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.color-scheme.sync-gsettings-label")
|
||||
description: I18n.tr("panels.color-scheme.sync-gsettings-description")
|
||||
checked: Settings.data.colorSchemes.syncGsettings
|
||||
onToggled: checked => {
|
||||
Settings.data.colorSchemes.syncGsettings = checked;
|
||||
if (checked)
|
||||
ColorSchemeService.pushSystemColorScheme();
|
||||
}
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("panels.color-scheme.dark-mode-mode-label")
|
||||
description: I18n.tr("panels.color-scheme.dark-mode-mode-description")
|
||||
|
||||
model: [
|
||||
{
|
||||
"name": I18n.tr("panels.color-scheme.dark-mode-mode-off"),
|
||||
"key": "off"
|
||||
},
|
||||
{
|
||||
"name": I18n.tr("panels.color-scheme.dark-mode-mode-manual"),
|
||||
"key": "manual"
|
||||
},
|
||||
{
|
||||
"name": I18n.tr("common.location"),
|
||||
"key": "location"
|
||||
}
|
||||
]
|
||||
|
||||
currentKey: Settings.data.colorSchemes.schedulingMode
|
||||
|
||||
onSelected: key => {
|
||||
Settings.data.colorSchemes.schedulingMode = key;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS
|
||||
visible: Settings.data.colorSchemes.schedulingMode === "manual"
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("panels.display.night-light-manual-schedule-label")
|
||||
description: I18n.tr("panels.display.night-light-manual-schedule-description")
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: false
|
||||
spacing: Style.marginS
|
||||
|
||||
NText {
|
||||
text: I18n.tr("panels.display.night-light-manual-schedule-sunrise")
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
model: root.timeOptions
|
||||
currentKey: Settings.data.colorSchemes.manualSunrise
|
||||
placeholder: I18n.tr("panels.display.night-light-manual-schedule-select-start")
|
||||
onSelected: key => Settings.data.colorSchemes.manualSunrise = key
|
||||
minimumWidth: 120
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: 20
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("panels.display.night-light-manual-schedule-sunset")
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
model: root.timeOptions
|
||||
currentKey: Settings.data.colorSchemes.manualSunset
|
||||
placeholder: I18n.tr("panels.display.night-light-manual-schedule-select-stop")
|
||||
onSelected: key => Settings.data.colorSchemes.manualSunset = key
|
||||
minimumWidth: 120
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.color-scheme.color-source-use-wallpaper-colors-label")
|
||||
description: I18n.tr("panels.color-scheme.color-source-use-wallpaper-colors-description")
|
||||
checked: Settings.data.colorSchemes.useWallpaperColors
|
||||
onToggled: checked => {
|
||||
Settings.data.colorSchemes.useWallpaperColors = checked;
|
||||
if (checked) {
|
||||
AppThemeService.generate();
|
||||
} else {
|
||||
ToastService.showNotice(I18n.tr("toast.wallpaper-colors.label"), I18n.tr("toast.wallpaper-colors.disabled"), "settings-color-scheme");
|
||||
if (Settings.data.colorSchemes.predefinedScheme) {
|
||||
ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.color-scheme.wallpaper-monitor-source-label")
|
||||
description: I18n.tr("panels.color-scheme.wallpaper-monitor-source-description")
|
||||
enabled: Settings.data.colorSchemes.useWallpaperColors
|
||||
model: {
|
||||
var m = [];
|
||||
if (Quickshell.screens) {
|
||||
for (var i = 0; i < Quickshell.screens.length; i++) {
|
||||
var screen = Quickshell.screens[i];
|
||||
var name = screen.name;
|
||||
var displayName = name + " (" + screen.width + "x" + screen.height + ")";
|
||||
m.push({
|
||||
"key": name,
|
||||
"name": displayName
|
||||
});
|
||||
}
|
||||
}
|
||||
return m;
|
||||
}
|
||||
currentKey: Settings.data.colorSchemes.monitorForColors || (screen ? screen.name : "")
|
||||
onSelected: key => {
|
||||
Settings.data.colorSchemes.monitorForColors = key;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
defaultValue: ""
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.color-scheme.wallpaper-method-label")
|
||||
description: I18n.tr("panels.color-scheme.wallpaper-method-description")
|
||||
enabled: Settings.data.colorSchemes.useWallpaperColors
|
||||
model: TemplateProcessor.schemeTypes
|
||||
currentKey: Settings.data.colorSchemes.generationMethod
|
||||
onSelected: key => {
|
||||
Settings.data.colorSchemes.generationMethod = key;
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
|
||||
NBox {
|
||||
visible: Settings.data.colorSchemes.useWallpaperColors
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: descriptionColumn.implicitHeight + Style.margin2L
|
||||
color: Color.mSurface
|
||||
|
||||
Column {
|
||||
id: descriptionColumn
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: Style.marginL
|
||||
spacing: Style.marginM
|
||||
|
||||
NText {
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
text: I18n.tr("panels.color-scheme.method-description." + Settings.data.colorSchemes.generationMethod)
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
Row {
|
||||
id: colorPreviewRow
|
||||
spacing: Style.marginS
|
||||
|
||||
property int diameter: 16 * Style.uiScaleRatio
|
||||
|
||||
Repeater {
|
||||
model: [Color.mPrimary, Color.mSecondary, Color.mTertiary, Color.mError]
|
||||
|
||||
Rectangle {
|
||||
width: colorPreviewRow.diameter
|
||||
height: colorPreviewRow.diameter
|
||||
radius: width * 0.5
|
||||
color: modelData
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
enabled: !Settings.data.colorSchemes.useWallpaperColors
|
||||
|
||||
NHeader {
|
||||
label: I18n.tr("panels.color-scheme.predefined-title")
|
||||
description: I18n.tr("panels.color-scheme.predefined-desc")
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
columns: 2
|
||||
rowSpacing: Style.marginM
|
||||
columnSpacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
|
||||
Repeater {
|
||||
model: ColorSchemeService.schemes
|
||||
|
||||
Rectangle {
|
||||
id: schemeItem
|
||||
|
||||
property string schemePath: modelData
|
||||
property string schemeName: root.extractSchemeName(modelData)
|
||||
|
||||
opacity: enabled ? 1.0 : 0.6
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
height: 50 * Style.uiScaleRatio
|
||||
radius: Style.radiusS
|
||||
color: root.getSchemeColor(schemeName, "mSurface")
|
||||
border.width: Style.borderL
|
||||
border.color: {
|
||||
if ((Settings.data.colorSchemes.predefinedScheme === schemeName) && schemeItem.enabled) {
|
||||
return Color.mSecondary;
|
||||
}
|
||||
if (itemMouseArea.containsMouse) {
|
||||
return Color.mHover;
|
||||
}
|
||||
return Color.mOutline;
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: scheme
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginL
|
||||
spacing: Style.marginS
|
||||
|
||||
NText {
|
||||
text: schemeItem.schemeName
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
wrapMode: Text.WordWrap
|
||||
maximumLineCount: 1
|
||||
}
|
||||
|
||||
property int diameter: 16 * Style.uiScaleRatio
|
||||
|
||||
Rectangle {
|
||||
width: scheme.diameter
|
||||
height: scheme.diameter
|
||||
radius: scheme.diameter * 0.5
|
||||
color: root.getSchemeColor(schemeItem.schemeName, "mPrimary")
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: scheme.diameter
|
||||
height: scheme.diameter
|
||||
radius: scheme.diameter * 0.5
|
||||
color: root.getSchemeColor(schemeItem.schemeName, "mSecondary")
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: scheme.diameter
|
||||
height: scheme.diameter
|
||||
radius: scheme.diameter * 0.5
|
||||
color: root.getSchemeColor(schemeItem.schemeName, "mTertiary")
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: scheme.diameter
|
||||
height: scheme.diameter
|
||||
radius: scheme.diameter * 0.5
|
||||
color: root.getSchemeColor(schemeItem.schemeName, "mError")
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: itemMouseArea
|
||||
anchors.fill: parent
|
||||
enabled: schemeItem.enabled
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
Settings.data.colorSchemes.useWallpaperColors = false;
|
||||
Logger.i("ColorSchemeTab", "Disabled wallpaper colors");
|
||||
|
||||
Settings.data.colorSchemes.predefinedScheme = schemeItem.schemeName;
|
||||
ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme);
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: (Settings.data.colorSchemes.predefinedScheme === schemeItem.schemeName) && schemeItem.enabled
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.rightMargin: 0
|
||||
anchors.topMargin: -3
|
||||
width: 20
|
||||
height: 20
|
||||
radius: Math.min(Style.radiusL, width / 2)
|
||||
color: Color.mSecondary
|
||||
border.width: Style.borderS
|
||||
border.color: Color.mOnSecondary
|
||||
|
||||
NIcon {
|
||||
icon: "check"
|
||||
pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSecondary
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationNormal
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("panels.color-scheme.download-button")
|
||||
icon: "download"
|
||||
onClicked: root.openDownloadPopup()
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.topMargin: Style.marginS
|
||||
}
|
||||
}
|
||||
}
|
||||
+972
@@ -0,0 +1,972 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
import qs.Services.Theming
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
Popup {
|
||||
id: root
|
||||
|
||||
property var availableSchemes: []
|
||||
property bool fetching: false
|
||||
property bool downloading: false
|
||||
property bool hasInitialData: false // Track if we've loaded data at least once
|
||||
property string downloadError: ""
|
||||
property string downloadingScheme: ""
|
||||
property string pendingApplyScheme: "" // Scheme name to apply after reload
|
||||
property string lastStderrOutput: "" // Store stderr from download process
|
||||
property real lastApiFetchTime: 0 // Track when we last fetched from API to prevent rapid calls
|
||||
property int minApiFetchInterval: 60 // Minimum seconds between API fetches (1 minute)
|
||||
|
||||
// Cache for remote scheme colors
|
||||
property var schemeColorsCache: ({})
|
||||
property int cacheVersion: 0
|
||||
|
||||
// Cache for available schemes list (uses ShellState singleton)
|
||||
property int schemesCacheUpdateFrequency: 2 * 60 * 60 // 2 hours in seconds
|
||||
|
||||
// Cache for repo branch info (to reduce API calls during downloads)
|
||||
property string cachedBranch: "main"
|
||||
property string cachedBranchSha: ""
|
||||
|
||||
width: Math.max(500, contentColumn.implicitWidth + Style.margin2XL)
|
||||
height: Math.min(800, contentColumn.implicitHeight + Style.margin2XL)
|
||||
padding: Style.marginXL
|
||||
modal: true
|
||||
closePolicy: Popup.NoAutoClose
|
||||
dim: false
|
||||
anchors.centerIn: parent
|
||||
|
||||
// Helper function to get color from cached scheme data
|
||||
function getSchemeColor(schemeName, colorKey) {
|
||||
var _ = cacheVersion; // Create dependency
|
||||
|
||||
if (schemeColorsCache[schemeName]) {
|
||||
var entry = schemeColorsCache[schemeName];
|
||||
var variant = entry;
|
||||
|
||||
// Check if scheme has dark/light variants
|
||||
if (entry.dark || entry.light) {
|
||||
variant = Settings.data.colorSchemes.darkMode ? (entry.dark || entry.light) : (entry.light || entry.dark);
|
||||
}
|
||||
|
||||
if (variant && variant[colorKey]) {
|
||||
return variant[colorKey];
|
||||
}
|
||||
}
|
||||
|
||||
// Return visible defaults while loading
|
||||
var defaults = {
|
||||
"mSurface": Color.mSurfaceVariant,
|
||||
"mPrimary": Color.mPrimary,
|
||||
"mSecondary": Color.mSecondary,
|
||||
"mTertiary": Color.mTertiary,
|
||||
"mError": Color.mError,
|
||||
"mOnSurface": Color.mOnSurfaceVariant
|
||||
};
|
||||
return defaults[colorKey] || Color.mOnSurfaceVariant;
|
||||
}
|
||||
|
||||
// Colors are now provided directly in the registry, no need to fetch individual files
|
||||
function fetchSchemeColors(scheme) {
|
||||
return;
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Color.mSurface
|
||||
radius: Style.radiusL
|
||||
border.color: Color.mPrimary
|
||||
border.width: Style.borderM
|
||||
}
|
||||
|
||||
function loadSchemesFromCache() {
|
||||
try {
|
||||
const now = Time.timestamp;
|
||||
const cacheData = ShellState.getColorSchemesList();
|
||||
const cachedSchemes = cacheData.schemes || [];
|
||||
const cachedTimestamp = cacheData.timestamp || 0;
|
||||
|
||||
// Check if cache is expired or missing
|
||||
if (!cachedTimestamp || (now >= cachedTimestamp + schemesCacheUpdateFrequency)) {
|
||||
// Migration is now handled in Settings.qml
|
||||
|
||||
// Only fetch from API if we haven't fetched recently (prevent rapid repeated calls)
|
||||
const timeSinceLastFetch = now - lastApiFetchTime;
|
||||
if (timeSinceLastFetch >= minApiFetchInterval) {
|
||||
Logger.d("ColorSchemeDownload", "Cache expired or missing, fetching new schemes");
|
||||
fetchAvailableSchemesFromAPI();
|
||||
return;
|
||||
} else {
|
||||
// Use cached data even if expired, to avoid rate limits
|
||||
Logger.d("ColorSchemeDownload", "Cache expired but recent API call detected, using cached data");
|
||||
if (cachedSchemes.length > 0) {
|
||||
availableSchemes = cachedSchemes;
|
||||
hasInitialData = true;
|
||||
fetching = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ageMinutes = Math.round((now - cachedTimestamp) / 60);
|
||||
Logger.d("ColorSchemeDownload", "Loading cached schemes from ShellState (age:", ageMinutes, "minutes)");
|
||||
|
||||
if (cachedSchemes.length > 0) {
|
||||
availableSchemes = cachedSchemes;
|
||||
// Restore color cache from cached schemes
|
||||
for (var i = 0; i < cachedSchemes.length; i++) {
|
||||
var cachedScheme = cachedSchemes[i];
|
||||
if (cachedScheme.dark || cachedScheme.light) {
|
||||
schemeColorsCache[cachedScheme.name] = {
|
||||
"dark": cachedScheme.dark || null,
|
||||
"light": cachedScheme.light || null
|
||||
};
|
||||
}
|
||||
}
|
||||
hasInitialData = true;
|
||||
cacheVersion++;
|
||||
fetching = false;
|
||||
} else {
|
||||
// Cache is empty, only fetch if we haven't fetched recently
|
||||
const timeSinceLastFetch = now - lastApiFetchTime;
|
||||
if (timeSinceLastFetch >= minApiFetchInterval) {
|
||||
fetchAvailableSchemesFromAPI();
|
||||
} else {
|
||||
Logger.d("ColorSchemeDownload", "Cache empty but recent API call detected, skipping fetch");
|
||||
fetching = false;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.e("ColorSchemeDownload", "Failed to load schemes from cache:", error);
|
||||
fetching = false;
|
||||
}
|
||||
}
|
||||
|
||||
function saveSchemesToCache() {
|
||||
try {
|
||||
ShellState.setColorSchemesList({
|
||||
schemes: availableSchemes,
|
||||
timestamp: Time.timestamp
|
||||
});
|
||||
Logger.d("ColorSchemeDownload", "Schemes list saved to ShellState");
|
||||
} catch (error) {
|
||||
Logger.e("ColorSchemeDownload", "Failed to save schemes to cache:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function fetchAvailableSchemes() {
|
||||
if (fetching) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to load from ShellState cache first
|
||||
if (typeof ShellState !== 'undefined' && ShellState.isLoaded) {
|
||||
loadSchemesFromCache();
|
||||
} else {
|
||||
// ShellState not ready, fetch directly from API
|
||||
fetchAvailableSchemesFromAPI();
|
||||
}
|
||||
}
|
||||
|
||||
function fetchAvailableSchemesFromAPI() {
|
||||
if (fetching) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we've fetched recently to prevent rapid repeated calls
|
||||
const now = Time.timestamp;
|
||||
const timeSinceLastFetch = now - lastApiFetchTime;
|
||||
if (timeSinceLastFetch < minApiFetchInterval) {
|
||||
Logger.d("ColorSchemeDownload", "Skipping API fetch - too soon since last fetch (", Math.round(timeSinceLastFetch), "s ago)");
|
||||
return;
|
||||
}
|
||||
|
||||
fetching = true;
|
||||
lastApiFetchTime = now;
|
||||
// Don't clear availableSchemes immediately to prevent flicker - keep showing old list while fetching
|
||||
// availableSchemes = [];
|
||||
downloadError = "";
|
||||
|
||||
// Fetch registry.json
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||
fetching = false;
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
var registry = JSON.parse(xhr.responseText);
|
||||
if (registry && registry.themes && Array.isArray(registry.themes)) {
|
||||
var schemes = [];
|
||||
// Process themes
|
||||
for (var i = 0; i < registry.themes.length; i++) {
|
||||
var theme = registry.themes[i];
|
||||
schemes.push({
|
||||
"name": theme.name,
|
||||
"path": theme.path,
|
||||
"dark": theme.dark || null,
|
||||
"light": theme.light || null
|
||||
});
|
||||
schemeColorsCache[theme.name] = {
|
||||
"dark": theme.dark || null,
|
||||
"light": theme.light || null
|
||||
};
|
||||
}
|
||||
availableSchemes = schemes;
|
||||
hasInitialData = true;
|
||||
cacheVersion++;
|
||||
Logger.d("ColorSchemeDownload", "Fetched", schemes.length, "available schemes from registry.json");
|
||||
// Save to cache
|
||||
saveSchemesToCache();
|
||||
} else {
|
||||
downloadError = I18n.tr("panels.color-scheme.download-error-invalid-response");
|
||||
Logger.e("ColorSchemeDownload", downloadError);
|
||||
}
|
||||
} catch (e) {
|
||||
downloadError = I18n.tr("panels.color-scheme.download-error-parse-failed", {
|
||||
"error": e.toString()
|
||||
});
|
||||
Logger.e("ColorSchemeDownload", downloadError);
|
||||
}
|
||||
} else if (xhr.status === 403) {
|
||||
// Rate limit hit - try to use cache if available
|
||||
downloadError = I18n.tr("panels.color-scheme.download-error-rate-limit");
|
||||
Logger.w("ColorSchemeDownload", downloadError);
|
||||
if (typeof ShellState !== 'undefined' && ShellState.isLoaded) {
|
||||
const cacheData = ShellState.getColorSchemesList();
|
||||
const cachedSchemes = cacheData.schemes || [];
|
||||
if (cachedSchemes.length > 0) {
|
||||
availableSchemes = cachedSchemes;
|
||||
// Restore color cache from cached schemes
|
||||
for (var j = 0; j < cachedSchemes.length; j++) {
|
||||
var cachedScheme = cachedSchemes[j];
|
||||
if (cachedScheme.dark || cachedScheme.light) {
|
||||
schemeColorsCache[cachedScheme.name] = {
|
||||
"dark": cachedScheme.dark || null,
|
||||
"light": cachedScheme.light || null
|
||||
};
|
||||
}
|
||||
}
|
||||
hasInitialData = true;
|
||||
cacheVersion++;
|
||||
Logger.i("ColorSchemeDownload", "Using cached schemes due to rate limit");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
downloadError = I18n.tr("panels.color-scheme.download-error-api-error", {
|
||||
"status": xhr.status
|
||||
});
|
||||
Logger.e("ColorSchemeDownload", downloadError);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
xhr.open("GET", "https://raw.githubusercontent.com/noctalia-dev/noctalia-colorschemes/main/registry.json");
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function downloadScheme(scheme) {
|
||||
if (downloading) {
|
||||
return;
|
||||
}
|
||||
|
||||
downloading = true;
|
||||
downloadingScheme = scheme.name;
|
||||
downloadError = "";
|
||||
|
||||
Logger.i("ColorSchemeDownload", "Downloading scheme:", scheme.name);
|
||||
|
||||
// Use cached branch/SHA if available, otherwise fetch
|
||||
if (cachedBranchSha) {
|
||||
// Use cached SHA directly
|
||||
getSchemeTreeWithSha(scheme, cachedBranch, cachedBranchSha);
|
||||
} else if (cachedBranch) {
|
||||
// We have branch name, just need SHA
|
||||
getSchemeTree(scheme, cachedBranch);
|
||||
} else {
|
||||
// Need to fetch branch info first
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
var repoInfo = JSON.parse(xhr.responseText);
|
||||
var defaultBranch = repoInfo.default_branch || "main";
|
||||
cachedBranch = defaultBranch;
|
||||
// Now get the tree for the scheme directory
|
||||
getSchemeTree(scheme, defaultBranch);
|
||||
} catch (e) {
|
||||
// Fallback: try to get files directly
|
||||
getSchemeFilesDirect(scheme);
|
||||
}
|
||||
} else {
|
||||
// Fallback: try to get files directly
|
||||
getSchemeFilesDirect(scheme);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.open("GET", "https://api.github.com/repos/noctalia-dev/noctalia-colorschemes");
|
||||
xhr.send();
|
||||
}
|
||||
}
|
||||
|
||||
function getSchemeTree(scheme, branch) {
|
||||
// First get the SHA of the branch
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
var refResponse = JSON.parse(xhr.responseText);
|
||||
var sha = refResponse.object ? refResponse.object.sha : null;
|
||||
if (sha) {
|
||||
// Cache the SHA for future downloads
|
||||
cachedBranchSha = sha;
|
||||
// Now get the tree
|
||||
getSchemeTreeWithSha(scheme, branch, sha);
|
||||
} else {
|
||||
// Fallback to direct method
|
||||
getSchemeFilesDirect(scheme);
|
||||
}
|
||||
} catch (e) {
|
||||
// Fallback to direct method
|
||||
getSchemeFilesDirect(scheme);
|
||||
}
|
||||
} else {
|
||||
// Fallback to direct method
|
||||
getSchemeFilesDirect(scheme);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.open("GET", "https://api.github.com/repos/noctalia-dev/noctalia-colorschemes/git/refs/heads/" + branch);
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function getSchemeTreeWithSha(scheme, branch, sha) {
|
||||
// Use git trees API to get all files recursively
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
var response = JSON.parse(xhr.responseText);
|
||||
if (response.tree && Array.isArray(response.tree)) {
|
||||
// Filter files that belong to this scheme
|
||||
var files = [];
|
||||
for (var i = 0; i < response.tree.length; i++) {
|
||||
var item = response.tree[i];
|
||||
if (item.type === "blob" && item.path.startsWith(scheme.path + "/")) {
|
||||
files.push({
|
||||
"path": item.path,
|
||||
"url": "https://raw.githubusercontent.com/noctalia-dev/noctalia-colorschemes/" + branch + "/" + item.path,
|
||||
"name": item.path.split("/").pop()
|
||||
});
|
||||
}
|
||||
}
|
||||
downloadSchemeFiles(scheme.name, files);
|
||||
} else {
|
||||
// Fallback to direct method
|
||||
getSchemeFilesDirect(scheme);
|
||||
}
|
||||
} catch (e) {
|
||||
downloadError = I18n.tr("panels.color-scheme.download-error-parse-failed", {
|
||||
"error": e.toString()
|
||||
});
|
||||
downloading = false;
|
||||
downloadingScheme = "";
|
||||
Logger.e("ColorSchemeDownload", downloadError);
|
||||
}
|
||||
} else {
|
||||
// Fallback to direct method
|
||||
getSchemeFilesDirect(scheme);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.open("GET", "https://api.github.com/repos/noctalia-dev/noctalia-colorschemes/git/trees/" + sha + "?recursive=1");
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function getSchemeFilesDirect(scheme) {
|
||||
// Fallback: get files directly using contents API (non-recursive, but works)
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
var response = JSON.parse(xhr.responseText);
|
||||
if (Array.isArray(response)) {
|
||||
// Recursively get all files
|
||||
getAllFilesRecursive(scheme, response, []);
|
||||
} else {
|
||||
downloadError = I18n.tr("panels.color-scheme.download-error-invalid-response");
|
||||
downloading = false;
|
||||
downloadingScheme = "";
|
||||
Logger.e("ColorSchemeDownload", downloadError);
|
||||
}
|
||||
} catch (e) {
|
||||
downloadError = I18n.tr("panels.color-scheme.download-error-parse-failed", {
|
||||
"error": e.toString()
|
||||
});
|
||||
downloading = false;
|
||||
downloadingScheme = "";
|
||||
Logger.e("ColorSchemeDownload", downloadError);
|
||||
}
|
||||
} else {
|
||||
downloadError = I18n.tr("panels.color-scheme.download-error-api-error", {
|
||||
"status": xhr.status
|
||||
});
|
||||
downloading = false;
|
||||
downloadingScheme = "";
|
||||
Logger.e("ColorSchemeDownload", downloadError);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.open("GET", "https://api.github.com/repos/noctalia-dev/noctalia-colorschemes/contents/" + scheme.path);
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function getAllFilesRecursive(scheme, items, allFiles, callback) {
|
||||
if (!callback) {
|
||||
callback = function () {
|
||||
downloadSchemeFiles(scheme.name, allFiles);
|
||||
};
|
||||
}
|
||||
|
||||
if (items.length === 0) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
var pending = 0;
|
||||
var completed = 0;
|
||||
|
||||
function checkComplete() {
|
||||
completed++;
|
||||
if (completed === items.length && pending === 0) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
var item = items[i];
|
||||
if (item.type === "file") {
|
||||
allFiles.push({
|
||||
"path": item.path,
|
||||
"url": item.download_url,
|
||||
"name": item.name
|
||||
});
|
||||
checkComplete();
|
||||
} else if (item.type === "dir") {
|
||||
pending++;
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.onreadystatechange = function (dirItem) {
|
||||
return function () {
|
||||
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||
pending--;
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
var dirResponse = JSON.parse(xhr.responseText);
|
||||
if (Array.isArray(dirResponse)) {
|
||||
getAllFilesRecursive(scheme, dirResponse, allFiles, function () {
|
||||
checkComplete();
|
||||
});
|
||||
} else {
|
||||
checkComplete();
|
||||
}
|
||||
} catch (e) {
|
||||
Logger.e("ColorSchemeDownload", "Failed to parse directory response:", e);
|
||||
checkComplete();
|
||||
}
|
||||
} else {
|
||||
checkComplete();
|
||||
}
|
||||
}
|
||||
};
|
||||
}(item);
|
||||
xhr.open("GET", item.url);
|
||||
xhr.send();
|
||||
} else {
|
||||
checkComplete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function downloadSchemeFiles(schemeName, files) {
|
||||
if (files.length === 0) {
|
||||
downloadError = I18n.tr("panels.color-scheme.download-error-no-files");
|
||||
downloading = false;
|
||||
downloadingScheme = "";
|
||||
Logger.e("ColorSchemeDownload", downloadError);
|
||||
return;
|
||||
}
|
||||
|
||||
var targetDir = ColorSchemeService.downloadedSchemesDirectory + "/" + schemeName;
|
||||
var downloadScript = "mkdir -p '" + targetDir + "'\n";
|
||||
|
||||
// Build download script for all files
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
var file = files[i];
|
||||
var filePath = file.path;
|
||||
// Remove scheme name and leading / from path
|
||||
var relativePath = filePath;
|
||||
if (filePath.startsWith(schemeName + "/")) {
|
||||
relativePath = filePath.substring(schemeName.length + 1);
|
||||
} else if (filePath.startsWith("/" + schemeName + "/")) {
|
||||
relativePath = filePath.substring(schemeName.length + 2);
|
||||
}
|
||||
var localPath = targetDir + "/" + relativePath;
|
||||
var localDir = localPath.substring(0, localPath.lastIndexOf('/'));
|
||||
|
||||
downloadScript += "mkdir -p '" + localDir + "'\n";
|
||||
var downloadUrl = file.url || file.download_url;
|
||||
if (downloadUrl) {
|
||||
downloadScript += "curl -L -s -o '" + localPath + "' '" + downloadUrl + "' || wget -q -O '" + localPath + "' '" + downloadUrl + "'\n";
|
||||
}
|
||||
}
|
||||
|
||||
Logger.d("ColorSchemeDownload", "Downloading", files.length, "files for scheme", schemeName);
|
||||
|
||||
// Execute download script
|
||||
var stderrOutput = "";
|
||||
var downloadProcess = Qt.createQmlObject(`
|
||||
import QtQuick
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
Process {
|
||||
id: downloadProcess
|
||||
command: ["sh", "-c", ` + JSON.stringify(downloadScript) + `]
|
||||
stderr: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (text && text.trim()) {
|
||||
Logger.e("ColorSchemeDownload", "Download stderr:", text);
|
||||
root.lastStderrOutput = text.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, root, "DownloadProcess_" + schemeName);
|
||||
|
||||
downloadProcess.exited.connect(function (exitCode) {
|
||||
if (exitCode === 0) {
|
||||
Logger.i("ColorSchemeDownload", "Scheme downloaded successfully:", schemeName);
|
||||
ToastService.showNotice(I18n.tr("panels.color-scheme.download-success-title"), I18n.tr("panels.color-scheme.download-success-description", {
|
||||
"scheme": schemeName
|
||||
}), "settings-color-scheme");
|
||||
// Set pending scheme to apply after reload
|
||||
pendingApplyScheme = schemeName;
|
||||
// Reload color schemes
|
||||
ColorSchemeService.loadColorSchemes();
|
||||
downloading = false;
|
||||
downloadingScheme = "";
|
||||
} else {
|
||||
var errorDetails = "Exit code: " + exitCode;
|
||||
if (root.lastStderrOutput) {
|
||||
errorDetails += " - " + root.lastStderrOutput;
|
||||
}
|
||||
downloadError = I18n.tr("panels.color-scheme.download-error-download-failed", {
|
||||
"code": exitCode
|
||||
}) + "\n" + errorDetails;
|
||||
Logger.e("ColorSchemeDownload", downloadError);
|
||||
ToastService.showError(I18n.tr("panels.color-scheme.download-error-title"), I18n.tr("panels.color-scheme.download-error-description", {
|
||||
"scheme": schemeName
|
||||
}) + "\n" + errorDetails);
|
||||
// Clean up the partially downloaded directory on failure
|
||||
var cleanupScript = "rm -rf '" + targetDir + "'";
|
||||
var cleanupProcess = Qt.createQmlObject(`
|
||||
import QtQuick
|
||||
import Quickshell.Io
|
||||
Process {
|
||||
id: cleanupProcess
|
||||
command: ["sh", "-c", ` + JSON.stringify(cleanupScript) + `]
|
||||
}
|
||||
`, root, "CleanupProcess_" + schemeName);
|
||||
|
||||
cleanupProcess.exited.connect(function (cleanupExitCode) {
|
||||
if (cleanupExitCode === 0) {
|
||||
Logger.d("ColorSchemeDownload", "Partially downloaded scheme directory cleaned up:", targetDir);
|
||||
} else {
|
||||
Logger.w("ColorSchemeDownload", "Failed to clean up partially downloaded scheme directory:", targetDir);
|
||||
}
|
||||
downloading = false;
|
||||
downloadingScheme = "";
|
||||
cleanupProcess.destroy();
|
||||
});
|
||||
|
||||
cleanupProcess.running = true;
|
||||
}
|
||||
root.lastStderrOutput = "";
|
||||
downloadProcess.destroy();
|
||||
});
|
||||
|
||||
downloadProcess.running = true;
|
||||
}
|
||||
|
||||
function isSchemeInstalled(schemeName) {
|
||||
// Check if scheme already exists in ColorSchemeService
|
||||
for (var i = 0; i < ColorSchemeService.schemes.length; i++) {
|
||||
var path = ColorSchemeService.schemes[i];
|
||||
if (path.indexOf("/" + schemeName + "/") !== -1 || path.indexOf("/" + schemeName + ".json") !== -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isSchemeDownloaded(schemeName) {
|
||||
// Check if scheme is in the downloaded directory (not preinstalled)
|
||||
for (var i = 0; i < ColorSchemeService.schemes.length; i++) {
|
||||
var path = ColorSchemeService.schemes[i];
|
||||
if ((path.indexOf("/" + schemeName + "/") !== -1 || path.indexOf("/" + schemeName + ".json") !== -1) && path.indexOf(ColorSchemeService.downloadedSchemesDirectory) !== -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function deleteScheme(schemeName) {
|
||||
if (downloading) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.i("ColorSchemeDownload", "Deleting scheme:", schemeName);
|
||||
|
||||
// Check if the deleted scheme is the currently active one
|
||||
var currentScheme = Settings.data.colorSchemes.predefinedScheme || "";
|
||||
var deletedSchemeDisplayName = ColorSchemeService.getBasename(schemeName);
|
||||
var needsReset = (currentScheme === deletedSchemeDisplayName);
|
||||
|
||||
// Only allow deleting downloaded schemes, not preinstalled ones
|
||||
var targetDir = ColorSchemeService.downloadedSchemesDirectory + "/" + schemeName;
|
||||
var deleteScript = "rm -rf '" + targetDir + "'";
|
||||
|
||||
var deleteProcess = Qt.createQmlObject(`
|
||||
import QtQuick
|
||||
import Quickshell.Io
|
||||
Process {
|
||||
id: deleteProcess
|
||||
command: ["sh", "-c", ` + JSON.stringify(deleteScript) + `]
|
||||
}
|
||||
`, root, "DeleteProcess_" + schemeName);
|
||||
|
||||
deleteProcess.exited.connect(function (exitCode) {
|
||||
if (exitCode === 0) {
|
||||
Logger.i("ColorSchemeDownload", "Scheme deleted successfully:", schemeName);
|
||||
ToastService.showNotice(I18n.tr("panels.color-scheme.delete-success-title"), I18n.tr("panels.color-scheme.delete-success-description", {
|
||||
"scheme": schemeName
|
||||
}), "settings-color-scheme");
|
||||
|
||||
// If the deleted scheme was the active one, reset to default BEFORE reloading
|
||||
if (needsReset) {
|
||||
Logger.i("ColorSchemeDownload", "Deleted scheme was active, resetting to Noctalia (default)");
|
||||
// Clear the setting immediately so ColorSchemeService won't try to apply the deleted scheme
|
||||
Settings.data.colorSchemes.predefinedScheme = "Noctalia (default)";
|
||||
// Apply the default scheme immediately
|
||||
ColorSchemeService.setPredefinedScheme("Noctalia (default)");
|
||||
}
|
||||
|
||||
// Reload color schemes
|
||||
ColorSchemeService.loadColorSchemes();
|
||||
} else {
|
||||
Logger.e("ColorSchemeDownload", "Delete failed with exit code:", exitCode);
|
||||
ToastService.showError(I18n.tr("panels.color-scheme.delete-error-title"), I18n.tr("panels.color-scheme.delete-error-description", {
|
||||
"scheme": schemeName
|
||||
}));
|
||||
}
|
||||
deleteProcess.destroy();
|
||||
});
|
||||
|
||||
deleteProcess.running = true;
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: ColorSchemeService
|
||||
function onScanningChanged() {
|
||||
// When scanning completes and we have a pending scheme, apply it
|
||||
if (!ColorSchemeService.scanning && pendingApplyScheme !== "") {
|
||||
var schemeToApply = pendingApplyScheme;
|
||||
pendingApplyScheme = ""; // Clear pending before applying
|
||||
|
||||
// Wait a tiny bit to ensure schemes array is updated
|
||||
applyTimer.schemeName = schemeToApply;
|
||||
applyTimer.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: applyTimer
|
||||
property string schemeName: ""
|
||||
interval: 100 // Small delay to ensure schemes array is populated
|
||||
onTriggered: {
|
||||
if (schemeName !== "") {
|
||||
Logger.i("ColorSchemeDownload", "Auto-applying downloaded scheme:", schemeName);
|
||||
// Use setPredefinedScheme which will apply and set it as the current scheme
|
||||
ColorSchemeService.setPredefinedScheme(schemeName);
|
||||
schemeName = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onOpened: {
|
||||
fetchAvailableSchemes();
|
||||
}
|
||||
|
||||
function preFetchSchemeColors() {
|
||||
if (availableSchemes.length > 0 && visible) {
|
||||
Qt.callLater(function () {
|
||||
// Just trigger a cache version update to ensure UI refreshes
|
||||
cacheVersion++;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onAvailableSchemesChanged: preFetchSchemeColors()
|
||||
onVisibleChanged: {
|
||||
preFetchSchemeColors();
|
||||
|
||||
// Load schemes from ShellState when popup becomes visible
|
||||
if (visible) {
|
||||
if (typeof ShellState !== 'undefined' && ShellState.isLoaded) {
|
||||
loadSchemesFromCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: typeof ShellState !== 'undefined' ? ShellState : null
|
||||
function onIsLoadedChanged() {
|
||||
if (root.visible && ShellState.isLoaded) {
|
||||
loadSchemesFromCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
id: contentColumn
|
||||
width: parent.width
|
||||
spacing: Style.marginL
|
||||
|
||||
// Header
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: I18n.tr("panels.color-scheme.download-title")
|
||||
pointSize: Style.fontSizeL
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mPrimary
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "refresh"
|
||||
tooltipText: I18n.tr("common.refresh")
|
||||
enabled: !fetching && !downloading
|
||||
onClicked: {
|
||||
// Force refresh by clearing cache timestamp and fetching directly from API
|
||||
if (typeof ShellState !== 'undefined' && ShellState.isLoaded) {
|
||||
ShellState.setColorSchemesList({
|
||||
schemes: [],
|
||||
timestamp: 0
|
||||
});
|
||||
}
|
||||
// Fetch directly from API to avoid cache check delay
|
||||
fetchAvailableSchemesFromAPI();
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
tooltipText: I18n.tr("common.close")
|
||||
onClicked: root.close()
|
||||
}
|
||||
}
|
||||
|
||||
// Separator
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
color: Color.mOutline
|
||||
}
|
||||
|
||||
// Error message
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: errorText.implicitHeight + Style.marginM
|
||||
visible: downloadError !== ""
|
||||
color: Color.mError
|
||||
radius: Style.radiusS
|
||||
|
||||
NText {
|
||||
id: errorText
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM
|
||||
text: downloadError
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mOnError
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
|
||||
// Loading indicator - only show on initial load, not during refresh
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
visible: fetching && !hasInitialData
|
||||
spacing: Style.marginM
|
||||
|
||||
NBusyIndicator {
|
||||
Layout.preferredWidth: 20
|
||||
Layout.preferredHeight: 20
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("panels.color-scheme.download-fetching")
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
}
|
||||
|
||||
// Schemes list - keep visible during refresh to prevent flicker
|
||||
NScrollView {
|
||||
id: schemesScrollView
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 400
|
||||
visible: hasInitialData && availableSchemes.length > 0
|
||||
verticalPolicy: ScrollBar.AsNeeded
|
||||
horizontalPolicy: ScrollBar.AlwaysOff
|
||||
gradientColor: Color.mSurface
|
||||
|
||||
ColumnLayout {
|
||||
width: schemesScrollView.availableWidth
|
||||
spacing: Style.marginS
|
||||
|
||||
Repeater {
|
||||
model: availableSchemes
|
||||
|
||||
Rectangle {
|
||||
id: schemeItem
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 50 * Style.uiScaleRatio
|
||||
radius: Style.radiusS
|
||||
property string schemeName: modelData.name
|
||||
color: root.getSchemeColor(schemeName, "mSurface")
|
||||
border.width: Style.borderL
|
||||
border.color: hoverHandler.hovered ? root.getSchemeColor(schemeName, "mPrimary") : Color.mOutline
|
||||
|
||||
HoverHandler {
|
||||
id: hoverHandler
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.InOutCubic
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.InOutCubic
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (root.visible && !root.schemeColorsCache[schemeName]) {
|
||||
root.fetchSchemeColors(modelData);
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: schemeRow
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Style.marginL
|
||||
anchors.rightMargin: Style.marginL
|
||||
spacing: Style.marginS
|
||||
|
||||
property string schemeName: modelData.name
|
||||
property int diameter: 16 * Style.uiScaleRatio
|
||||
property var colorKeys: ["mPrimary", "mSecondary", "mTertiary", "mError"]
|
||||
|
||||
NText {
|
||||
text: schemeRow.schemeName
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
// Color swatches
|
||||
Repeater {
|
||||
model: schemeRow.colorKeys
|
||||
Rectangle {
|
||||
width: schemeRow.diameter
|
||||
height: schemeRow.diameter
|
||||
radius: schemeRow.diameter * 0.5
|
||||
color: root.getSchemeColor(schemeRow.schemeName, modelData)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.InOutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Download/Delete button
|
||||
NIconButton {
|
||||
property bool isDownloading: downloading && downloadingScheme === schemeRow.schemeName
|
||||
property bool isInstalled: root.isSchemeInstalled(schemeRow.schemeName)
|
||||
property bool isDownloaded: root.isSchemeDownloaded(schemeRow.schemeName)
|
||||
|
||||
icon: isDownloading ? "" : (isDownloaded ? "trash" : "download")
|
||||
tooltipText: isDownloading ? I18n.tr("panels.color-scheme.download-downloading") : (isDownloaded ? I18n.tr("common.delete") : I18n.tr("common.download"))
|
||||
enabled: !downloading
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: !isInstalled || isDownloaded // Show button only if not installed (can download) or if downloaded (can delete)
|
||||
onClicked: isDownloaded ? root.deleteScheme(schemeRow.schemeName) : root.downloadScheme(modelData)
|
||||
|
||||
NBusyIndicator {
|
||||
anchors.centerIn: parent
|
||||
width: 16 * Style.uiScaleRatio
|
||||
height: 16 * Style.uiScaleRatio
|
||||
visible: parent.isDownloading
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Empty state
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
spacing: Style.marginM
|
||||
visible: !fetching && availableSchemes.length === 0 && downloadError === ""
|
||||
|
||||
NIcon {
|
||||
icon: "package"
|
||||
pointSize: 48
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("panels.color-scheme.download-empty")
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+318
@@ -0,0 +1,318 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.System
|
||||
import qs.Services.Theming
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
// Helper to format path description
|
||||
function getDesc(fallbackPath) {
|
||||
return I18n.tr("panels.color-scheme.templates-write-path", {
|
||||
"filepath": fallbackPath
|
||||
});
|
||||
}
|
||||
|
||||
// Build a combined list of all available templates from TemplateRegistry, sorted alphabetically
|
||||
readonly property var allTemplates: {
|
||||
var templates = [];
|
||||
|
||||
// Add terminals with category "terminal"
|
||||
for (var i = 0; i < TemplateRegistry.terminals.length; i++) {
|
||||
var t = TemplateRegistry.terminals[i];
|
||||
templates.push({
|
||||
"id": t.id,
|
||||
"name": t.name,
|
||||
"category": "terminal",
|
||||
"tooltip": getDesc(t.outputPath)
|
||||
});
|
||||
}
|
||||
|
||||
// Add applications
|
||||
for (var j = 0; j < TemplateRegistry.applications.length; j++) {
|
||||
var app = TemplateRegistry.applications[j];
|
||||
var path = "";
|
||||
|
||||
// Determine path to show
|
||||
if (app.outputs && app.outputs.length > 0) {
|
||||
var paths = [];
|
||||
for (var k = 0; k < app.outputs.length; k++) {
|
||||
paths.push(app.outputs[k].path);
|
||||
}
|
||||
path = paths.join("\n");
|
||||
} else if (app.id === "emacs") {
|
||||
// Emacs clients are detected dynamically by ProgramCheckerService
|
||||
var emacsClients = ProgramCheckerService.availableEmacsClients;
|
||||
if (emacsClients && emacsClients.length > 0) {
|
||||
var emacsPaths = [];
|
||||
for (var k = 0; k < emacsClients.length; k++) {
|
||||
emacsPaths.push(emacsClients[k].path);
|
||||
}
|
||||
path = emacsPaths.join("\n");
|
||||
} else {
|
||||
path = I18n.tr("panels.color-scheme.templates-none-detected");
|
||||
}
|
||||
} else if (app.clients && app.clients.length > 0) {
|
||||
var validClients = [];
|
||||
for (var k = 0; k < app.clients.length; k++) {
|
||||
var client = app.clients[k];
|
||||
var include = true;
|
||||
|
||||
if (app.id === "discord") {
|
||||
include = TemplateProcessor.isDiscordClientEnabled(client.name);
|
||||
} else if (app.id === "code") {
|
||||
// For code clients, resolve all theme paths dynamically (version-independent)
|
||||
if (TemplateProcessor.isCodeClientEnabled(client.name)) {
|
||||
var resolvedPaths = TemplateRegistry.resolvedCodeClientPaths(client.name);
|
||||
for (var p = 0; p < resolvedPaths.length; p++) {
|
||||
validClients.push(resolvedPaths[p]);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (include) {
|
||||
if (client.path)
|
||||
validClients.push(client.path);
|
||||
}
|
||||
}
|
||||
|
||||
if (validClients.length > 0) {
|
||||
path = validClients.join("\n");
|
||||
} else {
|
||||
path = I18n.tr("panels.color-scheme.templates-none-detected");
|
||||
}
|
||||
}
|
||||
|
||||
templates.push({
|
||||
"id": app.id,
|
||||
"name": app.name,
|
||||
"category": app.category || "misc",
|
||||
"tooltip": getDesc(path)
|
||||
});
|
||||
}
|
||||
|
||||
// Sort alphabetically by name
|
||||
templates.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
return templates;
|
||||
}
|
||||
|
||||
// Category filter
|
||||
property string selectedCategory: ""
|
||||
|
||||
// Build available categories dynamically
|
||||
readonly property var availableCategories: {
|
||||
var cats = {};
|
||||
for (var i = 0; i < allTemplates.length; i++) {
|
||||
cats[allTemplates[i].category] = true;
|
||||
}
|
||||
return Object.keys(cats).sort();
|
||||
}
|
||||
|
||||
// Filter toggle
|
||||
property bool showOnlyActive: false
|
||||
|
||||
// Filtered templates based on category, search, and toggle
|
||||
property string searchText: ""
|
||||
readonly property var filteredTemplates: {
|
||||
var result = allTemplates;
|
||||
|
||||
// Filter by category first (unless searching)
|
||||
if (selectedCategory !== "" && searchText.trim() === "") {
|
||||
result = result.filter(t => t.category === selectedCategory);
|
||||
}
|
||||
|
||||
// Search overrides category filter
|
||||
if (searchText.trim() !== "") {
|
||||
var query = searchText.toLowerCase().trim();
|
||||
result = result.filter(t => t.name.toLowerCase().includes(query));
|
||||
}
|
||||
|
||||
// Filter by active if enabled (and not searching)
|
||||
if (showOnlyActive && searchText.trim() === "") {
|
||||
result = result.filter(t => isTemplateActive(t.id));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Check if a template is active
|
||||
function isTemplateActive(templateId) {
|
||||
for (var i = 0; i < Settings.data.templates.activeTemplates.length; i++) {
|
||||
if (Settings.data.templates.activeTemplates[i].id === templateId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Toggle a template on/off
|
||||
function toggleTemplate(templateId) {
|
||||
var current = Settings.data.templates.activeTemplates.slice();
|
||||
var existingIndex = -1;
|
||||
|
||||
for (var i = 0; i < current.length; i++) {
|
||||
if (current[i].id === templateId) {
|
||||
existingIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
// Remove it
|
||||
current.splice(existingIndex, 1);
|
||||
} else {
|
||||
// Add it
|
||||
current.push({
|
||||
"id": templateId,
|
||||
"enabled": true
|
||||
});
|
||||
}
|
||||
|
||||
Settings.data.templates.activeTemplates = current;
|
||||
AppThemeService.generate();
|
||||
|
||||
// Clear search context on interaction to return to filtered view
|
||||
if (searchText !== "") {
|
||||
searchText = "";
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("panels.color-scheme.templates-desc")
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Category filter chips
|
||||
NTagFilter {
|
||||
tags: root.availableCategories
|
||||
selectedTag: root.selectedCategory
|
||||
onSelectedTagChanged: root.selectedCategory = selectedTag
|
||||
label: I18n.tr("panels.color-scheme.templates-filter-label")
|
||||
description: I18n.tr("panels.color-scheme.templates-filter-description")
|
||||
expanded: true
|
||||
}
|
||||
|
||||
// Search/filter input row
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
|
||||
NTextInput {
|
||||
Layout.fillWidth: true
|
||||
placeholderText: I18n.tr("placeholders.search")
|
||||
text: root.searchText
|
||||
onTextChanged: root.searchText = text
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "filter"
|
||||
tooltipText: root.showOnlyActive ? I18n.tr("actions.show-all") : I18n.tr("actions.show-active-only")
|
||||
|
||||
colorBg: root.showOnlyActive ? Color.mPrimary : Color.mSurface
|
||||
colorFg: root.showOnlyActive ? Color.mOnPrimary : Color.mOnSurface
|
||||
|
||||
onClicked: root.showOnlyActive = !root.showOnlyActive
|
||||
}
|
||||
}
|
||||
|
||||
// Chip grid - uniform columns
|
||||
GridLayout {
|
||||
Layout.fillWidth: true
|
||||
columns: 4
|
||||
columnSpacing: Style.marginS
|
||||
rowSpacing: Style.marginS
|
||||
|
||||
Repeater {
|
||||
model: filteredTemplates
|
||||
|
||||
Rectangle {
|
||||
id: chip
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.round(Style.baseWidgetSize * 0.9)
|
||||
radius: Style.iRadiusM
|
||||
color: chipMouse.containsMouse ? Color.mHover : (isActive ? Color.mPrimary : Color.mSurface)
|
||||
border.color: isActive ? Color.mPrimary : Color.mOutline
|
||||
border.width: Style.borderS
|
||||
|
||||
required property int index
|
||||
required property var modelData
|
||||
readonly property bool isActive: root.isTemplateActive(modelData.id)
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
id: chipText
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - Style.margin2L
|
||||
text: chip.modelData.name
|
||||
pointSize: Style.fontSizeS
|
||||
color: chipMouse.containsMouse ? Color.mOnHover : (isActive ? Color.mOnPrimary : Color.mOnSurface)
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
elide: Text.ElideRight
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: chipMouse
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
onClicked: root.toggleTemplate(chip.modelData.id)
|
||||
onEntered: {
|
||||
if (chip.modelData.tooltip) {
|
||||
TooltipService.show(chip, chip.modelData.tooltip, "bottom");
|
||||
}
|
||||
}
|
||||
onExited: {
|
||||
TooltipService.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No results message
|
||||
NText {
|
||||
visible: filteredTemplates.length === 0 && searchText.trim() !== ""
|
||||
text: I18n.tr("common.no-results")
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginM
|
||||
}
|
||||
|
||||
// User templates checkbox
|
||||
NCheckbox {
|
||||
label: I18n.tr("panels.color-scheme.templates-misc-user-templates-label")
|
||||
description: I18n.tr("panels.color-scheme.templates-misc-user-templates-description")
|
||||
checked: Settings.data.templates.enableUserTheming
|
||||
onToggled: checked => {
|
||||
Settings.data.templates.enableUserTheming = checked;
|
||||
if (checked) {
|
||||
TemplateRegistry.writeUserTemplatesToml();
|
||||
}
|
||||
AppThemeService.generate();
|
||||
}
|
||||
}
|
||||
}
|
||||
+786
@@ -0,0 +1,786 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Window
|
||||
import Quickshell
|
||||
import Quickshell.Bluetooth
|
||||
|
||||
import qs.Commons
|
||||
import qs.Services.Hardware
|
||||
import qs.Services.Networking
|
||||
import qs.Services.System
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: mainLayout.implicitHeight
|
||||
|
||||
// Configuration for shared use (e.g. by BluetoothPanel)
|
||||
property bool showOnlyLists: false
|
||||
|
||||
readonly property bool isScanningActive: BluetoothService.scanningActive
|
||||
readonly property bool isDiscoverable: BluetoothService.discoverable
|
||||
|
||||
// Device lists with local filtering logic
|
||||
readonly property var connectedDevices: {
|
||||
if (!BluetoothService.adapter || !BluetoothService.adapter.devices)
|
||||
return [];
|
||||
var filtered = BluetoothService.adapter.devices.values.filter(dev => dev && !dev.blocked && dev.connected);
|
||||
filtered = BluetoothService.dedupeDevices(filtered);
|
||||
return BluetoothService.sortDevices(filtered);
|
||||
}
|
||||
|
||||
readonly property var pairedDevices: {
|
||||
if (!BluetoothService.adapter || !BluetoothService.adapter.devices)
|
||||
return [];
|
||||
var filtered = BluetoothService.adapter.devices.values.filter(dev => dev && !dev.blocked && !dev.connected && (dev.paired || dev.trusted));
|
||||
filtered = BluetoothService.dedupeDevices(filtered);
|
||||
return BluetoothService.sortDevices(filtered);
|
||||
}
|
||||
|
||||
readonly property var unnamedAvailableDevices: {
|
||||
if (!BluetoothService.adapter || !BluetoothService.adapter.devices)
|
||||
return [];
|
||||
return BluetoothService.adapter.devices.values.filter(dev => dev && !dev.blocked && !dev.paired && !dev.trusted);
|
||||
}
|
||||
|
||||
readonly property var availableDevices: {
|
||||
var list = root.unnamedAvailableDevices;
|
||||
|
||||
if (Settings.data.network.bluetoothHideUnnamedDevices) {
|
||||
list = list.filter(function (dev) {
|
||||
var dn = dev.name || dev.deviceName || "";
|
||||
var s = String(dn).trim();
|
||||
if (s.length === 0)
|
||||
return false;
|
||||
var lower = s.toLowerCase();
|
||||
if (lower === "unknown" || lower === "unnamed" || lower === "n/a" || lower === "na")
|
||||
return false;
|
||||
var addr = dev.address || dev.bdaddr || dev.mac || "";
|
||||
if (addr.length > 0) {
|
||||
var normName = s.toLowerCase().replace(/[^0-9a-z]/g, "");
|
||||
var normAddr = String(addr).toLowerCase().replace(/[^0-9a-z]/g, "");
|
||||
if (normName.length > 0 && normName === normAddr)
|
||||
return false;
|
||||
}
|
||||
var macRegexComb = /^(([0-9A-Fa-f]{2}[:\-]){5}[0-9A-Fa-f]{2}|([0-9A-Fa-f]{4}\.){2}[0-9A-Fa-f]{4}|[0-9A-Fa-f]{12})$/;
|
||||
if (macRegexComb.test(s)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
list = BluetoothService.dedupeDevices(list);
|
||||
return BluetoothService.sortDevices(list);
|
||||
}
|
||||
|
||||
// For managing expanded device details
|
||||
property string expandedDeviceKey: ""
|
||||
property bool detailsGrid: (Settings.data.network.bluetoothDetailsViewMode === "grid")
|
||||
|
||||
// Combined visibility check: tab must be visible AND the window must be visible
|
||||
readonly property bool effectivelyVisible: root.visible && Window.window && Window.window.visible
|
||||
|
||||
Connections {
|
||||
target: BluetoothService
|
||||
function onEnabledChanged() {
|
||||
stateChangeDebouncer.restart();
|
||||
}
|
||||
function onDiscoverableChanged() {
|
||||
stateChangeDebouncer.restart();
|
||||
}
|
||||
}
|
||||
|
||||
onEffectivelyVisibleChanged: stateChangeDebouncer.restart()
|
||||
|
||||
Timer {
|
||||
id: stateChangeDebouncer
|
||||
interval: 100 // 100ms debounce
|
||||
repeat: false
|
||||
onTriggered: root._updateScanningState()
|
||||
}
|
||||
|
||||
function _updateScanningState() {
|
||||
if (effectivelyVisible && BluetoothService.enabled && !showOnlyLists) {
|
||||
Logger.d("BluetoothPrefs", "Panel/tab active");
|
||||
if (!isScanningActive) {
|
||||
BluetoothService.setScanActive(true);
|
||||
}
|
||||
if (!Settings.data.network.disableDiscoverability && !isDiscoverable) {
|
||||
BluetoothService.setDiscoverable(true);
|
||||
}
|
||||
} else {
|
||||
Logger.d("BluetoothPrefs", "Panel/tab inactive");
|
||||
if (isScanningActive && !showOnlyLists) {
|
||||
BluetoothService.setScanActive(false);
|
||||
}
|
||||
if (isDiscoverable && !showOnlyLists) {
|
||||
BluetoothService.setDiscoverable(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
// Ensure scanning is stopped when component is closed
|
||||
if (isScanningActive && !showOnlyLists) {
|
||||
BluetoothService.setScanActive(false);
|
||||
}
|
||||
// Ensure discoverable is disabled when component is closed
|
||||
if (isDiscoverable && !showOnlyLists) {
|
||||
BluetoothService.setDiscoverable(false);
|
||||
}
|
||||
Logger.d("BluetoothPrefs", "Panel closed");
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: mainLayout
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
spacing: Style.marginL
|
||||
|
||||
// Master Control Section
|
||||
NBox {
|
||||
visible: !root.showOnlyLists
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: masterControlCol.implicitHeight + Style.margin2L
|
||||
color: Color.mSurface
|
||||
|
||||
ColumnLayout {
|
||||
id: masterControlCol
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginL
|
||||
spacing: Style.marginM
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("common.bluetooth")
|
||||
icon: BluetoothService.enabled ? "bluetooth" : "bluetooth-off"
|
||||
checked: BluetoothService.enabled
|
||||
enabled: !NetworkService.airplaneModeEnabled && BluetoothService.bluetoothAvailable
|
||||
onToggled: checked => BluetoothService.setBluetoothEnabled(checked)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
visible: BluetoothService.enabled && isDiscoverable
|
||||
}
|
||||
|
||||
NText {
|
||||
visible: BluetoothService.enabled && isDiscoverable
|
||||
Layout.fillWidth: true
|
||||
text: I18n.tr("panels.connections.bluetooth-discoverable", {
|
||||
hostName: HostService.hostName
|
||||
})
|
||||
color: Color.mOnSurfaceVariant
|
||||
richTextEnabled: true
|
||||
wrapMode: Text.WordWrap
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
visible: !showOnlyLists
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Device List [1] (Connected)
|
||||
NBox {
|
||||
id: connectedDevicesBox
|
||||
visible: root.connectedDevices.length > 0 && BluetoothService.enabled
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: connectedDevicesCol.implicitHeight + Style.margin2M
|
||||
border.color: showOnlyLists ? Style.boxBorderColor : "transparent"
|
||||
color: showOnlyLists ? Color.mSurfaceVariant : "transparent"
|
||||
|
||||
ColumnLayout {
|
||||
id: connectedDevicesCol
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: Style.marginM
|
||||
anchors.bottomMargin: Style.marginM
|
||||
anchors.leftMargin: showOnlyLists ? Style.marginL : 0
|
||||
anchors.rightMargin: showOnlyLists ? Style.marginL : 0
|
||||
spacing: Style.marginM
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("bluetooth.panel.connected-devices")
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Style.marginS
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.connectedDevices
|
||||
delegate: nboxDelegate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Devices List [2] (Paired)
|
||||
NBox {
|
||||
id: pairedDevicesBox
|
||||
visible: root.pairedDevices.length > 0 && BluetoothService.enabled
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: pairedDevicesCol.implicitHeight + Style.margin2M
|
||||
border.color: showOnlyLists ? Style.boxBorderColor : "transparent"
|
||||
color: showOnlyLists ? Color.mSurfaceVariant : "transparent"
|
||||
|
||||
ColumnLayout {
|
||||
id: pairedDevicesCol
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: Style.marginM
|
||||
anchors.bottomMargin: Style.marginM
|
||||
anchors.leftMargin: showOnlyLists ? Style.marginL : 0
|
||||
anchors.rightMargin: showOnlyLists ? Style.marginL : 0
|
||||
spacing: Style.marginM
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("bluetooth.panel.paired-devices")
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Style.marginS
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.pairedDevices
|
||||
delegate: nboxDelegate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Device List [3] (Available)
|
||||
NBox {
|
||||
id: availableDevicesBox
|
||||
visible: !root.showOnlyLists && root.unnamedAvailableDevices.length > 0 && BluetoothService.enabled
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: availableDevicesCol.implicitHeight + Style.margin2M
|
||||
border.color: "transparent"
|
||||
color: showOnlyLists ? Color.mSurfaceVariant : "transparent"
|
||||
|
||||
ColumnLayout {
|
||||
id: availableDevicesCol
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: Style.marginM
|
||||
anchors.bottomMargin: Style.marginM
|
||||
spacing: Style.marginM
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Style.marginS
|
||||
spacing: Style.marginS
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("bluetooth.panel.available-devices")
|
||||
description: BluetoothService.scanningActive ? I18n.tr("bluetooth.panel.scanning") : ""
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.availableDevices
|
||||
delegate: nboxDelegate
|
||||
}
|
||||
|
||||
NText {
|
||||
visible: root.availableDevices.length === 0 && root.unnamedAvailableDevices.length > 0
|
||||
text: I18n.tr("panels.connections.bluetooth-devices-unnamed")
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mOnSurfaceVariant
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Style.marginL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
visible: !showOnlyLists
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NBox {
|
||||
id: miscSettingsBox
|
||||
visible: !root.showOnlyLists && BluetoothService.enabled
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: miscSettingsCol.implicitHeight + Style.margin2XL
|
||||
color: Color.mSurface
|
||||
|
||||
ColumnLayout {
|
||||
id: miscSettingsCol
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginXL
|
||||
spacing: Style.marginM
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.connections.bluetooth-auto-connect-label")
|
||||
description: I18n.tr("panels.connections.bluetooth-auto-connect-description")
|
||||
checked: Settings.data.network.bluetoothAutoConnect
|
||||
onToggled: checked => Settings.data.network.bluetoothAutoConnect = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.connections.hide-unnamed-devices-label")
|
||||
description: I18n.tr("panels.connections.hide-unnamed-devices-description")
|
||||
checked: Settings.data.network.bluetoothHideUnnamedDevices
|
||||
onToggled: checked => Settings.data.network.bluetoothHideUnnamedDevices = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.connections.disable-discoverability-label")
|
||||
description: I18n.tr("panels.connections.disable-discoverability-description")
|
||||
checked: Settings.data.network.disableDiscoverability
|
||||
onToggled: checked => {
|
||||
Settings.data.network.disableDiscoverability = checked;
|
||||
BluetoothService.setDiscoverable(!checked);
|
||||
}
|
||||
}
|
||||
|
||||
// RSSI Polling
|
||||
NToggle {
|
||||
label: I18n.tr("panels.connections.bluetooth-rssi-polling-label")
|
||||
description: I18n.tr("panels.connections.bluetooth-rssi-polling-description")
|
||||
checked: Settings.data.network.bluetoothRssiPollingEnabled
|
||||
onToggled: checked => Settings.data.network.bluetoothRssiPollingEnabled = checked
|
||||
}
|
||||
NSpinBox {
|
||||
label: I18n.tr("panels.connections.bluetooth-rssi-polling-interval-label")
|
||||
description: I18n.tr("panels.connections.bluetooth-rssi-polling-interval-description")
|
||||
from: 10000
|
||||
to: 120000
|
||||
stepSize: 1000
|
||||
value: Settings.data.network.bluetoothRssiPollIntervalMs
|
||||
defaultValue: Settings.getDefaultValue("network.bluetoothRssiPollIntervalMs")
|
||||
onValueChanged: Settings.data.network.bluetoothRssiPollIntervalMs = value
|
||||
suffix: " ms"
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: Settings.data.network.bluetoothRssiPollingEnabled
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shared Delegate
|
||||
Component {
|
||||
id: nboxDelegate
|
||||
NBox {
|
||||
id: device
|
||||
|
||||
readonly property bool canConnect: BluetoothService.canConnect(modelData)
|
||||
readonly property bool canDisconnect: BluetoothService.canDisconnect(modelData)
|
||||
readonly property bool canPair: BluetoothService.canPair(modelData)
|
||||
readonly property bool isBusy: BluetoothService.isDeviceBusy(modelData)
|
||||
readonly property bool isExpanded: root.expandedDeviceKey === BluetoothService.deviceKey(modelData)
|
||||
|
||||
function getContentColors(defaultColors = [Color.mSurface, Color.mOnSurface]) {
|
||||
if (modelData.pairing || modelData.state === BluetoothDeviceState.Connecting) {
|
||||
return [Color.mPrimary, Color.mOnPrimary];
|
||||
}
|
||||
if (modelData.connected && modelData.state !== BluetoothDeviceState.Disconnecting) {
|
||||
return [Color.mPrimary, Color.mOnPrimary];
|
||||
}
|
||||
if (modelData.blocked || modelData.state === BluetoothDeviceState.Disconnecting) {
|
||||
return [Color.mError, Color.mOnError];
|
||||
}
|
||||
return defaultColors;
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: deviceColumn.implicitHeight + (Style.marginXL)
|
||||
radius: Style.radiusM
|
||||
clip: true
|
||||
forceOpaque: true
|
||||
color: device.getContentColors()[0]
|
||||
|
||||
ColumnLayout {
|
||||
id: deviceColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM
|
||||
spacing: Style.marginS
|
||||
|
||||
RowLayout {
|
||||
id: deviceLayout
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
NIcon {
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
icon: BluetoothService.getDeviceIcon(modelData)
|
||||
pointSize: Style.fontSizeXXL
|
||||
color: device.getContentColors()[1]
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginXXS
|
||||
|
||||
NText {
|
||||
text: modelData.name || modelData.deviceName
|
||||
pointSize: Style.fontSizeM
|
||||
font.weight: modelData.connected ? Style.fontWeightBold : Style.fontWeightMedium
|
||||
elide: Text.ElideRight
|
||||
color: device.getContentColors()[1]
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NText {
|
||||
text: {
|
||||
const k = BluetoothService.getStatusKey(modelData);
|
||||
if (k === "pairing")
|
||||
return I18n.tr("common.pairing");
|
||||
if (k === "blocked")
|
||||
return I18n.tr("bluetooth.panel.blocked");
|
||||
if (k === "connecting")
|
||||
return I18n.tr("common.connecting");
|
||||
if (k === "disconnecting")
|
||||
return I18n.tr("common.disconnecting");
|
||||
return "";
|
||||
}
|
||||
visible: text !== ""
|
||||
pointSize: Style.fontSizeXS
|
||||
color: Qt.alpha(device.getContentColors([Color.mSurfaceVariant, Color.mOnSurfaceVariant])[1], Style.opacityHeavy)
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: modelData.batteryAvailable
|
||||
spacing: Style.marginS
|
||||
NIcon {
|
||||
icon: {
|
||||
var b = BluetoothService.getBatteryPercent(modelData);
|
||||
return BatteryService.getIcon(b !== null ? b : 0, false, false, b !== null);
|
||||
}
|
||||
pointSize: Style.fontSizeXS
|
||||
color: Qt.alpha(device.getContentColors()[1], Style.opacityHeavy)
|
||||
}
|
||||
NText {
|
||||
text: {
|
||||
var b = BluetoothService.getBatteryPercent(modelData);
|
||||
return b === null ? "-" : (b + "%");
|
||||
}
|
||||
pointSize: Style.fontSizeXS
|
||||
color: Qt.alpha(device.getContentColors([Color.mSurfaceVariant, Color.mOnSurfaceVariant])[1], Style.opacityHeavy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginS
|
||||
|
||||
NBusyIndicator {
|
||||
visible: isBusy
|
||||
running: visible && root.effectivelyVisible
|
||||
color: device.getContentColors()[1]
|
||||
size: Style.baseWidgetSize * 0.5
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
visible: modelData.connected && modelData.state !== BluetoothDeviceState.Disconnecting
|
||||
icon: "info"
|
||||
tooltipText: I18n.tr("common.info")
|
||||
baseSize: Style.baseWidgetSize * 0.75
|
||||
colorBg: Color.mSurfaceVariant
|
||||
colorFg: Color.mOnSurface
|
||||
colorBorder: "transparent"
|
||||
colorBorderHover: "transparent"
|
||||
onClicked: {
|
||||
const key = BluetoothService.deviceKey(modelData);
|
||||
root.expandedDeviceKey = (root.expandedDeviceKey === key) ? "" : key;
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
visible: !root.showOnlyLists && (modelData.paired || modelData.trusted) && !modelData.connected && !isBusy && !modelData.blocked
|
||||
icon: "trash"
|
||||
tooltipText: I18n.tr("common.unpair")
|
||||
baseSize: Style.baseWidgetSize * 0.75
|
||||
colorBg: Color.mPrimary
|
||||
colorFg: Color.mOnPrimary
|
||||
colorBorder: "transparent"
|
||||
colorBorderHover: "transparent"
|
||||
onClicked: BluetoothService.unpairDevice(modelData)
|
||||
}
|
||||
|
||||
NButton {
|
||||
id: button
|
||||
visible: modelData.state !== BluetoothDeviceState.Connecting && modelData.state !== BluetoothDeviceState.Disconnecting
|
||||
enabled: (canConnect || canDisconnect || (root.showOnlyLists ? false : canPair)) && !isBusy
|
||||
fontSize: Style.fontSizeS
|
||||
backgroundColor: modelData.connected ? Color.mSurfaceVariant : Color.mPrimary
|
||||
textColor: modelData.connected ? Color.mOnSurface : Color.mOnPrimary
|
||||
text: {
|
||||
if (modelData.pairing)
|
||||
return I18n.tr("common.pairing");
|
||||
if (modelData.blocked)
|
||||
return I18n.tr("bluetooth.panel.blocked");
|
||||
if (modelData.connected)
|
||||
return I18n.tr("common.disconnect");
|
||||
if (!root.showOnlyLists && device.canPair)
|
||||
return I18n.tr("common.pair");
|
||||
return I18n.tr("common.connect");
|
||||
}
|
||||
onClicked: {
|
||||
if (modelData.connected) {
|
||||
BluetoothService.disconnectDevice(modelData);
|
||||
} else {
|
||||
if (!root.showOnlyLists && device.canPair) {
|
||||
BluetoothService.pairDevice(modelData);
|
||||
} else {
|
||||
BluetoothService.connectDeviceWithTrust(modelData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Expanded info section
|
||||
Rectangle {
|
||||
visible: device.isExpanded
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: infoColumn.implicitHeight + Style.margin2S
|
||||
radius: Style.radiusXS
|
||||
color: Color.mSurfaceVariant
|
||||
border.width: Style.borderS
|
||||
border.color: Style.boxBorderColor
|
||||
clip: true
|
||||
|
||||
NIconButton {
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Style.marginS
|
||||
icon: root.detailsGrid ? "layout-list" : "layout-grid"
|
||||
tooltipText: root.detailsGrid ? I18n.tr("tooltips.list-view") : I18n.tr("tooltips.grid-view")
|
||||
baseSize: Style.baseWidgetSize * 0.65
|
||||
onClicked: {
|
||||
root.detailsGrid = !root.detailsGrid;
|
||||
Settings.data.network.bluetoothDetailsViewMode = root.detailsGrid ? "grid" : "list";
|
||||
}
|
||||
z: 1
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
id: infoColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginS
|
||||
flow: root.detailsGrid ? GridLayout.TopToBottom : GridLayout.LeftToRight
|
||||
rows: root.detailsGrid ? 3 : 6
|
||||
columns: root.detailsGrid ? 2 : 1
|
||||
columnSpacing: Style.marginM
|
||||
rowSpacing: Style.marginXS
|
||||
|
||||
// --- Item 1: Signal Strength ---
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: 1
|
||||
spacing: Style.marginXS
|
||||
NIcon {
|
||||
icon: BluetoothService.getSignalIcon(modelData)
|
||||
pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
NText {
|
||||
text: BluetoothService.getSignalStrength(modelData)
|
||||
pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
// --- Item 2: Battery ---
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: 1
|
||||
spacing: Style.marginXS
|
||||
NIcon {
|
||||
icon: {
|
||||
var b = BluetoothService.getBatteryPercent(modelData);
|
||||
return BatteryService.getIcon(b !== null ? b : 0, false, false, b !== null);
|
||||
}
|
||||
pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
NText {
|
||||
text: {
|
||||
var b = BluetoothService.getBatteryPercent(modelData);
|
||||
return b === null ? "-" : (b + "%");
|
||||
}
|
||||
pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
// --- Item 3: Pair state ---
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: 1
|
||||
spacing: Style.marginXS
|
||||
NIcon {
|
||||
icon: "link"
|
||||
pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
NText {
|
||||
text: modelData.paired ? I18n.tr("common.yes") : I18n.tr("common.no")
|
||||
pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
// --- Item 4: Trust state ---
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: 1
|
||||
spacing: Style.marginXS
|
||||
NIcon {
|
||||
icon: "shield-check"
|
||||
pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
NText {
|
||||
text: modelData.trusted ? I18n.tr("common.yes") : I18n.tr("common.no")
|
||||
pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
// --- Item 5: Address ---
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: 1
|
||||
spacing: Style.marginXS
|
||||
NIcon {
|
||||
icon: "hash"
|
||||
pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
NText {
|
||||
text: modelData.address || "-"
|
||||
pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
// --- Item 6: Auto-connect ---
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: 1
|
||||
Layout.topMargin: -Style.marginXXS
|
||||
spacing: Style.marginXS
|
||||
visible: Settings.data.network.bluetoothAutoConnect
|
||||
NIcon {
|
||||
icon: BluetoothService.getDeviceAutoConnect(modelData) ? "repeat" : "repeat-off"
|
||||
pointSize: Style.fontSizeXS
|
||||
}
|
||||
NCheckbox {
|
||||
label: I18n.tr("common.auto-connect")
|
||||
labelSize: Style.fontSizeXS
|
||||
baseSize: Style.baseWidgetSize * 0.5
|
||||
checked: BluetoothService.getDeviceAutoConnect(modelData)
|
||||
onToggled: checked => BluetoothService.setDeviceAutoConnect(modelData, checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PIN Authentication Overlay (This part needs some love :P)
|
||||
Rectangle {
|
||||
id: pinOverlay
|
||||
visible: !root.showOnlyLists && BluetoothService.pinRequired
|
||||
anchors.centerIn: parent
|
||||
width: Math.min(parent.width * 0.9, 400)
|
||||
height: pinCol.implicitHeight + Style.margin2L
|
||||
color: Color.mSurface
|
||||
radius: Style.radiusM
|
||||
border.color: Style.boxBorderColor
|
||||
border.width: Style.borderS
|
||||
z: 1000
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.AllButtons
|
||||
onClicked: mouse => mouse.accepted = true
|
||||
onWheel: wheel => wheel.accepted = true
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: pinCol
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginL
|
||||
spacing: Style.marginL
|
||||
|
||||
NIcon {
|
||||
icon: "lock"
|
||||
pointSize: 48
|
||||
color: Color.mPrimary
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
NText {
|
||||
text: I18n.tr("panels.connections.authentication-required")
|
||||
pointSize: Style.fontSizeXL
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
NText {
|
||||
text: I18n.tr("panels.connections.pin-instructions")
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
wrapMode: Text.WordWrap
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
NTextInput {
|
||||
id: pinInput
|
||||
Layout.fillWidth: true
|
||||
placeholderText: "123456"
|
||||
inputIconName: "key"
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
text = "";
|
||||
inputItem.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
inputItem.onEditingFinished: {
|
||||
if (text.length > 0) {
|
||||
BluetoothService.submitPin(text);
|
||||
text = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
spacing: Style.marginM
|
||||
NButton {
|
||||
text: I18n.tr("common.cancel")
|
||||
icon: "x"
|
||||
onClicked: BluetoothService.cancelPairing()
|
||||
}
|
||||
NButton {
|
||||
text: I18n.tr("common.confirm")
|
||||
icon: "check"
|
||||
backgroundColor: Color.mPrimary
|
||||
textColor: Color.mOnPrimary
|
||||
enabled: pinInput.text.length > 0
|
||||
onClicked: {
|
||||
BluetoothService.submitPin(pinInput.text);
|
||||
pinInput.text = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.Networking
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 0
|
||||
|
||||
NTabBar {
|
||||
id: subTabBar
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: Style.marginM
|
||||
distributeEvenly: true
|
||||
currentIndex: tabView.currentIndex
|
||||
|
||||
NTabButton {
|
||||
text: I18n.tr("common.wifi")
|
||||
tabIndex: 0
|
||||
checked: subTabBar.currentIndex === 0
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("common.bluetooth")
|
||||
tabIndex: 1
|
||||
checked: subTabBar.currentIndex === 1
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.marginL
|
||||
}
|
||||
|
||||
NTabView {
|
||||
id: tabView
|
||||
currentIndex: subTabBar.currentIndex
|
||||
|
||||
WifiSubTab {}
|
||||
BluetoothSubTab {}
|
||||
}
|
||||
}
|
||||
+1462
File diff suppressed because it is too large
Load Diff
+87
@@ -0,0 +1,87 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.System
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NComboBox {
|
||||
id: controlCenterPosition
|
||||
label: I18n.tr("common.position")
|
||||
description: I18n.tr("panels.control-center.position-description")
|
||||
Layout.fillWidth: true
|
||||
model: [
|
||||
{
|
||||
"key": "close_to_bar_button",
|
||||
"name": I18n.tr("positions.close-to-bar")
|
||||
},
|
||||
{
|
||||
"key": "center",
|
||||
"name": I18n.tr("positions.center")
|
||||
},
|
||||
{
|
||||
"key": "top_center",
|
||||
"name": I18n.tr("positions.top-center")
|
||||
},
|
||||
{
|
||||
"key": "top_left",
|
||||
"name": I18n.tr("positions.top-left")
|
||||
},
|
||||
{
|
||||
"key": "top_right",
|
||||
"name": I18n.tr("positions.top-right")
|
||||
},
|
||||
{
|
||||
"key": "bottom_center",
|
||||
"name": I18n.tr("positions.bottom-center")
|
||||
},
|
||||
{
|
||||
"key": "bottom_left",
|
||||
"name": I18n.tr("positions.bottom-left")
|
||||
},
|
||||
{
|
||||
"key": "bottom_right",
|
||||
"name": I18n.tr("positions.bottom-right")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.controlCenter.position
|
||||
onSelected: function (key) {
|
||||
Settings.data.controlCenter.position = key;
|
||||
}
|
||||
defaultValue: Settings.getDefaultValue("controlCenter.position")
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
id: diskPathComboBox
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.control-center.system-monitor-disk-path-label")
|
||||
description: I18n.tr("panels.control-center.system-monitor-disk-path-description")
|
||||
model: {
|
||||
const paths = Object.keys(SystemStatService.diskPercents).sort();
|
||||
return paths.map(path => ({
|
||||
key: path,
|
||||
name: path
|
||||
}));
|
||||
}
|
||||
currentKey: Settings.data.controlCenter.diskPath || "/"
|
||||
onSelected: key => Settings.data.controlCenter.diskPath = key
|
||||
defaultValue: Settings.getDefaultValue("controlCenter.diskPath") || "/"
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.System
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
required property var cardsModel
|
||||
required property list<var> cardsDefault
|
||||
|
||||
function saveCards() {
|
||||
var toSave = [];
|
||||
for (var i = 0; i < cardsModel.length; i++) {
|
||||
toSave.push({
|
||||
"id": cardsModel[i].id,
|
||||
"enabled": cardsModel[i].enabled
|
||||
});
|
||||
}
|
||||
Settings.data.controlCenter.cards = toSave;
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("panels.control-center.cards-desc")
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
Connections {
|
||||
target: Settings.data.location
|
||||
function onWeatherEnabledChanged() {
|
||||
// Auto-disable weather card when weather is disabled
|
||||
var newModel = root.cardsModel.slice();
|
||||
for (var i = 0; i < newModel.length; i++) {
|
||||
if (newModel[i].id === "weather-card") {
|
||||
newModel[i] = Object.assign({}, newModel[i], {
|
||||
"enabled": Settings.data.location.weatherEnabled
|
||||
});
|
||||
root.cardsModel = newModel;
|
||||
saveCards();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NReorderCheckboxes {
|
||||
Layout.fillWidth: true
|
||||
model: root.cardsModel
|
||||
disabledIds: Settings.data.location.weatherEnabled ? [] : ["weather-card"]
|
||||
onItemToggled: function (index, enabled) {
|
||||
var newModel = root.cardsModel.slice();
|
||||
newModel[index] = Object.assign({}, newModel[index], {
|
||||
"enabled": enabled
|
||||
});
|
||||
root.cardsModel = newModel;
|
||||
saveCards();
|
||||
}
|
||||
onItemsReordered: function (fromIndex, toIndex) {
|
||||
var newModel = root.cardsModel.slice();
|
||||
var item = newModel.splice(fromIndex, 1)[0];
|
||||
newModel.splice(toIndex, 0, item);
|
||||
root.cardsModel = newModel;
|
||||
saveCards();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.Noctalia
|
||||
import qs.Services.System
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 0
|
||||
|
||||
property list<var> cardsModel: []
|
||||
property list<var> cardsDefault: [
|
||||
{
|
||||
"id": "profile-card",
|
||||
"text": "Profile",
|
||||
"enabled": true,
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"id": "shortcuts-card",
|
||||
"text": "Shortcuts",
|
||||
"enabled": true,
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "audio-card",
|
||||
"text": "Audio Sliders",
|
||||
"enabled": true,
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "brightness-card",
|
||||
"text": "Brightness",
|
||||
"enabled": false,
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "weather-card",
|
||||
"text": "Weather",
|
||||
"enabled": true,
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "media-sysmon-card",
|
||||
"text": "Media and System Monitor",
|
||||
"enabled": true,
|
||||
"required": false
|
||||
}
|
||||
]
|
||||
|
||||
Component.onCompleted: {
|
||||
// Fill out availableWidgets ListModel
|
||||
availableWidgets.clear();
|
||||
var sortedEntries = ControlCenterWidgetRegistry.getAvailableWidgets().slice().sort();
|
||||
sortedEntries.forEach(entry => {
|
||||
const isPlugin = ControlCenterWidgetRegistry.isPluginWidget(entry);
|
||||
let displayName = entry;
|
||||
let badges = [];
|
||||
if (isPlugin) {
|
||||
const pluginId = entry.replace("plugin:", "");
|
||||
const manifest = PluginRegistry.getPluginManifest(pluginId);
|
||||
if (manifest && manifest.name) {
|
||||
displayName = manifest.name;
|
||||
} else {
|
||||
displayName = pluginId;
|
||||
}
|
||||
badges.push({
|
||||
"icon": "plugin",
|
||||
"color": Color.mSecondary
|
||||
});
|
||||
}
|
||||
availableWidgets.append({
|
||||
"key": entry,
|
||||
"name": displayName,
|
||||
"badges": badges
|
||||
});
|
||||
});
|
||||
// Starts empty
|
||||
cardsModel = [];
|
||||
|
||||
// Add the cards available in settings
|
||||
for (var i = 0; i < Settings.data.controlCenter.cards.length; i++) {
|
||||
const settingCard = Settings.data.controlCenter.cards[i];
|
||||
|
||||
for (var j = 0; j < cardsDefault.length; j++) {
|
||||
if (settingCard.id === cardsDefault[j].id) {
|
||||
var card = cardsDefault[j];
|
||||
card.enabled = settingCard.enabled;
|
||||
// Auto-disable weather card if weather is disabled
|
||||
if (card.id === "weather-card" && !Settings.data.location.weatherEnabled) {
|
||||
card.enabled = false;
|
||||
}
|
||||
cardsModel.push(card);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add any missing cards from default
|
||||
for (var i = 0; i < cardsDefault.length; i++) {
|
||||
var found = false;
|
||||
for (var j = 0; j < cardsModel.length; j++) {
|
||||
if (cardsModel[j].id === cardsDefault[i].id) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
var card = cardsDefault[i];
|
||||
// Auto-disable weather card if weather is disabled
|
||||
if (card.id === "weather-card" && !Settings.data.location.weatherEnabled) {
|
||||
card.enabled = false;
|
||||
}
|
||||
cardsModel.push(card);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NTabBar {
|
||||
id: subTabBar
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: Style.marginM
|
||||
distributeEvenly: true
|
||||
currentIndex: tabView.currentIndex
|
||||
|
||||
NTabButton {
|
||||
text: I18n.tr("common.appearance")
|
||||
tabIndex: 0
|
||||
checked: subTabBar.currentIndex === 0
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("common.cards")
|
||||
tabIndex: 1
|
||||
checked: subTabBar.currentIndex === 1
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("common.shortcuts")
|
||||
tabIndex: 2
|
||||
checked: subTabBar.currentIndex === 2
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.marginL
|
||||
}
|
||||
|
||||
NTabView {
|
||||
id: tabView
|
||||
currentIndex: subTabBar.currentIndex
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
AppearanceSubTab {}
|
||||
|
||||
CardsSubTab {
|
||||
cardsModel: root.cardsModel
|
||||
cardsDefault: root.cardsDefault
|
||||
}
|
||||
|
||||
ShortcutsSubTab {
|
||||
availableWidgets: availableWidgets
|
||||
onAddWidgetToSection: (widgetId, section) => _addWidgetToSection(widgetId, section)
|
||||
onRemoveWidgetFromSection: (section, index) => _removeWidgetFromSection(section, index)
|
||||
onReorderWidgetInSection: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex)
|
||||
onUpdateWidgetSettingsInSection: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings)
|
||||
onMoveWidgetBetweenSections: (fromSection, index, toSection) => _moveWidgetBetweenSections(fromSection, index, toSection)
|
||||
onOpenPluginSettingsRequested: manifest => pluginSettingsDialog.openPluginSettings(manifest)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------
|
||||
// Signal functions
|
||||
// ---------------------------------
|
||||
function _addWidgetToSection(widgetId, section) {
|
||||
var newWidget = {
|
||||
"id": widgetId
|
||||
};
|
||||
if (ControlCenterWidgetRegistry.widgetHasUserSettings(widgetId)) {
|
||||
var metadata = ControlCenterWidgetRegistry.widgetMetadata[widgetId];
|
||||
if (metadata) {
|
||||
Object.keys(metadata).forEach(function (key) {
|
||||
newWidget[key] = metadata[key];
|
||||
});
|
||||
}
|
||||
}
|
||||
Settings.data.controlCenter.shortcuts[section].push(newWidget);
|
||||
}
|
||||
|
||||
function _removeWidgetFromSection(section, index) {
|
||||
if (index >= 0 && index < Settings.data.controlCenter.shortcuts[section].length) {
|
||||
var newArray = Settings.data.controlCenter.shortcuts[section].slice();
|
||||
var removedWidgets = newArray.splice(index, 1);
|
||||
Settings.data.controlCenter.shortcuts[section] = newArray;
|
||||
}
|
||||
}
|
||||
|
||||
function _reorderWidgetInSection(section, fromIndex, toIndex) {
|
||||
if (fromIndex >= 0 && fromIndex < Settings.data.controlCenter.shortcuts[section].length && toIndex >= 0 && toIndex < Settings.data.controlCenter.shortcuts[section].length) {
|
||||
|
||||
// Create a new array to avoid modifying the original
|
||||
var newArray = Settings.data.controlCenter.shortcuts[section].slice();
|
||||
var item = newArray[fromIndex];
|
||||
newArray.splice(fromIndex, 1);
|
||||
newArray.splice(toIndex, 0, item);
|
||||
|
||||
Settings.data.controlCenter.shortcuts[section] = newArray;
|
||||
}
|
||||
}
|
||||
|
||||
function _moveWidgetBetweenSections(fromSection, index, toSection) {
|
||||
// Get the widget from the source section
|
||||
if (index >= 0 && index < Settings.data.controlCenter.shortcuts[fromSection].length) {
|
||||
var widget = Settings.data.controlCenter.shortcuts[fromSection][index];
|
||||
|
||||
// Remove from source section
|
||||
var sourceArray = Settings.data.controlCenter.shortcuts[fromSection].slice();
|
||||
sourceArray.splice(index, 1);
|
||||
Settings.data.controlCenter.shortcuts[fromSection] = sourceArray;
|
||||
|
||||
// Add to target section
|
||||
var targetArray = Settings.data.controlCenter.shortcuts[toSection].slice();
|
||||
targetArray.push(widget);
|
||||
Settings.data.controlCenter.shortcuts[toSection] = targetArray;
|
||||
}
|
||||
}
|
||||
|
||||
function _updateWidgetSettingsInSection(section, index, settings) {
|
||||
// Create a new array to trigger QML's change detection for persistence.
|
||||
// This is crucial for Settings.data to detect the change and persist it.
|
||||
var newSectionArray = Settings.data.controlCenter.shortcuts[section].slice();
|
||||
newSectionArray[index] = settings;
|
||||
Settings.data.controlCenter.shortcuts[section] = newSectionArray;
|
||||
Settings.saveImmediate();
|
||||
}
|
||||
|
||||
// Base list model for all combo boxes
|
||||
ListModel {
|
||||
id: availableWidgets
|
||||
}
|
||||
|
||||
// Shared Plugin Settings Popup
|
||||
NPluginSettingsPopup {
|
||||
id: pluginSettingsDialog
|
||||
parent: Overlay.overlay
|
||||
showToastOnSave: false
|
||||
}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.System
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
required property var availableWidgets
|
||||
|
||||
signal addWidgetToSection(string widgetId, string section)
|
||||
signal removeWidgetFromSection(string section, int index)
|
||||
signal reorderWidgetInSection(string section, int fromIndex, int toIndex)
|
||||
signal updateWidgetSettingsInSection(string section, int index, var settings)
|
||||
signal moveWidgetBetweenSections(string fromSection, int index, string toSection)
|
||||
signal openPluginSettingsRequested(var manifest)
|
||||
|
||||
function getSectionIcons() {
|
||||
return {
|
||||
"left": "arrow-bar-to-up",
|
||||
"right": "arrow-bar-to-down"
|
||||
};
|
||||
}
|
||||
|
||||
// Widgets Management Section
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
|
||||
// Sections
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.topMargin: Style.marginM
|
||||
spacing: Style.marginM
|
||||
|
||||
// Left
|
||||
NSectionEditor {
|
||||
sectionName: I18n.tr("positions.left")
|
||||
sectionId: "left"
|
||||
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/ControlCenter/ControlCenterWidgetSettingsDialog.qml")
|
||||
maxWidgets: Settings.data.controlCenter.shortcuts["right"].length > 5 ? 0 : (Settings.data.controlCenter.shortcuts["right"].length > 0 ? 5 : 10)
|
||||
widgetRegistry: ControlCenterWidgetRegistry
|
||||
widgetModel: Settings.data.controlCenter.shortcuts["left"]
|
||||
sectionIcons: root.getSectionIcons()
|
||||
availableWidgets: root.availableWidgets
|
||||
availableSections: ["left", "right"]
|
||||
onAddWidget: (widgetId, section) => root.addWidgetToSection(widgetId, section)
|
||||
onRemoveWidget: (section, index) => root.removeWidgetFromSection(section, index)
|
||||
onReorderWidget: (section, fromIndex, toIndex) => root.reorderWidgetInSection(section, fromIndex, toIndex)
|
||||
onUpdateWidgetSettings: (section, index, settings) => root.updateWidgetSettingsInSection(section, index, settings)
|
||||
onMoveWidget: (fromSection, index, toSection) => root.moveWidgetBetweenSections(fromSection, index, toSection)
|
||||
onOpenPluginSettingsRequested: manifest => root.openPluginSettingsRequested(manifest)
|
||||
}
|
||||
|
||||
// Right
|
||||
NSectionEditor {
|
||||
sectionName: I18n.tr("positions.right")
|
||||
sectionId: "right"
|
||||
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/ControlCenter/ControlCenterWidgetSettingsDialog.qml")
|
||||
maxWidgets: Settings.data.controlCenter.shortcuts["left"].length > 5 ? 0 : (Settings.data.controlCenter.shortcuts["left"].length > 0 ? 5 : 10)
|
||||
widgetRegistry: ControlCenterWidgetRegistry
|
||||
widgetModel: Settings.data.controlCenter.shortcuts["right"]
|
||||
sectionIcons: root.getSectionIcons()
|
||||
availableWidgets: root.availableWidgets
|
||||
availableSections: ["left", "right"]
|
||||
onAddWidget: (widgetId, section) => root.addWidgetToSection(widgetId, section)
|
||||
onRemoveWidget: (section, index) => root.removeWidgetFromSection(section, index)
|
||||
onReorderWidget: (section, fromIndex, toIndex) => root.reorderWidgetInSection(section, fromIndex, toIndex)
|
||||
onUpdateWidgetSettings: (section, index, settings) => root.updateWidgetSettingsInSection(section, index, settings)
|
||||
onMoveWidget: (fromSection, index, toSection) => root.moveWidgetBetweenSections(fromSection, index, toSection)
|
||||
onOpenPluginSettingsRequested: manifest => root.openPluginSettingsRequested(manifest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
+332
@@ -0,0 +1,332 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.Compositor
|
||||
import qs.Services.Noctalia
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
spacing: Style.marginL
|
||||
|
||||
// Available widgets model - declared early so Repeater delegates can access it
|
||||
property alias availableWidgetsModel: availableWidgets
|
||||
ListModel {
|
||||
id: availableWidgets
|
||||
}
|
||||
|
||||
// Listen for plugin widget registration/unregistration
|
||||
Connections {
|
||||
target: DesktopWidgetRegistry
|
||||
function onPluginWidgetRegistryUpdated() {
|
||||
updateAvailableWidgetsModel();
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.desktop-widgets.enabled-label")
|
||||
description: I18n.tr("panels.desktop-widgets.enabled-description")
|
||||
checked: Settings.data.desktopWidgets.enabled
|
||||
defaultValue: Settings.getDefaultValue("desktopWidgets.enabled")
|
||||
onToggled: checked => Settings.data.desktopWidgets.enabled = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
enabled: Settings.data.desktopWidgets.enabled
|
||||
label: I18n.tr("panels.desktop-widgets.overview-enabled-label")
|
||||
description: I18n.tr("panels.desktop-widgets.overview-enabled-description")
|
||||
checked: Settings.data.desktopWidgets.overviewEnabled
|
||||
defaultValue: Settings.getDefaultValue("desktopWidgets.overviewEnabled")
|
||||
onToggled: checked => Settings.data.desktopWidgets.overviewEnabled = checked
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
enabled: Settings.data.desktopWidgets.enabled
|
||||
|
||||
NLabel {
|
||||
description: I18n.tr("panels.desktop-widgets.cpu-intensive-note")
|
||||
}
|
||||
|
||||
NButton {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginM
|
||||
Layout.bottomMargin: Style.marginM
|
||||
text: DesktopWidgetRegistry.editMode ? I18n.tr("panels.desktop-widgets.edit-mode-exit-button") : I18n.tr("panels.desktop-widgets.edit-mode-button-label")
|
||||
icon: "edit"
|
||||
onClicked: {
|
||||
DesktopWidgetRegistry.editMode = !DesktopWidgetRegistry.editMode;
|
||||
if (DesktopWidgetRegistry.editMode && Settings.data.ui.settingsPanelMode !== "window") {
|
||||
var item = root.parent;
|
||||
while (item) {
|
||||
if (item.closeRequested !== undefined) {
|
||||
item.closeRequested();
|
||||
break;
|
||||
}
|
||||
item = item.parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// One NSectionEditor per monitor
|
||||
Repeater {
|
||||
model: Quickshell.screens
|
||||
|
||||
NSectionEditor {
|
||||
required property var modelData
|
||||
readonly property real compositorScale: {
|
||||
const info = CompositorService.displayScales[modelData.name];
|
||||
return (info && info.scale) ? info.scale : 1.0;
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
sectionName: modelData.name
|
||||
sectionSubtitle: {
|
||||
// Format scale to 2 decimal places to prevent overly long text
|
||||
var formattedScale = compositorScale.toFixed(2);
|
||||
return "(" + modelData.width + "x" + modelData.height + " @ " + formattedScale + "x)";
|
||||
}
|
||||
|
||||
sectionId: modelData.name
|
||||
screen: modelData
|
||||
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/DesktopWidgets/DesktopWidgetSettingsDialog.qml")
|
||||
widgetRegistry: DesktopWidgetRegistry
|
||||
widgetModel: getWidgetsForMonitor(modelData.name)
|
||||
availableWidgets: root.availableWidgetsModel
|
||||
availableSections: root.getScreenNames()
|
||||
sectionLabels: root.getScreenLabels()
|
||||
sectionIcons: root.getScreenIcons()
|
||||
draggable: false // Desktop widgets are positioned by X,Y, not list order
|
||||
crossSectionDraggable: true
|
||||
maxWidgets: -1
|
||||
onAddWidget: (widgetId, section) => _addWidgetToMonitor(modelData.name, widgetId)
|
||||
onRemoveWidget: (section, index) => _removeWidgetFromMonitor(modelData.name, index)
|
||||
onMoveWidget: (fromSection, index, toSection) => _moveWidgetToMonitor(fromSection, index, toSection)
|
||||
onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsForMonitor(modelData.name, index, settings)
|
||||
pluginSettingsEntryPoints: ["desktopWidgetSettings", "settings"]
|
||||
onOpenPluginSettingsRequested: (manifest, entryPoint) => pluginSettingsDialog.openPluginSettings(manifest, entryPoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shared Plugin Settings Popup
|
||||
NPluginSettingsPopup {
|
||||
id: pluginSettingsDialog
|
||||
parent: Overlay.overlay
|
||||
showToastOnSave: false
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
// Use Qt.callLater to ensure DesktopWidgetRegistry is ready
|
||||
Qt.callLater(updateAvailableWidgetsModel);
|
||||
}
|
||||
|
||||
// Helper to get screen names array
|
||||
function getScreenNames() {
|
||||
var names = [];
|
||||
for (var i = 0; i < Quickshell.screens.length; i++) {
|
||||
names.push(Quickshell.screens[i].name);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
// Helper to get screen labels map (just screen names, NSectionEditor adds "Move to" prefix)
|
||||
function getScreenLabels() {
|
||||
var labels = {};
|
||||
for (var i = 0; i < Quickshell.screens.length; i++) {
|
||||
var screen = Quickshell.screens[i];
|
||||
labels[screen.name] = screen.name;
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
|
||||
// Helper to get screen icons map
|
||||
function getScreenIcons() {
|
||||
var icons = {};
|
||||
for (var i = 0; i < Quickshell.screens.length; i++) {
|
||||
icons[Quickshell.screens[i].name] = "device-desktop";
|
||||
}
|
||||
return icons;
|
||||
}
|
||||
|
||||
function updateAvailableWidgetsModel() {
|
||||
availableWidgets.clear();
|
||||
try {
|
||||
if (typeof DesktopWidgetRegistry === "undefined" || !DesktopWidgetRegistry) {
|
||||
Logger.e("DesktopWidgetsTab", "DesktopWidgetRegistry is not available");
|
||||
// Retry after a short delay
|
||||
Qt.callLater(function () {
|
||||
if (typeof DesktopWidgetRegistry !== "undefined" && DesktopWidgetRegistry) {
|
||||
updateAvailableWidgetsModel();
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
var widgetIds = DesktopWidgetRegistry.getAvailableWidgets();
|
||||
Logger.d("DesktopWidgetsTab", "Found widgets:", widgetIds, "count:", widgetIds ? widgetIds.length : 0);
|
||||
if (!widgetIds || widgetIds.length === 0) {
|
||||
Logger.w("DesktopWidgetsTab", "No widgets found in registry");
|
||||
return;
|
||||
}
|
||||
for (var i = 0; i < widgetIds.length; i++) {
|
||||
var widgetId = widgetIds[i];
|
||||
var displayName = widgetId;
|
||||
|
||||
// Get plugin name for plugin widgets
|
||||
var isPlugin = false;
|
||||
if (DesktopWidgetRegistry.isPluginWidget(widgetId)) {
|
||||
isPlugin = true;
|
||||
var pluginId = widgetId.replace("plugin:", "");
|
||||
var manifest = PluginRegistry.getPluginManifest(pluginId);
|
||||
if (manifest && manifest.name) {
|
||||
displayName = manifest.name;
|
||||
}
|
||||
}
|
||||
|
||||
// Add plugin badge first (with custom color)
|
||||
const badges = [];
|
||||
if (isPlugin) {
|
||||
badges.push({
|
||||
"icon": "plugin",
|
||||
"color": Color.mSecondary
|
||||
});
|
||||
}
|
||||
if (DesktopWidgetRegistry.isCpuIntensive(widgetId)) {
|
||||
badges.push({
|
||||
"icon": "cpu-intensive",
|
||||
"color": Color.mSecondary
|
||||
});
|
||||
}
|
||||
|
||||
availableWidgets.append({
|
||||
"key": widgetId,
|
||||
"name": displayName,
|
||||
"badges": badges
|
||||
});
|
||||
}
|
||||
Logger.d("DesktopWidgetsTab", "Available widgets model count:", availableWidgets.count);
|
||||
} catch (e) {
|
||||
Logger.e("DesktopWidgetsTab", "Error updating available widgets:", e, e.stack);
|
||||
}
|
||||
}
|
||||
|
||||
// Get widgets for a specific monitor
|
||||
function getWidgetsForMonitor(monitorName) {
|
||||
var monitorWidgets = Settings.data.desktopWidgets.monitorWidgets || [];
|
||||
for (var i = 0; i < monitorWidgets.length; i++) {
|
||||
if (monitorWidgets[i].name === monitorName) {
|
||||
return monitorWidgets[i].widgets || [];
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
// Set widgets for a specific monitor
|
||||
function setWidgetsForMonitor(monitorName, widgets) {
|
||||
var monitorWidgets = Settings.data.desktopWidgets.monitorWidgets || [];
|
||||
var newMonitorWidgets = monitorWidgets.slice();
|
||||
var found = false;
|
||||
for (var i = 0; i < newMonitorWidgets.length; i++) {
|
||||
if (newMonitorWidgets[i].name === monitorName) {
|
||||
newMonitorWidgets[i] = {
|
||||
"name": monitorName,
|
||||
"widgets": widgets
|
||||
};
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
newMonitorWidgets.push({
|
||||
"name": monitorName,
|
||||
"widgets": widgets
|
||||
});
|
||||
}
|
||||
Settings.data.desktopWidgets.monitorWidgets = newMonitorWidgets;
|
||||
}
|
||||
|
||||
function _addWidgetToMonitor(monitorName, widgetId) {
|
||||
var newWidget = {
|
||||
"id": widgetId
|
||||
};
|
||||
if (DesktopWidgetRegistry.widgetHasUserSettings(widgetId)) {
|
||||
var metadata = DesktopWidgetRegistry.widgetMetadata[widgetId];
|
||||
if (metadata) {
|
||||
Object.keys(metadata).forEach(function (key) {
|
||||
newWidget[key] = metadata[key];
|
||||
});
|
||||
}
|
||||
}
|
||||
// Set default positions
|
||||
if (widgetId === "Clock") {
|
||||
newWidget.x = 50;
|
||||
newWidget.y = 50;
|
||||
} else if (widgetId === "MediaPlayer") {
|
||||
newWidget.x = 100;
|
||||
newWidget.y = 200;
|
||||
} else if (widgetId === "AudioVisualizer") {
|
||||
newWidget.x = 120;
|
||||
newWidget.y = 280;
|
||||
} else if (widgetId === "Weather") {
|
||||
newWidget.x = 100;
|
||||
newWidget.y = 300;
|
||||
} else {
|
||||
// Default position for plugin widgets
|
||||
newWidget.x = 150;
|
||||
newWidget.y = 150;
|
||||
}
|
||||
var widgets = getWidgetsForMonitor(monitorName).slice();
|
||||
widgets.push(newWidget);
|
||||
setWidgetsForMonitor(monitorName, widgets);
|
||||
}
|
||||
|
||||
function _removeWidgetFromMonitor(monitorName, index) {
|
||||
var widgets = getWidgetsForMonitor(monitorName);
|
||||
if (index >= 0 && index < widgets.length) {
|
||||
var newArray = widgets.slice();
|
||||
newArray.splice(index, 1);
|
||||
setWidgetsForMonitor(monitorName, newArray);
|
||||
}
|
||||
}
|
||||
|
||||
function _updateWidgetSettingsForMonitor(monitorName, index, settings) {
|
||||
var widgets = getWidgetsForMonitor(monitorName);
|
||||
if (index >= 0 && index < widgets.length) {
|
||||
var newArray = widgets.slice();
|
||||
newArray[index] = Object.assign({}, newArray[index], settings);
|
||||
setWidgetsForMonitor(monitorName, newArray);
|
||||
}
|
||||
}
|
||||
|
||||
function _moveWidgetToMonitor(fromMonitor, index, toMonitor) {
|
||||
Logger.i("DesktopWidgetsTab", "Moving widget from", fromMonitor, "index", index, "to", toMonitor);
|
||||
var sourceWidgets = getWidgetsForMonitor(fromMonitor);
|
||||
if (index < 0 || index >= sourceWidgets.length) {
|
||||
Logger.e("DesktopWidgetsTab", "Invalid index", index, "for monitor", fromMonitor);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the widget to move
|
||||
var newSourceWidgets = sourceWidgets.slice();
|
||||
var widget = Object.assign({}, newSourceWidgets.splice(index, 1)[0]);
|
||||
|
||||
// Reset position and scale to ensure widget is accessible on new screen
|
||||
widget.x = 100;
|
||||
widget.y = 100;
|
||||
widget.scale = 1.0;
|
||||
|
||||
// Add to destination monitor
|
||||
var destWidgets = getWidgetsForMonitor(toMonitor).slice();
|
||||
destWidgets.push(widget);
|
||||
|
||||
// Update both monitors
|
||||
setWidgetsForMonitor(fromMonitor, newSourceWidgets);
|
||||
setWidgetsForMonitor(toMonitor, destWidgets);
|
||||
}
|
||||
}
|
||||
+249
@@ -0,0 +1,249 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.Compositor
|
||||
import qs.Services.Hardware
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
|
||||
Repeater {
|
||||
model: Quickshell.screens || []
|
||||
delegate: NBox {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: Math.round(contentCol.implicitHeight + Style.margin2L)
|
||||
color: Color.mSurface
|
||||
|
||||
property var brightnessMonitor: BrightnessService.getMonitorForScreen(modelData)
|
||||
property real localBrightness: 0.5
|
||||
property bool localBrightnessChanging: false
|
||||
readonly property string automaticOptionLabel: {
|
||||
var baseLabel = I18n.tr("panels.display.monitors-backlight-device-auto-option");
|
||||
var autoDevicePath = (BrightnessService.availableBacklightDevices && BrightnessService.availableBacklightDevices.length > 0) ? BrightnessService.availableBacklightDevices[0] : "";
|
||||
if (autoDevicePath === "")
|
||||
return baseLabel;
|
||||
|
||||
var autoDeviceName = BrightnessService.getBacklightDeviceName(autoDevicePath) || autoDevicePath;
|
||||
return baseLabel + "(" + autoDeviceName + ")";
|
||||
}
|
||||
readonly property var backlightDeviceOptions: {
|
||||
var options = [
|
||||
{
|
||||
"key": "",
|
||||
"name": automaticOptionLabel
|
||||
}
|
||||
];
|
||||
|
||||
var devices = BrightnessService.availableBacklightDevices || [];
|
||||
for (var i = 0; i < devices.length; i++) {
|
||||
var devicePath = devices[i];
|
||||
var deviceName = BrightnessService.getBacklightDeviceName(devicePath) || devicePath;
|
||||
options.push({
|
||||
"key": devicePath,
|
||||
"name": deviceName
|
||||
});
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
onBrightnessMonitorChanged: {
|
||||
if (brightnessMonitor && !localBrightnessChanging)
|
||||
localBrightness = brightnessMonitor.brightness || 0.5;
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: BrightnessService
|
||||
function onMonitorBrightnessChanged(monitor, newBrightness) {
|
||||
if (monitor === brightnessMonitor && !localBrightnessChanging) {
|
||||
localBrightness = newBrightness;
|
||||
}
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
target: brightnessMonitor
|
||||
ignoreUnknownSignals: true
|
||||
function onBrightnessUpdated() {
|
||||
if (brightnessMonitor && !localBrightnessChanging) {
|
||||
localBrightness = brightnessMonitor.brightness || 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
Timer {
|
||||
id: debounceTimer
|
||||
interval: 120
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (brightnessMonitor && brightnessMonitor.brightnessControlAvailable && Math.abs(localBrightness - brightnessMonitor.brightness) >= 0.005) {
|
||||
brightnessMonitor.setBrightness(localBrightness);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: contentCol
|
||||
width: parent.width - 2 * Style.marginL
|
||||
x: Style.marginL
|
||||
y: Style.marginL
|
||||
spacing: Style.marginXXS
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
|
||||
NText {
|
||||
text: modelData.name || "Unknown"
|
||||
pointSize: Style.fontSizeL
|
||||
font.weight: Style.fontWeightSemiBold
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
}
|
||||
|
||||
NText {
|
||||
Layout.fillWidth: true
|
||||
readonly property real compositorScale: {
|
||||
const info = CompositorService.displayScales[modelData.name];
|
||||
return (info && info.scale) ? info.scale : 1.0;
|
||||
}
|
||||
text: {
|
||||
I18n.tr("system.monitor-description", {
|
||||
"model": modelData.model,
|
||||
"width": modelData.width * compositorScale,
|
||||
"height": modelData.height * compositorScale,
|
||||
"scale": compositorScale
|
||||
});
|
||||
}
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mOnSurfaceVariant
|
||||
wrapMode: Text.WordWrap
|
||||
horizontalAlignment: Text.AlignRight
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
visible: brightnessMonitor !== undefined && brightnessMonitor !== null
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginL
|
||||
|
||||
NText {
|
||||
text: I18n.tr("common.brightness")
|
||||
Layout.preferredWidth: 90
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
id: brightnessSlider
|
||||
from: 0
|
||||
to: 1
|
||||
value: localBrightness
|
||||
stepSize: 0.01
|
||||
enabled: brightnessMonitor ? brightnessMonitor.brightnessControlAvailable : false
|
||||
onMoved: value => {
|
||||
if (brightnessMonitor && brightnessMonitor.brightnessControlAvailable) {
|
||||
localBrightness = value;
|
||||
debounceTimer.restart();
|
||||
}
|
||||
}
|
||||
onPressedChanged: (pressed, value) => {
|
||||
localBrightnessChanging = pressed;
|
||||
if (brightnessMonitor && brightnessMonitor.brightnessControlAvailable) {
|
||||
if (pressed) {
|
||||
localBrightness = value;
|
||||
debounceTimer.restart();
|
||||
} else {
|
||||
localBrightness = value;
|
||||
debounceTimer.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NText {
|
||||
text: brightnessMonitor ? Math.round(localBrightness * 100) + "%" : "N/A"
|
||||
Layout.preferredWidth: 55
|
||||
horizontalAlignment: Text.AlignRight
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
opacity: brightnessMonitor && !brightnessMonitor.brightnessControlAvailable ? 0.5 : 1.0
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: 30
|
||||
Layout.fillHeight: true
|
||||
NIcon {
|
||||
icon: brightnessMonitor && brightnessMonitor.method == "internal" ? "device-laptop" : "device-desktop"
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
opacity: brightnessMonitor && !brightnessMonitor.brightnessControlAvailable ? 0.5 : 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
visible: brightnessMonitor && !brightnessMonitor.brightnessControlAvailable && !(brightnessMonitor.method === "internal" && brightnessMonitor.initInProgress)
|
||||
text: !Settings.data.brightness.enableDdcSupport ? I18n.tr("panels.display.monitors-brightness-unavailable-ddc-disabled") : I18n.tr("panels.display.monitors-brightness-unavailable-generic")
|
||||
pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
visible: brightnessMonitor && brightnessMonitor.method === "internal"
|
||||
label: I18n.tr("panels.display.monitors-backlight-device-label")
|
||||
description: I18n.tr("panels.display.monitors-backlight-device-description")
|
||||
model: backlightDeviceOptions
|
||||
currentKey: BrightnessService.getMappedBacklightDevice(modelData.name) || ""
|
||||
onSelected: key => BrightnessService.setMappedBacklightDevice(modelData.name, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.display.monitors-brightness-step-label")
|
||||
description: I18n.tr("panels.display.monitors-brightness-step-description")
|
||||
minimum: 1
|
||||
maximum: 50
|
||||
value: Settings.data.brightness.brightnessStep
|
||||
stepSize: 1
|
||||
suffix: "%"
|
||||
onValueChanged: Settings.data.brightness.brightnessStep = value
|
||||
defaultValue: Settings.getDefaultValue("brightness.brightnessStep")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.display.monitors-enforce-minimum-label")
|
||||
description: I18n.tr("panels.display.monitors-enforce-minimum-description")
|
||||
checked: Settings.data.brightness.enforceMinimum
|
||||
onToggled: checked => Settings.data.brightness.enforceMinimum = checked
|
||||
defaultValue: Settings.getDefaultValue("brightness.enforceMinimum")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.display.monitors-external-brightness-label")
|
||||
description: I18n.tr("panels.display.monitors-external-brightness-description")
|
||||
checked: Settings.data.brightness.enableDdcSupport
|
||||
onToggled: checked => {
|
||||
Settings.data.brightness.enableDdcSupport = checked;
|
||||
}
|
||||
defaultValue: Settings.getDefaultValue("brightness.enableDdcSupport")
|
||||
}
|
||||
}
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
import qs.Services.Location
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 0
|
||||
|
||||
// Time dropdown options (00:00 .. 23:30)
|
||||
ListModel {
|
||||
id: timeOptions
|
||||
}
|
||||
|
||||
function populateTimeOptions() {
|
||||
for (var h = 0; h < 24; h++) {
|
||||
for (var m = 0; m < 60; m += 30) {
|
||||
var hh = ("0" + h).slice(-2);
|
||||
var mm = ("0" + m).slice(-2);
|
||||
var key = hh + ":" + mm;
|
||||
timeOptions.append({
|
||||
"key": key,
|
||||
"name": key
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
Qt.callLater(populateTimeOptions);
|
||||
}
|
||||
|
||||
// Check for wlsunset availability when enabling Night Light
|
||||
Process {
|
||||
id: wlsunsetCheck
|
||||
command: ["sh", "-c", "command -v wlsunset"]
|
||||
running: false
|
||||
|
||||
onExited: function (exitCode) {
|
||||
if (exitCode === 0) {
|
||||
Settings.data.nightLight.enabled = true;
|
||||
NightLightService.apply();
|
||||
ToastService.showNotice(I18n.tr("common.night-light"), I18n.tr("common.enabled"), "nightlight-on");
|
||||
} else {
|
||||
Settings.data.nightLight.enabled = false;
|
||||
ToastService.showWarning(I18n.tr("common.night-light"), I18n.tr("toast.night-light.not-installed"));
|
||||
}
|
||||
}
|
||||
|
||||
stdout: StdioCollector {}
|
||||
stderr: StdioCollector {}
|
||||
}
|
||||
|
||||
NTabBar {
|
||||
id: subTabBar
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: Style.marginM
|
||||
distributeEvenly: true
|
||||
currentIndex: tabView.currentIndex
|
||||
|
||||
NTabButton {
|
||||
text: I18n.tr("common.brightness")
|
||||
tabIndex: 0
|
||||
checked: subTabBar.currentIndex === 0
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("common.night-light")
|
||||
tabIndex: 1
|
||||
checked: subTabBar.currentIndex === 1
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.marginL
|
||||
}
|
||||
|
||||
NTabView {
|
||||
id: tabView
|
||||
currentIndex: subTabBar.currentIndex
|
||||
|
||||
BrightnessSubTab {}
|
||||
NightLightSubTab {
|
||||
timeOptions: timeOptions
|
||||
onCheckWlsunset: wlsunsetCheck.running = true
|
||||
}
|
||||
}
|
||||
}
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.Location
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
property var timeOptions
|
||||
|
||||
signal checkWlsunset
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.display.night-light-enable-label")
|
||||
description: I18n.tr("panels.display.night-light-enable-description")
|
||||
checked: Settings.data.nightLight.enabled
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
root.checkWlsunset();
|
||||
} else {
|
||||
Settings.data.nightLight.enabled = false;
|
||||
Settings.data.nightLight.forced = false;
|
||||
NightLightService.apply();
|
||||
ToastService.showNotice(I18n.tr("common.night-light"), I18n.tr("common.disabled"), "nightlight-off");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
enabled: Settings.data.nightLight.enabled
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("panels.display.night-light-temperature-night")
|
||||
description: I18n.tr("panels.display.night-light-temperature-night-description")
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NSlider {
|
||||
id: nightSlider
|
||||
Layout.fillWidth: true
|
||||
from: 1000
|
||||
to: 6500
|
||||
value: Settings.data.nightLight.nightTemp
|
||||
|
||||
onValueChanged: {
|
||||
var dayTemp = parseInt(Settings.data.nightLight.dayTemp);
|
||||
var v = Math.round(value);
|
||||
if (!isNaN(dayTemp)) {
|
||||
var maxNight = dayTemp - 500;
|
||||
v = Math.min(maxNight, Math.max(1000, v));
|
||||
} else {
|
||||
v = Math.max(1000, v);
|
||||
}
|
||||
if (v !== value)
|
||||
value = v;
|
||||
}
|
||||
|
||||
onPressedChanged: {
|
||||
if (!pressed) {
|
||||
var dayTemp = parseInt(Settings.data.nightLight.dayTemp);
|
||||
var v = Math.round(value);
|
||||
if (!isNaN(dayTemp)) {
|
||||
var maxNight = dayTemp - 500;
|
||||
v = Math.min(maxNight, Math.max(1000, v));
|
||||
} else {
|
||||
v = Math.max(1000, v);
|
||||
}
|
||||
Settings.data.nightLight.nightTemp = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
text: nightSlider.value + "K"
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("panels.display.night-light-temperature-day")
|
||||
description: I18n.tr("panels.display.night-light-temperature-day-description")
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NSlider {
|
||||
id: daySlider
|
||||
Layout.fillWidth: true
|
||||
from: 1000
|
||||
to: 6500
|
||||
value: Settings.data.nightLight.dayTemp
|
||||
|
||||
onValueChanged: {
|
||||
var nightTemp = parseInt(Settings.data.nightLight.nightTemp);
|
||||
var v = Math.round(value);
|
||||
if (!isNaN(nightTemp)) {
|
||||
var minDay = nightTemp + 500;
|
||||
v = Math.max(minDay, Math.min(6500, v));
|
||||
} else {
|
||||
v = Math.min(6500, v);
|
||||
}
|
||||
if (v !== value)
|
||||
value = v;
|
||||
}
|
||||
|
||||
onPressedChanged: {
|
||||
if (!pressed) {
|
||||
var nightTemp = parseInt(Settings.data.nightLight.nightTemp);
|
||||
var v = Math.round(value);
|
||||
if (!isNaN(nightTemp)) {
|
||||
var minDay = nightTemp + 500;
|
||||
v = Math.max(minDay, Math.min(6500, v));
|
||||
} else {
|
||||
v = Math.min(6500, v);
|
||||
}
|
||||
Settings.data.nightLight.dayTemp = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
text: daySlider.value + "K"
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.display.night-light-auto-schedule-label")
|
||||
description: I18n.tr("panels.display.night-light-auto-schedule-description", {
|
||||
"location": LocationService.stableName
|
||||
})
|
||||
checked: Settings.data.nightLight.autoSchedule
|
||||
onToggled: checked => Settings.data.nightLight.autoSchedule = checked
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
visible: !Settings.data.nightLight.autoSchedule && !Settings.data.nightLight.forced
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("panels.display.night-light-manual-schedule-label")
|
||||
description: I18n.tr("panels.display.night-light-manual-schedule-description")
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
|
||||
NText {
|
||||
text: I18n.tr("panels.display.night-light-manual-schedule-sunrise")
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
model: root.timeOptions
|
||||
currentKey: Settings.data.nightLight.manualSunrise
|
||||
placeholder: I18n.tr("panels.display.night-light-manual-schedule-select-start")
|
||||
onSelected: key => Settings.data.nightLight.manualSunrise = key
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
|
||||
NText {
|
||||
text: I18n.tr("panels.display.night-light-manual-schedule-sunset")
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
model: root.timeOptions
|
||||
currentKey: Settings.data.nightLight.manualSunset
|
||||
placeholder: I18n.tr("panels.display.night-light-manual-schedule-select-stop")
|
||||
onSelected: key => Settings.data.nightLight.manualSunset = key
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.display.night-light-force-activation-label")
|
||||
description: I18n.tr("panels.display.night-light-force-activation-description")
|
||||
checked: Settings.data.nightLight.forced
|
||||
onToggled: checked => {
|
||||
Settings.data.nightLight.forced = checked;
|
||||
if (checked && !Settings.data.nightLight.enabled) {
|
||||
root.checkWlsunset();
|
||||
} else {
|
||||
NightLightService.apply();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+433
@@ -0,0 +1,433 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.System
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
readonly property color launcherPreviewColor: Color.resolveColorKey((Settings.data.dock.launcherIconColor !== undefined) ? Settings.data.dock.launcherIconColor : "none")
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.dock.enabled-label")
|
||||
description: I18n.tr("panels.dock.enabled-description")
|
||||
checked: Settings.data.dock.enabled
|
||||
defaultValue: Settings.getDefaultValue("dock.enabled")
|
||||
onToggled: checked => Settings.data.dock.enabled = checked
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
enabled: Settings.data.dock.enabled
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.dock.appearance-position-label")
|
||||
description: I18n.tr("panels.dock.appearance-position-description")
|
||||
model: [
|
||||
{
|
||||
"key": "top",
|
||||
"name": I18n.tr("positions.top")
|
||||
},
|
||||
{
|
||||
"key": "bottom",
|
||||
"name": I18n.tr("positions.bottom")
|
||||
},
|
||||
{
|
||||
"key": "left",
|
||||
"name": I18n.tr("positions.left")
|
||||
},
|
||||
{
|
||||
"key": "right",
|
||||
"name": I18n.tr("positions.right")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.dock.position
|
||||
defaultValue: Settings.getDefaultValue("dock.position")
|
||||
onSelected: key => Settings.data.dock.position = key
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.dock.appearance-type-label")
|
||||
description: I18n.tr("panels.dock.appearance-type-description")
|
||||
model: [
|
||||
{
|
||||
"key": "floating",
|
||||
"name": I18n.tr("panels.dock.appearance-type-floating")
|
||||
},
|
||||
{
|
||||
"key": "attached",
|
||||
"name": I18n.tr("panels.dock.appearance-type-attached")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.dock.dockType
|
||||
defaultValue: Settings.getDefaultValue("dock.dockType")
|
||||
onSelected: key => Settings.data.dock.dockType = key
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
visible: Settings.data.dock.dockType === "floating"
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.display.title")
|
||||
description: I18n.tr("panels.dock.appearance-display-description")
|
||||
model: [
|
||||
{
|
||||
"key": "always_visible",
|
||||
"name": I18n.tr("hide-modes.visible")
|
||||
},
|
||||
{
|
||||
"key": "auto_hide",
|
||||
"name": I18n.tr("panels.dock.appearance-display-auto-hide")
|
||||
},
|
||||
{
|
||||
"key": "exclusive",
|
||||
"name": I18n.tr("panels.dock.appearance-display-exclusive")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.dock.displayMode
|
||||
defaultValue: Settings.getDefaultValue("dock.displayMode")
|
||||
onSelected: key => {
|
||||
Settings.data.dock.displayMode = key;
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
visible: Settings.data.dock.dockType === "attached" && Settings.data.bar.barType === "framed"
|
||||
label: I18n.tr("panels.dock.appearance-sit-on-frame-label")
|
||||
description: I18n.tr("panels.dock.appearance-sit-on-frame-description")
|
||||
checked: Settings.data.dock.sitOnFrame
|
||||
defaultValue: Settings.getDefaultValue("dock.sitOnFrame")
|
||||
onToggled: checked => Settings.data.dock.sitOnFrame = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.dock.appearance-dock-indicator-label")
|
||||
description: I18n.tr("panels.dock.appearance-dock-indicator-description")
|
||||
checked: Settings.data.dock.showDockIndicator
|
||||
defaultValue: Settings.getDefaultValue("dock.showDockIndicator")
|
||||
onToggled: checked => Settings.data.dock.showDockIndicator = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
visible: Settings.data.dock.showDockIndicator
|
||||
label: I18n.tr("panels.dock.appearance-indicator-thickness-label")
|
||||
description: I18n.tr("panels.dock.appearance-indicator-thickness-description")
|
||||
checked: (Settings.data.dock.indicatorThickness || 3) >= 6
|
||||
defaultValue: (Settings.getDefaultValue("dock.indicatorThickness") || 3) >= 6
|
||||
onToggled: checked => Settings.data.dock.indicatorThickness = checked ? 6 : 3
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
Layout.fillWidth: true
|
||||
visible: Settings.data.dock.showDockIndicator
|
||||
label: I18n.tr("panels.dock.appearance-indicator-color-label")
|
||||
description: I18n.tr("panels.dock.appearance-indicator-color-description")
|
||||
currentKey: Settings.data.dock.indicatorColor || "primary"
|
||||
defaultValue: Settings.getDefaultValue("dock.indicatorColor")
|
||||
onSelected: key => Settings.data.dock.indicatorColor = key
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
visible: Settings.data.dock.showDockIndicator
|
||||
label: I18n.tr("panels.dock.appearance-indicator-opacity-label")
|
||||
description: I18n.tr("panels.dock.appearance-indicator-opacity-description")
|
||||
from: 0.1
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
showReset: true
|
||||
value: Settings.data.dock.indicatorOpacity
|
||||
defaultValue: Settings.getDefaultValue("dock.indicatorOpacity")
|
||||
onMoved: value => Settings.data.dock.indicatorOpacity = value
|
||||
text: Math.floor(Settings.data.dock.indicatorOpacity * 100) + "%"
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.osd.background-opacity-label")
|
||||
description: I18n.tr("panels.dock.appearance-background-opacity-description")
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
showReset: true
|
||||
value: Settings.data.dock.backgroundOpacity
|
||||
defaultValue: Settings.getDefaultValue("dock.backgroundOpacity")
|
||||
onMoved: value => Settings.data.dock.backgroundOpacity = value
|
||||
text: Math.floor(Settings.data.dock.backgroundOpacity * 100) + "%"
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.dock.appearance-dead-opacity-label")
|
||||
description: I18n.tr("panels.dock.appearance-dead-opacity-description")
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
showReset: true
|
||||
value: Settings.data.dock.deadOpacity
|
||||
defaultValue: Settings.getDefaultValue("dock.deadOpacity")
|
||||
onMoved: value => Settings.data.dock.deadOpacity = value
|
||||
text: Math.floor(Settings.data.dock.deadOpacity * 100) + "%"
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
visible: Settings.data.dock.dockType === "floating"
|
||||
label: I18n.tr("panels.dock.appearance-floating-distance-label")
|
||||
description: I18n.tr("panels.dock.appearance-floating-distance-description")
|
||||
from: 0
|
||||
to: 4
|
||||
stepSize: 0.01
|
||||
showReset: true
|
||||
value: Settings.data.dock.floatingRatio
|
||||
defaultValue: Settings.getDefaultValue("dock.floatingRatio")
|
||||
onMoved: value => Settings.data.dock.floatingRatio = value
|
||||
text: Math.floor(Settings.data.dock.floatingRatio * 100) + "%"
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.dock.appearance-icon-size-label")
|
||||
description: I18n.tr("panels.dock.appearance-icon-size-description")
|
||||
from: 0
|
||||
to: 2
|
||||
stepSize: 0.01
|
||||
showReset: true
|
||||
value: Settings.data.dock.size
|
||||
defaultValue: Settings.getDefaultValue("dock.size")
|
||||
onMoved: value => Settings.data.dock.size = value
|
||||
text: Math.floor(Settings.data.dock.size * 100) + "%"
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
visible: Settings.data.dock.dockType === "floating" && Settings.data.dock.displayMode === "auto_hide"
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.dock.appearance-hide-show-speed-label")
|
||||
description: I18n.tr("panels.dock.appearance-hide-show-speed-description")
|
||||
from: 0.1
|
||||
to: 2.0
|
||||
stepSize: 0.01
|
||||
showReset: true
|
||||
value: Settings.data.dock.animationSpeed
|
||||
defaultValue: Settings.getDefaultValue("dock.animationSpeed")
|
||||
onMoved: value => Settings.data.dock.animationSpeed = value
|
||||
text: (Settings.data.dock.animationSpeed * 100).toFixed(0) + "%"
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.dock.appearance-inactive-indicators-label")
|
||||
description: I18n.tr("panels.dock.appearance-inactive-indicators-description")
|
||||
checked: Settings.data.dock.inactiveIndicators
|
||||
defaultValue: Settings.getDefaultValue("dock.inactiveIndicators")
|
||||
onToggled: checked => Settings.data.dock.inactiveIndicators = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.dock.appearance-pinned-static-label")
|
||||
description: I18n.tr("panels.dock.appearance-pinned-static-description")
|
||||
checked: Settings.data.dock.pinnedStatic
|
||||
defaultValue: Settings.getDefaultValue("dock.pinnedStatic")
|
||||
onToggled: checked => Settings.data.dock.pinnedStatic = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.dock.appearance-group-apps-label")
|
||||
description: I18n.tr("panels.dock.appearance-group-apps-description")
|
||||
checked: Settings.data.dock.groupApps
|
||||
defaultValue: Settings.getDefaultValue("dock.groupApps")
|
||||
onToggled: checked => Settings.data.dock.groupApps = checked
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
visible: Settings.data.dock.groupApps
|
||||
label: I18n.tr("panels.dock.appearance-group-click-action-label")
|
||||
description: I18n.tr("panels.dock.appearance-group-click-action-description")
|
||||
model: [
|
||||
{
|
||||
"key": "cycle",
|
||||
"name": I18n.tr("panels.dock.appearance-group-click-action-cycle")
|
||||
},
|
||||
{
|
||||
"key": "list",
|
||||
"name": I18n.tr("panels.dock.appearance-group-click-action-list")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.dock.groupClickAction
|
||||
defaultValue: Settings.getDefaultValue("dock.groupClickAction")
|
||||
onSelected: key => Settings.data.dock.groupClickAction = key
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
visible: Settings.data.dock.groupApps
|
||||
label: I18n.tr("panels.dock.appearance-group-context-menu-mode-label")
|
||||
description: I18n.tr("panels.dock.appearance-group-context-menu-mode-description")
|
||||
model: [
|
||||
{
|
||||
"key": "list",
|
||||
"name": I18n.tr("panels.dock.appearance-group-context-menu-mode-list")
|
||||
},
|
||||
{
|
||||
"key": "extended",
|
||||
"name": I18n.tr("panels.dock.appearance-group-context-menu-mode-extended")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.dock.groupContextMenuMode
|
||||
defaultValue: Settings.getDefaultValue("dock.groupContextMenuMode")
|
||||
onSelected: key => Settings.data.dock.groupContextMenuMode = key
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
visible: Settings.data.dock.groupApps
|
||||
label: I18n.tr("panels.dock.appearance-group-indicator-style-label")
|
||||
description: I18n.tr("panels.dock.appearance-group-indicator-style-description")
|
||||
model: [
|
||||
{
|
||||
"key": "number",
|
||||
"name": I18n.tr("panels.dock.appearance-group-indicator-style-number")
|
||||
},
|
||||
{
|
||||
"key": "dots",
|
||||
"name": I18n.tr("panels.dock.appearance-group-indicator-style-dots")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.dock.groupIndicatorStyle
|
||||
defaultValue: Settings.getDefaultValue("dock.groupIndicatorStyle")
|
||||
onSelected: key => Settings.data.dock.groupIndicatorStyle = key
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.dock.monitors-only-same-monitor-label")
|
||||
description: I18n.tr("panels.dock.monitors-only-same-monitor-description")
|
||||
checked: Settings.data.dock.onlySameOutput
|
||||
defaultValue: Settings.getDefaultValue("dock.onlySameOutput")
|
||||
onToggled: checked => Settings.data.dock.onlySameOutput = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.dock.appearance-colorize-icons-label")
|
||||
description: I18n.tr("panels.dock.appearance-colorize-icons-description")
|
||||
checked: Settings.data.dock.colorizeIcons
|
||||
defaultValue: Settings.getDefaultValue("dock.colorizeIcons")
|
||||
onToggled: checked => Settings.data.dock.colorizeIcons = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.dock.appearance-show-launcher-icon-label")
|
||||
description: I18n.tr("panels.dock.appearance-show-launcher-icon-description")
|
||||
checked: Settings.data.dock.showLauncherIcon
|
||||
defaultValue: Settings.getDefaultValue("dock.showLauncherIcon")
|
||||
onToggled: checked => Settings.data.dock.showLauncherIcon = checked
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
visible: Settings.data.dock.showLauncherIcon
|
||||
label: I18n.tr("panels.dock.appearance-launcher-position-label")
|
||||
description: I18n.tr("panels.dock.appearance-launcher-position-description")
|
||||
model: [
|
||||
{
|
||||
"key": "start",
|
||||
"name": I18n.tr("panels.dock.appearance-launcher-position-start")
|
||||
},
|
||||
{
|
||||
"key": "end",
|
||||
"name": I18n.tr("panels.dock.appearance-launcher-position-end")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.dock.launcherPosition
|
||||
defaultValue: Settings.getDefaultValue("dock.launcherPosition")
|
||||
onSelected: key => Settings.data.dock.launcherPosition = key
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
visible: Settings.data.dock.showLauncherIcon
|
||||
label: I18n.tr("panels.dock.appearance-launcher-use-distro-logo-label")
|
||||
description: I18n.tr("panels.dock.appearance-launcher-use-distro-logo-description")
|
||||
checked: Settings.data.dock.launcherUseDistroLogo
|
||||
defaultValue: Settings.getDefaultValue("dock.launcherUseDistroLogo")
|
||||
onToggled: checked => Settings.data.dock.launcherUseDistroLogo = checked
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: Settings.data.dock.showLauncherIcon
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.dock.appearance-launcher-icon-label")
|
||||
description: I18n.tr("panels.dock.appearance-launcher-icon-description")
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
visible: !Settings.data.dock.launcherUseDistroLogo
|
||||
enabled: !Settings.data.dock.launcherUseDistroLogo
|
||||
icon: (Settings.data.dock.launcherIcon && Settings.data.dock.launcherIcon !== "") ? Settings.data.dock.launcherIcon : "search"
|
||||
colorFg: root.launcherPreviewColor
|
||||
colorFgHover: root.launcherPreviewColor
|
||||
tooltipText: I18n.tr("bar.control-center.browse-library")
|
||||
onClicked: launcherIconPicker.open()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: Settings.data.dock.launcherUseDistroLogo
|
||||
width: Style.toOdd(Style.baseWidgetSize * Style.uiScaleRatio)
|
||||
height: width
|
||||
radius: Math.min(Style.iRadiusL, width / 2)
|
||||
color: Color.smartAlpha(Color.mSurfaceVariant)
|
||||
border.color: Color.mOutline
|
||||
border.width: Style.borderS
|
||||
|
||||
Image {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width * 0.62
|
||||
height: width
|
||||
source: HostService.osLogo
|
||||
fillMode: Image.PreserveAspectFit
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
layer.enabled: visible
|
||||
layer.effect: ShaderEffect {
|
||||
property color targetColor: root.launcherPreviewColor
|
||||
property real colorizeMode: 2.0
|
||||
|
||||
fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NIconPicker {
|
||||
id: launcherIconPicker
|
||||
initialIcon: (Settings.data.dock.launcherIcon && Settings.data.dock.launcherIcon !== "") ? Settings.data.dock.launcherIcon : "search"
|
||||
onIconSelected: iconName => {
|
||||
Settings.data.dock.launcherIcon = iconName;
|
||||
Settings.saveImmediate();
|
||||
}
|
||||
}
|
||||
|
||||
NColorChoice {
|
||||
Layout.fillWidth: true
|
||||
visible: Settings.data.dock.showLauncherIcon
|
||||
label: I18n.tr("common.select-icon-color")
|
||||
currentKey: Settings.data.dock.launcherIconColor
|
||||
defaultValue: Settings.getDefaultValue("dock.launcherIconColor")
|
||||
onSelected: key => Settings.data.dock.launcherIconColor = key
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 0
|
||||
|
||||
NTabBar {
|
||||
id: subTabBar
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: Style.marginM
|
||||
distributeEvenly: true
|
||||
currentIndex: tabView.currentIndex
|
||||
|
||||
NTabButton {
|
||||
text: I18n.tr("common.appearance")
|
||||
tabIndex: 0
|
||||
checked: subTabBar.currentIndex === 0
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("common.monitors")
|
||||
tabIndex: 1
|
||||
checked: subTabBar.currentIndex === 1
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.marginL
|
||||
}
|
||||
|
||||
NTabView {
|
||||
id: tabView
|
||||
currentIndex: subTabBar.currentIndex
|
||||
|
||||
AppearanceSubTab {}
|
||||
MonitorsSubTab {}
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.Compositor
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
enabled: Settings.data.dock.enabled
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
// 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;
|
||||
});
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("panels.dock.monitors-desc")
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Quickshell.screens || []
|
||||
delegate: NCheckbox {
|
||||
Layout.fillWidth: true
|
||||
readonly property real compositorScale: {
|
||||
const info = CompositorService.displayScales[modelData.name];
|
||||
return (info && info.scale) ? info.scale : 1.0;
|
||||
}
|
||||
label: modelData.name || "Unknown"
|
||||
description: {
|
||||
I18n.tr("system.monitor-description", {
|
||||
"model": modelData.model,
|
||||
"width": modelData.width * compositorScale,
|
||||
"height": modelData.height * compositorScale,
|
||||
"scale": compositorScale
|
||||
});
|
||||
}
|
||||
checked: (Settings.data.dock.monitors || []).indexOf(modelData.name) !== -1
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
Settings.data.dock.monitors = root.addMonitor(Settings.data.dock.monitors, modelData.name);
|
||||
} else {
|
||||
Settings.data.dock.monitors = root.removeMonitor(Settings.data.dock.monitors, modelData.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import "../../../../../Helpers/QtObj2JS.js" as QtObj2JS
|
||||
import qs.Commons
|
||||
import qs.Services.System
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
// Profile section
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginL
|
||||
|
||||
// Avatar preview
|
||||
NImageRounded {
|
||||
Layout.preferredWidth: 128 * Style.uiScaleRatio
|
||||
Layout.preferredHeight: width
|
||||
radius: width / 2
|
||||
imagePath: Settings.preprocessPath(Settings.data.general.avatarImage)
|
||||
fallbackIcon: "person"
|
||||
borderColor: Color.mPrimary
|
||||
borderWidth: Style.borderM
|
||||
Layout.alignment: Qt.AlignTop
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
NText {
|
||||
text: HostService.displayName
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mPrimary
|
||||
}
|
||||
|
||||
NTextInputButton {
|
||||
label: I18n.tr("panels.general.profile-picture-label")
|
||||
description: I18n.tr("panels.general.profile-picture-description")
|
||||
text: Settings.data.general.avatarImage
|
||||
placeholderText: '~/.face' // don't translate path
|
||||
buttonIcon: "photo"
|
||||
buttonTooltip: I18n.tr("panels.general.profile-tooltip")
|
||||
onInputTextChanged: text => Settings.data.general.avatarImage = text
|
||||
onButtonClicked: {
|
||||
avatarPicker.openFilePicker();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NFilePicker {
|
||||
id: avatarPicker
|
||||
title: I18n.tr("panels.general.profile-select-avatar")
|
||||
selectionMode: "files"
|
||||
initialPath: Settings.preprocessPath(Settings.data.general.avatarImage).substr(0, Settings.preprocessPath(Settings.data.general.avatarImage).lastIndexOf("/")) || Quickshell.env("HOME")
|
||||
nameFilters: ImageCacheService.basicImageFilters
|
||||
onAccepted: paths => {
|
||||
if (paths.length > 0) {
|
||||
Settings.data.general.avatarImage = paths[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginM
|
||||
Layout.bottomMargin: Style.marginM
|
||||
}
|
||||
|
||||
// Fonts
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
// Font configuration section
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NSearchableComboBox {
|
||||
label: I18n.tr("panels.general.fonts-default-label")
|
||||
description: I18n.tr("panels.general.fonts-default-description")
|
||||
model: FontService.availableFonts
|
||||
currentKey: Settings.data.ui.fontDefault
|
||||
placeholder: I18n.tr("panels.general.fonts-default-placeholder")
|
||||
searchPlaceholder: I18n.tr("panels.general.fonts-default-search-placeholder")
|
||||
popupHeight: 420
|
||||
defaultValue: Settings.getDefaultValue("ui.fontDefault")
|
||||
settingsPath: "ui.fontDefault"
|
||||
onSelected: key => Settings.data.ui.fontDefault = key
|
||||
}
|
||||
|
||||
NSearchableComboBox {
|
||||
label: I18n.tr("panels.general.fonts-monospace-label")
|
||||
description: I18n.tr("panels.general.fonts-monospace-description")
|
||||
model: FontService.monospaceFonts
|
||||
currentKey: Settings.data.ui.fontFixed
|
||||
placeholder: I18n.tr("panels.general.fonts-monospace-placeholder")
|
||||
searchPlaceholder: I18n.tr("panels.general.fonts-monospace-search-placeholder")
|
||||
popupHeight: 320
|
||||
defaultValue: Settings.getDefaultValue("ui.fontFixed")
|
||||
settingsPath: "ui.fontFixed"
|
||||
onSelected: key => Settings.data.ui.fontFixed = key
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.general.fonts-default-scale-label")
|
||||
description: I18n.tr("panels.general.fonts-default-scale-description")
|
||||
from: 0.75
|
||||
to: 1.25
|
||||
stepSize: 0.01
|
||||
showReset: true
|
||||
value: Settings.data.ui.fontDefaultScale
|
||||
defaultValue: Settings.getDefaultValue("ui.fontDefaultScale")
|
||||
onMoved: value => Settings.data.ui.fontDefaultScale = value
|
||||
text: Math.floor(Settings.data.ui.fontDefaultScale * 100) + "%"
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.general.fonts-monospace-scale-label")
|
||||
description: I18n.tr("panels.general.fonts-monospace-scale-description")
|
||||
from: 0.75
|
||||
to: 1.25
|
||||
stepSize: 0.01
|
||||
showReset: true
|
||||
value: Settings.data.ui.fontFixedScale
|
||||
defaultValue: Settings.getDefaultValue("ui.fontFixedScale")
|
||||
onMoved: value => Settings.data.ui.fontFixedScale = value
|
||||
text: Math.floor(Settings.data.ui.fontFixedScale * 100) + "%"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginM
|
||||
Layout.bottomMargin: Style.marginM
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.general.reverse-scrolling-label")
|
||||
description: I18n.tr("panels.general.reverse-scrolling-description")
|
||||
checked: Settings.data.general.reverseScroll
|
||||
defaultValue: Settings.getDefaultValue("general.reverseScroll")
|
||||
onToggled: checked => Settings.data.general.reverseScroll = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.general.smooth-scrolling-label")
|
||||
description: I18n.tr("panels.general.smooth-scrolling-description")
|
||||
checked: Settings.data.general.smoothScrollEnabled
|
||||
defaultValue: Settings.getDefaultValue("general.smoothScrollEnabled")
|
||||
onToggled: checked => Settings.data.general.smoothScrollEnabled = checked
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginM
|
||||
Layout.bottomMargin: Style.marginM
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NButton {
|
||||
icon: "wand"
|
||||
text: I18n.tr("panels.general.launch-setup-wizard")
|
||||
outlined: true
|
||||
Layout.fillWidth: true
|
||||
onClicked: {
|
||||
var targetScreen = PanelService.openedPanel ? PanelService.openedPanel.screen : (Quickshell.screens.length > 0 ? Quickshell.screens[0] : null);
|
||||
if (!targetScreen) {
|
||||
return;
|
||||
}
|
||||
var setupPanel = PanelService.getPanel("setupWizardPanel", targetScreen);
|
||||
if (setupPanel) {
|
||||
setupPanel.telemetryOnlyMode = false;
|
||||
setupPanel.open();
|
||||
} else {
|
||||
Qt.callLater(() => {
|
||||
var sp = PanelService.getPanel("setupWizardPanel", targetScreen);
|
||||
if (sp) {
|
||||
sp.telemetryOnlyMode = false;
|
||||
sp.open();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NButton {
|
||||
icon: "external-link"
|
||||
text: I18n.tr("common.documentation")
|
||||
outlined: true
|
||||
Layout.fillWidth: true
|
||||
onClicked: {
|
||||
Qt.openUrlExternally("https://docs.noctalia.dev");
|
||||
}
|
||||
}
|
||||
|
||||
NButton {
|
||||
icon: "json"
|
||||
text: I18n.tr("panels.general.copy-settings")
|
||||
outlined: true
|
||||
Layout.fillWidth: true
|
||||
onClicked: {
|
||||
var plainData = QtObj2JS.qtObjectToPlainObject(Settings.data);
|
||||
var json = JSON.stringify(plainData, null, 2);
|
||||
Quickshell.execDetached(["wl-copy", json]);
|
||||
ToastService.showNotice(I18n.tr("panels.general.settings-copied"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
width: parent.width
|
||||
|
||||
// Keybinds section
|
||||
NLabel {
|
||||
label: I18n.tr("panels.general.keybinds-title")
|
||||
description: I18n.tr("panels.general.keybinds-description")
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NKeybindRecorder {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.general.keybinds-up")
|
||||
currentKeybinds: Settings.data.general.keybinds.keyUp
|
||||
defaultKeybind: "Up"
|
||||
settingsPath: "general.keybinds.keyUp"
|
||||
onKeybindsChanged: newKeybinds => Settings.data.general.keybinds.keyUp = newKeybinds
|
||||
}
|
||||
|
||||
NKeybindRecorder {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.general.keybinds-down")
|
||||
currentKeybinds: Settings.data.general.keybinds.keyDown
|
||||
defaultKeybind: "Down"
|
||||
settingsPath: "general.keybinds.keyDown"
|
||||
onKeybindsChanged: newKeybinds => Settings.data.general.keybinds.keyDown = newKeybinds
|
||||
}
|
||||
|
||||
NKeybindRecorder {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.general.keybinds-left")
|
||||
currentKeybinds: Settings.data.general.keybinds.keyLeft
|
||||
defaultKeybind: "Left"
|
||||
settingsPath: "general.keybinds.keyLeft"
|
||||
onKeybindsChanged: newKeybinds => Settings.data.general.keybinds.keyLeft = newKeybinds
|
||||
}
|
||||
|
||||
NKeybindRecorder {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.general.keybinds-right")
|
||||
currentKeybinds: Settings.data.general.keybinds.keyRight
|
||||
defaultKeybind: "Right"
|
||||
settingsPath: "general.keybinds.keyRight"
|
||||
onKeybindsChanged: newKeybinds => Settings.data.general.keybinds.keyRight = newKeybinds
|
||||
}
|
||||
|
||||
NKeybindRecorder {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.general.keybinds-enter")
|
||||
currentKeybinds: Settings.data.general.keybinds.keyEnter
|
||||
defaultKeybind: "Return"
|
||||
settingsPath: "general.keybinds.keyEnter"
|
||||
onKeybindsChanged: newKeybinds => Settings.data.general.keybinds.keyEnter = newKeybinds
|
||||
}
|
||||
|
||||
NKeybindRecorder {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.general.keybinds-escape")
|
||||
currentKeybinds: Settings.data.general.keybinds.keyEscape
|
||||
defaultKeybind: "Esc"
|
||||
settingsPath: "general.keybinds.keyEscape"
|
||||
onKeybindsChanged: newKeybinds => Settings.data.general.keybinds.keyEscape = newKeybinds
|
||||
}
|
||||
|
||||
NKeybindRecorder {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.general.keybinds-remove")
|
||||
currentKeybinds: Settings.data.general.keybinds.keyRemove
|
||||
defaultKeybind: "Del"
|
||||
settingsPath: "general.keybinds.keyRemove"
|
||||
onKeybindsChanged: newKeybinds => Settings.data.general.keybinds.keyRemove = newKeybinds
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import "../../../../Helpers/QtObj2JS.js" as QtObj2JS
|
||||
import "General"
|
||||
import qs.Commons
|
||||
import qs.Services.System
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 0
|
||||
|
||||
NTabBar {
|
||||
id: subTabBar
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: Style.marginM
|
||||
distributeEvenly: true
|
||||
currentIndex: tabView.currentIndex
|
||||
|
||||
NTabButton {
|
||||
text: I18n.tr("panels.general.tab-basics")
|
||||
tabIndex: 0
|
||||
checked: subTabBar.currentIndex === 0
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("panels.general.tab-keybinds")
|
||||
tabIndex: 1
|
||||
checked: subTabBar.currentIndex === 1
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.marginL
|
||||
}
|
||||
|
||||
NTabView {
|
||||
id: tabView
|
||||
currentIndex: subTabBar.currentIndex
|
||||
BasicsSubTab {}
|
||||
KeybindsSubTab {}
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
width: parent.width
|
||||
|
||||
// Enable/Disable Toggle
|
||||
NToggle {
|
||||
label: I18n.tr("panels.hooks.system-hooks-enable-label")
|
||||
description: I18n.tr("panels.hooks.system-hooks-enable-description")
|
||||
checked: Settings.data.hooks.enabled
|
||||
onToggled: checked => Settings.data.hooks.enabled = checked
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Info section
|
||||
ColumnLayout {
|
||||
spacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("panels.hooks.info-parameters-label")
|
||||
description: I18n.tr("panels.hooks.info-parameters-description")
|
||||
}
|
||||
}
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
Popup {
|
||||
id: root
|
||||
modal: true
|
||||
closePolicy: Popup.NoAutoClose
|
||||
dim: true
|
||||
anchors.centerIn: parent
|
||||
|
||||
// Use a reasonable width/height relative to parent or fixed
|
||||
width: Math.min(600 * Style.uiScaleRatio, parent.width * 0.9)
|
||||
height: Math.min(contentLayout.implicitHeight + padding * 2, parent.height * 0.9)
|
||||
padding: Style.marginL
|
||||
|
||||
property string hookLabel: ""
|
||||
property string hookDescription: ""
|
||||
property string hookPlaceholder: ""
|
||||
property string initialValue: ""
|
||||
|
||||
signal saved(string newValue)
|
||||
signal test(string value)
|
||||
|
||||
property var _savedSlot: null
|
||||
property var _testSlot: null
|
||||
|
||||
background: Rectangle {
|
||||
color: Color.mSurface
|
||||
radius: Style.radiusL
|
||||
border.color: Color.mOutline
|
||||
border.width: Style.borderS
|
||||
}
|
||||
|
||||
onOpened: {
|
||||
commandInput.text = initialValue;
|
||||
commandInput.forceActiveFocus();
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
id: contentLayout
|
||||
spacing: Style.marginL
|
||||
|
||||
// Header
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
NText {
|
||||
text: root.hookLabel
|
||||
font.weight: Style.fontWeightBold
|
||||
pointSize: Style.fontSizeL
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
onClicked: root.close()
|
||||
}
|
||||
}
|
||||
|
||||
// Description/Help
|
||||
NText {
|
||||
text: root.hookDescription
|
||||
color: Color.mOnSurfaceVariant
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Input Area
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
|
||||
NTextInput {
|
||||
id: commandInput
|
||||
Layout.fillWidth: true
|
||||
placeholderText: root.hookPlaceholder
|
||||
// Allow multiline? Hooks are usually oneline commands but can be scripts.
|
||||
// NTextInput is likely single line. Let's assume single line for now as per previous implementation.
|
||||
}
|
||||
}
|
||||
|
||||
// Action Buttons
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("common.test")
|
||||
icon: "caret-right"
|
||||
onClicked: root.test(commandInput.text)
|
||||
// Disable test if empty? Or maybe allow testing defined script.
|
||||
enabled: commandInput.text !== ""
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
} // Spacer
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("common.cancel")
|
||||
outlined: true
|
||||
onClicked: root.close()
|
||||
}
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("common.save")
|
||||
icon: "check"
|
||||
backgroundColor: Color.mPrimary
|
||||
textColor: Color.mOnPrimary
|
||||
onClicked: {
|
||||
root.saved(commandInput.text);
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
RowLayout {
|
||||
id: root
|
||||
|
||||
property string label: ""
|
||||
property string description: ""
|
||||
property string value: ""
|
||||
|
||||
signal editClicked
|
||||
|
||||
spacing: Style.marginM
|
||||
|
||||
NLabel {
|
||||
label: root.label
|
||||
description: root.description
|
||||
labelColor: root.value ? Color.mPrimary : Color.mOnSurface
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "settings"
|
||||
onClicked: root.editClicked()
|
||||
tooltipText: I18n.tr("common.edit")
|
||||
}
|
||||
}
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.Control
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
enabled: Settings.data.hooks.enabled
|
||||
spacing: Style.marginL
|
||||
width: parent.width
|
||||
|
||||
// Shared Edit Popup
|
||||
HookEditPopup {
|
||||
id: editPopup
|
||||
parent: Overlay.overlay
|
||||
}
|
||||
|
||||
// Helper to open popup
|
||||
function openEdit(label, description, placeholder, value, onSave, onTest) {
|
||||
editPopup.hookLabel = label;
|
||||
editPopup.hookDescription = description;
|
||||
editPopup.hookPlaceholder = placeholder;
|
||||
editPopup.initialValue = value;
|
||||
|
||||
// Disconnect previous signals
|
||||
try {
|
||||
editPopup.saved.disconnect(editPopup._savedSlot);
|
||||
} catch (e) {}
|
||||
try {
|
||||
editPopup.test.disconnect(editPopup._testSlot);
|
||||
} catch (e) {}
|
||||
|
||||
// Define slots
|
||||
editPopup._savedSlot = onSave;
|
||||
editPopup._testSlot = onTest;
|
||||
|
||||
// Connect new signals
|
||||
editPopup.saved.connect(editPopup._savedSlot);
|
||||
editPopup.test.connect(editPopup._testSlot);
|
||||
|
||||
editPopup.open();
|
||||
}
|
||||
|
||||
// Startup Hook
|
||||
HookRow {
|
||||
label: I18n.tr("panels.hooks.noctalia-started-label")
|
||||
description: I18n.tr("panels.hooks.noctalia-started-description")
|
||||
value: Settings.data.hooks.startup
|
||||
onEditClicked: openEdit(label, description, I18n.tr("panels.hooks.noctalia-started-placeholder"), value, newValue => {
|
||||
Settings.data.hooks.startup = newValue;
|
||||
Settings.saveImmediate();
|
||||
}, val => {
|
||||
HooksService.executeStartupHook();
|
||||
})
|
||||
}
|
||||
|
||||
// Wallpaper Hook
|
||||
HookRow {
|
||||
label: I18n.tr("panels.hooks.wallpaper-changed-label")
|
||||
description: I18n.tr("panels.hooks.wallpaper-changed-description")
|
||||
value: Settings.data.hooks.wallpaperChange
|
||||
onEditClicked: openEdit(label, description, I18n.tr("panels.hooks.wallpaper-changed-placeholder"), value, newValue => {
|
||||
Settings.data.hooks.wallpaperChange = newValue;
|
||||
Settings.saveImmediate();
|
||||
}, val => {
|
||||
if (val)
|
||||
Quickshell.execDetached(["sh", "-lc", val.replace("$1", "test_wallpaper_path").replace("$2", "test_screen").replace("$3", "dark")]);
|
||||
})
|
||||
}
|
||||
|
||||
// Color Generation Hook
|
||||
HookRow {
|
||||
label: I18n.tr("panels.hooks.color-generation-label")
|
||||
description: I18n.tr("panels.hooks.color-generation-description")
|
||||
value: Settings.data.hooks.colorGeneration
|
||||
onEditClicked: openEdit(label, description, I18n.tr("panels.hooks.color-generation-placeholder"), value, newValue => {
|
||||
Settings.data.hooks.colorGeneration = newValue;
|
||||
Settings.saveImmediate();
|
||||
}, val => {
|
||||
if (val)
|
||||
Quickshell.execDetached(["sh", "-lc", val.replace("$1", "dark")]);
|
||||
})
|
||||
}
|
||||
|
||||
// Theme Hook
|
||||
HookRow {
|
||||
label: I18n.tr("panels.hooks.theme-changed-label")
|
||||
description: I18n.tr("panels.hooks.theme-changed-description")
|
||||
value: Settings.data.hooks.darkModeChange
|
||||
onEditClicked: openEdit(label, description, I18n.tr("panels.hooks.theme-changed-placeholder"), value, newValue => {
|
||||
Settings.data.hooks.darkModeChange = newValue;
|
||||
Settings.saveImmediate();
|
||||
}, val => {
|
||||
if (val)
|
||||
Quickshell.execDetached(["sh", "-lc", val.replace("$1", "true")]);
|
||||
})
|
||||
}
|
||||
|
||||
// Screen Lock Hook
|
||||
HookRow {
|
||||
label: I18n.tr("panels.hooks.screen-lock-label")
|
||||
description: I18n.tr("panels.hooks.screen-lock-description")
|
||||
value: Settings.data.hooks.screenLock
|
||||
onEditClicked: openEdit(label, description, I18n.tr("panels.hooks.screen-lock-placeholder"), value, newValue => {
|
||||
Settings.data.hooks.screenLock = newValue;
|
||||
Settings.saveImmediate();
|
||||
}, val => {
|
||||
if (val)
|
||||
Quickshell.execDetached(["sh", "-lc", val]);
|
||||
})
|
||||
}
|
||||
|
||||
// Screen Unlock Hook
|
||||
HookRow {
|
||||
label: I18n.tr("panels.hooks.screen-unlock-label")
|
||||
description: I18n.tr("panels.hooks.screen-unlock-description")
|
||||
value: Settings.data.hooks.screenUnlock
|
||||
onEditClicked: openEdit(label, description, I18n.tr("panels.hooks.screen-unlock-placeholder"), value, newValue => {
|
||||
Settings.data.hooks.screenUnlock = newValue;
|
||||
Settings.saveImmediate();
|
||||
}, val => {
|
||||
if (val)
|
||||
Quickshell.execDetached(["sh", "-lc", val]);
|
||||
})
|
||||
}
|
||||
|
||||
// Performance Mode Enabled Hook
|
||||
HookRow {
|
||||
label: I18n.tr("panels.hooks.performance-mode-enabled-label")
|
||||
description: I18n.tr("panels.hooks.performance-mode-enabled-description")
|
||||
value: Settings.data.hooks.performanceModeEnabled
|
||||
onEditClicked: openEdit(label, description, I18n.tr("panels.hooks.performance-mode-enabled-placeholder"), value, newValue => {
|
||||
Settings.data.hooks.performanceModeEnabled = newValue;
|
||||
Settings.saveImmediate();
|
||||
}, val => {
|
||||
if (val)
|
||||
Quickshell.execDetached(["sh", "-lc", val]);
|
||||
})
|
||||
}
|
||||
|
||||
// Performance Mode Disabled Hook
|
||||
HookRow {
|
||||
label: I18n.tr("panels.hooks.performance-mode-disabled-label")
|
||||
description: I18n.tr("panels.hooks.performance-mode-disabled-description")
|
||||
value: Settings.data.hooks.performanceModeDisabled
|
||||
onEditClicked: openEdit(label, description, I18n.tr("panels.hooks.performance-mode-disabled-placeholder"), value, newValue => {
|
||||
Settings.data.hooks.performanceModeDisabled = newValue;
|
||||
Settings.saveImmediate();
|
||||
}, val => {
|
||||
if (val)
|
||||
Quickshell.execDetached(["sh", "-lc", val]);
|
||||
})
|
||||
}
|
||||
|
||||
// Session Hook
|
||||
HookRow {
|
||||
label: I18n.tr("panels.hooks.session-label")
|
||||
description: I18n.tr("panels.hooks.session-description")
|
||||
value: Settings.data.hooks.session
|
||||
onEditClicked: openEdit(label, description, I18n.tr("panels.hooks.session-placeholder"), value, newValue => {
|
||||
Settings.data.hooks.session = newValue;
|
||||
Settings.saveImmediate();
|
||||
}, val => {
|
||||
if (val)
|
||||
Quickshell.execDetached(["sh", "-lc", val + " test"]);
|
||||
})
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 0
|
||||
|
||||
NTabBar {
|
||||
id: subTabBar
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: Style.marginM
|
||||
distributeEvenly: true
|
||||
currentIndex: tabView.currentIndex
|
||||
|
||||
NTabButton {
|
||||
text: I18n.tr("common.general")
|
||||
tabIndex: 0
|
||||
checked: subTabBar.currentIndex === 0
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("panels.hooks.title")
|
||||
tabIndex: 1
|
||||
checked: subTabBar.currentIndex === 1
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.marginS
|
||||
}
|
||||
|
||||
NTabView {
|
||||
id: tabView
|
||||
currentIndex: subTabBar.currentIndex
|
||||
|
||||
GeneralSubTab {}
|
||||
HooksListSubTab {}
|
||||
}
|
||||
}
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.Power
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
// Master enable
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.idle.enable-label")
|
||||
description: I18n.tr("panels.idle.enable-description")
|
||||
checked: Settings.data.idle.enabled
|
||||
defaultValue: Settings.getDefaultValue("idle.enabled")
|
||||
onToggled: checked => Settings.data.idle.enabled = checked
|
||||
}
|
||||
|
||||
// Live idle status
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
enabled: Settings.data.idle.enabled
|
||||
visible: IdleService.nativeIdleMonitorAvailable
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("panels.idle.status-label")
|
||||
description: I18n.tr("panels.idle.status-description")
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NText {
|
||||
Layout.alignment: Qt.AlignBottom | Qt.AlignRight
|
||||
text: IdleService.idleSeconds > 0 ? I18n.trp("common.second", IdleService.idleSeconds) : I18n.tr("common.active")
|
||||
family: Settings.data.ui.fontFixed
|
||||
pointSize: Style.fontSizeM
|
||||
color: IdleService.idleSeconds > 0 ? Color.mPrimary : Color.mOnSurfaceVariant
|
||||
}
|
||||
}
|
||||
|
||||
NLabel {
|
||||
visible: !IdleService.nativeIdleMonitorAvailable
|
||||
description: I18n.tr("panels.idle.unavailable")
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
IdleCommandEditPopup {
|
||||
id: editPopup
|
||||
parent: Overlay.overlay
|
||||
}
|
||||
|
||||
function openEdit(actionName, cmdVal, resumeCmdVal, onSaveCmd, onSaveResume) {
|
||||
editPopup.editIndex = -1;
|
||||
editPopup.showCommand = true;
|
||||
editPopup.showTimeout = false;
|
||||
editPopup.titleText = I18n.tr("common.edit") + " " + actionName;
|
||||
editPopup.timeoutValue = 0;
|
||||
editPopup.commandValue = cmdVal;
|
||||
editPopup.resumeCommandValue = resumeCmdVal;
|
||||
|
||||
try {
|
||||
editPopup.saved.disconnect(editPopup._savedSlot);
|
||||
} catch (e) {}
|
||||
|
||||
editPopup._savedSlot = function (timeout, cmd, resumeCmd, name) {
|
||||
onSaveCmd(cmd);
|
||||
onSaveResume(resumeCmd);
|
||||
};
|
||||
|
||||
editPopup.saved.connect(editPopup._savedSlot);
|
||||
editPopup.open();
|
||||
}
|
||||
|
||||
// Timeout spinboxes and resume commands
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginL
|
||||
enabled: Settings.data.idle.enabled
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("panels.idle.timeouts-label")
|
||||
description: I18n.tr("panels.idle.timeouts-description")
|
||||
}
|
||||
|
||||
DefaultActionRow {
|
||||
actionName: I18n.tr("panels.idle.screen-off-label")
|
||||
actionDescription: I18n.tr("panels.idle.screen-off-description")
|
||||
timeoutValue: Settings.data.idle.screenOffTimeout
|
||||
defaultValue: Settings.getDefaultValue("idle.screenOffTimeout")
|
||||
command: Settings.data.idle.screenOffCommand
|
||||
resumeCommand: Settings.data.idle.resumeScreenOffCommand
|
||||
onActionTimeoutChanged: val => Settings.data.idle.screenOffTimeout = val
|
||||
onActionCommandChanged: cmd => {
|
||||
Settings.data.idle.screenOffCommand = cmd;
|
||||
Settings.saveImmediate();
|
||||
}
|
||||
onActionResumeCommandChanged: cmd => {
|
||||
Settings.data.idle.resumeScreenOffCommand = cmd;
|
||||
Settings.saveImmediate();
|
||||
}
|
||||
}
|
||||
|
||||
DefaultActionRow {
|
||||
actionName: I18n.tr("panels.idle.lock-label")
|
||||
actionDescription: I18n.tr("panels.idle.lock-description")
|
||||
timeoutValue: Settings.data.idle.lockTimeout
|
||||
defaultValue: Settings.getDefaultValue("idle.lockTimeout")
|
||||
command: Settings.data.idle.lockCommand
|
||||
resumeCommand: Settings.data.idle.resumeLockCommand
|
||||
onActionTimeoutChanged: val => Settings.data.idle.lockTimeout = val
|
||||
onActionCommandChanged: cmd => {
|
||||
Settings.data.idle.lockCommand = cmd;
|
||||
Settings.saveImmediate();
|
||||
}
|
||||
onActionResumeCommandChanged: cmd => {
|
||||
Settings.data.idle.resumeLockCommand = cmd;
|
||||
Settings.saveImmediate();
|
||||
}
|
||||
}
|
||||
|
||||
DefaultActionRow {
|
||||
actionName: I18n.tr("common.suspend")
|
||||
actionDescription: I18n.tr("panels.idle.suspend-description")
|
||||
timeoutValue: Settings.data.idle.suspendTimeout
|
||||
defaultValue: Settings.getDefaultValue("idle.suspendTimeout")
|
||||
command: Settings.data.idle.suspendCommand
|
||||
resumeCommand: Settings.data.idle.resumeSuspendCommand
|
||||
onActionTimeoutChanged: val => Settings.data.idle.suspendTimeout = val
|
||||
onActionCommandChanged: cmd => {
|
||||
Settings.data.idle.suspendCommand = cmd;
|
||||
Settings.saveImmediate();
|
||||
}
|
||||
onActionResumeCommandChanged: cmd => {
|
||||
Settings.data.idle.resumeSuspendCommand = cmd;
|
||||
Settings.saveImmediate();
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
label: I18n.tr("panels.idle.fade-duration-label")
|
||||
description: I18n.tr("panels.idle.fade-duration-description")
|
||||
from: 1
|
||||
to: 60
|
||||
suffix: "s"
|
||||
value: Settings.data.idle.fadeDuration
|
||||
defaultValue: Settings.getDefaultValue("idle.fadeDuration")
|
||||
onValueChanged: Settings.data.idle.fadeDuration = value
|
||||
}
|
||||
}
|
||||
|
||||
component DefaultActionRow: RowLayout {
|
||||
id: rowRoot
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
property string actionName
|
||||
property string actionDescription
|
||||
property alias timeoutValue: spinBox.value
|
||||
property int defaultValue
|
||||
property string command
|
||||
property string resumeCommand
|
||||
|
||||
signal actionTimeoutChanged(int newValue)
|
||||
signal actionCommandChanged(string newCmd)
|
||||
signal actionResumeCommandChanged(string newCmd)
|
||||
|
||||
NSpinBox {
|
||||
id: spinBox
|
||||
Layout.fillWidth: true
|
||||
label: rowRoot.actionName
|
||||
description: rowRoot.actionDescription
|
||||
from: 0
|
||||
to: 86400
|
||||
suffix: "s"
|
||||
defaultValue: rowRoot.defaultValue
|
||||
onValueChanged: rowRoot.actionTimeoutChanged(value)
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
icon: "settings"
|
||||
tooltipText: I18n.tr("common.edit")
|
||||
onClicked: root.openEdit(rowRoot.actionName, rowRoot.command, rowRoot.resumeCommand, rowRoot.actionCommandChanged, rowRoot.actionResumeCommandChanged)
|
||||
}
|
||||
}
|
||||
}
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.Power
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
enabled: Settings.data.idle.enabled
|
||||
|
||||
property bool _saving: false
|
||||
|
||||
ListModel {
|
||||
id: entriesModel
|
||||
}
|
||||
|
||||
function _loadToModel() {
|
||||
if (_saving)
|
||||
return;
|
||||
entriesModel.clear();
|
||||
var entries = [];
|
||||
try {
|
||||
entries = JSON.parse(Settings.data.idle.customCommands);
|
||||
} catch (e) {
|
||||
Logger.w("CustomSubTab", "Failed to parse customCommands:", e);
|
||||
}
|
||||
for (var i = 0; i < entries.length; i++) {
|
||||
entriesModel.append({
|
||||
"name": String(entries[i].name || ""),
|
||||
"timeout": parseInt(entries[i].timeout) || 60,
|
||||
"command": String(entries[i].command || ""),
|
||||
"resumeCommand": String(entries[i].resumeCommand || "")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function _saveFromModel() {
|
||||
_saving = true;
|
||||
var arr = [];
|
||||
for (var i = 0; i < entriesModel.count; i++) {
|
||||
var item = entriesModel.get(i);
|
||||
arr.push({
|
||||
"name": item.name,
|
||||
"timeout": item.timeout,
|
||||
"command": item.command,
|
||||
"resumeCommand": item.resumeCommand
|
||||
});
|
||||
}
|
||||
Settings.data.idle.customCommands = JSON.stringify(arr);
|
||||
_saving = false;
|
||||
}
|
||||
|
||||
function _removeEntry(index) {
|
||||
entriesModel.remove(index, 1);
|
||||
_saveFromModel();
|
||||
}
|
||||
|
||||
Component.onCompleted: Qt.callLater(_loadToModel)
|
||||
|
||||
Connections {
|
||||
target: Settings.data.idle
|
||||
function onCustomCommandsChanged() {
|
||||
root._loadToModel();
|
||||
}
|
||||
}
|
||||
|
||||
// Shared Edit Popup
|
||||
IdleCommandEditPopup {
|
||||
id: editPopup
|
||||
parent: Overlay.overlay
|
||||
}
|
||||
|
||||
function openEdit(index, nameVal, timeoutVal, cmdVal, resumeCmdVal) {
|
||||
editPopup.editIndex = index;
|
||||
editPopup.nameValue = nameVal;
|
||||
editPopup.timeoutValue = timeoutVal;
|
||||
editPopup.commandValue = cmdVal;
|
||||
editPopup.resumeCommandValue = resumeCmdVal;
|
||||
editPopup.showName = true;
|
||||
|
||||
try {
|
||||
editPopup.saved.disconnect(editPopup._savedSlot);
|
||||
} catch (e) {}
|
||||
|
||||
editPopup._savedSlot = function (timeout, cmd, resumeCmd, name) {
|
||||
if (index >= 0 && index < entriesModel.count) {
|
||||
entriesModel.setProperty(index, "name", name);
|
||||
entriesModel.setProperty(index, "timeout", timeout);
|
||||
entriesModel.setProperty(index, "command", cmd);
|
||||
entriesModel.setProperty(index, "resumeCommand", resumeCmd);
|
||||
} else {
|
||||
entriesModel.append({
|
||||
"name": name,
|
||||
"timeout": timeout,
|
||||
"command": cmd,
|
||||
"resumeCommand": resumeCmd
|
||||
});
|
||||
}
|
||||
root._saveFromModel();
|
||||
};
|
||||
|
||||
editPopup.saved.connect(editPopup._savedSlot);
|
||||
editPopup.open();
|
||||
}
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("panels.idle.custom-label")
|
||||
description: I18n.tr("panels.idle.custom-description")
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginS
|
||||
Layout.bottomMargin: Style.marginS
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: entriesModel
|
||||
|
||||
delegate: RowLayout {
|
||||
id: entryDelegate
|
||||
required property int index
|
||||
required property string name
|
||||
required property int timeout
|
||||
required property string command
|
||||
required property string resumeCommand
|
||||
|
||||
spacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
Layout.fillWidth: true
|
||||
label: entryDelegate.name || I18n.tr("panels.idle.custom-entry-unnamed")
|
||||
description: I18n.trp("common.second", entryDelegate.timeout)
|
||||
labelColor: (entryDelegate.command || entryDelegate.resumeCommand) ? Color.mPrimary : Color.mOnSurface
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "settings"
|
||||
tooltipText: I18n.tr("common.edit")
|
||||
onClicked: root.openEdit(entryDelegate.index, entryDelegate.name, entryDelegate.timeout, entryDelegate.command, entryDelegate.resumeCommand)
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "trash"
|
||||
tooltipText: I18n.tr("panels.idle.custom-entry-delete")
|
||||
onClicked: root._removeEntry(entryDelegate.index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("panels.idle.custom-add")
|
||||
icon: "add"
|
||||
enabled: Settings.data.idle.enabled
|
||||
onClicked: {
|
||||
root.openEdit(-1, "", 60, "", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
Popup {
|
||||
id: root
|
||||
modal: true
|
||||
closePolicy: Popup.NoAutoClose
|
||||
dim: true
|
||||
anchors.centerIn: parent
|
||||
|
||||
width: Math.min(600 * Style.uiScaleRatio, parent.width * 0.9)
|
||||
height: Math.min(contentLayout.implicitHeight + padding * 2, parent.height * 0.9)
|
||||
padding: Style.marginL
|
||||
|
||||
property int editIndex: -1
|
||||
property int timeoutValue: 60
|
||||
property string commandValue: ""
|
||||
property string resumeCommandValue: ""
|
||||
property string nameValue: ""
|
||||
property bool showCommand: true
|
||||
property bool showTimeout: true
|
||||
property bool showName: false
|
||||
property string titleText: root.editIndex >= 0 ? I18n.tr("panels.idle.custom-entry-edit") : I18n.tr("panels.idle.custom-entry-new")
|
||||
|
||||
signal saved(int timeout, string command, string resumeCommand, string name)
|
||||
|
||||
property var _savedSlot: null
|
||||
|
||||
background: Rectangle {
|
||||
color: Color.mSurface
|
||||
radius: Style.radiusL
|
||||
border.color: Color.mOutline
|
||||
border.width: Style.borderS
|
||||
}
|
||||
|
||||
onOpened: {
|
||||
nameInput.text = nameValue;
|
||||
timeoutSpinBox.value = timeoutValue;
|
||||
commandInput.text = commandValue;
|
||||
resumeCommandInput.text = resumeCommandValue;
|
||||
if (showName) {
|
||||
nameInput.forceActiveFocus();
|
||||
} else {
|
||||
timeoutSpinBox.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
id: contentLayout
|
||||
spacing: Style.marginL
|
||||
|
||||
// Header
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
NText {
|
||||
text: root.titleText
|
||||
font.weight: Style.fontWeightBold
|
||||
pointSize: Style.fontSizeL
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
onClicked: root.close()
|
||||
}
|
||||
}
|
||||
|
||||
// Input Area
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NTextInput {
|
||||
id: nameInput
|
||||
visible: root.showName
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.idle.custom-entry-name")
|
||||
placeholderText: I18n.tr("panels.idle.custom-entry-name-placeholder")
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
id: timeoutSpinBox
|
||||
visible: root.showTimeout
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.idle.custom-entry-timeout")
|
||||
from: 0
|
||||
to: 86400
|
||||
suffix: "s"
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
id: commandInput
|
||||
visible: root.showCommand
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.idle.custom-entry-command")
|
||||
placeholderText: "notify-send \"Idle\""
|
||||
fontFamily: Settings.data.ui.fontFixed
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
id: resumeCommandInput
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.idle.resume-command-label")
|
||||
placeholderText: "notify-send \"Welcome back!\""
|
||||
fontFamily: Settings.data.ui.fontFixed
|
||||
}
|
||||
}
|
||||
|
||||
// Action Buttons
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
} // Spacer
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("common.cancel")
|
||||
outlined: true
|
||||
onClicked: root.close()
|
||||
}
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("common.save")
|
||||
icon: "check"
|
||||
backgroundColor: Color.mPrimary
|
||||
textColor: Color.mOnPrimary
|
||||
onClicked: {
|
||||
root.saved(timeoutSpinBox.value, commandInput.text, resumeCommandInput.text, nameInput.text);
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 0
|
||||
|
||||
NTabBar {
|
||||
id: subTabBar
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: Style.marginM
|
||||
distributeEvenly: true
|
||||
currentIndex: tabView.currentIndex
|
||||
|
||||
NTabButton {
|
||||
text: I18n.tr("panels.idle.tab-behavior")
|
||||
tabIndex: 0
|
||||
checked: subTabBar.currentIndex === 0
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("panels.idle.tab-custom")
|
||||
tabIndex: 1
|
||||
checked: subTabBar.currentIndex === 1
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.marginL
|
||||
}
|
||||
|
||||
NTabView {
|
||||
id: tabView
|
||||
currentIndex: subTabBar.currentIndex
|
||||
|
||||
BehaviorSubTab {}
|
||||
CustomSubTab {}
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.System
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.launcher.settings-clipboard-history-label")
|
||||
description: I18n.tr("panels.launcher.settings-clipboard-history-description")
|
||||
checked: Settings.data.appLauncher.enableClipboardHistory
|
||||
onToggled: checked => Settings.data.appLauncher.enableClipboardHistory = checked
|
||||
defaultValue: Settings.getDefaultValue("appLauncher.enableClipboardHistory")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.launcher.settings-clip-preview-label")
|
||||
description: I18n.tr("panels.launcher.settings-clip-preview-description")
|
||||
checked: Settings.data.appLauncher.enableClipPreview
|
||||
onToggled: checked => Settings.data.appLauncher.enableClipPreview = checked
|
||||
defaultValue: Settings.getDefaultValue("appLauncher.enableClipPreview")
|
||||
enabled: Settings.data.appLauncher.enableClipboardHistory
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.launcher.settings-clip-wrap-text-label")
|
||||
description: I18n.tr("panels.launcher.settings-clip-wrap-text-description")
|
||||
checked: Settings.data.appLauncher.clipboardWrapText
|
||||
onToggled: checked => Settings.data.appLauncher.clipboardWrapText = checked
|
||||
defaultValue: Settings.getDefaultValue("appLauncher.clipboardWrapText")
|
||||
enabled: Settings.data.appLauncher.enableClipboardHistory
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.launcher.settings-auto-paste-label")
|
||||
description: I18n.tr("panels.launcher.settings-auto-paste-description")
|
||||
checked: Settings.data.appLauncher.autoPasteClipboard
|
||||
onToggled: checked => Settings.data.appLauncher.autoPasteClipboard = checked
|
||||
defaultValue: Settings.getDefaultValue("appLauncher.autoPasteClipboard")
|
||||
enabled: Settings.data.appLauncher.enableClipboardHistory && ProgramCheckerService.wtypeAvailable
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.launcher.settings-clip-smart-icons-label")
|
||||
description: I18n.tr("panels.launcher.settings-clip-smart-icons-description")
|
||||
checked: Settings.data.appLauncher.enableClipboardSmartIcons
|
||||
onToggled: checked => Settings.data.appLauncher.enableClipboardSmartIcons = checked
|
||||
defaultValue: Settings.getDefaultValue("appLauncher.enableClipboardSmartIcons")
|
||||
enabled: Settings.data.appLauncher.enableClipboardHistory
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.launcher.settings-clip-chips-label")
|
||||
description: I18n.tr("panels.launcher.settings-clip-chips-description")
|
||||
checked: Settings.data.appLauncher.enableClipboardChips
|
||||
onToggled: checked => Settings.data.appLauncher.enableClipboardChips = checked
|
||||
defaultValue: Settings.getDefaultValue("appLauncher.enableClipboardChips")
|
||||
enabled: Settings.data.appLauncher.enableClipboardHistory
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
visible: Settings.data.appLauncher.enableClipboardHistory
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
label: I18n.tr("panels.launcher.settings-clipboard-watch-text-label")
|
||||
description: I18n.tr("panels.launcher.settings-clipboard-watch-text-description")
|
||||
Layout.fillWidth: true
|
||||
text: Settings.data.appLauncher.clipboardWatchTextCommand
|
||||
onTextChanged: Settings.data.appLauncher.clipboardWatchTextCommand = text
|
||||
enabled: Settings.data.appLauncher.enableClipboardHistory
|
||||
visible: Settings.data.appLauncher.enableClipboardHistory
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
label: I18n.tr("panels.launcher.settings-clipboard-watch-image-label")
|
||||
description: I18n.tr("panels.launcher.settings-clipboard-watch-image-description")
|
||||
Layout.fillWidth: true
|
||||
text: Settings.data.appLauncher.clipboardWatchImageCommand
|
||||
onTextChanged: Settings.data.appLauncher.clipboardWatchImageCommand = text
|
||||
enabled: Settings.data.appLauncher.enableClipboardHistory
|
||||
visible: Settings.data.appLauncher.enableClipboardHistory
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.System
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NTextInput {
|
||||
label: I18n.tr("panels.launcher.settings-terminal-command-label")
|
||||
description: I18n.tr("panels.launcher.settings-terminal-command-description")
|
||||
Layout.fillWidth: true
|
||||
text: Settings.data.appLauncher.terminalCommand
|
||||
onTextChanged: {
|
||||
Settings.data.appLauncher.terminalCommand = text;
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.launcher.settings-custom-launch-prefix-enabled-label")
|
||||
description: I18n.tr("panels.launcher.settings-custom-launch-prefix-enabled-description")
|
||||
checked: Settings.data.appLauncher.customLaunchPrefixEnabled
|
||||
onToggled: checked => Settings.data.appLauncher.customLaunchPrefixEnabled = checked
|
||||
defaultValue: Settings.getDefaultValue("appLauncher.customLaunchPrefixEnabled")
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
label: I18n.tr("panels.launcher.settings-custom-launch-prefix-label")
|
||||
description: I18n.tr("panels.launcher.settings-custom-launch-prefix-description")
|
||||
Layout.fillWidth: true
|
||||
text: Settings.data.appLauncher.customLaunchPrefix
|
||||
enabled: Settings.data.appLauncher.customLaunchPrefixEnabled
|
||||
visible: Settings.data.appLauncher.customLaunchPrefixEnabled
|
||||
onTextChanged: Settings.data.appLauncher.customLaunchPrefix = text
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
label: I18n.tr("panels.launcher.settings-annotation-tool-label")
|
||||
description: I18n.tr("panels.launcher.settings-annotation-tool-description")
|
||||
Layout.fillWidth: true
|
||||
text: Settings.data.appLauncher.screenshotAnnotationTool
|
||||
placeholderText: I18n.tr("panels.launcher.settings-annotation-tool-placeholder")
|
||||
onTextChanged: Settings.data.appLauncher.screenshotAnnotationTool = text
|
||||
}
|
||||
}
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("common.position")
|
||||
description: I18n.tr("panels.launcher.settings-position-description")
|
||||
Layout.fillWidth: true
|
||||
model: [
|
||||
{
|
||||
"key": "follow_bar",
|
||||
"name": I18n.tr("positions.follow-bar")
|
||||
},
|
||||
{
|
||||
"key": "center",
|
||||
"name": I18n.tr("positions.center")
|
||||
},
|
||||
{
|
||||
"key": "top_center",
|
||||
"name": I18n.tr("positions.top-center")
|
||||
},
|
||||
{
|
||||
"key": "top_left",
|
||||
"name": I18n.tr("positions.top-left")
|
||||
},
|
||||
{
|
||||
"key": "top_right",
|
||||
"name": I18n.tr("positions.top-right")
|
||||
},
|
||||
{
|
||||
"key": "bottom_left",
|
||||
"name": I18n.tr("positions.bottom-left")
|
||||
},
|
||||
{
|
||||
"key": "bottom_right",
|
||||
"name": I18n.tr("positions.bottom-right")
|
||||
},
|
||||
{
|
||||
"key": "bottom_center",
|
||||
"name": I18n.tr("positions.bottom-center")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.appLauncher.position
|
||||
onSelected: function (key) {
|
||||
Settings.data.appLauncher.position = key;
|
||||
}
|
||||
|
||||
defaultValue: Settings.getDefaultValue("appLauncher.position")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.launcher.settings-overlay-layer-label")
|
||||
description: I18n.tr("panels.launcher.settings-overlay-layer-description")
|
||||
checked: Settings.data.appLauncher.overviewLayer
|
||||
onToggled: checked => Settings.data.appLauncher.overviewLayer = checked
|
||||
defaultValue: Settings.getDefaultValue("appLauncher.overviewLayer")
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("panels.launcher.settings-view-mode-label")
|
||||
description: I18n.tr("panels.launcher.settings-view-mode-description")
|
||||
Layout.fillWidth: true
|
||||
model: [
|
||||
{
|
||||
"key": "list",
|
||||
"name": I18n.tr("options.launcher-view-mode.list")
|
||||
},
|
||||
{
|
||||
"key": "grid",
|
||||
"name": I18n.tr("options.launcher-view-mode.grid")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.appLauncher.viewMode
|
||||
onSelected: function (key) {
|
||||
Settings.data.appLauncher.viewMode = key;
|
||||
}
|
||||
defaultValue: Settings.getDefaultValue("appLauncher.viewMode")
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("panels.launcher.settings-density-label")
|
||||
description: I18n.tr("panels.launcher.settings-density-description")
|
||||
Layout.fillWidth: true
|
||||
model: [
|
||||
{
|
||||
"key": "compact",
|
||||
"name": I18n.tr("options.launcher-density.compact")
|
||||
},
|
||||
{
|
||||
"key": "default",
|
||||
"name": I18n.tr("options.launcher-density.default")
|
||||
},
|
||||
{
|
||||
"key": "comfortable",
|
||||
"name": I18n.tr("options.launcher-density.comfortable")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.appLauncher.density || "compact"
|
||||
onSelected: function (key) {
|
||||
Settings.data.appLauncher.density = key;
|
||||
}
|
||||
defaultValue: Settings.getDefaultValue("appLauncher.density")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.launcher.settings-show-categories-label")
|
||||
description: I18n.tr("panels.launcher.settings-show-categories-description")
|
||||
checked: Settings.data.appLauncher.showCategories
|
||||
onToggled: checked => Settings.data.appLauncher.showCategories = checked
|
||||
defaultValue: Settings.getDefaultValue("appLauncher.showCategories")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.launcher.settings-icon-mode-label")
|
||||
description: I18n.tr("panels.launcher.settings-icon-mode-description")
|
||||
checked: Settings.data.appLauncher.iconMode === "native"
|
||||
onToggled: checked => Settings.data.appLauncher.iconMode = checked ? "native" : "tabler"
|
||||
defaultValue: Settings.getDefaultValue("appLauncher.iconMode") === "native"
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.launcher.settings-show-icon-background-label")
|
||||
description: I18n.tr("panels.launcher.settings-show-icon-background-description")
|
||||
checked: Settings.data.appLauncher.showIconBackground
|
||||
onToggled: checked => Settings.data.appLauncher.showIconBackground = checked
|
||||
defaultValue: Settings.getDefaultValue("appLauncher.showIconBackground")
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.launcher.settings-sort-by-usage-label")
|
||||
description: I18n.tr("panels.launcher.settings-sort-by-usage-description")
|
||||
checked: Settings.data.appLauncher.sortByMostUsed
|
||||
onToggled: checked => Settings.data.appLauncher.sortByMostUsed = checked
|
||||
defaultValue: Settings.getDefaultValue("appLauncher.sortByMostUsed")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.launcher.settings-enable-settings-search-label")
|
||||
description: I18n.tr("panels.launcher.settings-enable-settings-search-description")
|
||||
checked: Settings.data.appLauncher.enableSettingsSearch
|
||||
onToggled: checked => Settings.data.appLauncher.enableSettingsSearch = checked
|
||||
defaultValue: Settings.getDefaultValue("appLauncher.enableSettingsSearch")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.launcher.settings-enable-windows-search-label")
|
||||
description: I18n.tr("panels.launcher.settings-enable-windows-search-description")
|
||||
checked: Settings.data.appLauncher.enableWindowsSearch
|
||||
onToggled: checked => Settings.data.appLauncher.enableWindowsSearch = checked
|
||||
defaultValue: Settings.getDefaultValue("appLauncher.enableWindowsSearch")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.launcher.settings-enable-session-search-label")
|
||||
description: I18n.tr("panels.launcher.settings-enable-session-search-description")
|
||||
checked: Settings.data.appLauncher.enableSessionSearch
|
||||
onToggled: checked => Settings.data.appLauncher.enableSessionSearch = checked
|
||||
defaultValue: Settings.getDefaultValue("appLauncher.enableSessionSearch")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.launcher.settings-ignore-mouse-input-label")
|
||||
description: I18n.tr("panels.launcher.settings-ignore-mouse-input-description")
|
||||
checked: Settings.data.appLauncher.ignoreMouseInput
|
||||
onToggled: checked => Settings.data.appLauncher.ignoreMouseInput = checked
|
||||
defaultValue: Settings.getDefaultValue("appLauncher.ignoreMouseInput")
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 0
|
||||
|
||||
NTabBar {
|
||||
id: subTabBar
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: Style.marginM
|
||||
distributeEvenly: true
|
||||
currentIndex: tabView.currentIndex
|
||||
|
||||
NTabButton {
|
||||
text: I18n.tr("common.general")
|
||||
tabIndex: 0
|
||||
checked: subTabBar.currentIndex === 0
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("common.clipboard")
|
||||
tabIndex: 1
|
||||
checked: subTabBar.currentIndex === 1
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("common.execute")
|
||||
tabIndex: 2
|
||||
checked: subTabBar.currentIndex === 2
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.marginL
|
||||
}
|
||||
|
||||
NTabView {
|
||||
id: tabView
|
||||
currentIndex: subTabBar.currentIndex
|
||||
|
||||
GeneralSubTab {}
|
||||
ClipboardSubTab {}
|
||||
ExecuteSubTab {}
|
||||
}
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
function insertToken(token) {
|
||||
if (formatInput.inputItem) {
|
||||
var input = formatInput.inputItem;
|
||||
var cursorPos = input.cursorPosition;
|
||||
var currentText = input.text;
|
||||
var newText = currentText.substring(0, cursorPos) + token + currentText.substring(cursorPos);
|
||||
input.text = newText + " ";
|
||||
input.cursorPosition = cursorPos + token.length + 1;
|
||||
input.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("panels.lock-screen.clock-style-label")
|
||||
description: I18n.tr("panels.lock-screen.clock-style-description")
|
||||
model: [
|
||||
{
|
||||
"key": "analog",
|
||||
"name": I18n.tr("panels.lock-screen.clock-style-analog")
|
||||
},
|
||||
{
|
||||
"key": "digital",
|
||||
"name": I18n.tr("panels.lock-screen.clock-style-digital")
|
||||
},
|
||||
{
|
||||
"key": "custom",
|
||||
"name": I18n.tr("panels.lock-screen.clock-style-custom")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.general.clockStyle
|
||||
onSelected: key => Settings.data.general.clockStyle = key
|
||||
defaultValue: Settings.getDefaultValue("general.clockStyle")
|
||||
z: 10
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
id: formatInput
|
||||
label: I18n.tr("panels.lock-screen.clock-format-label")
|
||||
description: I18n.tr("panels.lock-screen.clock-format-description")
|
||||
text: Settings.data.general.clockFormat
|
||||
onTextChanged: Settings.data.general.clockFormat = text
|
||||
visible: Settings.data.general.clockStyle === "custom"
|
||||
defaultValue: Settings.getDefaultValue("general.clockFormat")
|
||||
}
|
||||
|
||||
NDateTimeTokens {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 300
|
||||
visible: Settings.data.general.clockStyle === "custom"
|
||||
onTokenClicked: token => root.insertToken(token)
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.lock-screen.password-chars-label")
|
||||
description: I18n.tr("panels.lock-screen.password-chars-description")
|
||||
checked: Settings.data.general.passwordChars
|
||||
onToggled: checked => Settings.data.general.passwordChars = checked
|
||||
defaultValue: Settings.getDefaultValue("general.passwordChars")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.lock-screen.compact-lockscreen-label")
|
||||
description: I18n.tr("panels.lock-screen.compact-lockscreen-description")
|
||||
checked: Settings.data.general.compactLockScreen
|
||||
onToggled: checked => Settings.data.general.compactLockScreen = checked
|
||||
defaultValue: Settings.getDefaultValue("general.compactLockScreen")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.lock-screen.enable-lockscreen-media-controls-label")
|
||||
description: I18n.tr("panels.lock-screen.enable-lockscreen-media-controls-description")
|
||||
checked: Settings.data.general.enableLockScreenMediaControls
|
||||
onToggled: checked => Settings.data.general.enableLockScreenMediaControls = checked
|
||||
visible: !Settings.data.general.compactLockScreen
|
||||
defaultValue: Settings.getDefaultValue("general.enableLockScreenMediaControls")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.lock-screen.lock-screen-animations-label")
|
||||
description: I18n.tr("panels.lock-screen.lock-screen-animations-description")
|
||||
checked: Settings.data.general.lockScreenAnimations
|
||||
onToggled: checked => Settings.data.general.lockScreenAnimations = checked
|
||||
defaultValue: Settings.getDefaultValue("general.lockScreenAnimations")
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.lock-screen.lock-screen-blur-strength-label")
|
||||
description: I18n.tr("panels.lock-screen.lock-screen-blur-strength-description")
|
||||
from: 0.0
|
||||
to: 1.0
|
||||
stepSize: 0.01
|
||||
showReset: true
|
||||
value: Settings.data.general.lockScreenBlur
|
||||
onMoved: value => Settings.data.general.lockScreenBlur = value
|
||||
text: ((Settings.data.general.lockScreenBlur) * 100).toFixed(0) + "%"
|
||||
defaultValue: Settings.getDefaultValue("general.lockScreenBlur")
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.lock-screen.lock-screen-tint-strength-label")
|
||||
description: I18n.tr("panels.lock-screen.lock-screen-tint-strength-description")
|
||||
from: 0.0
|
||||
to: 1.0
|
||||
stepSize: 0.01
|
||||
showReset: true
|
||||
value: Settings.data.general.lockScreenTint
|
||||
onMoved: value => Settings.data.general.lockScreenTint = value
|
||||
text: ((Settings.data.general.lockScreenTint) * 100).toFixed(0) + "%"
|
||||
defaultValue: Settings.getDefaultValue("general.lockScreenTint")
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.lock-screen.lock-on-suspend-label")
|
||||
description: I18n.tr("panels.lock-screen.lock-on-suspend-description")
|
||||
checked: Settings.data.general.lockOnSuspend
|
||||
onToggled: checked => Settings.data.general.lockOnSuspend = checked
|
||||
defaultValue: Settings.getDefaultValue("general.lockOnSuspend")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.lock-screen.auto-start-auth-label")
|
||||
description: I18n.tr("panels.lock-screen.auto-start-auth-description")
|
||||
checked: Settings.data.general.autoStartAuth
|
||||
onToggled: checked => Settings.data.general.autoStartAuth = checked
|
||||
defaultValue: Settings.getDefaultValue("general.autoStartAuth")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.lock-screen.allow-password-with-fprintd-label")
|
||||
description: I18n.tr("panels.lock-screen.allow-password-with-fprintd-description")
|
||||
checked: Settings.data.general.allowPasswordWithFprintd
|
||||
onToggled: checked => Settings.data.general.allowPasswordWithFprintd = checked
|
||||
defaultValue: Settings.getDefaultValue("general.allowPasswordWithFprintd")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.lock-screen.show-session-buttons-label")
|
||||
description: I18n.tr("panels.lock-screen.show-session-buttons-description")
|
||||
checked: Settings.data.general.showSessionButtonsOnLockScreen
|
||||
onToggled: checked => Settings.data.general.showSessionButtonsOnLockScreen = checked
|
||||
defaultValue: Settings.getDefaultValue("general.showSessionButtonsOnLockScreen")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.lock-screen.show-hibernate-label")
|
||||
description: I18n.tr("panels.lock-screen.show-hibernate-description")
|
||||
checked: Settings.data.general.showHibernateOnLockScreen
|
||||
onToggled: checked => Settings.data.general.showHibernateOnLockScreen = checked
|
||||
visible: Settings.data.general.showSessionButtonsOnLockScreen
|
||||
defaultValue: Settings.getDefaultValue("general.showSessionButtonsOnLockScreen")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.session-menu.enable-countdown-label")
|
||||
description: I18n.tr("panels.session-menu.enable-countdown-description")
|
||||
checked: Settings.data.general.enableLockScreenCountdown
|
||||
onToggled: checked => Settings.data.general.enableLockScreenCountdown = checked
|
||||
visible: Settings.data.general.showSessionButtonsOnLockScreen
|
||||
defaultValue: Settings.getDefaultValue("general.enableLockScreenCountdown")
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
visible: Settings.data.general.showSessionButtonsOnLockScreen && Settings.data.general.enableLockScreenCountdown
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.session-menu.countdown-duration-label")
|
||||
description: I18n.tr("panels.session-menu.countdown-duration-description")
|
||||
from: 1000
|
||||
to: 30000
|
||||
stepSize: 1000
|
||||
showReset: true
|
||||
value: Settings.data.general.lockScreenCountdownDuration
|
||||
onMoved: value => Settings.data.general.lockScreenCountdownDuration = value
|
||||
text: Math.round(Settings.data.general.lockScreenCountdownDuration / 1000) + "s"
|
||||
defaultValue: Settings.getDefaultValue("general.lockScreenCountdownDuration")
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 0
|
||||
|
||||
NTabBar {
|
||||
id: subTabBar
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: Style.marginM
|
||||
distributeEvenly: true
|
||||
currentIndex: tabView.currentIndex
|
||||
|
||||
NTabButton {
|
||||
text: I18n.tr("common.appearance")
|
||||
tabIndex: 0
|
||||
checked: subTabBar.currentIndex === 0
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("common.behavior")
|
||||
tabIndex: 1
|
||||
checked: subTabBar.currentIndex === 1
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("common.monitors")
|
||||
tabIndex: 2
|
||||
checked: subTabBar.currentIndex === 2
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.marginL
|
||||
}
|
||||
|
||||
NTabView {
|
||||
id: tabView
|
||||
currentIndex: subTabBar.currentIndex
|
||||
|
||||
AppearanceSubTab {}
|
||||
BehaviorSubTab {}
|
||||
MonitorsSubTab {}
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.Compositor
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
enabled: true
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
// 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;
|
||||
});
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("panels.lock-screen.monitors-desc")
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Quickshell.screens || []
|
||||
delegate: NCheckbox {
|
||||
Layout.fillWidth: true
|
||||
readonly property real compositorScale: {
|
||||
const info = CompositorService.displayScales[modelData.name];
|
||||
return (info && info.scale) ? info.scale : 1.0;
|
||||
}
|
||||
label: modelData.name || "Unknown"
|
||||
description: {
|
||||
I18n.tr("system.monitor-description", {
|
||||
"model": modelData.model,
|
||||
"width": modelData.width * compositorScale,
|
||||
"height": modelData.height * compositorScale,
|
||||
"scale": compositorScale
|
||||
});
|
||||
}
|
||||
checked: (Settings.data.general.lockScreenMonitors || []).indexOf(modelData.name) !== -1
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
Settings.data.general.lockScreenMonitors = root.addMonitor(Settings.data.general.lockScreenMonitors, modelData.name);
|
||||
} else {
|
||||
Settings.data.general.lockScreenMonitors = root.removeMonitor(Settings.data.general.lockScreenMonitors, modelData.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
enabled: Settings.data.notifications.enabled
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.notifications.duration-respect-expire-label")
|
||||
description: I18n.tr("panels.notifications.duration-respect-expire-description")
|
||||
checked: Settings.data.notifications.respectExpireTimeout
|
||||
onToggled: checked => Settings.data.notifications.respectExpireTimeout = checked
|
||||
defaultValue: Settings.getDefaultValue("notifications.respectExpireTimeout")
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.notifications.duration-low-urgency-label")
|
||||
description: I18n.tr("panels.notifications.duration-low-urgency-description")
|
||||
from: 1
|
||||
to: 30
|
||||
stepSize: 1
|
||||
showReset: true
|
||||
value: Settings.data.notifications.lowUrgencyDuration
|
||||
onMoved: value => Settings.data.notifications.lowUrgencyDuration = value
|
||||
text: Settings.data.notifications.lowUrgencyDuration + "s"
|
||||
defaultValue: Settings.getDefaultValue("notifications.lowUrgencyDuration")
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.notifications.duration-normal-urgency-label")
|
||||
description: I18n.tr("panels.notifications.duration-normal-urgency-description")
|
||||
from: 1
|
||||
to: 30
|
||||
stepSize: 1
|
||||
showReset: true
|
||||
value: Settings.data.notifications.normalUrgencyDuration
|
||||
onMoved: value => Settings.data.notifications.normalUrgencyDuration = value
|
||||
text: Settings.data.notifications.normalUrgencyDuration + "s"
|
||||
defaultValue: Settings.getDefaultValue("notifications.normalUrgencyDuration")
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.notifications.duration-critical-urgency-label")
|
||||
description: I18n.tr("panels.notifications.duration-critical-urgency-description")
|
||||
from: 1
|
||||
to: 30
|
||||
stepSize: 1
|
||||
showReset: true
|
||||
value: Settings.data.notifications.criticalUrgencyDuration
|
||||
onMoved: value => Settings.data.notifications.criticalUrgencyDuration = value
|
||||
text: Settings.data.notifications.criticalUrgencyDuration + "s"
|
||||
defaultValue: Settings.getDefaultValue("notifications.criticalUrgencyDuration")
|
||||
}
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.Compositor
|
||||
import qs.Services.System
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
property var addMonitor
|
||||
property var removeMonitor
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.notifications.settings-enabled-label")
|
||||
description: I18n.tr("panels.notifications.settings-enabled-description")
|
||||
checked: Settings.data.notifications.enabled !== false
|
||||
onToggled: checked => Settings.data.notifications.enabled = checked
|
||||
defaultValue: Settings.getDefaultValue("notifications.enabled")
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
enabled: Settings.data.notifications.enabled
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("panels.notifications.settings-density-label")
|
||||
description: I18n.tr("panels.notifications.settings-density-description")
|
||||
model: [
|
||||
{
|
||||
"key": "default",
|
||||
"name": I18n.tr("options.notification-density.default")
|
||||
},
|
||||
{
|
||||
"key": "compact",
|
||||
"name": I18n.tr("options.notification-density.compact")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.notifications.density || "default"
|
||||
onSelected: key => Settings.data.notifications.density = key
|
||||
defaultValue: Settings.getDefaultValue("notifications.density")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("tooltips.do-not-disturb-enabled")
|
||||
description: I18n.tr("panels.notifications.settings-do-not-disturb-description")
|
||||
checked: NotificationService.doNotDisturb
|
||||
onToggled: checked => NotificationService.doNotDisturb = checked
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("common.position")
|
||||
description: I18n.tr("panels.notifications.settings-location-description")
|
||||
model: [
|
||||
{
|
||||
"key": "top",
|
||||
"name": I18n.tr("positions.top-center")
|
||||
},
|
||||
{
|
||||
"key": "top_left",
|
||||
"name": I18n.tr("positions.top-left")
|
||||
},
|
||||
{
|
||||
"key": "top_right",
|
||||
"name": I18n.tr("positions.top-right")
|
||||
},
|
||||
{
|
||||
"key": "bottom",
|
||||
"name": I18n.tr("positions.bottom-center")
|
||||
},
|
||||
{
|
||||
"key": "bottom_left",
|
||||
"name": I18n.tr("positions.bottom-left")
|
||||
},
|
||||
{
|
||||
"key": "bottom_right",
|
||||
"name": I18n.tr("positions.bottom-right")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.notifications.location || "top_right"
|
||||
onSelected: key => Settings.data.notifications.location = key
|
||||
defaultValue: Settings.getDefaultValue("notifications.location")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.osd.always-on-top-label")
|
||||
description: I18n.tr("panels.notifications.settings-always-on-top-description")
|
||||
checked: Settings.data.notifications.overlayLayer
|
||||
onToggled: checked => Settings.data.notifications.overlayLayer = checked
|
||||
defaultValue: Settings.getDefaultValue("notifications.overlayLayer")
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.osd.background-opacity-label")
|
||||
description: I18n.tr("panels.notifications.settings-background-opacity-description")
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
showReset: true
|
||||
value: Settings.data.notifications.backgroundOpacity
|
||||
onMoved: value => Settings.data.notifications.backgroundOpacity = value
|
||||
text: Math.round(Settings.data.notifications.backgroundOpacity * 100) + "%"
|
||||
defaultValue: Settings.getDefaultValue("notifications.backgroundOpacity")
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("panels.notifications.monitors-desc")
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Quickshell.screens || []
|
||||
delegate: NCheckbox {
|
||||
Layout.fillWidth: true
|
||||
readonly property real compositorScale: {
|
||||
const info = CompositorService.displayScales[modelData.name];
|
||||
return (info && info.scale) ? info.scale : 1.0;
|
||||
}
|
||||
label: modelData.name || I18n.tr("common.unknown")
|
||||
description: {
|
||||
I18n.tr("system.monitor-description", {
|
||||
"model": modelData.model,
|
||||
"width": modelData.width * compositorScale,
|
||||
"height": modelData.height * compositorScale,
|
||||
"scale": compositorScale
|
||||
});
|
||||
}
|
||||
checked: (Settings.data.notifications.monitors || []).indexOf(modelData.name) !== -1
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
Settings.data.notifications.monitors = root.addMonitor(Settings.data.notifications.monitors, modelData.name);
|
||||
} else {
|
||||
Settings.data.notifications.monitors = root.removeMonitor(Settings.data.notifications.monitors, modelData.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
enabled: Settings.data.notifications.enabled
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.notifications.history-clear-dismiss-label")
|
||||
description: I18n.tr("panels.notifications.history-clear-dismiss-description")
|
||||
checked: Settings.data.notifications.clearDismissed
|
||||
onToggled: checked => Settings.data.notifications.clearDismissed = checked
|
||||
defaultValue: Settings.getDefaultValue("notifications.clearDismissed")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.notifications.settings-markdown-label")
|
||||
description: I18n.tr("panels.notifications.settings-markdown-description")
|
||||
checked: Settings.data.notifications.enableMarkdown
|
||||
onToggled: checked => Settings.data.notifications.enableMarkdown = checked
|
||||
defaultValue: Settings.getDefaultValue("notifications.enableMarkdown")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.notifications.history-low-urgency-label")
|
||||
description: I18n.tr("panels.notifications.history-low-urgency-description")
|
||||
checked: Settings.data.notifications?.saveToHistory?.low !== false
|
||||
onToggled: checked => Settings.data.notifications.saveToHistory.low = checked
|
||||
defaultValue: Settings.getDefaultValue("notifications.saveToHistory.low")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.notifications.history-normal-urgency-label")
|
||||
description: I18n.tr("panels.notifications.history-normal-urgency-description")
|
||||
checked: Settings.data.notifications?.saveToHistory?.normal !== false
|
||||
onToggled: checked => Settings.data.notifications.saveToHistory.normal = checked
|
||||
defaultValue: Settings.getDefaultValue("notifications.saveToHistory.normal")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.notifications.history-critical-urgency-label")
|
||||
description: I18n.tr("panels.notifications.history-critical-urgency-description")
|
||||
checked: Settings.data.notifications?.saveToHistory?.critical !== false
|
||||
onToggled: checked => Settings.data.notifications.saveToHistory.critical = checked
|
||||
defaultValue: Settings.getDefaultValue("notifications.saveToHistory.critical")
|
||||
}
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.System
|
||||
import qs.Widgets
|
||||
|
||||
Popup {
|
||||
id: root
|
||||
modal: true
|
||||
closePolicy: Popup.NoAutoClose
|
||||
dim: true
|
||||
anchors.centerIn: parent
|
||||
|
||||
width: Math.min(500 * Style.uiScaleRatio, parent.width * 0.9)
|
||||
padding: Style.marginL
|
||||
|
||||
property int editIndex: -1
|
||||
property string patternValue: ""
|
||||
property string actionValue: "block"
|
||||
|
||||
signal saved(string pattern, string action)
|
||||
|
||||
property var _savedSlot: null
|
||||
property string _selectedAction: "block"
|
||||
|
||||
background: Rectangle {
|
||||
color: Color.mSurface
|
||||
radius: Style.radiusL
|
||||
border.color: Color.mOutline
|
||||
border.width: Style.borderS
|
||||
}
|
||||
|
||||
onOpened: {
|
||||
patternInput.text = patternValue;
|
||||
actionCombo.currentKey = actionValue;
|
||||
_selectedAction = actionValue;
|
||||
patternInput.forceActiveFocus();
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
id: contentLayout
|
||||
spacing: Style.marginL
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
NText {
|
||||
text: editIndex >= 0 ? I18n.tr("panels.notifications.rules-edit") : I18n.tr("panels.notifications.rules-add")
|
||||
font.weight: Style.fontWeightBold
|
||||
pointSize: Style.fontSizeL
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
onClicked: root.close()
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NTextInput {
|
||||
id: patternInput
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.notifications.rules-pattern-label")
|
||||
placeholderText: I18n.tr("panels.notifications.rules-pattern-placeholder")
|
||||
fontFamily: Settings.data.ui.fontFixed
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
id: actionCombo
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.notifications.rules-action-label")
|
||||
model: [
|
||||
{
|
||||
"key": "block",
|
||||
"name": I18n.tr("panels.notifications.rules-action-block")
|
||||
},
|
||||
{
|
||||
"key": "mute",
|
||||
"name": I18n.tr("panels.notifications.rules-action-mute")
|
||||
},
|
||||
{
|
||||
"key": "hide",
|
||||
"name": I18n.tr("panels.notifications.rules-action-hide")
|
||||
}
|
||||
]
|
||||
currentKey: actionValue
|
||||
onSelected: key => {
|
||||
actionValue = key;
|
||||
_selectedAction = key;
|
||||
}
|
||||
}
|
||||
|
||||
NLabel {
|
||||
Layout.fillWidth: true
|
||||
label: _selectedAction === "block" ? I18n.tr("panels.notifications.rules-action-block-desc") : (_selectedAction === "mute" ? I18n.tr("panels.notifications.rules-action-mute-desc") : I18n.tr("panels.notifications.rules-action-hide-desc"))
|
||||
labelColor: Color.mOnSurfaceVariant
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("common.cancel")
|
||||
outlined: true
|
||||
onClicked: root.close()
|
||||
}
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("common.save")
|
||||
icon: "check"
|
||||
backgroundColor: Color.mPrimary
|
||||
textColor: Color.mOnPrimary
|
||||
enabled: patternInput.text.trim() !== ""
|
||||
onClicked: {
|
||||
root.saved(patternInput.text.trim(), _selectedAction || "block");
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 0
|
||||
|
||||
// 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;
|
||||
});
|
||||
}
|
||||
|
||||
// File pickers for sound sub-tab
|
||||
function openUnifiedSoundPicker() {
|
||||
unifiedSoundFilePicker.open();
|
||||
}
|
||||
function openLowSoundPicker() {
|
||||
lowSoundFilePicker.open();
|
||||
}
|
||||
function openNormalSoundPicker() {
|
||||
normalSoundFilePicker.open();
|
||||
}
|
||||
function openCriticalSoundPicker() {
|
||||
criticalSoundFilePicker.open();
|
||||
}
|
||||
|
||||
NTabBar {
|
||||
id: subTabBar
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: Style.marginM
|
||||
distributeEvenly: false // this is too cramped on this tab to split evenly
|
||||
currentIndex: tabView.currentIndex
|
||||
|
||||
NTabButton {
|
||||
text: I18n.tr("common.appearance")
|
||||
tabIndex: 0
|
||||
checked: subTabBar.currentIndex === 0
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("common.duration")
|
||||
tabIndex: 1
|
||||
checked: subTabBar.currentIndex === 1
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("common.history")
|
||||
tabIndex: 2
|
||||
checked: subTabBar.currentIndex === 2
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("common.sound")
|
||||
tabIndex: 3
|
||||
checked: subTabBar.currentIndex === 3
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("common.toast")
|
||||
tabIndex: 4
|
||||
checked: subTabBar.currentIndex === 4
|
||||
}
|
||||
NTabButton {
|
||||
text: I18n.tr("panels.notifications.rules-tab")
|
||||
tabIndex: 5
|
||||
checked: subTabBar.currentIndex === 5
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.marginL
|
||||
}
|
||||
|
||||
NTabView {
|
||||
id: tabView
|
||||
currentIndex: subTabBar.currentIndex
|
||||
|
||||
GeneralSubTab {
|
||||
addMonitor: root.addMonitor
|
||||
removeMonitor: root.removeMonitor
|
||||
}
|
||||
DurationSubTab {}
|
||||
HistorySubTab {}
|
||||
SoundSubTab {
|
||||
onOpenUnifiedPicker: root.openUnifiedSoundPicker()
|
||||
onOpenLowPicker: root.openLowSoundPicker()
|
||||
onOpenNormalPicker: root.openNormalSoundPicker()
|
||||
onOpenCriticalPicker: root.openCriticalSoundPicker()
|
||||
}
|
||||
ToastSubTab {}
|
||||
RulesSubTab {}
|
||||
}
|
||||
|
||||
// File Pickers for Sound Files
|
||||
NFilePicker {
|
||||
id: unifiedSoundFilePicker
|
||||
title: I18n.tr("panels.notifications.sounds-files-unified-select-title")
|
||||
selectionMode: "files"
|
||||
initialPath: Quickshell.env("HOME")
|
||||
nameFilters: ["*.wav", "*.mp3", "*.ogg", "*.flac", "*.m4a", "*.aac"]
|
||||
onAccepted: paths => {
|
||||
if (paths.length > 0) {
|
||||
const soundPath = paths[0];
|
||||
Settings.data.notifications.sounds.normalSoundFile = soundPath;
|
||||
Settings.data.notifications.sounds.lowSoundFile = soundPath;
|
||||
Settings.data.notifications.sounds.criticalSoundFile = soundPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NFilePicker {
|
||||
id: lowSoundFilePicker
|
||||
title: I18n.tr("panels.notifications.sounds-files-low-select-title")
|
||||
selectionMode: "files"
|
||||
initialPath: Quickshell.env("HOME")
|
||||
nameFilters: ["*.wav", "*.mp3", "*.ogg", "*.flac", "*.m4a", "*.aac"]
|
||||
onAccepted: paths => {
|
||||
if (paths.length > 0) {
|
||||
Settings.data.notifications.sounds.lowSoundFile = paths[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NFilePicker {
|
||||
id: normalSoundFilePicker
|
||||
title: I18n.tr("panels.notifications.sounds-files-normal-select-title")
|
||||
selectionMode: "files"
|
||||
initialPath: Quickshell.env("HOME")
|
||||
nameFilters: ["*.wav", "*.mp3", "*.ogg", "*.flac", "*.m4a", "*.aac"]
|
||||
onAccepted: paths => {
|
||||
if (paths.length > 0) {
|
||||
Settings.data.notifications.sounds.normalSoundFile = paths[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NFilePicker {
|
||||
id: criticalSoundFilePicker
|
||||
title: I18n.tr("panels.notifications.sounds-files-critical-select-title")
|
||||
selectionMode: "files"
|
||||
initialPath: Quickshell.env("HOME")
|
||||
nameFilters: ["*.wav", "*.mp3", "*.ogg", "*.flac", "*.m4a", "*.aac"]
|
||||
onAccepted: paths => {
|
||||
if (paths.length > 0) {
|
||||
Settings.data.notifications.sounds.criticalSoundFile = paths[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user