mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-18 09:14:01 -04:00
cc: redesign 7 with a persistent footer and individually tiled buttons
This commit is contained in:
+1
-1
Submodule dank-qml-common updated: 7092ee89ba...4bd5171013
Generated
+3
-3
@@ -3,11 +3,11 @@
|
||||
"dank-qml-common": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1790691843,
|
||||
"narHash": "sha256-ZoqLNYx8vgjFV18mI3l8vvkG9SufqZD2qr7q/j+aCns=",
|
||||
"lastModified": 1790707225,
|
||||
"narHash": "sha256-EIM8uHjiGEhXQnwWkpCLPsM3khAVLOq8amaWVLlvkLg=",
|
||||
"owner": "AvengeMedia",
|
||||
"repo": "dank-qml-common",
|
||||
"rev": "7092ee89ba7bab4afe74a215f31b999ae1546b9f",
|
||||
"rev": "4bd5171013a256ebf66b7720564a58411172843c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -21,7 +21,7 @@ Singleton {
|
||||
id: root
|
||||
readonly property var log: Log.scoped("SettingsData")
|
||||
|
||||
readonly property int settingsConfigVersion: 34
|
||||
readonly property int settingsConfigVersion: 35
|
||||
|
||||
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
|
||||
|
||||
|
||||
@@ -434,25 +434,37 @@ var LOCAL_SPEC = {
|
||||
controlCenterWidgets: {
|
||||
def: [
|
||||
{
|
||||
id: "userCard",
|
||||
id: "user",
|
||||
enabled: true,
|
||||
w: 6.5,
|
||||
h: 1.5
|
||||
w: 5,
|
||||
h: 1
|
||||
},
|
||||
{
|
||||
id: "quickActions",
|
||||
id: "settings",
|
||||
enabled: true,
|
||||
w: 1.5,
|
||||
h: 1.5
|
||||
w: 1,
|
||||
h: 1
|
||||
},
|
||||
{
|
||||
id: "volumeSlider",
|
||||
id: "lock",
|
||||
enabled: true,
|
||||
w: 1,
|
||||
h: 1
|
||||
},
|
||||
{
|
||||
id: "power",
|
||||
enabled: true,
|
||||
w: 1,
|
||||
h: 1
|
||||
},
|
||||
{
|
||||
id: "brightnessSlider",
|
||||
enabled: true,
|
||||
w: 4,
|
||||
h: 1
|
||||
},
|
||||
{
|
||||
id: "brightnessSlider",
|
||||
id: "volumeSlider",
|
||||
enabled: true,
|
||||
w: 4,
|
||||
h: 1
|
||||
@@ -481,14 +493,38 @@ var LOCAL_SPEC = {
|
||||
w: 4,
|
||||
h: 1
|
||||
},
|
||||
{
|
||||
id: "darkMode",
|
||||
enabled: true,
|
||||
w: 2,
|
||||
h: 1
|
||||
},
|
||||
{
|
||||
id: "nightMode",
|
||||
enabled: true,
|
||||
w: 2,
|
||||
h: 1
|
||||
},
|
||||
{
|
||||
id: "doNotDisturb",
|
||||
enabled: true,
|
||||
w: 2,
|
||||
h: 1
|
||||
},
|
||||
{
|
||||
id: "idleInhibitor",
|
||||
enabled: true,
|
||||
w: 2,
|
||||
h: 1
|
||||
},
|
||||
{
|
||||
id: "battery",
|
||||
enabled: true,
|
||||
w: 4,
|
||||
h: 1
|
||||
},
|
||||
{
|
||||
id: "darkMode",
|
||||
id: "diskUsage",
|
||||
enabled: true,
|
||||
w: 4,
|
||||
h: 1
|
||||
|
||||
@@ -15,8 +15,9 @@ var SESSION_BACKED_PLUGIN_IDS = ["dankNotepadModule"];
|
||||
var STALE_WIDGET_KEYS = ["desktopClockEnabled", "desktopClockStyle", "desktopClockTransparency", "desktopClockColorMode", "desktopClockCustomColor", "desktopClockShowDate", "desktopClockShowAnalogNumbers", "desktopClockShowAnalogSeconds", "desktopClockX", "desktopClockY", "desktopClockWidth", "desktopClockHeight", "desktopClockDisplayPreferences", "systemMonitorEnabled", "systemMonitorShowHeader", "systemMonitorTransparency", "systemMonitorColorMode", "systemMonitorCustomColor", "systemMonitorShowCpu", "systemMonitorShowCpuGraph", "systemMonitorShowCpuTemp", "systemMonitorShowGpuTemp", "systemMonitorGpuPciId", "systemMonitorShowMemory", "systemMonitorShowMemoryGraph", "systemMonitorShowNetwork", "systemMonitorShowNetworkGraph", "systemMonitorShowDisk", "systemMonitorShowTopProcesses", "systemMonitorTopProcessCount", "systemMonitorTopProcessSortBy", "systemMonitorGraphInterval", "systemMonitorLayoutMode", "systemMonitorX", "systemMonitorY", "systemMonitorWidth", "systemMonitorHeight", "systemMonitorDisplayPreferences", "systemMonitorVariants", "desktopWidgetPositions"];
|
||||
|
||||
var BAR_WIDGET_LIST_KEYS = ["leftWidgets", "centerWidgets", "rightWidgets"];
|
||||
var CC_LEGACY_HEADER_IDS = ["userCard", "quickActions"];
|
||||
var CC_HEADER_OPTION_KEYS = ["hostname", "compositor", "uptime", "badge", "actions", "background", "powerAccent"];
|
||||
var CC_HEADER_IDS = ["userCard", "quickActions", "header"];
|
||||
var CC_ACTION_TILE_IDS = ["settings", "lock", "power"];
|
||||
var CC_USER_KEYS = ["w", "h", "col", "row", "hostname", "compositor", "uptime", "badge"];
|
||||
|
||||
var REMOVED_KEYS_V21 = ["showBattery", "showCapsLockIndicator", "showClipboard", "showClock", "showControlCenterButton", "showCpuUsage", "showFocusedWindow", "showLauncherButton", "showMemUsage", "showMusic", "showNotificationButton", "showPrivacyButton", "showSystemTray", "showWeather", "showWorkspaceSwitcher", "hideBrightnessSlider", "updaterHideWidget", "workspaceScrolling", "appLauncherViewMode", "spotlightModalViewMode", "audioDeviceScrollVolumeEnabled", "desktopClockX", "desktopClockY", "desktopClockWidth", "desktopClockHeight", "desktopClockDisplayPreferences", "systemMonitorX", "systemMonitorY", "systemMonitorWidth", "systemMonitorHeight", "systemMonitorDisplayPreferences", "systemMonitorVariants"];
|
||||
|
||||
@@ -700,79 +701,42 @@ function migrateToVersion(obj, targetVersion) {
|
||||
settings.configVersion = 31;
|
||||
}
|
||||
|
||||
if (currentVersion < 33 && targetVersion >= 33) {
|
||||
if (currentVersion < 35 && targetVersion >= 35) {
|
||||
if (Array.isArray(settings.controlCenterWidgets))
|
||||
settings.controlCenterWidgets = migrateControlCenterHeader(settings.controlCenterWidgets);
|
||||
settings.configVersion = 33;
|
||||
}
|
||||
|
||||
if (currentVersion < 34 && targetVersion >= 34) {
|
||||
if (Array.isArray(settings.controlCenterWidgets))
|
||||
settings.controlCenterWidgets = splitControlCenterHeader(settings.controlCenterWidgets, settings.controlCenterColumns ?? SpecModule.SPEC.controlCenterColumns.def);
|
||||
settings.configVersion = 34;
|
||||
settings.controlCenterWidgets = migrateControlCenterHeader(settings.controlCenterWidgets, currentVersion < 33, settings.controlCenterColumns ?? 8);
|
||||
settings.configVersion = 35;
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
function migrateControlCenterHeader(widgets) {
|
||||
const rest = widgets.filter(widget => !CC_LEGACY_HEADER_IDS.includes(widget?.id));
|
||||
if (rest.some(widget => widget?.id === "header"))
|
||||
return rest;
|
||||
const header = {
|
||||
id: "header",
|
||||
function migrateControlCenterHeader(widgets, fixedHeader, columns) {
|
||||
const header = widgets.filter(widget => CC_HEADER_IDS.includes(widget?.id));
|
||||
if (!fixedHeader && header.length === 0)
|
||||
return widgets;
|
||||
const rest = widgets.filter(widget => !CC_HEADER_IDS.includes(widget?.id));
|
||||
const actions = header.find(widget => Array.isArray(widget?.actions))?.actions ?? [];
|
||||
const wanted = id => !rest.some(widget => widget?.id === id) && actions.find(action => action?.id === id)?.enabled !== false;
|
||||
const tiles = CC_ACTION_TILE_IDS.filter(wanted).map(id => ({
|
||||
id: id,
|
||||
enabled: true,
|
||||
w: 1,
|
||||
h: 1
|
||||
}));
|
||||
const identity = header.length === 0 ? {} : header.find(widget => widget.id === "userCard" || (widget.id === "header" && widget.showUser !== false));
|
||||
if (!identity || rest.some(widget => widget?.id === "user"))
|
||||
return tiles.concat(rest);
|
||||
const user = {
|
||||
id: "user",
|
||||
enabled: true,
|
||||
w: 8,
|
||||
w: Math.max(1, columns - tiles.length),
|
||||
h: 1
|
||||
};
|
||||
for (const legacy of widgets) {
|
||||
if (!CC_LEGACY_HEADER_IDS.includes(legacy?.id))
|
||||
continue;
|
||||
for (const key of CC_HEADER_OPTION_KEYS) {
|
||||
if (key in legacy)
|
||||
header[key] = legacy[key];
|
||||
}
|
||||
for (const key of CC_USER_KEYS) {
|
||||
if (key in identity)
|
||||
user[key] = identity[key];
|
||||
}
|
||||
return [header].concat(rest);
|
||||
}
|
||||
|
||||
function splitControlCenterHeader(widgets, columns) {
|
||||
const userDefaults = SpecModule.SPEC.controlCenterWidgets.def.find(widget => widget.id === "userCard");
|
||||
const actionDefaults = SpecModule.SPEC.controlCenterWidgets.def.find(widget => widget.id === "quickActions");
|
||||
const result = [];
|
||||
for (const widget of widgets) {
|
||||
if (widget?.id !== "header") {
|
||||
result.push(widget);
|
||||
continue;
|
||||
}
|
||||
const actions = Object.assign({}, actionDefaults);
|
||||
const user = Object.assign({}, userDefaults, {
|
||||
w: Math.max(1, Math.min(Number(widget.w) || columns, columns) - actions.w),
|
||||
h: Math.max(userDefaults.h, Number(widget.h) || userDefaults.h)
|
||||
});
|
||||
for (const key of ["hostname", "compositor", "uptime", "badge", "background"]) {
|
||||
if (key in widget)
|
||||
user[key] = widget[key];
|
||||
}
|
||||
for (const key of ["actions", "powerAccent"]) {
|
||||
if (key in widget)
|
||||
actions[key] = widget[key];
|
||||
}
|
||||
if (Number.isFinite(widget.row)) {
|
||||
user.row = widget.row;
|
||||
actions.row = widget.row;
|
||||
}
|
||||
if (Number.isFinite(widget.col)) {
|
||||
user.col = widget.col;
|
||||
actions.col = widget.col + (widget.showUser === false ? 0 : user.w);
|
||||
}
|
||||
if (widget.showUser !== false)
|
||||
result.push(user);
|
||||
result.push(actions);
|
||||
}
|
||||
if (!result.some(widget => widget?.id === "quickActions"))
|
||||
result.unshift(Object.assign({}, actionDefaults));
|
||||
return result;
|
||||
return [user].concat(tiles, rest);
|
||||
}
|
||||
|
||||
function migrateBarWidgetGlobals(settings) {
|
||||
|
||||
@@ -246,7 +246,7 @@ Item {
|
||||
osdStartupTimer.start();
|
||||
if (SettingsData.controlCenterWidgets.some(widget => widget.id === "diskUsage" && widget.enabled !== false))
|
||||
DgopService.initializeDiskMounts();
|
||||
if (SettingsData.controlCenterWidgets.some(widget => widget.id === "userCard" && widget.enabled !== false && widget.uptime !== false))
|
||||
if (SettingsData.controlCenterWidgets.some(widget => widget.id === "user" && widget.enabled !== false && widget.uptime !== false))
|
||||
DgopService.dgopAvailable;
|
||||
|
||||
// These are dummy references just to trigger the singletons onCompleted to trigger
|
||||
|
||||
@@ -14,7 +14,6 @@ Singleton {
|
||||
readonly property real gridStep: 0.5
|
||||
property int columnPreview: 0
|
||||
readonly property int gridColumns: columnPreview > 0 ? columnPreview : clampColumns(SettingsData.controlCenterColumns)
|
||||
readonly property real sheetWidth: sheetWidthFor(gridColumns)
|
||||
|
||||
function clampColumns(value) {
|
||||
const columns = Math.round(Number(value));
|
||||
@@ -45,9 +44,7 @@ Singleton {
|
||||
readonly property real sliderRowHeight: Theme.minimumTouchTargetSize
|
||||
readonly property real stripTrackHeight: 32
|
||||
readonly property real stripHandleHeight: stripTrackHeight + Theme.sliderHandleGap * 2
|
||||
readonly property real stripIconSize: Theme.iconSize
|
||||
readonly property real gridGap: Theme.spacingS
|
||||
readonly property real tilePaddingH: Theme.spacingL
|
||||
readonly property real iconScale: SettingsData.controlCenterIconScale
|
||||
readonly property real tileIconSize: Theme.iconSizeLarge * iconScale
|
||||
readonly property real iconBoxSize: Theme.minimumTouchTargetSize * iconScale
|
||||
@@ -57,19 +54,11 @@ Singleton {
|
||||
readonly property real iconBoxIconSize: Theme.iconSize * iconScale
|
||||
readonly property real tallMeterThickness: 28
|
||||
readonly property real tileTextGap: Theme.spacingM
|
||||
readonly property real actionSize: Theme.minimumTouchTargetSize
|
||||
readonly property real actionIconSize: Theme.iconSizeMedium
|
||||
readonly property real actionGap: Theme.spacingXS
|
||||
readonly property real headerHeight: actionSize * 2 + actionGap
|
||||
|
||||
function actionSpan(count) {
|
||||
return Math.ceil((actionSize * count + actionGap * Math.max(0, count - 1) + gridGap) / (columnWidth + gridGap) / gridStep) * gridStep;
|
||||
}
|
||||
|
||||
function actionCapacity(span) {
|
||||
const size = span * (columnWidth + gridGap) - gridGap;
|
||||
return Math.max(1, Math.floor((size + actionGap) / (actionSize + actionGap)));
|
||||
}
|
||||
readonly property real footerHeight: iconBoxSize
|
||||
readonly property real footerGap: Theme.spacingM
|
||||
readonly property real footerAppIconSize: Theme.iconSize * iconScale
|
||||
readonly property int footerMaxAppIcons: 3
|
||||
readonly property real shapeButtonSize: Theme.iconSize
|
||||
|
||||
readonly property real detailDialogInset: Theme.spacingL
|
||||
readonly property real detailDialogPadding: Theme.spacingS
|
||||
@@ -98,6 +87,9 @@ Singleton {
|
||||
readonly property real dialogWidth: 320
|
||||
readonly property real libraryPanelWidth: 400
|
||||
readonly property real libraryPanelHeight: 400
|
||||
readonly property real widgetSheetMinHeight: libraryPanelHeight
|
||||
readonly property real previewMinWidth: columnWidth * 1.5
|
||||
readonly property real previewTileHeight: Theme.minimumTouchTargetSize
|
||||
readonly property real configMenuWidth: 260
|
||||
readonly property real vpnPopoutListHeight: 200
|
||||
readonly property real headerDropdownWidth: 120
|
||||
@@ -144,6 +136,7 @@ Singleton {
|
||||
case "audioOutput":
|
||||
case "audioInput":
|
||||
case "diskUsage":
|
||||
case "runningApps":
|
||||
return detailHeightList;
|
||||
default:
|
||||
return detailHeightDefault;
|
||||
|
||||
@@ -22,10 +22,12 @@ FocusScope {
|
||||
property real minimumContentHeight: 0
|
||||
property vector4d cornerRadii: Qt.vector4d(Theme.windowRadius, Theme.windowRadius, Theme.windowRadius, Theme.windowRadius)
|
||||
property real coverage: 0
|
||||
property var runningToplevels: []
|
||||
|
||||
signal dismissed
|
||||
signal backRequested
|
||||
signal collapseRequested
|
||||
signal closeRequested
|
||||
signal codecSelectorRequested(var device)
|
||||
signal portSelectorRequested(var node)
|
||||
|
||||
@@ -119,6 +121,8 @@ FocusScope {
|
||||
return diskUsageComponent;
|
||||
case "brightnessSlider":
|
||||
return brightnessComponent;
|
||||
case "runningApps":
|
||||
return runningAppsComponent;
|
||||
}
|
||||
if (sectionId.startsWith("builtin_") || sectionId.startsWith("plugin_"))
|
||||
return pluginComponent;
|
||||
@@ -338,6 +342,10 @@ FocusScope {
|
||||
function onDismissRequested() {
|
||||
root.backRequested();
|
||||
}
|
||||
|
||||
function onCloseRequested() {
|
||||
root.closeRequested();
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
@@ -399,6 +407,13 @@ FocusScope {
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: runningAppsComponent
|
||||
RunningAppsDetail {
|
||||
toplevels: root.runningToplevels
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: pluginComponent
|
||||
PluginDetailShell {
|
||||
|
||||
@@ -12,7 +12,6 @@ DankGridEditChrome {
|
||||
signal configRequested(var anchor)
|
||||
|
||||
hasOptions: WidgetUtils.hasOptions(widgetData.id)
|
||||
removable: !WidgetUtils.isPinned(widgetData.id)
|
||||
buttonSize: Theme.iconSize
|
||||
hitOverflow: CcMetrics.gridGap / 2
|
||||
iconSize: PopoutMetrics.chromeIconSize
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Modules.ControlCenter
|
||||
import qs.Modules.ControlCenter.Widgets
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
property bool editMode: false
|
||||
property var toplevels: []
|
||||
property bool resetArmed: false
|
||||
|
||||
signal runningAppsRequested
|
||||
signal addWidgetRequested
|
||||
signal resetRequested
|
||||
signal editToggled
|
||||
signal cancelRequested
|
||||
|
||||
readonly property var appIds: {
|
||||
const ids = [];
|
||||
for (const toplevel of toplevels) {
|
||||
const id = toplevel?.appId ?? "";
|
||||
if (!ids.includes(id))
|
||||
ids.push(id);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
readonly property real leadingWidth: Math.max(0, width - trailing.width - CcMetrics.footerGap)
|
||||
|
||||
implicitHeight: CcMetrics.footerHeight
|
||||
|
||||
function cancelConfirmation() {
|
||||
resetArmed = false;
|
||||
}
|
||||
|
||||
onEditModeChanged: cancelConfirmation()
|
||||
|
||||
StyledButton {
|
||||
id: appsChip
|
||||
|
||||
readonly property real iconBox: height - Theme.spacingS * 2
|
||||
|
||||
anchors.left: parent.left
|
||||
height: parent.height
|
||||
width: Math.min(Theme.spacingS + icons.width + Theme.spacingS + countLabel.implicitWidth + Theme.spacingXS + chevron.width + Theme.spacingM, root.leadingWidth)
|
||||
radius: Theme.fullRadius(width, height)
|
||||
color: CcMetrics.tileInactiveColor
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
visible: !root.editMode && root.toplevels.length > 0
|
||||
Accessible.name: countLabel.text
|
||||
onClicked: root.runningAppsRequested()
|
||||
|
||||
Row {
|
||||
id: icons
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: root.appIds.slice(0, CcMetrics.footerMaxAppIcons)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
required property string modelData
|
||||
|
||||
width: appsChip.iconBox
|
||||
height: width
|
||||
radius: Theme.fullRadius(width, height)
|
||||
color: Theme.surfaceContainerHighest
|
||||
|
||||
CcAppIcon {
|
||||
anchors.centerIn: parent
|
||||
appId: parent.modelData
|
||||
iconSize: CcMetrics.footerAppIconSize
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: countLabel
|
||||
anchors.left: icons.right
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.right: chevron.left
|
||||
anchors.rightMargin: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.toplevels.length === 1 ? I18n.tr("%1 window", "singular, %1 is 1, open windows in the control center footer").arg(1) : I18n.tr("%1 windows", "plural, %1 is a count of open windows in the control center footer").arg(root.toplevels.length)
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Theme.fontWeightMedium
|
||||
color: CcMetrics.tileInactiveContent
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
id: chevron
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
name: I18n.isRtl ? "chevron_left" : "chevron_right"
|
||||
size: Theme.iconSizeSmall
|
||||
color: Theme.onSurfaceVariant
|
||||
}
|
||||
|
||||
FocusRing {
|
||||
visible: parent.visualFocus
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
control: parent
|
||||
stateColor: CcMetrics.tileInactiveContent
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: editActions
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
visible: root.editMode
|
||||
|
||||
DankButton {
|
||||
id: addButton
|
||||
buttonHeight: CcMetrics.footerHeight
|
||||
maximumWidth: root.leadingWidth
|
||||
iconName: "add"
|
||||
text: I18n.tr("Add widget")
|
||||
backgroundColor: Theme.secondaryContainer
|
||||
textColor: Theme.onSecondaryContainer
|
||||
onClicked: {
|
||||
root.cancelConfirmation();
|
||||
root.addWidgetRequested();
|
||||
}
|
||||
}
|
||||
|
||||
DankButton {
|
||||
buttonHeight: CcMetrics.footerHeight
|
||||
maximumWidth: Math.max(0, root.leadingWidth - addButton.width - editActions.spacing)
|
||||
readonly property string label: I18n.tr("Defaults", "noun, control center edit button restoring the default layout")
|
||||
readonly property string confirmLabel: I18n.tr("Confirm")
|
||||
|
||||
iconName: "settings_backup_restore"
|
||||
text: root.resetArmed ? confirmLabel : label
|
||||
reserveText: root.resetArmed ? label : confirmLabel
|
||||
backgroundColor: root.resetArmed ? Theme.primary : Theme.surfaceContainerHighest
|
||||
textColor: root.resetArmed ? Theme.onPrimary : Theme.onSurface
|
||||
onClicked: {
|
||||
if (!root.resetArmed) {
|
||||
root.resetArmed = true;
|
||||
return;
|
||||
}
|
||||
root.resetArmed = false;
|
||||
root.resetRequested();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: trailing
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankActionButton {
|
||||
buttonSize: CcMetrics.footerHeight
|
||||
iconName: "close"
|
||||
iconSize: CcMetrics.iconBoxIconSize
|
||||
iconColor: CcMetrics.tileInactiveContent
|
||||
backgroundColor: CcMetrics.tileInactiveColor
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
tooltipText: I18n.tr("Discard")
|
||||
visible: root.editMode
|
||||
onClicked: root.cancelRequested()
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
buttonSize: CcMetrics.footerHeight
|
||||
iconName: "check"
|
||||
iconSize: CcMetrics.iconBoxIconSize
|
||||
iconColor: Theme.onPrimary
|
||||
backgroundColor: Theme.primary
|
||||
tooltipText: I18n.tr("Save")
|
||||
visible: root.editMode
|
||||
onClicked: root.editToggled()
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
buttonSize: CcMetrics.footerHeight
|
||||
iconName: "edit"
|
||||
iconSize: CcMetrics.iconBoxIconSize
|
||||
iconColor: CcMetrics.tileInactiveContent
|
||||
backgroundColor: CcMetrics.tileInactiveColor
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
tooltipText: I18n.tr("Edit")
|
||||
visible: !root.editMode
|
||||
onClicked: root.editToggled()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,24 +24,54 @@ DankEditableGrid {
|
||||
signal lockRequested
|
||||
signal powerRequested
|
||||
signal settingsRequested
|
||||
signal editRequested
|
||||
signal headerTapped
|
||||
signal closeRequested
|
||||
property bool tapToClose: false
|
||||
|
||||
readonly property real gridHeight: layoutHeight
|
||||
readonly property real cellWidth: (width + CcMetrics.gridGap) / columns
|
||||
readonly property CcTileSlot draggingSlot: tileRepeater.itemAt(draggingSourceIndex) as CcTileSlot
|
||||
|
||||
sourceItems: (SettingsData.controlCenterWidgets || []).map(widget => Object.assign({}, widget, WidgetUtils.clampSize(widget, Infinity)))
|
||||
readonly property var savedWidgets: SettingsData.controlCenterWidgets || []
|
||||
readonly property var shownIndices: savedWidgets.reduce((indices, widget, i) => WidgetUtils.isShown(widget) ? indices.concat([i]) : indices, [])
|
||||
|
||||
sourceItems: shownIndices.map(i => Object.assign({}, savedWidgets[i], sizeWithHiddenTwin(i)))
|
||||
slotLayout: GridUtils.packCards(layoutItems.map(widget => Object.assign({}, widget, WidgetUtils.clampSize(widget, columns, maximumRows))), placementOrder, columns, width, CcMetrics.gridGap, cellWidth - CcMetrics.gridGap, I18n.isRtl, null, CcMetrics.gridStep, true)
|
||||
placeholderRadius: draggingSlot?.tileItem?.bodyRadius ?? Theme.fullRadius(width, CcMetrics.tileHeight)
|
||||
|
||||
onLayoutCommitted: items => model.setLayout(items)
|
||||
onLayoutCommitted: items => model.setLayout(withHidden(items))
|
||||
|
||||
function sizeWithHiddenTwin(index) {
|
||||
const widget = savedWidgets[index];
|
||||
const size = WidgetUtils.clampSize(widget, Infinity);
|
||||
if (!WidgetUtils.isUnplaced(widget))
|
||||
return size;
|
||||
for (const neighbor of [index + 1, index - 1]) {
|
||||
const twin = savedWidgets[neighbor];
|
||||
if (!twin || shownIndices.includes(neighbor) || !WidgetUtils.isUnplaced(twin))
|
||||
continue;
|
||||
const twinSize = WidgetUtils.clampSize(twin, Infinity);
|
||||
if (twinSize.w !== size.w || twinSize.h !== size.h)
|
||||
continue;
|
||||
size.w += twinSize.w;
|
||||
return size;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
function savedIndex(index) {
|
||||
return shownIndices[index] ?? -1;
|
||||
}
|
||||
|
||||
function withHidden(items) {
|
||||
const widgets = savedWidgets.slice();
|
||||
shownIndices.forEach((saved, i) => widgets[saved] = items[i]);
|
||||
return widgets;
|
||||
}
|
||||
|
||||
function displayedItems() {
|
||||
return GridUtils.placedItems(sourceItems.map(widget => Object.assign({}, widget, {
|
||||
return withHidden(GridUtils.placedItems(sourceItems.map(widget => Object.assign({}, widget, {
|
||||
"w": WidgetUtils.clampSize(widget, columns).w
|
||||
})), slotLayout.slots);
|
||||
})), slotLayout.slots));
|
||||
}
|
||||
|
||||
Repeater {
|
||||
|
||||
@@ -20,7 +20,7 @@ DankEditableGridSlot {
|
||||
onPressAndHold: {
|
||||
if (!editChrome.hasOptions)
|
||||
return;
|
||||
root.grid.configRequested(root.index, root.widgetData, editChrome);
|
||||
root.grid.configRequested(root.grid.savedIndex(root.index), root.widgetData, editChrome);
|
||||
}
|
||||
|
||||
onResizeRequested: (requestedWidth, requestedHeight) => {
|
||||
@@ -34,17 +34,6 @@ DankEditableGridSlot {
|
||||
else
|
||||
width = Math.min(2, sizeSpec.maxW);
|
||||
}
|
||||
if (widgetData.id === "quickActions") {
|
||||
const count = WidgetUtils.enabledQuickActions(widgetData).length;
|
||||
if (height !== current.h)
|
||||
width = Math.max(width, CcMetrics.actionSpan(Math.ceil(count / CcMetrics.actionCapacity(height))));
|
||||
const size = WidgetUtils.clampSize(Object.assign({}, widgetData, {
|
||||
w: width,
|
||||
h: height
|
||||
}), grid.columns, grid.maximumRows);
|
||||
width = size.w;
|
||||
height = size.h;
|
||||
}
|
||||
const changes = {};
|
||||
if (width !== current.w)
|
||||
changes.w = width;
|
||||
@@ -124,6 +113,10 @@ DankEditableGridSlot {
|
||||
return;
|
||||
root.grid.expandClicked(root.widgetData);
|
||||
}
|
||||
|
||||
function onOptionChanged(key, value) {
|
||||
root.grid.model?.setOption(root.grid.savedIndex(root.index), key, value);
|
||||
}
|
||||
}
|
||||
|
||||
CcEditChrome {
|
||||
@@ -135,7 +128,7 @@ DankEditableGridSlot {
|
||||
visible: root.grid.editMode
|
||||
enabled: root.interactionEnabled
|
||||
widgetData: root.widgetData
|
||||
resizeEdgeWidth: root.widgetData.id === "quickActions" ? Theme.spacingL : -1
|
||||
passthrough: root.passthrough
|
||||
dragging: root.dragging
|
||||
resizing: root.resizing
|
||||
cornerRadius: root.tileItem?.bodyRadius ?? Theme.fullRadius(root.width, root.height)
|
||||
@@ -144,7 +137,7 @@ DankEditableGridSlot {
|
||||
onResizeMoved: (px, py) => root.resizeTo(px, py)
|
||||
onResizeEnded: root.finishResize()
|
||||
onResizeCanceled: root.cancelResize()
|
||||
onRemoveRequested: root.grid.removeWidget(root.index)
|
||||
onConfigRequested: anchor => root.grid.configRequested(root.index, root.widgetData, anchor)
|
||||
onRemoveRequested: root.grid.removeWidget(root.grid.savedIndex(root.index))
|
||||
onConfigRequested: anchor => root.grid.configRequested(root.grid.savedIndex(root.index), root.widgetData, anchor)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Common
|
||||
import qs.Modules.ControlCenter
|
||||
import qs.Widgets
|
||||
import "../utils/widgets.js" as WidgetUtils
|
||||
|
||||
DankBottomSheet {
|
||||
id: root
|
||||
|
||||
property var widgets: []
|
||||
readonly property string query: searchField.text.trim()
|
||||
readonly property var matches: WidgetUtils.filterWidgets(widgets, query)
|
||||
readonly property var categories: [
|
||||
{
|
||||
"id": "user",
|
||||
"title": I18n.tr("User")
|
||||
},
|
||||
{
|
||||
"id": "network",
|
||||
"title": I18n.tr("Network")
|
||||
},
|
||||
{
|
||||
"id": "audio",
|
||||
"title": I18n.tr("Audio")
|
||||
},
|
||||
{
|
||||
"id": "display",
|
||||
"title": I18n.tr("Display")
|
||||
},
|
||||
{
|
||||
"id": "system",
|
||||
"title": I18n.tr("System")
|
||||
},
|
||||
{
|
||||
"id": "plugins",
|
||||
"title": I18n.tr("Plugins")
|
||||
}
|
||||
]
|
||||
readonly property var shownCategories: categories.filter(category => widgets.some(widget => (widget.category ?? "plugins") === category.id))
|
||||
|
||||
signal chosen(string widgetId)
|
||||
signal clearRequested
|
||||
|
||||
title: I18n.tr("Widgets")
|
||||
initialFocusItem: searchField
|
||||
|
||||
onOpenedChanged: {
|
||||
if (opened)
|
||||
searchField.text = "";
|
||||
}
|
||||
|
||||
headerActions: [
|
||||
DankActionButton {
|
||||
buttonSize: Theme.buttonHeightS
|
||||
iconName: "clear_all"
|
||||
iconColor: Theme.error
|
||||
tooltipText: I18n.tr("Clear All")
|
||||
onClicked: root.clearRequested()
|
||||
}
|
||||
]
|
||||
|
||||
component PreviewGrid: Grid {
|
||||
id: grid
|
||||
|
||||
property var entries: []
|
||||
|
||||
signal chosen(string widgetId)
|
||||
|
||||
columns: Math.max(1, Math.floor((width + spacing) / (CcMetrics.previewMinWidth + spacing)))
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: grid.entries
|
||||
|
||||
StyledButton {
|
||||
id: preview
|
||||
|
||||
required property var modelData
|
||||
readonly property bool isUser: modelData.id === "user"
|
||||
readonly property bool available: modelData.enabled !== false
|
||||
|
||||
width: (grid.width - grid.spacing * (grid.columns - 1)) / grid.columns
|
||||
height: CcMetrics.previewTileHeight + Theme.spacingXS + label.implicitHeight + Theme.spacingS * 2
|
||||
radius: Theme.cornerRadiusL
|
||||
Accessible.name: modelData.text
|
||||
Accessible.description: modelData.warning ?? modelData.description ?? ""
|
||||
onClicked: {
|
||||
if (!available)
|
||||
return;
|
||||
grid.chosen(modelData.id);
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: miniTile
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
y: Theme.spacingS
|
||||
width: preview.isUser ? height : parent.width - Theme.spacingS * 2
|
||||
height: CcMetrics.previewTileHeight
|
||||
radius: Theme.fullRadius(width, height)
|
||||
color: preview.available ? CcMetrics.tileInactiveColor : Theme.onSurface_12
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: preview.modelData.icon
|
||||
size: CcMetrics.iconBoxIconSize
|
||||
color: preview.available ? CcMetrics.tileInactiveIcon : Theme.onSurface_38
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: label
|
||||
anchors.top: miniTile.bottom
|
||||
anchors.topMargin: Theme.spacingXS
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: parent.width - Theme.spacingXS * 2
|
||||
text: preview.modelData.text
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: preview.available ? Theme.surfaceText : Theme.onSurface_38
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.Wrap
|
||||
maximumLineCount: 2
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
FocusRing {
|
||||
visible: preview.visualFocus
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
control: preview
|
||||
tooltipText: preview.modelData.warning ?? ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankSearchField {
|
||||
id: searchField
|
||||
width: parent.width
|
||||
height: Theme.fieldHeightLarge
|
||||
placeholderText: I18n.tr("Search widgets...")
|
||||
onAccepted: {
|
||||
const first = root.matches.find(widget => widget.enabled !== false);
|
||||
if (first)
|
||||
root.chosen(first.id);
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.query === "" ? root.shownCategories : []
|
||||
|
||||
DankCollapsibleSection {
|
||||
id: section
|
||||
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: parent?.width ?? 0
|
||||
title: modelData.title
|
||||
expanded: index === 0
|
||||
|
||||
PreviewGrid {
|
||||
Layout.fillWidth: true
|
||||
entries: root.widgets.filter(widget => (widget.category ?? "plugins") === section.modelData.id)
|
||||
onChosen: widgetId => root.chosen(widgetId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PreviewGrid {
|
||||
width: parent.width
|
||||
visible: root.query !== ""
|
||||
entries: root.query !== "" ? root.matches : []
|
||||
onChosen: widgetId => root.chosen(widgetId)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
visible: root.matches.length === 0
|
||||
topPadding: Theme.spacingL
|
||||
bottomPadding: Theme.spacingL
|
||||
text: I18n.tr("No widgets available")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Common
|
||||
import qs.Modules.ControlCenter
|
||||
import qs.Modules.ControlCenter.Widgets
|
||||
import qs.Widgets
|
||||
|
||||
Row {
|
||||
id: root
|
||||
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
property var availableWidgets: []
|
||||
property var popupScreen: null
|
||||
property real popoutX: 0
|
||||
property real popoutY: 0
|
||||
property real popoutWidth: 0
|
||||
property real popoutHeight: 0
|
||||
|
||||
signal addWidget(string widgetId)
|
||||
signal resetToDefault
|
||||
signal clearAll
|
||||
signal finishRequested
|
||||
signal cancelRequested
|
||||
|
||||
readonly property real buttonWidth: (width - spacing * 4 - Theme.buttonHeightS * 2) / 3
|
||||
|
||||
height: Theme.buttonHeightS
|
||||
spacing: Theme.spacingS
|
||||
|
||||
function openWidgetLibrary() {
|
||||
libraryLoader.active = true;
|
||||
const window = libraryLoader.item;
|
||||
if (!window)
|
||||
return;
|
||||
if (popupScreen)
|
||||
window.screen = popupScreen;
|
||||
window.visible = true;
|
||||
}
|
||||
|
||||
function closeWidgetLibrary() {
|
||||
if (!libraryLoader.item)
|
||||
return;
|
||||
libraryLoader.item.visible = false;
|
||||
}
|
||||
|
||||
onAddWidget: closeWidgetLibrary()
|
||||
onVisibleChanged: {
|
||||
if (visible)
|
||||
return;
|
||||
closeWidgetLibrary();
|
||||
libraryLoader.active = false;
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
id: libraryLoader
|
||||
|
||||
active: false
|
||||
|
||||
PanelWindow {
|
||||
id: addWidgetWindow
|
||||
|
||||
screen: root.popupScreen
|
||||
visible: false
|
||||
color: "transparent"
|
||||
|
||||
WlrLayershell.namespace: "dms:control-center-widget-library"
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: PopoutManager.screenshotActive ? WlrKeyboardFocus.None : (visible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None)
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
WindowBlur {
|
||||
targetWindow: addWidgetWindow
|
||||
surfaceColor: widgetLibraryPanel.color
|
||||
blurX: widgetLibraryPanel.x
|
||||
blurY: widgetLibraryPanel.y
|
||||
blurWidth: addWidgetWindow.visible ? widgetLibraryPanel.width : 0
|
||||
blurHeight: addWidgetWindow.visible ? widgetLibraryPanel.height : 0
|
||||
blurRadius: Theme.windowRadius
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onClicked: root.closeWidgetLibrary()
|
||||
}
|
||||
|
||||
CcWidgetLibrary {
|
||||
id: widgetLibraryPanel
|
||||
|
||||
width: Math.min(implicitWidth, addWidgetWindow.width - Theme.spacingL * 2)
|
||||
height: Math.min(implicitHeight, addWidgetWindow.height - Theme.spacingL * 2)
|
||||
x: Math.round(Math.max(Theme.spacingL, Math.min(addWidgetWindow.width - width - Theme.spacingL, root.popoutWidth > 0 ? root.popoutX + (root.popoutWidth - width) / 2 : (addWidgetWindow.width - width) / 2)))
|
||||
y: Math.round(Math.max(Theme.spacingL, Math.min(addWidgetWindow.height - height - Theme.spacingL, root.popoutHeight > 0 ? root.popoutY + (root.popoutHeight - height) / 2 : (addWidgetWindow.height - height) / 2)))
|
||||
widgets: root.availableWidgets
|
||||
onChosen: widgetId => root.addWidget(widgetId)
|
||||
onDismissed: root.closeWidgetLibrary()
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible)
|
||||
widgetLibraryPanel.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankButton {
|
||||
width: root.buttonWidth
|
||||
buttonHeight: Theme.buttonHeightS
|
||||
iconName: "add"
|
||||
text: I18n.tr("Add widget")
|
||||
backgroundColor: Theme.secondaryContainer
|
||||
textColor: Theme.onSecondaryContainer
|
||||
onClicked: root.openWidgetLibrary()
|
||||
}
|
||||
|
||||
DankButton {
|
||||
width: root.buttonWidth
|
||||
buttonHeight: Theme.buttonHeightS
|
||||
iconName: "settings_backup_restore"
|
||||
text: I18n.tr("Defaults", "noun, control center edit button restoring the default layout")
|
||||
backgroundColor: Theme.chipSurface
|
||||
textColor: Theme.surfaceText
|
||||
onClicked: root.resetToDefault()
|
||||
}
|
||||
|
||||
DankButton {
|
||||
width: root.buttonWidth
|
||||
buttonHeight: Theme.buttonHeightS
|
||||
iconName: "clear_all"
|
||||
text: I18n.tr("Reset")
|
||||
backgroundColor: Theme.errorHover
|
||||
textColor: Theme.error
|
||||
onClicked: root.clearAll()
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
buttonSize: Theme.buttonHeightS
|
||||
iconName: "close"
|
||||
iconColor: Theme.onSecondaryContainer
|
||||
backgroundColor: Theme.secondaryContainer
|
||||
tooltipText: I18n.tr("Cancel")
|
||||
onClicked: root.cancelRequested()
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
buttonSize: Theme.buttonHeightS
|
||||
iconName: "check"
|
||||
iconColor: Theme.primaryText
|
||||
backgroundColor: Theme.primary
|
||||
tooltipText: I18n.tr("Finish")
|
||||
onClicked: root.finishRequested()
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import qs.Modules.ControlCenter
|
||||
import qs.Modules.ControlCenter.Widgets
|
||||
import qs.Modules.DankBar.Widgets
|
||||
import qs.Modules.DankDash
|
||||
import qs.Modules.Settings.Widgets
|
||||
import "../utils/widgets.js" as WidgetUtils
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
@@ -29,9 +28,7 @@ Item {
|
||||
readonly property bool isPlugin: widgetId.startsWith("plugin_")
|
||||
readonly property bool isDisk: widgetId === "diskUsage"
|
||||
readonly property bool isIdleInhibitor: widgetId === "idleInhibitor"
|
||||
readonly property bool isUser: widgetId === "userCard"
|
||||
readonly property bool isQuickActions: widgetId === "quickActions"
|
||||
readonly property var quickActions: isQuickActions ? WidgetUtils.quickActions(widgetData) : []
|
||||
readonly property bool isUser: widgetId === "user"
|
||||
|
||||
visible: widgetIndex >= 0 || contextMenu.renderActive
|
||||
|
||||
@@ -67,20 +64,8 @@ Item {
|
||||
contextMenu.hide();
|
||||
}
|
||||
|
||||
function toggleAction(id, enabled) {
|
||||
persistOption("actions", quickActions.map(action => action.id === id ? Object.assign({}, action, {
|
||||
"enabled": enabled
|
||||
}) : action));
|
||||
}
|
||||
|
||||
function persistOption(key, value) {
|
||||
const widgets = (SettingsData.controlCenterWidgets || []).slice();
|
||||
if (widgetIndex < 0 || widgetIndex >= widgets.length)
|
||||
return;
|
||||
widgets[widgetIndex] = Object.assign({}, widgets[widgetIndex], {
|
||||
[key]: value
|
||||
});
|
||||
SettingsData.set("controlCenterWidgets", widgets);
|
||||
WidgetUtils.setOption(widgetIndex, key, value);
|
||||
}
|
||||
|
||||
DankContextMenu {
|
||||
@@ -125,58 +110,6 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
SettingsReorderList {
|
||||
id: actionList
|
||||
|
||||
visible: root.isQuickActions
|
||||
model: root.quickActions
|
||||
onReordered: indices => root.persistOption("actions", indices.map(i => root.quickActions[i]))
|
||||
|
||||
delegate: SettingsReorderRow {
|
||||
required property var modelData
|
||||
readonly property bool locked: modelData.id === "edit"
|
||||
|
||||
reorderList: actionList
|
||||
paddingH: CcMetrics.rowPaddingH
|
||||
paddingV: CcMetrics.rowPaddingV
|
||||
rowColor: dragging ? Theme.blend(CcMetrics.rowColor, Theme.onSurface, Theme.stateLayerDrag) : CcMetrics.rowColor
|
||||
iconName: WidgetUtils.quickActionIcon(modelData.id)
|
||||
title: I18n.tr(WidgetUtils.quickActionLabel(modelData.id))
|
||||
clickable: !locked
|
||||
onClicked: root.toggleAction(modelData.id, !modelData.enabled)
|
||||
|
||||
DankToggle {
|
||||
hideText: true
|
||||
text: parent.title
|
||||
activeFocusOnTab: false
|
||||
checked: modelData.enabled
|
||||
enabled: !locked
|
||||
onToggled: value => root.toggleAction(modelData.id, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CcToggleRow {
|
||||
visible: root.isUser || root.isQuickActions
|
||||
text: I18n.tr("Background")
|
||||
checked: root.isUser ? root.widgetData?.background !== false : root.widgetData?.background === true
|
||||
onToggled: checked => root.persistOption("background", checked)
|
||||
}
|
||||
|
||||
CcToggleRow {
|
||||
visible: root.isQuickActions && root.widgetData?.background === true
|
||||
text: I18n.tr("Button backgrounds")
|
||||
checked: root.widgetData?.buttonBackgrounds === true
|
||||
onToggled: checked => root.persistOption("buttonBackgrounds", checked)
|
||||
}
|
||||
|
||||
CcToggleRow {
|
||||
visible: root.isQuickActions
|
||||
text: I18n.tr("Highlight power", "toggle that gives the control center power button the error color")
|
||||
checked: root.widgetData?.powerAccent === true
|
||||
onToggled: checked => root.persistOption("powerAccent", checked)
|
||||
}
|
||||
|
||||
CcToggleRow {
|
||||
visible: root.isDisk
|
||||
text: I18n.tr("Show mount path", "toggle in control center disk usage widget to turn mount path display on or off")
|
||||
|
||||
@@ -2,6 +2,7 @@ pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Modules.ControlCenter.Components
|
||||
import qs.Modules.ControlCenter.Models
|
||||
import qs.Modules.ControlCenter.Details
|
||||
@@ -18,10 +19,22 @@ FocusScope {
|
||||
|
||||
readonly property bool pageOpen: (host.expandedSection ?? "") !== ""
|
||||
readonly property real gridHeight: widgetGrid.gridHeight
|
||||
readonly property real chromeHeight: Theme.spacingS + footer.height
|
||||
readonly property bool widgetSheetOpen: widgetSheetLoader.item?.active ?? false
|
||||
readonly property real coveredAmount: Math.max(detailPage.opacity, widgetSheetLoader.item?.progress ?? 0)
|
||||
property bool widgetSheetRequested: false
|
||||
property int toplevelRevision: 0
|
||||
readonly property var runningToplevels: {
|
||||
toplevelRevision;
|
||||
const all = CompositorService.sortedToplevels ?? [];
|
||||
const current = CompositorService.filterCurrentWorkspace(all, host.triggerScreen?.name) || [];
|
||||
return current.concat(all.filter(toplevel => !current.includes(toplevel)));
|
||||
}
|
||||
readonly property string placedWidgetIds: (SettingsData.controlCenterWidgets || []).map(w => w.id).sort().join(",")
|
||||
readonly property real targetImplicitHeight: {
|
||||
let total = CcMetrics.sheetPadding * 2 + gridHeight;
|
||||
if (host.editMode)
|
||||
total += Theme.spacingS + editControls.height;
|
||||
const total = CcMetrics.sheetPadding * 2 + gridHeight + chromeHeight;
|
||||
if (widgetSheetOpen)
|
||||
return Math.max(total, CcMetrics.widgetSheetMinHeight);
|
||||
if (detailPage.shownSection === "")
|
||||
return total;
|
||||
return Math.max(total, detailPage.topInset + detailPage.minimumHeight + CcMetrics.detailDialogInset);
|
||||
@@ -34,7 +47,7 @@ FocusScope {
|
||||
readonly property vector4d surfaceCornerRadii: host.surfaceCornerRadii ?? Qt.vector4d(Theme.windowRadius, Theme.windowRadius, Theme.windowRadius, Theme.windowRadius)
|
||||
readonly property int gridColumnCap: host.gridColumnCap ?? CcMetrics.columnCapFor((host.triggerScreen?.width ?? CcMetrics.sheetWidthDefault + Theme.spacingL * 2) - Theme.spacingL * 2)
|
||||
readonly property int gridColumns: host.gridColumns ?? Math.min(CcMetrics.gridColumns, gridColumnCap)
|
||||
readonly property real availableGridHeight: (host.availableHeight ?? (host.triggerScreen?.height ?? CcMetrics.fallbackScreenHeight) - CcMetrics.maxHeightInset) - CcMetrics.sheetPadding * 2 - Theme.spacingS - editControls.height
|
||||
readonly property real availableGridHeight: (host.availableHeight ?? (host.triggerScreen?.height ?? CcMetrics.fallbackScreenHeight) - CcMetrics.maxHeightInset) - CcMetrics.sheetPadding * 2 - chromeHeight
|
||||
readonly property real editGutter: host.editGutter ?? 0
|
||||
readonly property DankPanelResizer panelResizer: DankPanelResizer {
|
||||
popout: root.host
|
||||
@@ -141,7 +154,32 @@ FocusScope {
|
||||
SettingsData.set("controlCenterColumns", snapshot.columns);
|
||||
}
|
||||
|
||||
function openWidgetSheet() {
|
||||
const sheet = widgetSheetLoader.item;
|
||||
if (!sheet) {
|
||||
widgetSheetRequested = true;
|
||||
return;
|
||||
}
|
||||
sheet.opened = true;
|
||||
}
|
||||
|
||||
function closeWidgetSheet() {
|
||||
if (!widgetSheetLoader.item)
|
||||
return;
|
||||
widgetSheetLoader.item.opened = false;
|
||||
}
|
||||
|
||||
Keys.onEscapePressed: event => {
|
||||
if (footer.resetArmed) {
|
||||
footer.cancelConfirmation();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
if (widgetSheetLoader.item?.opened) {
|
||||
closeWidgetSheet();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
if (configOverlayLoader.item?.visible) {
|
||||
configOverlayLoader.item.close();
|
||||
event.accepted = true;
|
||||
@@ -180,8 +218,10 @@ FocusScope {
|
||||
} else {
|
||||
panelResizer.cancel();
|
||||
editSnapshot = null;
|
||||
widgetSheetRequested = false;
|
||||
}
|
||||
forceActiveFocus();
|
||||
if (!activeFocus)
|
||||
forceActiveFocus();
|
||||
}
|
||||
|
||||
DankGridEditChrome {
|
||||
@@ -191,7 +231,7 @@ FocusScope {
|
||||
anchors.margins: -(contentInset + Theme.spacingS)
|
||||
z: 1
|
||||
visible: root.host.editMode
|
||||
enabled: detailPage.shownSection === ""
|
||||
enabled: detailPage.shownSection === "" && !root.widgetSheetOpen
|
||||
edgeResize: root.panelResizing || root.panelResizer.sideMovable(-1, root.gridColumns)
|
||||
cornerResize: root.panelResizing || root.panelResizer.sideMovable(1, root.gridColumns)
|
||||
horizontalResize: true
|
||||
@@ -257,7 +297,7 @@ FocusScope {
|
||||
|
||||
width: parent.width
|
||||
height: root.gridHeight
|
||||
opacity: CcMetrics.hideCoveredContent ? 1 - detailPage.opacity : 1
|
||||
opacity: CcMetrics.hideCoveredContent ? 1 - root.coveredAmount : 1
|
||||
|
||||
CcTileGrid {
|
||||
id: widgetGrid
|
||||
@@ -275,8 +315,7 @@ FocusScope {
|
||||
onRemoveWidget: index => widgetModel.removeWidget(index)
|
||||
onConfigRequested: (index, widgetData, anchor) => root.openConfigOverlay(index, widgetData, anchor)
|
||||
onColorPickerRequested: root.host.openColorPicker()
|
||||
onHeaderTapped: root.host.close()
|
||||
onEditRequested: root.host.editMode = !root.host.editMode
|
||||
onCloseRequested: root.host.close()
|
||||
onSettingsRequested: root.host.openSettings()
|
||||
onLockRequested: {
|
||||
root.host.close();
|
||||
@@ -295,27 +334,17 @@ FocusScope {
|
||||
}
|
||||
}
|
||||
|
||||
EditControls {
|
||||
id: editControls
|
||||
CcFooter {
|
||||
id: footer
|
||||
|
||||
width: parent.width
|
||||
visible: root.host.editMode
|
||||
popupScreen: root.host.screen
|
||||
popoutX: root.host.alignedX
|
||||
popoutY: root.host.alignedY
|
||||
popoutWidth: root.host.alignedWidth
|
||||
popoutHeight: root.host.alignedHeight
|
||||
availableWidgets: {
|
||||
if (!root.host.editMode)
|
||||
return [];
|
||||
const existingIds = (SettingsData.controlCenterWidgets || []).map(w => w.id);
|
||||
const allWidgets = widgetModel.baseWidgetDefinitions.concat(widgetModel.getPluginWidgets());
|
||||
return allWidgets.filter(w => w.allowMultiple || !existingIds.includes(w.id));
|
||||
}
|
||||
onAddWidget: widgetId => widgetModel.addWidget(widgetId)
|
||||
onResetToDefault: () => widgetModel.resetToDefault()
|
||||
onClearAll: () => widgetModel.clearAll()
|
||||
onFinishRequested: root.host.editMode = false
|
||||
editMode: root.host.editMode
|
||||
toplevels: root.runningToplevels
|
||||
opacity: body.opacity
|
||||
onRunningAppsRequested: root.navigateTo("runningApps")
|
||||
onAddWidgetRequested: root.openWidgetSheet()
|
||||
onResetRequested: widgetModel.resetToDefault()
|
||||
onEditToggled: root.host.editMode = !root.host.editMode
|
||||
onCancelRequested: root.cancelEdit()
|
||||
}
|
||||
}
|
||||
@@ -337,8 +366,10 @@ FocusScope {
|
||||
transientSurfaceTracker: root.host.transientSurfaceTracker
|
||||
onCodecSelectorRequested: device => root.showCodecSelector(device)
|
||||
onPortSelectorRequested: node => root.showPortSelector(node)
|
||||
runningToplevels: root.runningToplevels
|
||||
onBackRequested: root.goBack()
|
||||
onCollapseRequested: root.host.collapseAll()
|
||||
onCloseRequested: root.host.close()
|
||||
onDismissed: {
|
||||
const target = root.detailReturnFocus;
|
||||
root.detailReturnFocus = null;
|
||||
@@ -374,6 +405,47 @@ FocusScope {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: CompositorService
|
||||
|
||||
function onToplevelsChanged() {
|
||||
root.toplevelRevision++;
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: widgetSheetLoader
|
||||
|
||||
anchors.fill: parent
|
||||
z: CcMetrics.overlayZ
|
||||
active: root.host.editMode
|
||||
asynchronous: true
|
||||
onLoaded: {
|
||||
if (!root.widgetSheetRequested)
|
||||
return;
|
||||
root.widgetSheetRequested = false;
|
||||
item.opened = true;
|
||||
}
|
||||
sourceComponent: CcWidgetSheet {
|
||||
scrimRadii: root.surfaceCornerRadii
|
||||
surfaceColor: CcMetrics.dialogColor
|
||||
widgets: {
|
||||
const existingIds = root.placedWidgetIds.split(",");
|
||||
const allWidgets = widgetModel.baseWidgetDefinitions.concat(widgetModel.getPluginWidgets());
|
||||
return allWidgets.filter(w => w.allowMultiple || !existingIds.includes(w.id));
|
||||
}
|
||||
onDismissRequested: opened = false
|
||||
onChosen: widgetId => {
|
||||
widgetModel.addWidget(widgetId);
|
||||
opened = false;
|
||||
}
|
||||
onClearRequested: {
|
||||
widgetModel.clearAll();
|
||||
opened = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: configOverlayLoader
|
||||
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Modules.ControlCenter
|
||||
import qs.Modules.ControlCenter.Widgets
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
readonly property string title: I18n.tr("Running apps")
|
||||
property var toplevels: []
|
||||
readonly property int count: toplevels.length
|
||||
|
||||
signal closeRequested
|
||||
signal dismissRequested
|
||||
|
||||
onCountChanged: {
|
||||
if (count === 0)
|
||||
dismissRequested();
|
||||
}
|
||||
|
||||
DankFlickable {
|
||||
anchors.fill: parent
|
||||
contentHeight: column.height
|
||||
clip: true
|
||||
|
||||
Column {
|
||||
id: column
|
||||
width: parent.width
|
||||
spacing: CcMetrics.detailContentGap
|
||||
|
||||
CcGroup {
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: root.toplevels
|
||||
objectProp: CompositorService.toplevelKey
|
||||
}
|
||||
|
||||
CcListRow {
|
||||
id: row
|
||||
|
||||
required property var modelData
|
||||
readonly property string appId: modelData?.appId ?? ""
|
||||
readonly property string appName: Paths.getAppName(appId, DesktopEntries.heuristicLookup(appId))
|
||||
readonly property var placement: CompositorService.toplevelPlacement(modelData)
|
||||
readonly property string workspaceLabel: placement.workspace ? I18n.tr("Workspace %1", "control center running app row, %1 is a workspace number or name").arg(placement.workspace) : ""
|
||||
readonly property string outputLabel: Quickshell.screens.length > 1 ? (SettingsData.getScreenDisplayName(Quickshell.screens.find(screen => screen.name === placement.output)) || placement.output) : ""
|
||||
|
||||
title: modelData?.title || appName
|
||||
subtitle: [modelData?.title ? appName : "", workspaceLabel, outputLabel].filter(text => text !== "").join(" · ")
|
||||
clickable: true
|
||||
onClicked: {
|
||||
modelData?.activate();
|
||||
root.closeRequested();
|
||||
}
|
||||
|
||||
leading: CcAppIcon {
|
||||
appId: row.appId
|
||||
iconSize: Theme.iconSizeLarge
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconName: "close"
|
||||
iconColor: Theme.onSurfaceVariant
|
||||
Accessible.name: I18n.tr("Close")
|
||||
onClicked: row.modelData?.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,11 +104,11 @@ QtObject {
|
||||
readonly property Component colorPickerTile: Component {
|
||||
ColorPickerTile {}
|
||||
}
|
||||
readonly property Component userCard: Component {
|
||||
UserCard {}
|
||||
readonly property Component userTile: Component {
|
||||
UserTile {}
|
||||
}
|
||||
readonly property Component quickActions: Component {
|
||||
QuickActions {}
|
||||
readonly property Component actionTile: Component {
|
||||
ActionTile {}
|
||||
}
|
||||
readonly property Component pluginTile: Component {
|
||||
PluginTile {}
|
||||
@@ -163,10 +163,12 @@ QtObject {
|
||||
return diskUsageTile;
|
||||
case "colorPicker":
|
||||
return colorPickerTile;
|
||||
case "userCard":
|
||||
return userCard;
|
||||
case "quickActions":
|
||||
return quickActions;
|
||||
case "user":
|
||||
return userTile;
|
||||
case "lock":
|
||||
case "power":
|
||||
case "settings":
|
||||
return actionTile;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -186,11 +188,39 @@ QtObject {
|
||||
|
||||
readonly property var baseWidgetDefinitions: [
|
||||
{
|
||||
"id": "userCard",
|
||||
"id": "user",
|
||||
"text": I18n.tr("User"),
|
||||
"description": I18n.tr("Show hostname"),
|
||||
"description": I18n.tr("Profile"),
|
||||
"icon": "person",
|
||||
"type": "info",
|
||||
"category": "user",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"id": "settings",
|
||||
"text": I18n.tr("Settings"),
|
||||
"description": "",
|
||||
"icon": "settings",
|
||||
"type": "action",
|
||||
"category": "system",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"id": "lock",
|
||||
"text": I18n.tr("Lock"),
|
||||
"description": I18n.tr("Lock screen"),
|
||||
"icon": "lock",
|
||||
"type": "action",
|
||||
"category": "system",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"id": "power",
|
||||
"text": I18n.tr("Power"),
|
||||
"description": I18n.tr("Power menu"),
|
||||
"icon": "power_settings_new",
|
||||
"type": "action",
|
||||
"category": "system",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
@@ -199,6 +229,7 @@ QtObject {
|
||||
"description": I18n.tr("Blue light filter"),
|
||||
"icon": "nightlight",
|
||||
"type": "toggle",
|
||||
"category": "display",
|
||||
"enabled": NightModeService.automationAvailable,
|
||||
"warning": !NightModeService.automationAvailable ? I18n.tr("Requires night mode support") : undefined
|
||||
},
|
||||
@@ -208,6 +239,7 @@ QtObject {
|
||||
"description": I18n.tr("System theme toggle"),
|
||||
"icon": "contrast",
|
||||
"type": "toggle",
|
||||
"category": "display",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
@@ -216,6 +248,7 @@ QtObject {
|
||||
"description": I18n.tr("Block notifications"),
|
||||
"icon": "do_not_disturb_on",
|
||||
"type": "toggle",
|
||||
"category": "system",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
@@ -224,6 +257,7 @@ QtObject {
|
||||
"description": I18n.tr("Prevent screen timeout"),
|
||||
"icon": "motion_sensor_active",
|
||||
"type": "toggle",
|
||||
"category": "system",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
@@ -232,6 +266,7 @@ QtObject {
|
||||
"description": I18n.tr("Wi-Fi and Ethernet connection"),
|
||||
"icon": "wifi",
|
||||
"type": "connection",
|
||||
"category": "network",
|
||||
"enabled": NetworkService.wifiAvailable,
|
||||
"warning": !NetworkService.wifiAvailable ? I18n.tr("Wi-Fi not available") : undefined
|
||||
},
|
||||
@@ -241,6 +276,7 @@ QtObject {
|
||||
"description": I18n.tr("Device connections"),
|
||||
"icon": "bluetooth",
|
||||
"type": "connection",
|
||||
"category": "network",
|
||||
"enabled": BluetoothService.available,
|
||||
"warning": !BluetoothService.available ? I18n.tr("Bluetooth not available") : undefined
|
||||
},
|
||||
@@ -250,6 +286,7 @@ QtObject {
|
||||
"description": I18n.tr("Speaker settings"),
|
||||
"icon": "volume_up",
|
||||
"type": "connection",
|
||||
"category": "audio",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
@@ -258,6 +295,7 @@ QtObject {
|
||||
"description": I18n.tr("Microphone settings"),
|
||||
"icon": "mic",
|
||||
"type": "connection",
|
||||
"category": "audio",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
@@ -266,6 +304,7 @@ QtObject {
|
||||
"description": I18n.tr("Audio volume control"),
|
||||
"icon": "volume_up",
|
||||
"type": "slider",
|
||||
"category": "audio",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
@@ -274,6 +313,7 @@ QtObject {
|
||||
"description": I18n.tr("Display brightness control"),
|
||||
"icon": "brightness_6",
|
||||
"type": "slider",
|
||||
"category": "display",
|
||||
"enabled": BrightnessService.brightnessAvailable,
|
||||
"warning": !BrightnessService.brightnessAvailable ? I18n.tr("Brightness control not available") : undefined,
|
||||
"allowMultiple": true
|
||||
@@ -284,15 +324,18 @@ QtObject {
|
||||
"description": I18n.tr("Microphone volume control"),
|
||||
"icon": "mic",
|
||||
"type": "slider",
|
||||
"category": "audio",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"id": "battery",
|
||||
"text": I18n.tr("Battery"),
|
||||
"text": BatteryService.batteryAvailable || !PowerProfileWatcher.available ? I18n.tr("Battery") : I18n.tr("Power profile"),
|
||||
"description": I18n.tr("Battery and power management"),
|
||||
"icon": "battery_std",
|
||||
"type": "action",
|
||||
"enabled": true
|
||||
"category": "system",
|
||||
"enabled": BatteryService.batteryAvailable || PowerProfileWatcher.available,
|
||||
"warning": !BatteryService.batteryAvailable && !PowerProfileWatcher.available ? I18n.tr("No battery") : undefined
|
||||
},
|
||||
{
|
||||
"id": "diskUsage",
|
||||
@@ -300,6 +343,7 @@ QtObject {
|
||||
"description": I18n.tr("Filesystem usage monitoring"),
|
||||
"icon": "storage",
|
||||
"type": "action",
|
||||
"category": "system",
|
||||
"enabled": DgopService.dgopAvailable,
|
||||
"warning": !DgopService.dgopAvailable ? I18n.tr("Requires 'dgop' tool") : undefined,
|
||||
"allowMultiple": true
|
||||
@@ -310,6 +354,7 @@ QtObject {
|
||||
"description": I18n.tr("Choose colors from palette"),
|
||||
"icon": "palette",
|
||||
"type": "action",
|
||||
"category": "display",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
@@ -318,6 +363,7 @@ QtObject {
|
||||
"description": I18n.tr("VPN Connections"),
|
||||
"icon": "vpn_key",
|
||||
"type": "builtin_plugin",
|
||||
"category": "network",
|
||||
"enabled": DMSNetworkService.available,
|
||||
"warning": !DMSNetworkService.available ? I18n.tr("VPN not available") : undefined,
|
||||
"isBuiltinPlugin": true
|
||||
@@ -328,6 +374,7 @@ QtObject {
|
||||
"description": I18n.tr("Print Server Management"),
|
||||
"icon": "Print",
|
||||
"type": "builtin_plugin",
|
||||
"category": "system",
|
||||
"enabled": CupsService.cupsAvailable,
|
||||
"warning": !CupsService.cupsAvailable ? I18n.tr("CUPS not available") : undefined,
|
||||
"isBuiltinPlugin": true
|
||||
@@ -338,6 +385,7 @@ QtObject {
|
||||
"description": I18n.tr("Tailscale Network", "Tailscale control center widget description"),
|
||||
"icon": "device_hub",
|
||||
"type": "builtin_plugin",
|
||||
"category": "network",
|
||||
"enabled": TailscaleService.available,
|
||||
"warning": !TailscaleService.available ? I18n.tr("Tailscale not available", "Warning when Tailscale service is not running") : undefined,
|
||||
"isBuiltinPlugin": true
|
||||
@@ -348,6 +396,7 @@ QtObject {
|
||||
"description": I18n.tr("Switch between display configurations"),
|
||||
"icon": "monitor",
|
||||
"type": "builtin_plugin",
|
||||
"category": "display",
|
||||
"enabled": true,
|
||||
"isBuiltinPlugin": true
|
||||
}
|
||||
@@ -394,6 +443,7 @@ QtObject {
|
||||
"description": plugin.description || "",
|
||||
"icon": plugin.icon || "extension",
|
||||
"type": "plugin",
|
||||
"category": "plugins",
|
||||
"enabled": true,
|
||||
"isPlugin": true
|
||||
});
|
||||
@@ -419,11 +469,15 @@ QtObject {
|
||||
WidgetUtils.setLayout(widgets);
|
||||
}
|
||||
|
||||
function setOption(index, key, value) {
|
||||
WidgetUtils.setOption(index, key, value);
|
||||
}
|
||||
|
||||
function resetToDefault() {
|
||||
WidgetUtils.resetToDefault(columns);
|
||||
WidgetUtils.resetToDefault();
|
||||
}
|
||||
|
||||
function clearAll() {
|
||||
WidgetUtils.clearAll(columns);
|
||||
WidgetUtils.clearAll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modules.ControlCenter
|
||||
|
||||
CcTile {
|
||||
id: root
|
||||
|
||||
readonly property string action: widgetData?.id ?? ""
|
||||
|
||||
toggle: false
|
||||
iconName: widgetDef?.icon ?? ""
|
||||
title: widgetDef?.text ?? ""
|
||||
restIconColor: action === "power" ? Theme.error : CcMetrics.tileInactiveIcon
|
||||
|
||||
onClicked: {
|
||||
switch (action) {
|
||||
case "lock":
|
||||
host?.lockRequested();
|
||||
return;
|
||||
case "power":
|
||||
host?.powerRequested();
|
||||
return;
|
||||
case "settings":
|
||||
host?.settingsRequested();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,17 @@ CcTile {
|
||||
id: root
|
||||
|
||||
readonly property bool available: BatteryService.batteryAvailable
|
||||
readonly property bool profileMode: !available && PowerProfileWatcher.available
|
||||
|
||||
iconName: BatteryService.getBatteryIcon()
|
||||
title: available ? I18n.tr("Battery") : I18n.tr("No battery")
|
||||
iconName: profileMode ? Theme.getPowerProfileIcon(PowerProfileWatcher.currentProfile) : BatteryService.getBatteryIcon()
|
||||
title: {
|
||||
if (profileMode)
|
||||
return I18n.tr("Power profile");
|
||||
return available ? I18n.tr("Battery") : I18n.tr("No battery");
|
||||
}
|
||||
subtitle: {
|
||||
if (profileMode)
|
||||
return Theme.getPowerProfileLabel(PowerProfileWatcher.currentProfile);
|
||||
if (!available)
|
||||
return I18n.tr("Not available");
|
||||
if (BatteryService.isCharging)
|
||||
@@ -21,7 +28,8 @@ CcTile {
|
||||
return `${BatteryService.batteryLevel}%`;
|
||||
}
|
||||
active: available && (BatteryService.isCharging || BatteryService.isPluggedIn)
|
||||
opensPage: true
|
||||
opensPage: !profileMode
|
||||
showExpand: profileMode
|
||||
tallContent: Component {
|
||||
Item {
|
||||
BatteryMeter {
|
||||
@@ -35,7 +43,13 @@ CcTile {
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: expandClicked()
|
||||
onClicked: {
|
||||
if (!profileMode) {
|
||||
expandClicked();
|
||||
return;
|
||||
}
|
||||
PowerProfileWatcher.cycleProfile();
|
||||
}
|
||||
expandedContent: Component {
|
||||
CcTileActions {
|
||||
actions: PowerProfileWatcher.availableProfiles.map(profile => ({
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property string appId: ""
|
||||
property real iconSize: Theme.iconSize
|
||||
|
||||
implicitWidth: iconSize
|
||||
implicitHeight: iconSize
|
||||
|
||||
IconImage {
|
||||
id: image
|
||||
anchors.fill: parent
|
||||
source: root.appId ? Paths.getAppIcon(root.appId, DesktopEntries.heuristicLookup(root.appId)) : ""
|
||||
smooth: true
|
||||
mipmap: true
|
||||
asynchronous: true
|
||||
visible: status === Image.Ready
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "apps"
|
||||
size: root.iconSize
|
||||
color: Theme.onSurfaceVariant
|
||||
visible: image.status === Image.Error || image.source.toString() === ""
|
||||
}
|
||||
}
|
||||
@@ -70,7 +70,6 @@ Item {
|
||||
iconName: root.iconName
|
||||
iconSize: CcMetrics.iconBoxIconSize
|
||||
iconColor: CcMetrics.tileInactiveContent
|
||||
visible: root.tall
|
||||
enabled: root.sliderEnabled && root.interactive
|
||||
tooltipText: root.iconTooltip
|
||||
Accessible.name: root.iconLabel
|
||||
@@ -110,6 +109,7 @@ Item {
|
||||
id: trackArea
|
||||
objectName: "sliderTrackArea"
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: root.tall ? 0 : action.width + Theme.spacingS
|
||||
anchors.right: parent.right
|
||||
y: root.vertical ? (root.showNumber ? labels.height + Theme.spacingS : 0) : root.tall ? action.height + Theme.spacingM : 0
|
||||
height: Math.max(0, (root.vertical ? action.y - Theme.spacingS : parent.height) - y)
|
||||
@@ -131,14 +131,8 @@ Item {
|
||||
return "l";
|
||||
return "m";
|
||||
}
|
||||
insetIcon: root.tall ? "" : root.iconName
|
||||
insetIconPosition: "end"
|
||||
insetIconClickable: true
|
||||
insetIconTooltip: root.iconTooltip
|
||||
insetIconLabel: root.iconLabel
|
||||
Accessible.name: root.sliderLabel
|
||||
showValue: !root.vertical
|
||||
onInsetIconClicked: root.iconClicked()
|
||||
onSliderValueChanged: newValue => root.sliderValueChanged(newValue)
|
||||
|
||||
Binding on trackHeight {
|
||||
@@ -152,12 +146,6 @@ Item {
|
||||
value: CcMetrics.stripHandleHeight
|
||||
restoreMode: Binding.RestoreBinding
|
||||
}
|
||||
|
||||
Binding on insetIconSize {
|
||||
when: !root.tall
|
||||
value: CcMetrics.stripIconSize
|
||||
restoreMode: Binding.RestoreBinding
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ Item {
|
||||
readonly property bool showSubtitle: subtitle !== "" && (!stacked || height - tilePadding * 2 >= iconExtent + Theme.spacingS + titleLabel.implicitHeight + Theme.spacingXXS + subtitleLabel.implicitHeight)
|
||||
property bool showExpand: false
|
||||
property bool opensPage: false
|
||||
property color restIconColor: CcMetrics.tileInactiveIcon
|
||||
property Component tallContent: null
|
||||
property bool interactive: true
|
||||
property bool iconBlinking: false
|
||||
@@ -73,7 +74,7 @@ Item {
|
||||
return Theme.onSurface_38;
|
||||
if (hasIconBox)
|
||||
return active ? CcMetrics.tileActiveContent : CcMetrics.tileInactiveContent;
|
||||
return bodyActive ? CcMetrics.tileActiveContent : CcMetrics.tileInactiveIcon;
|
||||
return bodyActive ? CcMetrics.tileActiveContent : root.restIconColor;
|
||||
}
|
||||
readonly property color iconBoxColor: {
|
||||
if (!enabled)
|
||||
|
||||
@@ -2,17 +2,13 @@ import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modules.ControlCenter
|
||||
import qs.Widgets
|
||||
import "../utils/widgets.js" as WidgetUtils
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property var widgets: []
|
||||
readonly property var matches: {
|
||||
const query = searchField.text.trim().toLowerCase();
|
||||
if (!query)
|
||||
return widgets;
|
||||
return widgets.filter(widget => [widget.text, widget.description, widget.id].some(value => (value || "").toLowerCase().includes(query)));
|
||||
}
|
||||
readonly property var matches: WidgetUtils.filterWidgets(widgets, searchField.text)
|
||||
|
||||
signal chosen(string widgetId)
|
||||
signal dismissed
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modules.ControlCenter
|
||||
import qs.Widgets
|
||||
import "../utils/widgets.js" as WidgetUtils
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var widgetData: ({})
|
||||
property var widgetDef: null
|
||||
property var host: null
|
||||
property bool live: true
|
||||
property bool interactive: true
|
||||
property real columns: 1.5
|
||||
property real rows: 1.5
|
||||
property bool compact: false
|
||||
|
||||
readonly property Item passthrough: actionGrid
|
||||
readonly property bool editMode: host?.editMode ?? false
|
||||
readonly property var actions: WidgetUtils.enabledQuickActions(widgetData)
|
||||
readonly property bool background: widgetData?.background === true
|
||||
readonly property bool buttonBackgrounds: widgetData?.buttonBackgrounds === true
|
||||
readonly property real bodyRadius: background ? Theme.cornerRadiusXL : Theme.cornerRadiusL
|
||||
readonly property int actionColumns: Math.max(1, Math.min(actions.length, Math.floor((width + CcMetrics.actionGap) / (CcMetrics.actionSize + CcMetrics.actionGap))))
|
||||
|
||||
width: parent?.width ?? 0
|
||||
height: CcMetrics.headerHeight
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
function trigger(id) {
|
||||
switch (id) {
|
||||
case "lock":
|
||||
host?.lockRequested();
|
||||
return;
|
||||
case "power":
|
||||
host?.powerRequested();
|
||||
return;
|
||||
case "settings":
|
||||
host?.settingsRequested();
|
||||
return;
|
||||
case "edit":
|
||||
host?.editRequested();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: root.bodyRadius
|
||||
color: Theme.foregroundColor(Theme.cardSurface)
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
visible: root.background
|
||||
}
|
||||
|
||||
Grid {
|
||||
id: actionGrid
|
||||
|
||||
anchors.centerIn: parent
|
||||
columns: root.actionColumns
|
||||
spacing: CcMetrics.actionGap
|
||||
|
||||
Repeater {
|
||||
model: root.actions
|
||||
|
||||
DankActionButton {
|
||||
required property var modelData
|
||||
readonly property string actionId: modelData.id
|
||||
readonly property bool destructive: root.widgetData?.powerAccent === true && actionId === "power"
|
||||
readonly property bool editing: actionId === "edit" && root.editMode
|
||||
|
||||
buttonSize: CcMetrics.actionSize
|
||||
iconSize: CcMetrics.actionIconSize
|
||||
circular: root.background
|
||||
iconName: WidgetUtils.quickActionIcon(actionId)
|
||||
iconColor: editing ? Theme.onPrimary : destructive ? Theme.error : Theme.onSurface
|
||||
backgroundColor: editing ? Theme.primary : root.background ? (root.buttonBackgrounds ? Theme.foregroundColor(Theme.chipSurface) : "transparent") : Theme.foregroundColor(Theme.cardSurface)
|
||||
tooltipText: I18n.tr(WidgetUtils.quickActionLabel(actionId))
|
||||
checkable: actionId === "edit"
|
||||
checked: editing
|
||||
Accessible.checkable: checkable
|
||||
Accessible.checked: checked
|
||||
onClicked: root.trigger(actionId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modules.ControlCenter
|
||||
import qs.Modules.DankDash
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var widgetData: ({})
|
||||
property var widgetDef: null
|
||||
property var host: null
|
||||
property bool live: true
|
||||
property bool interactive: true
|
||||
property real columns: 4
|
||||
property real rows: 1.5
|
||||
property bool compact: false
|
||||
|
||||
readonly property bool tapToClose: interactive && (host?.tapToClose ?? false)
|
||||
readonly property bool background: widgetData?.background !== false
|
||||
readonly property real bodyRadius: Theme.cornerRadiusXL
|
||||
|
||||
width: parent?.width ?? 0
|
||||
height: CcMetrics.headerHeight
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: root.bodyRadius
|
||||
color: Theme.foregroundColor(Theme.cardSurface)
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
visible: root.background
|
||||
}
|
||||
|
||||
UserIdentity {
|
||||
id: identity
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: root.background ? Theme.spacingL : 0
|
||||
anchors.rightMargin: anchors.leftMargin
|
||||
anchors.topMargin: Theme.spacingS
|
||||
anchors.bottomMargin: Theme.spacingS
|
||||
live: root.live
|
||||
options: DashRegistry.resolvedOptions("user", root.widgetData)
|
||||
avatarSize: narrow ? height : Math.min(height - Theme.spacingM, width / 3)
|
||||
narrow: width < Theme.minimumTouchTargetSize + Theme.spacingM + Theme.buttonHeightM
|
||||
stacked: root.height >= CcMetrics.headerHeight
|
||||
contentColor: Theme.onSurface
|
||||
mutedColor: Theme.onSurfaceVariant
|
||||
}
|
||||
|
||||
StyledButton {
|
||||
anchors.fill: parent
|
||||
visible: root.tapToClose
|
||||
radius: root.bodyRadius
|
||||
Accessible.name: I18n.tr("Close")
|
||||
onClicked: root.host?.headerTapped()
|
||||
|
||||
FocusRing {
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
visible: parent.visualFocus
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
control: parent
|
||||
tooltipText: I18n.tr("Close")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import qs.Common
|
||||
import qs.Modules.ControlCenter
|
||||
import qs.Modules.DankDash
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import "../utils/widgets.js" as WidgetUtils
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var widgetData: ({})
|
||||
property var widgetDef: null
|
||||
property var host: null
|
||||
property bool live: true
|
||||
property bool interactive: true
|
||||
property real columns: 2
|
||||
property real rows: 2
|
||||
property bool compact: false
|
||||
|
||||
signal optionChanged(string key, var value)
|
||||
|
||||
readonly property bool wide: width >= height * 2
|
||||
readonly property real avatarSide: Math.min(width, height)
|
||||
readonly property string shape: widgetData?.shape ?? WidgetUtils.USER_SHAPES[0]
|
||||
readonly property bool editMode: host?.editMode ?? false
|
||||
readonly property bool tapToClose: interactive && (host?.tapToClose ?? false)
|
||||
readonly property Item passthrough: shapeButton
|
||||
readonly property real bodyRadius: Theme.fullRadius(avatarSide, avatarSide)
|
||||
readonly property real fallbackGlyphRatio: 0.5
|
||||
readonly property string avatarSource: {
|
||||
const image = PortalService.profileImage;
|
||||
if (image === "")
|
||||
return "";
|
||||
return image.startsWith("/") ? "file://" + image : image;
|
||||
}
|
||||
readonly property bool hasImage: picture.status === Image.Ready
|
||||
|
||||
width: parent?.width ?? 0
|
||||
height: parent?.height ?? 0
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
Accessible.role: Accessible.StaticText
|
||||
Accessible.name: UserInfoService.fullName || UserInfoService.username
|
||||
|
||||
UserIdentity {
|
||||
anchors.fill: parent
|
||||
visible: root.wide
|
||||
live: root.live && root.wide
|
||||
options: DashRegistry.resolvedOptions("user", root.widgetData)
|
||||
avatarSize: root.height
|
||||
stacked: root.rows >= 2
|
||||
contentColor: Theme.onSurface
|
||||
mutedColor: Theme.onSurfaceVariant
|
||||
}
|
||||
|
||||
Item {
|
||||
id: avatar
|
||||
|
||||
anchors.centerIn: parent
|
||||
width: root.avatarSide
|
||||
height: root.avatarSide
|
||||
visible: !root.wide
|
||||
|
||||
DankMaterialShape {
|
||||
anchors.fill: parent
|
||||
shape: root.shape
|
||||
color: Theme.primaryContainer
|
||||
visible: !root.hasImage
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "person"
|
||||
size: Math.round(root.avatarSide * root.fallbackGlyphRatio)
|
||||
color: Theme.onPrimaryContainer
|
||||
filled: true
|
||||
visible: !root.hasImage
|
||||
}
|
||||
|
||||
Item {
|
||||
id: avatarMask
|
||||
|
||||
anchors.fill: parent
|
||||
layer.enabled: root.hasImage
|
||||
visible: false
|
||||
|
||||
DankMaterialShape {
|
||||
anchors.fill: parent
|
||||
shape: root.shape
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
id: picture
|
||||
|
||||
anchors.fill: parent
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
smooth: true
|
||||
mipmap: true
|
||||
retainWhileLoading: true
|
||||
visible: root.hasImage
|
||||
sourceSize.width: Math.max(1, Math.ceil(width * Screen.devicePixelRatio))
|
||||
sourceSize.height: Math.max(1, Math.ceil(height * Screen.devicePixelRatio))
|
||||
source: avatar.visible ? root.avatarSource : ""
|
||||
layer.enabled: root.hasImage
|
||||
layer.effect: MultiEffect {
|
||||
maskEnabled: true
|
||||
maskSource: avatarMask
|
||||
maskThresholdMin: 0.5
|
||||
maskSpreadAtMin: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledButton {
|
||||
anchors.fill: parent
|
||||
visible: root.tapToClose
|
||||
radius: root.bodyRadius
|
||||
Accessible.name: I18n.tr("Close")
|
||||
onClicked: root.host?.closeRequested()
|
||||
|
||||
FocusRing {
|
||||
visible: parent.visualFocus
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
control: parent
|
||||
cornerRadius: root.bodyRadius
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: shapeButton
|
||||
|
||||
anchors.bottom: avatar.bottom
|
||||
anchors.left: avatar.left
|
||||
buttonSize: CcMetrics.shapeButtonSize
|
||||
iconSize: Theme.iconSizeSmall
|
||||
iconName: "shuffle"
|
||||
iconColor: Theme.onSurface
|
||||
backgroundColor: Theme.surfaceContainerHighest
|
||||
Accessible.name: I18n.tr("Shuffle")
|
||||
visible: root.editMode && avatar.visible
|
||||
onClicked: root.optionChanged("shape", WidgetUtils.nextUserShape(root.shape))
|
||||
}
|
||||
}
|
||||
@@ -1,71 +1,44 @@
|
||||
.import qs.Common as Common
|
||||
.import qs.Services as Services
|
||||
.import qs.Modules.ControlCenter as ControlCenter
|
||||
.import "../../../Common/GridLayout.js" as GridLayout
|
||||
|
||||
var PINNED_IDS = ["quickActions"];
|
||||
var OPTION_IDS = ["diskUsage", "brightnessSlider", "idleInhibitor", "userCard", "quickActions"];
|
||||
var QUICK_ACTIONS = [
|
||||
{
|
||||
"id": "lock",
|
||||
"icon": "lock",
|
||||
"label": "Lock"
|
||||
},
|
||||
{
|
||||
"id": "power",
|
||||
"icon": "power_settings_new",
|
||||
"label": "Power"
|
||||
},
|
||||
{
|
||||
"id": "settings",
|
||||
"icon": "settings",
|
||||
"label": "Settings"
|
||||
},
|
||||
{
|
||||
"id": "edit",
|
||||
"icon": "edit",
|
||||
"label": "Edit"
|
||||
}
|
||||
];
|
||||
var QUICK_ACTION_IDS = QUICK_ACTIONS.map(action => action.id);
|
||||
var OPTION_IDS = ["diskUsage", "brightnessSlider", "idleInhibitor", "user"];
|
||||
var ACTION_IDS = ["settings", "lock", "power"];
|
||||
var USER_SHAPES = ["circle", "cookie4", "cookie7", "cookie12", "clover4", "clover8", "sunny", "pentagon", "arch", "slanted", "gem", "square"];
|
||||
|
||||
function isSliderWidget(id) {
|
||||
return id === "volumeSlider" || id === "brightnessSlider" || id === "inputVolumeSlider";
|
||||
}
|
||||
|
||||
function isPinned(id) {
|
||||
return PINNED_IDS.includes(id);
|
||||
function isShown(widget) {
|
||||
switch (widget?.id) {
|
||||
case "battery":
|
||||
return Services.BatteryService.batteryAvailable || Services.PowerProfileWatcher.available;
|
||||
case "diskUsage":
|
||||
return Services.DgopService.dgopAvailable;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function isUnplaced(widget) {
|
||||
return !Number.isFinite(widget?.col) || !Number.isFinite(widget?.row);
|
||||
}
|
||||
|
||||
function hasOptions(id) {
|
||||
return OPTION_IDS.includes(id) || String(id ?? "").startsWith("plugin_");
|
||||
}
|
||||
|
||||
function quickActions(widgetData) {
|
||||
const saved = Array.isArray(widgetData?.actions) ? widgetData.actions : [];
|
||||
const out = saved.filter(action => QUICK_ACTION_IDS.includes(action?.id)).map(action => ({
|
||||
"id": action.id,
|
||||
"enabled": action.id === "edit" || action.enabled !== false
|
||||
}));
|
||||
for (const id of QUICK_ACTION_IDS) {
|
||||
if (!out.some(action => action.id === id))
|
||||
out.push({
|
||||
"id": id,
|
||||
"enabled": true
|
||||
});
|
||||
}
|
||||
return out;
|
||||
function filterWidgets(widgets, query) {
|
||||
const needle = query.trim().toLowerCase();
|
||||
if (!needle)
|
||||
return widgets;
|
||||
return widgets.filter(widget => [widget.text, widget.description, widget.id].some(value => (value || "").toLowerCase().includes(needle)));
|
||||
}
|
||||
|
||||
function enabledQuickActions(widgetData) {
|
||||
return quickActions(widgetData).filter(action => action.enabled);
|
||||
}
|
||||
|
||||
function quickActionIcon(id) {
|
||||
return QUICK_ACTIONS.find(action => action.id === id)?.icon ?? "";
|
||||
}
|
||||
|
||||
function quickActionLabel(id) {
|
||||
return QUICK_ACTIONS.find(action => action.id === id)?.label ?? "";
|
||||
function nextUserShape(shape) {
|
||||
return USER_SHAPES[(USER_SHAPES.indexOf(shape) + 1) % USER_SHAPES.length];
|
||||
}
|
||||
|
||||
function defaultWidget(id, columns) {
|
||||
@@ -77,10 +50,6 @@ function defaultWidget(id, columns) {
|
||||
}, columns));
|
||||
}
|
||||
|
||||
function pinnedWidgets(columns) {
|
||||
return PINNED_IDS.map(id => defaultWidget(id, columns));
|
||||
}
|
||||
|
||||
function sizeSpec(widget, columns, rows = Infinity) {
|
||||
const spec = {
|
||||
"w": 4,
|
||||
@@ -91,18 +60,11 @@ function sizeSpec(widget, columns, rows = Infinity) {
|
||||
"maxH": rows,
|
||||
"step": ControlCenter.CcMetrics.gridStep
|
||||
};
|
||||
switch (widget?.id) {
|
||||
case "userCard":
|
||||
spec.w = Math.max(1, (Number.isFinite(columns) ? columns : ControlCenter.CcMetrics.defaultColumns) - ControlCenter.CcMetrics.actionSpan(2));
|
||||
spec.h = ControlCenter.CcMetrics.actionSpan(2);
|
||||
return spec;
|
||||
case "quickActions":
|
||||
spec.w = ControlCenter.CcMetrics.actionSpan(2);
|
||||
spec.h = ControlCenter.CcMetrics.actionSpan(2);
|
||||
return spec;
|
||||
default:
|
||||
return spec;
|
||||
}
|
||||
if (widget?.id === "user")
|
||||
spec.w = (Number.isFinite(columns) ? columns : ControlCenter.CcMetrics.defaultColumns) - ACTION_IDS.length;
|
||||
if (ACTION_IDS.includes(widget?.id))
|
||||
spec.w = 1;
|
||||
return spec;
|
||||
}
|
||||
|
||||
function clampSize(widget, columns, rows = Infinity) {
|
||||
@@ -111,18 +73,6 @@ function clampSize(widget, columns, rows = Infinity) {
|
||||
"w": GridLayout.dimension(widget.w, spec.minW, spec.maxW, spec.w, spec.step),
|
||||
"h": GridLayout.dimension(widget.h, spec.minH, spec.maxH, spec.h, spec.step)
|
||||
};
|
||||
if (widget.id === "quickActions") {
|
||||
const count = enabledQuickActions(widget).length;
|
||||
const capacity = ControlCenter.CcMetrics.actionCapacity(size.w);
|
||||
const neededRows = ControlCenter.CcMetrics.actionSpan(Math.ceil(count / capacity));
|
||||
if (neededRows <= rows) {
|
||||
size.h = Math.max(size.h, neededRows);
|
||||
return size;
|
||||
}
|
||||
size.h = rows;
|
||||
size.w = Math.min(columns, Math.max(size.w, ControlCenter.CcMetrics.actionSpan(Math.ceil(count / ControlCenter.CcMetrics.actionCapacity(rows)))));
|
||||
return size;
|
||||
}
|
||||
if (!isSliderWidget(widget.id) || size.w >= 2 || size.h >= 2)
|
||||
return size;
|
||||
if (rows > 1)
|
||||
@@ -161,21 +111,30 @@ function generateUniqueId() {
|
||||
|
||||
function removeWidget(index) {
|
||||
const widgets = Common.SettingsData.controlCenterWidgets.slice();
|
||||
if (index < 0 || index >= widgets.length || isPinned(widgets[index]?.id))
|
||||
if (index < 0 || index >= widgets.length)
|
||||
return;
|
||||
widgets.splice(index, 1);
|
||||
Common.SettingsData.set("controlCenterWidgets", widgets);
|
||||
}
|
||||
|
||||
function setOption(index, key, value) {
|
||||
const widgets = Common.SettingsData.controlCenterWidgets.slice();
|
||||
if (index < 0 || index >= widgets.length)
|
||||
return;
|
||||
widgets[index] = Object.assign({}, widgets[index], {
|
||||
[key]: value
|
||||
});
|
||||
Common.SettingsData.set("controlCenterWidgets", widgets);
|
||||
}
|
||||
|
||||
function setLayout(widgets) {
|
||||
Common.SettingsData.set("controlCenterWidgets", widgets);
|
||||
}
|
||||
|
||||
function resetToDefault(columns) {
|
||||
const ids = ["userCard", "quickActions", "volumeSlider", "brightnessSlider", "wifi", "bluetooth", "audioOutput", "audioInput", "nightMode", "darkMode"];
|
||||
Common.SettingsData.set("controlCenterWidgets", ids.map(id => defaultWidget(id, columns)));
|
||||
function resetToDefault() {
|
||||
Common.SettingsData.resetToDefault(["controlCenterWidgets", "controlCenterColumns"]);
|
||||
}
|
||||
|
||||
function clearAll(columns) {
|
||||
Common.SettingsData.set("controlCenterWidgets", pinnedWidgets(columns));
|
||||
function clearAll() {
|
||||
Common.SettingsData.set("controlCenterWidgets", []);
|
||||
}
|
||||
|
||||
@@ -74,20 +74,7 @@ BasePill {
|
||||
readonly property bool _currentWorkspace: SettingsData.widgetOption("runningApps", widgetData, "runningAppsCurrentWorkspace")
|
||||
readonly property bool _currentMonitor: SettingsData.widgetOption("runningApps", widgetData, "runningAppsCurrentMonitor")
|
||||
readonly property bool _groupByApp: SettingsData.widgetOption("runningApps", widgetData, "runningAppsGroupByApp")
|
||||
readonly property string windowModelKey: {
|
||||
if (_groupByApp)
|
||||
return "appId";
|
||||
switch (CompositorService.compositor) {
|
||||
case "aqueous":
|
||||
return AqueousService.available ? "aqueousKey" : "address";
|
||||
case "niri":
|
||||
return "niriWindowId";
|
||||
case "mango":
|
||||
return "mangoWindowId";
|
||||
default:
|
||||
return "address";
|
||||
}
|
||||
}
|
||||
readonly property string windowModelKey: _groupByApp ? "appId" : CompositorService.toplevelKey
|
||||
|
||||
readonly property var sortedToplevels: {
|
||||
_toplevelsUpdateTrigger;
|
||||
|
||||
@@ -790,6 +790,57 @@ Singleton {
|
||||
return toplevels;
|
||||
}
|
||||
|
||||
readonly property string toplevelKey: {
|
||||
switch (compositor) {
|
||||
case "aqueous":
|
||||
return AqueousService.available ? "aqueousKey" : "address";
|
||||
case "niri":
|
||||
return "niriWindowId";
|
||||
case "mango":
|
||||
return "mangoWindowId";
|
||||
default:
|
||||
return "address";
|
||||
}
|
||||
}
|
||||
|
||||
function toplevelPlacement(toplevel) {
|
||||
if (!toplevel)
|
||||
return {
|
||||
"workspace": "",
|
||||
"output": ""
|
||||
};
|
||||
if (isAqueous && AqueousService.available) {
|
||||
const workspace = AqueousService.workspaces.find(w => w.id === toplevel.aqueousWorkspaceId);
|
||||
return {
|
||||
"workspace": String(workspace?.name || workspace?.number || ""),
|
||||
"output": AqueousService.outputs.find(o => o.id === toplevel.aqueousOutputId)?.name ?? ""
|
||||
};
|
||||
}
|
||||
if (useNiriSorting) {
|
||||
const workspace = NiriService.allWorkspaces.find(w => w.id === toplevel.niriWorkspaceId);
|
||||
return {
|
||||
"workspace": String(workspace?.name || workspace?.idx || ""),
|
||||
"output": workspace?.output ?? ""
|
||||
};
|
||||
}
|
||||
if (useMangoSorting)
|
||||
return {
|
||||
"workspace": (toplevel.mangoTags || []).join(", "),
|
||||
"output": toplevel.mangoMonitor ?? ""
|
||||
};
|
||||
if (isHyprland) {
|
||||
const workspace = WindowModel.hyprlandToplevelFor(Array.from(Hyprland.toplevels?.values || []), toplevel)?.workspace;
|
||||
return {
|
||||
"workspace": workspace?.name ?? "",
|
||||
"output": workspace?.monitor?.name ?? ""
|
||||
};
|
||||
}
|
||||
return {
|
||||
"workspace": "",
|
||||
"output": toplevel.screens?.[0]?.name ?? ""
|
||||
};
|
||||
}
|
||||
|
||||
function fullscreenToplevelOnScreen(screenOrName) {
|
||||
const screenName = _screenName(screenOrName);
|
||||
if (isAqueous && AqueousService.available)
|
||||
|
||||
@@ -185,7 +185,7 @@ ShellRoot {
|
||||
h: 12
|
||||
},
|
||||
{
|
||||
id: "diskUsage",
|
||||
id: "colorPicker",
|
||||
w: 2,
|
||||
h: 2
|
||||
}
|
||||
@@ -295,17 +295,6 @@ ShellRoot {
|
||||
verticalSlider.destroy();
|
||||
wait(0);
|
||||
|
||||
const stripSlider = sliderComponent.createObject(scene);
|
||||
let iconClicks = 0;
|
||||
stripSlider.iconClicked.connect(() => iconClicks++);
|
||||
size(stripSlider, 4, 1);
|
||||
settle();
|
||||
check(stripSlider.slider.mapToItem(stripSlider, 0, 0).x === 0 && stripSlider.slider.width === stripSlider.width, "strip slider track spans the row");
|
||||
mouseClick(stripSlider.slider, stripSlider.slider.width - Theme.spacingXS - CcMetrics.stripIconSize / 2, stripSlider.slider.height / 2);
|
||||
check(iconClicks === 1 && stripSlider.slider.value === 50, "strip slider inset icon takes the click without moving the value");
|
||||
stripSlider.destroy();
|
||||
wait(0);
|
||||
|
||||
SessionData.recentColors = Array.from({
|
||||
length: 18
|
||||
}, (_, i) => Qt.rgba(i / 18, 0.5, 0.5, 1).toString());
|
||||
|
||||
@@ -26,30 +26,7 @@ test("sparse settings migrate without inventing grid keys", () => {
|
||||
assert.equal(sparse.controlCenterWidgets, undefined);
|
||||
});
|
||||
|
||||
test("split header tiles collapse into one pinned header ahead of the layout", () => {
|
||||
const migrated = store.migrateToVersion({ configVersion: 32, controlCenterWidgets: [{ id: "userCard", w: 4, h: 1, hostname: false }, { id: "wifi", w: 4, h: 1, col: 0, row: 0 }, { id: "quickActions", w: 4, h: 1, powerAccent: true, actions: [{ id: "power", enabled: true }] }] }, 33);
|
||||
assert.deepEqual(JSON.parse(JSON.stringify(migrated.controlCenterWidgets)), [{ id: "header", enabled: true, w: 8, h: 1, hostname: false, actions: [{ id: "power", enabled: true }], powerAccent: true }, { id: "wifi", w: 4, h: 1, col: 0, row: 0 }]);
|
||||
assert.equal(migrated.configVersion, 33);
|
||||
assert.deepEqual(JSON.parse(JSON.stringify(store.migrateToVersion({ configVersion: 31, controlCenterWidgets: [{ id: "wifi", w: 4, h: 1 }] }, 33).controlCenterWidgets.map(w => w.id))), ["header", "wifi"]);
|
||||
assert.equal(store.migrateToVersion({ configVersion: 31 }, 33).controlCenterWidgets, undefined);
|
||||
});
|
||||
|
||||
test("header splits into independently placed user and action widgets with their options", () => {
|
||||
const wifi = { id: "wifi", w: 4, h: 1, col: 0, row: 0 };
|
||||
const actions = [{ id: "settings", enabled: false }, { id: "edit", enabled: true }];
|
||||
const migrated = store.migrateToVersion({ configVersion: 33, controlCenterColumns: 10, controlCenterWidgets: [wifi, { id: "header", w: 8, h: 2, col: 1, row: 3, hostname: false, compositor: false, uptime: false, badge: false, background: false, powerAccent: true, actions }] }, 34);
|
||||
assert.deepEqual(JSON.parse(JSON.stringify(migrated.controlCenterWidgets)), [wifi,
|
||||
{ id: "userCard", enabled: true, w: 6.5, h: 2, hostname: false, compositor: false, uptime: false, badge: false, background: false, col: 1, row: 3 },
|
||||
{ id: "quickActions", enabled: true, w: 1.5, h: 1.5, actions, powerAccent: true, col: 7.5, row: 3 }
|
||||
]);
|
||||
assert.equal(migrated.configVersion, 34);
|
||||
assert.equal(store.migrateToVersion(migrated, 34), null);
|
||||
});
|
||||
|
||||
test("header migration respects hidden identity and retains an edit entry point", () => {
|
||||
const hidden = store.migrateToVersion({ configVersion: 33, controlCenterWidgets: [{ id: "header", showUser: false, col: 2, row: 1 }] }, 34);
|
||||
assert.deepEqual(JSON.parse(JSON.stringify(hidden.controlCenterWidgets)), [{ id: "quickActions", enabled: true, w: 1.5, h: 1.5, col: 2, row: 1 }]);
|
||||
const absent = store.migrateToVersion({ configVersion: 33, controlCenterWidgets: [] }, 34);
|
||||
assert.equal(absent.controlCenterWidgets[0].id, "quickActions");
|
||||
assert.equal(store.migrateToVersion({ configVersion: 33 }, 34).controlCenterWidgets, undefined);
|
||||
test("header entries become a user row and lock, power and settings tiles", () => {
|
||||
const released = store.migrateToVersion({ configVersion: 18, controlCenterWidgets: [{ id: "wifi", width: 50 }] }, 35);
|
||||
assert.deepEqual(JSON.parse(JSON.stringify(released.controlCenterWidgets.map(w => [w.id, w.w, w.h]))), [["user", 5, 1], ["settings", 1, 1], ["lock", 1, 1], ["power", 1, 1], ["wifi", 4, 1]]);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user