1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-05-15 00:32:47 -04:00

feat: add Display Profiles builtin control center widget (#2410)

* feat: add Display Profiles builtin control center widget

Adds a new built-in widget that lets users switch between their configured
display profiles directly from the Control Center, without opening Settings.

- Widget shows the active profile name and cycles profiles on pill click
- Detail panel shows all profiles as a button group for direct selection
- Integrates with existing DisplayConfigState/SettingsData singletons
- Follows the same loader/instance pattern as VPN, CUPS, and Tailscale widgets

* fix: refine display profiles widget behavior
This commit is contained in:
Divya Jain
2026-05-14 19:15:05 +05:30
committed by GitHub
parent 459ec47310
commit a33c7e0250
4 changed files with 299 additions and 0 deletions
@@ -0,0 +1,249 @@
import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.Plugins
import qs.Modules.Settings.DisplayConfig
PluginComponent {
id: root
readonly property var allProfiles: DisplayConfigState.validatedProfiles || ({})
readonly property var profiles: {
const result = [];
for (const id in allProfiles) {
if (allProfiles[id].name)
result.push({ id: id, name: allProfiles[id].name });
}
return result;
}
readonly property bool autoMode: SettingsData.displayProfileAutoSelect
readonly property string activeProfileId: SettingsData.getActiveDisplayProfile(CompositorService.compositor)
readonly property var activeProfile: allProfiles[activeProfileId] || null
readonly property string activeProfileName: activeProfile?.name ?? ""
readonly property string displayProfileLabel: {
if (autoMode)
return I18n.tr("Auto");
if (activeProfileName.length > 0)
return activeProfileName;
if (profiles.length === 0)
return I18n.tr("No profiles");
return I18n.tr("None active");
}
ccWidgetIcon: "monitor"
ccWidgetPrimaryText: I18n.tr("Display")
ccWidgetSecondaryText: displayProfileLabel
ccWidgetIsActive: autoMode || activeProfileId.length > 0
onCcWidgetToggled: cycleNext()
function setAutoMode(enabled) {
SettingsData.displayProfileAutoSelect = enabled;
if (!enabled)
SettingsData.setActiveDisplayProfile(CompositorService.compositor, "");
SettingsData.saveSettings();
if (enabled)
DisplayConfigState.applyAutoConfig();
}
function cycleNext() {
if (autoMode || profiles.length < 2)
return;
const idx = profiles.findIndex(p => p.id === activeProfileId);
const next = profiles[(idx + 1) % profiles.length];
DisplayConfigState.activateProfile(next.id);
}
ccDetailContent: Component {
Rectangle {
id: detailRoot
implicitHeight: detailColumn.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius
color: Theme.nestedSurface
border.color: Theme.outlineMedium
border.width: Theme.layerOutlineWidth
Column {
id: detailColumn
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingS
Item {
width: parent.width
height: 32
StyledText {
text: I18n.tr("Display Profiles")
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
}
Row {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
Rectangle {
id: autoButton
width: autoLabel.implicitWidth + Theme.spacingL * 2
height: 28
radius: 14
color: root.autoMode ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : (autoMouseArea.containsMouse ? Theme.surfaceLight : "transparent")
border.color: root.autoMode ? Theme.primary : Theme.outlineMedium
border.width: root.autoMode ? 1 : Theme.layerOutlineWidth
StyledText {
id: autoLabel
anchors.centerIn: parent
text: I18n.tr("Auto")
color: root.autoMode ? Theme.primary : Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
}
MouseArea {
id: autoMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.setAutoMode(!root.autoMode)
}
}
DankActionButton {
id: settingsButton
anchors.verticalCenter: parent.verticalCenter
iconName: "settings"
buttonSize: 28
iconSize: 16
iconColor: Theme.surfaceVariantText
onClicked: {
PopoutService.closeControlCenter();
PopoutService.openSettingsWithTab("displays");
}
}
}
}
StyledText {
visible: root.autoMode
width: parent.width
text: I18n.tr("Auto mode is on. Manual profile selection is disabled.")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
}
StyledText {
visible: root.profiles.length === 0
width: parent.width
text: I18n.tr("No display profiles found. Create them in Settings > Displays.")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
}
Column {
visible: root.profiles.length > 0
width: parent.width
spacing: Theme.spacingXS
opacity: root.autoMode ? 0.55 : 1.0
Repeater {
model: root.profiles
delegate: Rectangle {
required property var modelData
readonly property bool isActive: modelData.id === root.activeProfileId && !root.autoMode
width: detailColumn.width
height: 44
radius: Theme.cornerRadius
color: {
if (isActive)
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12);
if (profileMouseArea.containsMouse)
return Theme.surfaceLight;
return Theme.floatingSurface;
}
border.color: isActive ? Theme.primary : Theme.outlineMedium
border.width: isActive ? 1 : Theme.layerOutlineWidth
StyledText {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
text: modelData.name
color: Theme.surfaceText
font.pixelSize: Theme.fontSizeMedium
font.weight: isActive ? Font.Medium : Font.Normal
}
StyledText {
anchors.right: parent.right
anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
visible: isActive
text: I18n.tr("Active")
color: Theme.primary
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
}
MouseArea {
id: profileMouseArea
anchors.fill: parent
hoverEnabled: true
enabled: !root.autoMode
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: DisplayConfigState.activateProfile(modelData.id)
}
}
}
}
}
}
}
horizontalBarPill: Component {
Row {
spacing: Theme.spacingXS
DankIcon {
name: "monitor"
color: Theme.primary
size: root.iconSize
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: root.displayProfileLabel
color: Theme.surfaceText
font.pixelSize: Theme.fontSizeSmall
anchors.verticalCenter: parent.verticalCenter
}
}
}
verticalBarPill: Component {
Column {
spacing: 2
DankIcon {
name: "monitor"
color: Theme.primary
size: root.iconSize
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: root.displayProfileLabel
color: Theme.surfaceText
font.pixelSize: Theme.fontSizeSmall
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
}
@@ -135,6 +135,12 @@ Item {
}
builtinInstance = widgetModel.tailscaleBuiltinInstance;
}
if (builtinId === "builtin_display_profiles") {
if (widgetModel?.displayProfilesLoader) {
widgetModel.displayProfilesLoader.active = true;
}
builtinInstance = widgetModel.displayProfilesBuiltinInstance;
}
if (!builtinInstance || !builtinInstance.ccDetailContent) {
return;
@@ -924,6 +924,12 @@ Column {
}
builtinInstance = Qt.binding(() => root.model?.tailscaleBuiltinInstance);
}
if (id === "builtin_display_profiles") {
if (root.model?.displayProfilesLoader) {
root.model.displayProfilesLoader.active = true;
}
builtinInstance = Qt.binding(() => root.model?.displayProfilesBuiltinInstance);
}
}
sourceComponent: {
@@ -11,6 +11,7 @@ QtObject {
property var vpnBuiltinInstance: null
property var cupsBuiltinInstance: null
property var tailscaleBuiltinInstance: null
property var displayProfilesBuiltinInstance: null
property var vpnLoader: Loader {
active: false
@@ -93,6 +94,34 @@ QtObject {
}
}
property var displayProfilesLoader: Loader {
active: false
sourceComponent: Component {
DisplayProfilesWidget {}
}
onItemChanged: {
root.displayProfilesBuiltinInstance = item;
}
onActiveChanged: {
if (!active)
root.displayProfilesBuiltinInstance = null;
}
Connections {
target: SettingsData
function onControlCenterWidgetsChanged() {
const widgets = SettingsData.controlCenterWidgets || [];
const hasWidget = widgets.some(w => w.id === "builtin_display_profiles");
if (!hasWidget && displayProfilesLoader.active) {
root.log.debug("No Display Profiles widget in control center, deactivating loader");
displayProfilesLoader.active = false;
}
}
}
}
readonly property var coreWidgetDefinitions: [
{
"id": "nightMode",
@@ -242,6 +271,15 @@ QtObject {
"enabled": TailscaleService.available,
"warning": !TailscaleService.available ? I18n.tr("Tailscale not available", "Warning when Tailscale service is not running") : undefined,
"isBuiltinPlugin": true
},
{
"id": "builtin_display_profiles",
"text": I18n.tr("Display Profiles"),
"description": I18n.tr("Switch between display configurations"),
"icon": "monitor",
"type": "builtin_plugin",
"enabled": true,
"isBuiltinPlugin": true
}
]