1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-06-16 16:15:23 -04:00

popout: fix opening popouts across monitors

cc/brightness: fix delegate bindings and pinning
This commit is contained in:
bbedward
2026-05-19 11:23:03 -04:00
parent 4845299cc2
commit cdc1102092
6 changed files with 249 additions and 134 deletions
@@ -59,7 +59,9 @@ Item {
ignoreUnknownSignals: true ignoreUnknownSignals: true
function onDeviceNameChanged(newDeviceName) { function onDeviceNameChanged(newDeviceName) {
if (root.expandedWidgetData && root.expandedWidgetData.id === "brightnessSlider") { if (!root.expandedWidgetData || root.expandedWidgetData.id !== "brightnessSlider") {
return;
}
const widgets = SettingsData.controlCenterWidgets || []; const widgets = SettingsData.controlCenterWidgets || [];
const newWidgets = widgets.map(w => { const newWidgets = widgets.map(w => {
if (w.id === "brightnessSlider" && w.instanceId === root.expandedWidgetData.instanceId) { if (w.id === "brightnessSlider" && w.instanceId === root.expandedWidgetData.instanceId) {
@@ -70,10 +72,6 @@ Item {
return w; return w;
}); });
SettingsData.set("controlCenterWidgets", newWidgets); SettingsData.set("controlCenterWidgets", newWidgets);
if (root.collapseCallback) {
root.collapseCallback();
}
}
} }
} }
@@ -23,79 +23,103 @@ Rectangle {
if (!screenName) if (!screenName)
return ""; return "";
const screen = Quickshell.screens.find(s => s.name === screenName); const screen = Quickshell.screens.find(s => s.name === screenName);
if (screen) { if (screen)
return SettingsData.getScreenDisplayName(screen); return SettingsData.getScreenDisplayName(screen);
} if (SettingsData.displayNameMode === "model" && screenModel && screenModel.length > 0)
if (SettingsData.displayNameMode === "model" && screenModel && screenModel.length > 0) {
return screenModel; return screenModel;
}
return screenName; return screenName;
} }
function resolveDeviceName() { function resolveCurrentDevice() {
if (!DisplayService.brightnessAvailable || !DisplayService.devices || DisplayService.devices.length === 0) { const devices = DisplayService.devices || [];
if (!DisplayService.brightnessAvailable || devices.length === 0)
return ""; return "";
}
const pinKey = getScreenPinKey(); const pinKey = getScreenPinKey();
if (pinKey.length > 0) { if (pinKey.length > 0) {
const pins = SettingsData.brightnessDevicePins || {}; const pins = SettingsData.brightnessDevicePins || {};
const pinnedDevice = pins[pinKey]; const pinnedDevice = pins[pinKey];
if (pinnedDevice && pinnedDevice.length > 0) { if (pinnedDevice && pinnedDevice.length > 0) {
const found = DisplayService.devices.find(dev => dev.name === pinnedDevice); const found = devices.find(d => d.name === pinnedDevice);
if (found) if (found)
return found.name; return found.name;
} }
} }
if (instanceId) {
const widgets = SettingsData.controlCenterWidgets || [];
const widget = widgets.find(w => w.id === "brightnessSlider" && w.instanceId === instanceId);
if (widget && typeof widget.deviceName === "string" && widget.deviceName.length > 0) {
const found = devices.find(d => d.name === widget.deviceName);
if (found)
return found.name;
}
}
if (DisplayService.currentDevice) {
const found = devices.find(d => d.name === DisplayService.currentDevice);
if (found)
return found.name;
}
if (initialDeviceName && initialDeviceName.length > 0) { if (initialDeviceName && initialDeviceName.length > 0) {
const found = DisplayService.devices.find(dev => dev.name === initialDeviceName); const found = devices.find(d => d.name === initialDeviceName);
if (found) if (found)
return found.name; return found.name;
} }
const currentDeviceNameFromService = DisplayService.currentDevice; const backlight = devices.find(d => d.class === "backlight");
if (currentDeviceNameFromService) {
const found = DisplayService.devices.find(dev => dev.name === currentDeviceNameFromService);
if (found)
return found.name;
}
const backlight = DisplayService.devices.find(d => d.class === "backlight");
if (backlight) if (backlight)
return backlight.name; return backlight.name;
const ddc = DisplayService.devices.find(d => d.class === "ddc"); const ddc = devices.find(d => d.class === "ddc");
if (ddc) if (ddc)
return ddc.name; return ddc.name;
return DisplayService.devices.length > 0 ? DisplayService.devices[0].name : ""; return devices[0].name;
}
function selectDevice(deviceName) {
if (!deviceName || deviceName === root.currentDeviceName) {
return;
}
const pinKey = getScreenPinKey();
if (pinKey.length > 0) {
const pins = SettingsData.brightnessDevicePins || {};
const existing = pins[pinKey];
if (existing && existing !== deviceName) {
const next = JSON.parse(JSON.stringify(pins));
delete next[pinKey];
SettingsData.set("brightnessDevicePins", next);
}
}
root.currentDeviceName = deviceName;
DisplayService.setCurrentDevice(deviceName, true);
Qt.callLater(() => root.deviceNameChanged(deviceName));
} }
Component.onCompleted: { Component.onCompleted: {
currentDeviceName = resolveDeviceName(); root.currentDeviceName = resolveCurrentDevice();
} }
property bool isPinnedToScreen: { function isDevicePinnedToScreen(deviceName) {
const pinKey = getScreenPinKey(); const pinKey = getScreenPinKey();
if (!pinKey || pinKey.length === 0) if (!pinKey || !deviceName)
return false; return false;
const pins = SettingsData.brightnessDevicePins || {}; const pins = SettingsData.brightnessDevicePins || {};
return pins[pinKey] === currentDeviceName; return pins[pinKey] === deviceName;
} }
function togglePinToScreen() { function togglePinForDevice(deviceName) {
const pinKey = getScreenPinKey(); const pinKey = getScreenPinKey();
if (!pinKey || pinKey.length === 0 || !currentDeviceName || currentDeviceName.length === 0) if (!pinKey || !deviceName)
return; return;
const pins = JSON.parse(JSON.stringify(SettingsData.brightnessDevicePins || {})); const pins = JSON.parse(JSON.stringify(SettingsData.brightnessDevicePins || {}));
if (pins[pinKey] === deviceName) {
if (isPinnedToScreen) {
delete pins[pinKey]; delete pins[pinKey];
} else { } else {
pins[pinKey] = currentDeviceName; pins[pinKey] = deviceName;
} }
SettingsData.set("brightnessDevicePins", pins); SettingsData.set("brightnessDevicePins", pins);
} }
@@ -153,18 +177,23 @@ Rectangle {
} }
Rectangle { Rectangle {
id: monitorHeader
width: parent.width width: parent.width
height: 40 height: 40
visible: screenName && screenName.length > 0 && DisplayService.devices && DisplayService.devices.length > 1 visible: screenName && screenName.length > 0 && DisplayService.devices && DisplayService.devices.length > 1
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency) color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
property bool currentDevicePinned: root.isDevicePinnedToScreen(currentDeviceName)
Item { Item {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingM anchors.margins: Theme.spacingM
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.right: globalPinButton.left
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM spacing: Theme.spacingM
@@ -180,47 +209,51 @@ Rectangle {
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight
width: parent.width - Theme.iconSize - Theme.spacingM
} }
} }
Rectangle { Rectangle {
id: globalPinButton
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: pinRow.width + Theme.spacingS * 2 width: globalPinRow.width + Theme.spacingS * 2
height: 28 height: 28
radius: height / 2 radius: height / 2
color: isPinnedToScreen ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceText, 0.05) color: monitorHeader.currentDevicePinned ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : Theme.withAlpha(Theme.surfaceText, 0.05)
Row { Row {
id: pinRow id: globalPinRow
anchors.centerIn: parent anchors.centerIn: parent
spacing: 4 spacing: 4
DankIcon { DankIcon {
name: isPinnedToScreen ? "push_pin" : "push_pin" name: "push_pin"
size: 16 size: 16
color: isPinnedToScreen ? Theme.primary : Theme.surfaceText color: monitorHeader.currentDevicePinned ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
StyledText { StyledText {
text: isPinnedToScreen ? I18n.tr("Pinned") : I18n.tr("Pin") text: monitorHeader.currentDevicePinned ? I18n.tr("Pinned") : I18n.tr("Pin")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: isPinnedToScreen ? Theme.primary : Theme.surfaceText color: monitorHeader.currentDevicePinned ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
DankRipple { DankRipple {
id: pinRipple id: globalPinRipple
cornerRadius: parent.radius cornerRadius: parent.radius
} }
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => pinRipple.trigger(mouse.x, mouse.y) enabled: currentDeviceName && currentDeviceName.length > 0
onClicked: root.togglePinToScreen() onPressed: mouse => globalPinRipple.trigger(mouse.x, mouse.y)
onClicked: root.togglePinForDevice(currentDeviceName)
} }
} }
} }
@@ -229,9 +262,17 @@ Rectangle {
Repeater { Repeater {
model: DisplayService.devices || [] model: DisplayService.devices || []
delegate: Rectangle { delegate: Rectangle {
id: deviceCard
required property var modelData required property var modelData
required property int index required property int index
readonly property bool selected: !!(modelData && modelData.name === root.currentDeviceName)
readonly property bool devicePinnedHere: {
SettingsData.brightnessDevicePins;
return root.isDevicePinnedToScreen(modelData ? modelData.name : "");
}
property real deviceBrightness: { property real deviceBrightness: {
DisplayService.brightnessVersion; DisplayService.brightnessVersion;
return DisplayService.getDeviceBrightness(modelData.name); return DisplayService.getDeviceBrightness(modelData.name);
@@ -241,8 +282,8 @@ Rectangle {
height: 100 height: 100
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency) color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
border.color: modelData.name === currentDeviceName ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) border.color: selected ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: modelData.name === currentDeviceName ? 2 : 0 border.width: selected ? 2 : 0
Column { Column {
anchors.fill: parent anchors.fill: parent
@@ -251,10 +292,12 @@ Rectangle {
Item { Item {
width: parent.width width: parent.width
height: Math.max(deviceIconColumn.height, deviceInfoColumn.height, exponentControls.height) height: Math.max(deviceIconColumn.height, deviceInfoColumn.height, rightControls.height)
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.right: rightControls.left
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM spacing: Theme.spacingM
@@ -281,7 +324,7 @@ Rectangle {
} }
} }
size: Theme.iconSize size: Theme.iconSize
color: modelData.name === currentDeviceName ? Theme.primary : Theme.surfaceText color: deviceCard.selected ? Theme.primary : Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
@@ -296,7 +339,7 @@ Rectangle {
Column { Column {
id: deviceInfoColumn id: deviceInfoColumn
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: parent.parent.width - deviceIconColumn.width - exponentControls.width - Theme.spacingM * 3 width: parent.width - deviceIconColumn.width - Theme.spacingM
StyledText { StyledText {
text: { text: {
@@ -309,7 +352,7 @@ Rectangle {
} }
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: modelData.name === currentDeviceName ? Font.Medium : Font.Normal font.weight: deviceCard.selected ? Font.Medium : Font.Normal
elide: Text.ElideRight elide: Text.ElideRight
width: parent.width width: parent.width
horizontalAlignment: Text.AlignLeft horizontalAlignment: Text.AlignLeft
@@ -345,14 +388,18 @@ Rectangle {
} }
Row { Row {
id: exponentControls id: rightControls
width: 140
height: 28 height: 28
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
z: 1
Row {
id: exponentControls
height: 28
spacing: Theme.spacingXS spacing: Theme.spacingXS
visible: SessionData.getBrightnessExponential(modelData.name) visible: SessionData.getBrightnessExponential(modelData.name)
z: 1
StyledRect { StyledRect {
width: 28 width: 28
@@ -422,6 +469,29 @@ Rectangle {
} }
} }
} }
StyledRect {
id: pinButton
width: 28
height: 28
radius: Theme.cornerRadius
visible: root.screenName && root.screenName.length > 0 && DisplayService.devices && DisplayService.devices.length > 1
color: devicePinnedHere ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
DankIcon {
anchors.centerIn: parent
name: "push_pin"
size: 14
color: devicePinnedHere ? Theme.primary : Theme.surfaceText
}
StateLayer {
stateColor: Theme.primary
cornerRadius: parent.radius
onClicked: root.togglePinForDevice(modelData.name)
}
}
}
} }
Rectangle { Rectangle {
@@ -474,22 +544,11 @@ Rectangle {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
anchors.bottomMargin: 28 anchors.bottomMargin: 28
anchors.rightMargin: SessionData.getBrightnessExponential(modelData.name) ? 145 : 0 anchors.rightMargin: rightControls.width + Theme.spacingS
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => deviceRipple.trigger(mouse.x, mouse.y) onPressed: mouse => deviceRipple.trigger(mouse.x, mouse.y)
onClicked: { onClicked: root.selectDevice(modelData.name)
const pinKey = root.getScreenPinKey();
if (pinKey.length > 0 && modelData.name !== currentDeviceName) {
const pins = JSON.parse(JSON.stringify(SettingsData.brightnessDevicePins || {}));
if (pins[pinKey]) {
delete pins[pinKey];
SettingsData.set("brightnessDevicePins", pins);
}
}
currentDeviceName = modelData.name;
deviceNameChanged(modelData.name);
}
} }
} }
} }
@@ -1,4 +1,5 @@
import QtQuick import QtQuick
import Quickshell
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
@@ -31,8 +32,10 @@ Row {
} }
if (screenName && screenName.length > 0) { if (screenName && screenName.length > 0) {
const screen = Quickshell.screens.find(s => s.name === screenName);
const pinKey = screen ? SettingsData.getScreenDisplayName(screen) : screenName;
const pins = SettingsData.brightnessDevicePins || {}; const pins = SettingsData.brightnessDevicePins || {};
const pinnedDevice = pins[screenName]; const pinnedDevice = pins[pinKey];
if (pinnedDevice && pinnedDevice.length > 0) { if (pinnedDevice && pinnedDevice.length > 0) {
const found = DisplayService.devices.find(dev => dev.name === pinnedDevice); const found = DisplayService.devices.find(dev => dev.name === pinnedDevice);
if (found) { if (found) {
+24 -4
View File
@@ -357,13 +357,18 @@ Item {
animationsEnabled = false; animationsEnabled = false;
_primeContent = true; _primeContent = true;
if (_lastOpenedScreen !== null && _lastOpenedScreen !== screen) { const screenChanged = _lastOpenedScreen !== null && _lastOpenedScreen !== screen;
if (screenChanged) {
// Hide on this tick so Qt actually tears down the wl_surface; the show
// gets deferred below so the unmap is processed before the remap.
contentWindow.visible = false; contentWindow.visible = false;
} }
_lastOpenedScreen = screen; _lastOpenedScreen = screen;
if (contentContainer) { if (contentContainer) {
// animationsEnabled is false here, so this snaps to closed without animating. // Snap morph closed only on a fresh open; on screen-change re-open we stay at 1
// because shouldBeVisible doesn't change and won't drive morph back to 1.
if (!shouldBeVisible)
morph.openProgress = 0; morph.openProgress = 0;
_captureChromeAnimTravel(); _captureChromeAnimTravel();
} }
@@ -375,12 +380,25 @@ Item {
_chromeClaimId = ""; _chromeClaimId = "";
} }
if (screenChanged) {
// Defer the show one event-loop tick. Qt coalesces a synchronous
// false→true visibility flip into a no-op, leaving WindowBlur committed
// to the previous screen's wl_surface. Splitting the flip across ticks
// forces a real surface destroy+create so BackgroundEffect.surfaceCreated
// fires and the blur region republishes on the new surface.
Qt.callLater(() => {
if (!root.shouldBeVisible)
return;
contentWindow.visible = true; contentWindow.visible = true;
popoutBlur.kick();
});
} else {
contentWindow.visible = true;
}
animationsEnabled = true; animationsEnabled = true;
shouldBeVisible = true; shouldBeVisible = true;
if (shouldBeVisible && screen) { if (shouldBeVisible && screen) {
contentWindow.visible = true;
PopoutManager.showPopout(popoutHandle); PopoutManager.showPopout(popoutHandle);
opened(); opened();
} }
@@ -1081,7 +1099,9 @@ Item {
Connections { Connections {
target: contentWindow target: contentWindow
function onVisibleChanged() { function onVisibleChanged() {
if (!contentWindow.visible) // open() flips contentWindow.visible to rebind the layer surface to
// a new screen; don't deactivate the wrapper while still open.
if (!contentWindow.visible && !root.shouldBeVisible)
contentWrapper._renderActive = false; contentWrapper._renderActive = false;
} }
} }
+28 -4
View File
@@ -287,21 +287,43 @@ Item {
_frozenMaskWidth = maskWidth; _frozenMaskWidth = maskWidth;
_frozenMaskHeight = maskHeight; _frozenMaskHeight = maskHeight;
if (_lastOpenedScreen !== null && _lastOpenedScreen !== screen) { const screenChanged = _lastOpenedScreen !== null && _lastOpenedScreen !== screen;
if (screenChanged) {
// Hide on this tick so Qt actually tears down the wl_surface; the show
// gets deferred below so the unmap is processed before the remap.
contentWindow.visible = false; contentWindow.visible = false;
backgroundWindow.visible = false; backgroundWindow.visible = false;
} }
_lastOpenedScreen = screen; _lastOpenedScreen = screen;
if (contentContainer) { if (contentContainer && !shouldBeVisible) {
// animationsEnabled is false here, so this snaps to closed without animating. // Snap morph closed only on a fresh open; on screen-change re-open we stay at 1
// because shouldBeVisible doesn't change and won't drive morph back to 1.
morph.openProgress = 0; morph.openProgress = 0;
} }
_setSurfaceGeometry(alignedX, alignedY, alignedWidth, alignedHeight); _setSurfaceGeometry(alignedX, alignedY, alignedWidth, alignedHeight);
if (screenChanged) {
// Defer the show one event-loop tick. Qt coalesces a synchronous
// false→true visibility flip into a no-op, leaving WindowBlur committed
// to the previous screen's wl_surface. Splitting the flip across ticks
// forces a real surface destroy+create so BackgroundEffect.surfaceCreated
// fires and the blur region republishes on the new surface.
Qt.callLater(() => {
if (!root.shouldBeVisible)
return;
if (root.backgroundWindowRequired)
backgroundWindow.visible = true;
contentWindow.visible = true;
popoutBlur.kick();
_bgCommitWindow = true;
bgCommitSettleTimer.restart();
});
} else {
if (backgroundWindowRequired) if (backgroundWindowRequired)
backgroundWindow.visible = true; backgroundWindow.visible = true;
contentWindow.visible = true; contentWindow.visible = true;
}
animationsEnabled = true; animationsEnabled = true;
shouldBeVisible = true; shouldBeVisible = true;
@@ -840,7 +862,9 @@ Item {
Connections { Connections {
target: contentWindow target: contentWindow
function onVisibleChanged() { function onVisibleChanged() {
if (!contentWindow.visible) // open() flips contentWindow.visible to rebind the layer surface to
// a new screen; don't deactivate the wrapper while still open.
if (!contentWindow.visible && !root.shouldBeVisible)
contentWrapper._renderActive = false; contentWrapper._renderActive = false;
} }
} }
+11
View File
@@ -34,6 +34,17 @@ Item {
targetWindow.BackgroundEffect.blurRegion = _active ? blurRegion : null; targetWindow.BackgroundEffect.blurRegion = _active ? blurRegion : null;
} }
// Force BackgroundEffect to re-publish the blur region on the current wl_surface.
// Clearing first bypasses Quickshell's same-Region dedup in BackgroundEffect::setBlurRegion,
// setting pendingBlurRegion=true so the next polish actually ships the region — needed
// when the underlying surface has been remapped (e.g. PanelWindow.screen change).
function kick() {
if (!targetWindow)
return;
targetWindow.BackgroundEffect.blurRegion = null;
targetWindow.BackgroundEffect.blurRegion = _active ? blurRegion : null;
}
on_ActiveChanged: _apply() on_ActiveChanged: _apply()
onTargetWindowChanged: _apply() onTargetWindowChanged: _apply()