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

Compare commits

..

23 Commits

Author SHA1 Message Date
purian23
f88cc45e0d (frameMode): New Modal & Launcher connections 2026-04-17 00:30:35 -04:00
purian23
745570bc8b (Notifications): Update body card expansions 2026-04-16 17:01:41 -04:00
purian23
62049ee242 (frame): QOL Control Center & Notification updates 2026-04-16 17:01:41 -04:00
purian23
ee4e37be33 feat(Frame): Close the gaps 2026-04-16 17:01:41 -04:00
purian23
a409f9c679 frame(Notifications): Update Arc path & Motion 2026-04-16 17:01:41 -04:00
purian23
7dab5d9201 (frame): Update animation sync w/Dank Popouts 2026-04-16 17:01:41 -04:00
purian23
480fe9810a (frame): Performance round 2026-04-16 17:01:41 -04:00
purian23
adc0da8079 (frame): Update Connected blur Arcs & Enable shadow modes 2026-04-16 17:01:41 -04:00
purian23
4fdc85d807 frame(ConnectedMode): Wire up Notifications 2026-04-16 17:01:41 -04:00
purian23
0ddd747e15 (frame): Update connected mode animation & motion logic 2026-04-16 17:01:41 -04:00
purian23
c7ddb97bf1 (frame): implement ConnectedModeState to better handle component sync 2026-04-16 17:01:41 -04:00
purian23
cc6d0bae0a (frameMode): Restore user settings when exiting frame mode
- Align blur settings in non-FrameMode motion settings
2026-04-16 17:01:41 -04:00
purian23
21059ece06 (frame): Update connected mode with blur 2026-04-16 17:01:41 -04:00
purian23
13cff1a734 (frame): Update connected mode & opacity connection settings 2026-04-16 17:01:41 -04:00
purian23
505592e1b5 (frameInMotion): Initial Unified Frame Connected Mode 2026-04-16 17:01:41 -04:00
purian23
e52ad3319e Add Directional Motion options 2026-04-16 17:01:41 -04:00
purian23
4360815e85 Initial staging for Animation & Motion effects 2026-04-16 17:01:41 -04:00
purian23
85642fa085 (frame): Add blur support & cleanup 2026-04-16 17:01:41 -04:00
purian23
168cb71d79 (frame): Multi-monitor support 2026-04-16 17:01:41 -04:00
purian23
5da8280795 Connected frames & defaults 2026-04-16 17:01:41 -04:00
purian23
6738f1707c Continue frame implementation 2026-04-16 17:01:41 -04:00
purian23
bee15566a9 Initial framework 2026-04-16 17:01:41 -04:00
bbedward
7ced91ede1 notifications: add configurable durations for do not disturb
fixes #1481
2026-04-16 16:51:05 -04:00
28 changed files with 1859 additions and 72 deletions

View File

@@ -278,4 +278,193 @@ Singleton {
notificationStates = next;
return true;
}
// DankModal / DankLauncherV2Modal State
readonly property var emptyModalState: ({
"visible": false,
"barSide": "bottom",
"bodyX": 0,
"bodyY": 0,
"bodyW": 0,
"bodyH": 0,
"animX": 0,
"animY": 0,
"omitStartConnector": false,
"omitEndConnector": false
})
property var modalStates: ({})
function _cloneModalStates() {
const next = {};
for (const screenName in modalStates)
next[screenName] = modalStates[screenName];
return next;
}
function _normalizeModalState(state) {
return {
"visible": !!(state && state.visible),
"barSide": state && state.barSide ? state.barSide : "bottom",
"bodyX": Number(state && state.bodyX !== undefined ? state.bodyX : 0),
"bodyY": Number(state && state.bodyY !== undefined ? state.bodyY : 0),
"bodyW": Number(state && state.bodyW !== undefined ? state.bodyW : 0),
"bodyH": Number(state && state.bodyH !== undefined ? state.bodyH : 0),
"animX": Number(state && state.animX !== undefined ? state.animX : 0),
"animY": Number(state && state.animY !== undefined ? state.animY : 0),
"omitStartConnector": !!(state && state.omitStartConnector),
"omitEndConnector": !!(state && state.omitEndConnector)
};
}
function _sameModalGeometry(a, b) {
if (!a || !b)
return false;
return Math.abs(Number(a.bodyX) - Number(b.bodyX)) < 0.5
&& Math.abs(Number(a.bodyY) - Number(b.bodyY)) < 0.5
&& Math.abs(Number(a.bodyW) - Number(b.bodyW)) < 0.5
&& Math.abs(Number(a.bodyH) - Number(b.bodyH)) < 0.5
&& Math.abs(Number(a.animX) - Number(b.animX)) < 0.5
&& Math.abs(Number(a.animY) - Number(b.animY)) < 0.5;
}
function _sameModalState(a, b) {
if (!a || !b)
return false;
return a.visible === b.visible
&& a.barSide === b.barSide
&& a.omitStartConnector === b.omitStartConnector
&& a.omitEndConnector === b.omitEndConnector
&& _sameModalGeometry(a, b);
}
function setModalState(screenName, state) {
if (!screenName || !state)
return false;
const normalized = _normalizeModalState(state);
if (_sameModalState(modalStates[screenName], normalized))
return true;
const next = _cloneModalStates();
next[screenName] = normalized;
modalStates = next;
return true;
}
function clearModalState(screenName) {
if (!screenName || !modalStates[screenName])
return false;
const next = _cloneModalStates();
delete next[screenName];
modalStates = next;
return true;
}
function setModalAnim(screenName, animX, animY) {
if (!screenName)
return false;
const cur = modalStates[screenName];
if (!cur)
return false;
let changed = false;
const nextAnimX = animX !== undefined ? Number(animX) : cur.animX;
const nextAnimY = animY !== undefined ? Number(animY) : cur.animY;
if (Math.abs(nextAnimX - cur.animX) >= 0.5 || Math.abs(nextAnimY - cur.animY) >= 0.5) {
const updated = {
"visible": cur.visible,
"barSide": cur.barSide,
"bodyX": cur.bodyX,
"bodyY": cur.bodyY,
"bodyW": cur.bodyW,
"bodyH": cur.bodyH,
"animX": nextAnimX,
"animY": nextAnimY,
"omitStartConnector": cur.omitStartConnector,
"omitEndConnector": cur.omitEndConnector
};
const next = _cloneModalStates();
next[screenName] = updated;
modalStates = next;
changed = true;
}
return changed;
}
function setModalBody(screenName, bodyX, bodyY, bodyW, bodyH) {
if (!screenName)
return false;
const cur = modalStates[screenName];
if (!cur)
return false;
const nx = bodyX !== undefined ? Number(bodyX) : cur.bodyX;
const ny = bodyY !== undefined ? Number(bodyY) : cur.bodyY;
const nw = bodyW !== undefined ? Number(bodyW) : cur.bodyW;
const nh = bodyH !== undefined ? Number(bodyH) : cur.bodyH;
if (Math.abs(nx - cur.bodyX) < 0.5
&& Math.abs(ny - cur.bodyY) < 0.5
&& Math.abs(nw - cur.bodyW) < 0.5
&& Math.abs(nh - cur.bodyH) < 0.5)
return false;
const updated = {
"visible": cur.visible,
"barSide": cur.barSide,
"bodyX": nx,
"bodyY": ny,
"bodyW": nw,
"bodyH": nh,
"animX": cur.animX,
"animY": cur.animY,
"omitStartConnector": cur.omitStartConnector,
"omitEndConnector": cur.omitEndConnector
};
const next = _cloneModalStates();
next[screenName] = updated;
modalStates = next;
return true;
}
// ─── Dock retract coordination ────────────────────────────────
property var dockRetractRequests: ({})
function _cloneRetractRequests() {
const next = {};
for (const k in dockRetractRequests)
next[k] = dockRetractRequests[k];
return next;
}
function requestDockRetract(requesterId, screenName, side) {
if (!requesterId || !screenName || !side)
return false;
const existing = dockRetractRequests[requesterId];
if (existing && existing.screenName === screenName && existing.side === side)
return true;
const next = _cloneRetractRequests();
next[requesterId] = { "screenName": screenName, "side": side };
dockRetractRequests = next;
return true;
}
function releaseDockRetract(requesterId) {
if (!requesterId || !dockRetractRequests[requesterId])
return false;
const next = _cloneRetractRequests();
delete next[requesterId];
dockRetractRequests = next;
return true;
}
function dockRetractActiveForSide(screenName, side) {
if (!screenName || !side)
return false;
for (const k in dockRetractRequests) {
const r = dockRetractRequests[k];
if (r && r.screenName === screenName && r.side === side)
return true;
}
return false;
}
}

View File

@@ -29,9 +29,33 @@ Singleton {
property bool isLightMode: false
property bool doNotDisturb: false
property real doNotDisturbUntil: 0
property bool isSwitchingMode: false
property bool suppressOSD: true
Timer {
id: dndExpireTimer
repeat: false
running: false
onTriggered: root.setDoNotDisturb(false)
}
function _armDndExpireTimer() {
dndExpireTimer.stop();
if (!doNotDisturb || doNotDisturbUntil <= 0)
return;
const remaining = doNotDisturbUntil - Date.now();
if (remaining <= 0) {
setDoNotDisturb(false);
return;
}
dndExpireTimer.interval = remaining;
dndExpireTimer.start();
}
onDoNotDisturbChanged: _armDndExpireTimer()
onDoNotDisturbUntilChanged: _armDndExpireTimer()
Timer {
id: osdSuppressTimer
interval: 2000
@@ -49,6 +73,7 @@ Singleton {
function onSessionResumed() {
root.suppressOSD = true;
osdSuppressTimer.restart();
root._applyDndExpirySanity();
}
}
@@ -190,6 +215,7 @@ Singleton {
}
Store.parse(root, obj);
_applyDndExpirySanity();
_loadedSessionSnapshot = getCurrentSessionJson();
_hasLoaded = true;
@@ -271,6 +297,7 @@ Singleton {
}
Store.parse(root, obj);
_applyDndExpirySanity();
_loadedSessionSnapshot = getCurrentSessionJson();
_hasLoaded = true;
@@ -288,6 +315,16 @@ Singleton {
}
}
function _applyDndExpirySanity() {
if (doNotDisturb && doNotDisturbUntil > 0 && Date.now() >= doNotDisturbUntil) {
doNotDisturb = false;
doNotDisturbUntil = 0;
} else if (!doNotDisturb && doNotDisturbUntil !== 0) {
doNotDisturbUntil = 0;
}
_armDndExpireTimer();
}
function saveSettings() {
if (isGreeterMode || _parseError || !_hasLoaded)
return;
@@ -357,8 +394,21 @@ Singleton {
});
}
function setDoNotDisturb(enabled) {
function setDoNotDisturb(enabled, durationMinutes) {
const minutes = Number(durationMinutes) || 0;
doNotDisturb = enabled;
doNotDisturbUntil = (enabled && minutes > 0) ? Date.now() + minutes * 60 * 1000 : 0;
saveSettings();
}
function setDoNotDisturbUntilTimestamp(timestampMs) {
const target = Number(timestampMs) || 0;
if (target <= Date.now()) {
setDoNotDisturb(false);
return;
}
doNotDisturb = true;
doNotDisturbUntil = target;
saveSettings();
}

View File

@@ -237,6 +237,9 @@ Singleton {
onFrameBlurEnabledChanged: saveSettings()
property bool frameCloseGaps: false
onFrameCloseGapsChanged: saveSettings()
property string frameLauncherEmergeSide: "bottom"
onFrameLauncherEmergeSideChanged: saveSettings()
readonly property string frameModalEmergeSide: frameLauncherEmergeSide === "top" ? "bottom" : "top"
property int previousDirectionalMode: 1
onPreviousDirectionalModeChanged: saveSettings()
property var connectedFrameBarStyleBackups: ({})
@@ -2206,6 +2209,12 @@ Singleton {
return edges;
}
function frameEdgeInsetForSide(screen, side) {
if (!frameEnabled || !screen) return 0;
const edges = getActiveBarEdgesForScreen(screen);
return edges.includes(side) ? frameBarSize : frameThickness;
}
function getActiveBarThicknessForScreen(screen) {
if (frameEnabled) return frameBarSize;
if (!screen) return frameThickness;

View File

@@ -3,6 +3,7 @@
var SPEC = {
isLightMode: { def: false },
doNotDisturb: { def: false },
doNotDisturbUntil: { def: 0 },
wallpaperPath: { def: "" },
perMonitorWallpaper: { def: false },

View File

@@ -553,7 +553,8 @@ var SPEC = {
frameBarSize: { def: 40 },
frameShowOnOverview: { def: false },
frameBlurEnabled: { def: true },
frameCloseGaps: { def: false }
frameCloseGaps: { def: false },
frameLauncherEmergeSide: { def: "bottom" }
};
function getValidKeys() {

View File

@@ -26,6 +26,33 @@ Item {
property bool closeOnEscapeKey: true
property bool closeOnBackgroundClick: true
property string animationType: "scale"
// Opposite side from the launcher by default; subclasses may override
property string preferredConnectedBarSide: SettingsData.frameModalEmergeSide
readonly property bool frameConnectedMode: SettingsData.frameEnabled
&& Theme.isConnectedEffect
&& !!effectiveScreen
&& SettingsData.isScreenInPreferences(effectiveScreen, SettingsData.frameScreenPreferences)
readonly property string resolvedConnectedBarSide: frameConnectedMode ? preferredConnectedBarSide : ""
readonly property bool frameOwnsConnectedChrome: frameConnectedMode && resolvedConnectedBarSide !== ""
function _dockOccupiesSide(side) {
if (!SettingsData.showDock) return false;
switch (side) {
case "top": return SettingsData.dockPosition === SettingsData.Position.Top;
case "bottom": return SettingsData.dockPosition === SettingsData.Position.Bottom;
case "left": return SettingsData.dockPosition === SettingsData.Position.Left;
case "right": return SettingsData.dockPosition === SettingsData.Position.Right;
}
return false;
}
readonly property bool _dockBlocksEmergence: frameOwnsConnectedChrome
&& _dockOccupiesSide(resolvedConnectedBarSide)
readonly property bool connectedMotionParity: Theme.isConnectedEffect
property int animationDuration: connectedMotionParity ? Theme.popoutAnimationDuration : Theme.modalAnimationDuration
property real animationScaleCollapsed: Theme.effectScaleCollapsed
@@ -66,6 +93,107 @@ Item {
property bool animationsEnabled: true
// ─── Connected chrome sync ────────────────────────────────────────────────
property string _chromeClaimId: ""
property bool _fullSyncPending: false
function _nextChromeClaimId() {
return layerNamespace + ":modal:" + (new Date()).getTime() + ":" + Math.floor(Math.random() * 1000);
}
function _currentScreenName() {
return effectiveScreen ? effectiveScreen.name : "";
}
function _publishModalChromeState() {
const screenName = _currentScreenName();
if (!screenName) return;
ConnectedModeState.setModalState(screenName, {
"visible": shouldBeVisible || contentWindow.visible,
"barSide": resolvedConnectedBarSide,
"bodyX": alignedX,
"bodyY": alignedY,
"bodyW": alignedWidth,
"bodyH": alignedHeight,
"animX": modalContainer ? modalContainer.animX : 0,
"animY": modalContainer ? modalContainer.animY : 0,
"omitStartConnector": false,
"omitEndConnector": false
});
}
function _syncModalChromeState() {
if (!frameOwnsConnectedChrome) {
_releaseModalChrome();
return;
}
if (!_chromeClaimId)
_chromeClaimId = _nextChromeClaimId();
_publishModalChromeState();
if (_dockBlocksEmergence && (shouldBeVisible || contentWindow.visible))
ConnectedModeState.requestDockRetract(_chromeClaimId, _currentScreenName(), resolvedConnectedBarSide);
else
ConnectedModeState.releaseDockRetract(_chromeClaimId);
}
function _flushFullSync() {
_fullSyncPending = false;
_syncModalChromeState();
}
function _queueFullSync() {
if (_fullSyncPending) return;
_fullSyncPending = true;
Qt.callLater(() => {
if (root && typeof root._flushFullSync === "function")
root._flushFullSync();
});
}
function _syncModalAnim() {
if (!frameOwnsConnectedChrome || !_chromeClaimId) return;
const screenName = _currentScreenName();
if (!screenName || !modalContainer) return;
ConnectedModeState.setModalAnim(screenName, modalContainer.animX, modalContainer.animY);
}
function _syncModalBody() {
if (!frameOwnsConnectedChrome || !_chromeClaimId) return;
const screenName = _currentScreenName();
if (!screenName) return;
ConnectedModeState.setModalBody(screenName, alignedX, alignedY, alignedWidth, alignedHeight);
}
function _releaseModalChrome() {
if (_chromeClaimId) {
ConnectedModeState.releaseDockRetract(_chromeClaimId);
_chromeClaimId = "";
}
const screenName = _currentScreenName();
if (screenName)
ConnectedModeState.clearModalState(screenName);
}
onFrameOwnsConnectedChromeChanged: _syncModalChromeState()
onResolvedConnectedBarSideChanged: _queueFullSync()
onShouldBeVisibleChanged: _queueFullSync()
onAlignedXChanged: _syncModalBody()
onAlignedYChanged: _syncModalBody()
onAlignedWidthChanged: _syncModalBody()
onAlignedHeightChanged: _syncModalBody()
Component.onDestruction: _releaseModalChrome()
Connections {
target: contentWindow
function onVisibleChanged() {
if (contentWindow.visible)
root._syncModalChromeState();
else
root._releaseModalChrome();
}
}
function open() {
closeTimer.stop();
animationsEnabled = false;
@@ -167,9 +295,11 @@ Item {
}
}
// shadowRenderPadding is zeroed when frame owns the chrome
// Wayland then clips any content translating past
readonly property var shadowLevel: Theme.elevationLevel3
readonly property real shadowFallbackOffset: 6
readonly property real shadowRenderPadding: (root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, Theme.elevationLightDirection, shadowFallbackOffset, 8, 16) : 0
readonly property real shadowRenderPadding: (!frameOwnsConnectedChrome && root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, Theme.elevationLightDirection, shadowFallbackOffset, 8, 16) : 0
readonly property real shadowMotionPadding: {
if (Theme.isConnectedEffect)
return 0;
@@ -187,7 +317,47 @@ Item {
readonly property real alignedWidth: Theme.px(modalWidth, dpr)
readonly property real alignedHeight: Theme.px(modalHeight, dpr)
readonly property real alignedX: Theme.snap((() => {
function _frameEdgeInset(side) {
if (!effectiveScreen) return 0;
return SettingsData.frameEdgeInsetForSide(effectiveScreen, side);
}
// frameEdgeInsetForSide is the full inset; do not add frameBarSize
readonly property real _connectedAlignedX: {
switch (resolvedConnectedBarSide) {
case "top":
case "bottom": {
const insetL = _frameEdgeInset("left");
const insetR = _frameEdgeInset("right");
const usable = Math.max(0, screenWidth - insetL - insetR);
return insetL + Math.max(0, (usable - alignedWidth) / 2);
}
case "left":
return _frameEdgeInset("left");
case "right":
return screenWidth - alignedWidth - _frameEdgeInset("right");
}
return 0;
}
readonly property real _connectedAlignedY: {
switch (resolvedConnectedBarSide) {
case "top":
return _frameEdgeInset("top");
case "bottom":
return screenHeight - alignedHeight - _frameEdgeInset("bottom");
case "left":
case "right": {
const insetT = _frameEdgeInset("top");
const insetB = _frameEdgeInset("bottom");
const usable = Math.max(0, screenHeight - insetT - insetB);
return insetT + Math.max(0, (usable - alignedHeight) / 2);
}
}
return 0;
}
readonly property real alignedX: Theme.snap(frameOwnsConnectedChrome ? _connectedAlignedX : (() => {
switch (positioning) {
case "center":
return (screenWidth - alignedWidth) / 2;
@@ -200,7 +370,7 @@ Item {
}
})(), dpr)
readonly property real alignedY: Theme.snap((() => {
readonly property real alignedY: Theme.snap(frameOwnsConnectedChrome ? _connectedAlignedY : (() => {
switch (positioning) {
case "center":
return (screenHeight - alignedHeight) / 2;
@@ -271,12 +441,12 @@ Item {
WindowBlur {
targetWindow: contentWindow
blurEnabled: root.effectiveBlurEnabled
blurEnabled: root.effectiveBlurEnabled && !root.frameOwnsConnectedChrome
readonly property real s: Math.min(1, modalContainer.scaleValue)
blurX: modalContainer.x + modalContainer.width * (1 - s) * 0.5 + Theme.snap(modalContainer.animX, root.dpr)
blurY: modalContainer.y + modalContainer.height * (1 - s) * 0.5 + Theme.snap(modalContainer.animY, root.dpr)
blurWidth: (root.shouldBeVisible && animatedContent.opacity > 0) ? modalContainer.width * s : 0
blurHeight: (root.shouldBeVisible && animatedContent.opacity > 0) ? modalContainer.height * s : 0
blurWidth: (root.shouldBeVisible && animatedContent.opacity > 0 && !root.frameOwnsConnectedChrome) ? modalContainer.width * s : 0
blurHeight: (root.shouldBeVisible && animatedContent.opacity > 0 && !root.frameOwnsConnectedChrome) ? modalContainer.height * s : 0
blurRadius: root.effectiveCornerRadius
}
@@ -392,7 +562,17 @@ Item {
readonly property real customDistRight: root.screenWidth - customAnchorX
readonly property real customDistTop: customAnchorY
readonly property real customDistBottom: root.screenHeight - customAnchorY
// Connected emergence: travel from the resolved bar edge, matching DankPopout cadence.
readonly property real connectedEmergenceTravelX: Math.max(root.animationOffset, root.alignedWidth + Theme.spacingL)
readonly property real connectedEmergenceTravelY: Math.max(root.animationOffset, root.alignedHeight + Theme.spacingL)
readonly property real offsetX: {
if (root.frameOwnsConnectedChrome) {
switch (root.resolvedConnectedBarSide) {
case "left": return -connectedEmergenceTravelX;
case "right": return connectedEmergenceTravelX;
}
return 0;
}
if (typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2 && Theme.isDirectionalEffect)
return 0;
if (slide && !directionalEffect && !depthEffect)
@@ -428,6 +608,13 @@ Item {
return 0;
}
readonly property real offsetY: {
if (root.frameOwnsConnectedChrome) {
switch (root.resolvedConnectedBarSide) {
case "top": return -connectedEmergenceTravelY;
case "bottom": return connectedEmergenceTravelY;
}
return 0;
}
if (typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2 && Theme.isDirectionalEffect)
return 0;
if (slide && !directionalEffect && !depthEffect)
@@ -467,6 +654,9 @@ Item {
property real animX: root.shouldBeVisible ? 0 : root.frozenMotionOffsetX
property real animY: root.shouldBeVisible ? 0 : root.frozenMotionOffsetY
onAnimXChanged: if (root.frameOwnsConnectedChrome) root._syncModalAnim()
onAnimYChanged: if (root.frameOwnsConnectedChrome) root._syncModalAnim()
readonly property real computedScaleCollapsed: (typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2 && Theme.isDirectionalEffect) ? 0.0 : root.animationScaleCollapsed
property real scaleValue: root.shouldBeVisible ? 1.0 : computedScaleCollapsed
@@ -527,18 +717,18 @@ Item {
level: root.shadowLevel
fallbackOffset: root.shadowFallbackOffset
targetRadius: root.effectiveCornerRadius
targetColor: root.effectiveBackgroundColor
borderColor: root.effectiveBorderColor
borderWidth: root.effectiveBorderWidth
shadowEnabled: root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
targetColor: root.frameOwnsConnectedChrome ? "transparent" : root.effectiveBackgroundColor
borderColor: root.frameOwnsConnectedChrome ? "transparent" : root.effectiveBorderColor
borderWidth: root.frameOwnsConnectedChrome ? 0 : root.effectiveBorderWidth
shadowEnabled: !root.frameOwnsConnectedChrome && root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
}
Rectangle {
anchors.fill: parent
radius: root.effectiveCornerRadius
color: "transparent"
border.color: root.connectedSurfaceOverride ? "transparent" : BlurService.borderColor
border.width: root.connectedSurfaceOverride ? 0 : BlurService.borderWidth
border.color: (root.connectedSurfaceOverride || root.frameOwnsConnectedChrome) ? "transparent" : BlurService.borderColor
border.width: (root.connectedSurfaceOverride || root.frameOwnsConnectedChrome) ? 0 : BlurService.borderWidth
z: 100
}

View File

@@ -63,8 +63,72 @@ Item {
}
readonly property int modalWidth: Math.min(baseWidth, screenWidth - 100)
readonly property int modalHeight: Math.min(baseHeight, screenHeight - 100)
readonly property real modalX: (screenWidth - modalWidth) / 2
readonly property real modalY: (screenHeight - modalHeight) / 2
readonly property string preferredConnectedBarSide: SettingsData.frameLauncherEmergeSide
readonly property bool frameConnectedMode: SettingsData.frameEnabled
&& Theme.isConnectedEffect
&& !!effectiveScreen
&& SettingsData.isScreenInPreferences(effectiveScreen, SettingsData.frameScreenPreferences)
readonly property string resolvedConnectedBarSide: frameConnectedMode ? preferredConnectedBarSide : ""
readonly property bool frameOwnsConnectedChrome: frameConnectedMode && resolvedConnectedBarSide !== ""
function _dockOccupiesSide(side) {
if (!SettingsData.showDock) return false;
switch (side) {
case "top": return SettingsData.dockPosition === SettingsData.Position.Top;
case "bottom": return SettingsData.dockPosition === SettingsData.Position.Bottom;
case "left": return SettingsData.dockPosition === SettingsData.Position.Left;
case "right": return SettingsData.dockPosition === SettingsData.Position.Right;
}
return false;
}
readonly property bool _dockBlocksEmergence: frameOwnsConnectedChrome && _dockOccupiesSide(resolvedConnectedBarSide)
function _frameEdgeInset(side) {
if (!effectiveScreen) return 0;
return SettingsData.frameEdgeInsetForSide(effectiveScreen, side);
}
// frameEdgeInsetForSide is the full inset; do not add frameBarSize
readonly property real _connectedModalX: {
switch (resolvedConnectedBarSide) {
case "top":
case "bottom": {
const insetL = _frameEdgeInset("left");
const insetR = _frameEdgeInset("right");
const usable = Math.max(0, screenWidth - insetL - insetR);
return insetL + Math.max(0, (usable - modalWidth) / 2);
}
case "left":
return _frameEdgeInset("left");
case "right":
return screenWidth - modalWidth - _frameEdgeInset("right");
}
return (screenWidth - modalWidth) / 2;
}
readonly property real _connectedModalY: {
switch (resolvedConnectedBarSide) {
case "top":
return _frameEdgeInset("top");
case "bottom":
return screenHeight - modalHeight - _frameEdgeInset("bottom");
case "left":
case "right": {
const insetT = _frameEdgeInset("top");
const insetB = _frameEdgeInset("bottom");
const usable = Math.max(0, screenHeight - insetT - insetB);
return insetT + Math.max(0, (usable - modalHeight) / 2);
}
}
return (screenHeight - modalHeight) / 2;
}
readonly property real modalX: frameOwnsConnectedChrome ? _connectedModalX : ((screenWidth - modalWidth) / 2)
readonly property real modalY: frameOwnsConnectedChrome ? _connectedModalY : ((screenHeight - modalHeight) / 2)
readonly property bool connectedSurfaceOverride: Theme.isConnectedEffect
readonly property int launcherAnimationDuration: Theme.isConnectedEffect ? Theme.popoutAnimationDuration : Theme.modalAnimationDuration
@@ -93,10 +157,11 @@ Item {
readonly property int effectiveBorderWidth: connectedSurfaceOverride ? 0 : borderWidth
readonly property bool effectiveBlurEnabled: Theme.connectedSurfaceBlurEnabled
// Shadow padding for the content window (render padding only, no motion padding)
// Shadow padding for the content window (render padding only, no motion padding).
// Zeroed when frame owns the chrome and Wayland clips past the bar edge
readonly property var shadowLevel: Theme.elevationLevel3
readonly property real shadowFallbackOffset: 6
readonly property real shadowRenderPadding: (Theme.elevationEnabled && SettingsData.modalElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, Theme.elevationLightDirection, shadowFallbackOffset, 8, 16) : 0
readonly property real shadowRenderPadding: (!frameOwnsConnectedChrome && Theme.elevationEnabled && SettingsData.modalElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, Theme.elevationLightDirection, shadowFallbackOffset, 8, 16) : 0
readonly property real shadowPad: Theme.snap(shadowRenderPadding, dpr)
readonly property real alignedWidth: Theme.px(modalWidth, dpr)
readonly property real alignedHeight: Theme.px(modalHeight, dpr)
@@ -123,6 +188,107 @@ Item {
signal dialogClosed
// ─── Connected chrome sync ────────────────────────────────────────────────
property string _chromeClaimId: ""
property bool _fullSyncPending: false
function _nextChromeClaimId() {
return "dms:launcher-v2:" + (new Date()).getTime() + ":" + Math.floor(Math.random() * 1000);
}
function _currentScreenName() {
return effectiveScreen ? effectiveScreen.name : "";
}
function _publishModalChromeState() {
const screenName = _currentScreenName();
if (!screenName) return;
ConnectedModeState.setModalState(screenName, {
"visible": spotlightOpen || contentWindow.visible,
"barSide": resolvedConnectedBarSide,
"bodyX": alignedX,
"bodyY": alignedY,
"bodyW": alignedWidth,
"bodyH": alignedHeight,
"animX": contentContainer ? contentContainer.animX : 0,
"animY": contentContainer ? contentContainer.animY : 0,
"omitStartConnector": false,
"omitEndConnector": false
});
}
function _syncModalChromeState() {
if (!frameOwnsConnectedChrome) {
_releaseModalChrome();
return;
}
if (!_chromeClaimId)
_chromeClaimId = _nextChromeClaimId();
_publishModalChromeState();
if (_dockBlocksEmergence && (spotlightOpen || contentWindow.visible))
ConnectedModeState.requestDockRetract(_chromeClaimId, _currentScreenName(), resolvedConnectedBarSide);
else
ConnectedModeState.releaseDockRetract(_chromeClaimId);
}
function _flushFullSync() {
_fullSyncPending = false;
_syncModalChromeState();
}
function _queueFullSync() {
if (_fullSyncPending) return;
_fullSyncPending = true;
Qt.callLater(() => {
if (root && typeof root._flushFullSync === "function")
root._flushFullSync();
});
}
function _syncModalAnim() {
if (!frameOwnsConnectedChrome || !_chromeClaimId) return;
const screenName = _currentScreenName();
if (!screenName || !contentContainer) return;
ConnectedModeState.setModalAnim(screenName, contentContainer.animX, contentContainer.animY);
}
function _syncModalBody() {
if (!frameOwnsConnectedChrome || !_chromeClaimId) return;
const screenName = _currentScreenName();
if (!screenName) return;
ConnectedModeState.setModalBody(screenName, alignedX, alignedY, alignedWidth, alignedHeight);
}
function _releaseModalChrome() {
if (_chromeClaimId) {
ConnectedModeState.releaseDockRetract(_chromeClaimId);
_chromeClaimId = "";
}
const screenName = _currentScreenName();
if (screenName)
ConnectedModeState.clearModalState(screenName);
}
onFrameOwnsConnectedChromeChanged: _syncModalChromeState()
onResolvedConnectedBarSideChanged: _queueFullSync()
onSpotlightOpenChanged: _queueFullSync()
onAlignedXChanged: _syncModalBody()
onAlignedYChanged: _syncModalBody()
onAlignedWidthChanged: _syncModalBody()
onAlignedHeightChanged: _syncModalBody()
Component.onDestruction: _releaseModalChrome()
Connections {
target: contentWindow
function onVisibleChanged() {
if (contentWindow.visible)
root._syncModalChromeState();
else
root._releaseModalChrome();
}
}
function _ensureContentLoadedAndInitialize(query, mode) {
_pendingQuery = query || "";
_pendingMode = mode || "";
@@ -418,12 +584,12 @@ Item {
WindowBlur {
targetWindow: contentWindow
blurEnabled: root.effectiveBlurEnabled
blurEnabled: root.effectiveBlurEnabled && !root.frameOwnsConnectedChrome
readonly property real s: Math.min(1, contentContainer.scaleValue)
blurX: root._ccX + root.alignedWidth * (1 - s) * 0.5 + Theme.snap(contentContainer.animX, root.dpr)
blurY: root._ccY + root.alignedHeight * (1 - s) * 0.5 + Theme.snap(contentContainer.animY, root.dpr)
blurWidth: (root.spotlightOpen || root.isClosing) && contentWrapper.opacity > 0 ? root.alignedWidth * s : 0
blurHeight: (root.spotlightOpen || root.isClosing) && contentWrapper.opacity > 0 ? root.alignedHeight * s : 0
blurWidth: (root.spotlightOpen || root.isClosing) && contentWrapper.opacity > 0 && !root.frameOwnsConnectedChrome ? root.alignedWidth * s : 0
blurHeight: (root.spotlightOpen || root.isClosing) && contentWrapper.opacity > 0 && !root.frameOwnsConnectedChrome ? root.alignedHeight * s : 0
blurRadius: root.cornerRadius
}
@@ -491,7 +657,16 @@ Item {
readonly property bool directionalEffect: Theme.isDirectionalEffect
readonly property bool depthEffect: Theme.isDepthEffect
readonly property real _connectedTravelX: Math.max(Theme.effectAnimOffset, root.alignedWidth + Theme.spacingL)
readonly property real _connectedTravelY: Math.max(Theme.effectAnimOffset, root.alignedHeight + Theme.spacingL)
readonly property real collapsedMotionX: {
if (root.frameOwnsConnectedChrome) {
switch (root.resolvedConnectedBarSide) {
case "left": return -_connectedTravelX;
case "right": return _connectedTravelX;
}
return 0;
}
if (directionalEffect) {
if (dockLeft)
return -(root._ccX + root.alignedWidth + Theme.effectAnimOffset);
@@ -503,6 +678,13 @@ Item {
return 0;
}
readonly property real collapsedMotionY: {
if (root.frameOwnsConnectedChrome) {
switch (root.resolvedConnectedBarSide) {
case "top": return -_connectedTravelY;
case "bottom": return _connectedTravelY;
}
return 0;
}
if (directionalEffect) {
if (dockTop)
return -(root._ccY + root.alignedHeight + Theme.effectAnimOffset);
@@ -520,6 +702,9 @@ Item {
property real animY: root._motionActive ? 0 : root._frozenMotionY
property real scaleValue: root._motionActive ? 1.0 : (Theme.isDirectionalEffect && typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2 ? Theme.effectScaleCollapsed : (Theme.isDirectionalEffect ? 1 : Theme.effectScaleCollapsed))
onAnimXChanged: if (root.frameOwnsConnectedChrome) root._syncModalAnim()
onAnimYChanged: if (root.frameOwnsConnectedChrome) root._syncModalAnim()
Behavior on animX {
enabled: root.animationsEnabled
DankAnim {
@@ -575,11 +760,11 @@ Item {
y: contentWrapper.y
level: root.shadowLevel
fallbackOffset: root.shadowFallbackOffset
targetColor: root.backgroundColor
borderColor: root.effectiveBorderColor
borderWidth: root.effectiveBorderWidth
targetColor: root.frameOwnsConnectedChrome ? "transparent" : root.backgroundColor
borderColor: root.frameOwnsConnectedChrome ? "transparent" : root.effectiveBorderColor
borderWidth: root.frameOwnsConnectedChrome ? 0 : root.effectiveBorderWidth
targetRadius: root.cornerRadius
shadowEnabled: Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
shadowEnabled: !root.frameOwnsConnectedChrome && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
}
// contentWrapper moves inside static contentContainer — DankPopout pattern

View File

@@ -281,13 +281,16 @@ FocusScope {
anchors.rightMargin: root.parentModal?.borderWidth ?? 1
anchors.bottomMargin: root.parentModal?.borderWidth ?? 1
readonly property bool showFooter: SettingsData.dankLauncherV2Size !== "micro" && SettingsData.dankLauncherV2ShowFooter
height: showFooter ? 36 : 0
readonly property bool _connectedArcAtFooter: (root.parentModal?.frameOwnsConnectedChrome ?? false) && (root.parentModal?.resolvedConnectedBarSide === "bottom")
height: showFooter ? (_connectedArcAtFooter ? 76 : 36) : 0
visible: showFooter
clip: true
Rectangle {
anchors.fill: parent
anchors.topMargin: -Theme.cornerRadius
// In connected mode the launcher provides the surface so update the toolbar for arcs
visible: !(root.parentModal?.frameOwnsConnectedChrome ?? false)
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
radius: Theme.cornerRadius
}
@@ -295,7 +298,7 @@ FocusScope {
Row {
id: modeButtonsRow
anchors.left: parent.left
anchors.leftMargin: Theme.spacingXS
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
layoutDirection: I18n.isRtl ? Qt.RightToLeft : Qt.LeftToRight
spacing: 2
@@ -367,7 +370,7 @@ FocusScope {
Row {
id: hintsRow
anchors.right: parent.right
anchors.rightMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
layoutDirection: I18n.isRtl ? Qt.RightToLeft : Qt.LeftToRight
spacing: Theme.spacingM

View File

@@ -372,10 +372,10 @@ Popup {
anchors.fill: parent
implicitWidth: Math.max(180, menuColumn.implicitWidth + Theme.spacingS * 2)
implicitHeight: menuColumn.implicitHeight + Theme.spacingS * 2
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
color: BlurService.enabled ? Theme.surfaceContainer : Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
radius: Theme.cornerRadius
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1
border.color: BlurService.enabled ? BlurService.borderColor : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: BlurService.enabled ? BlurService.borderWidth : 1
Rectangle {
anchors.fill: parent
@@ -438,7 +438,7 @@ Popup {
if (root.keyboardNavigation && root.selectedMenuIndex === menuItemDelegate.itemIndex) {
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2);
}
return itemMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent";
return itemMouseArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent";
}
Row {

View File

@@ -144,10 +144,48 @@ DankModal {
return "NOTIFICATION_MODAL_TOGGLE_DND_SUCCESS";
}
function enableDoNotDisturbFor(minutes: int): string {
if (minutes <= 0) {
return "ERROR: minutes must be > 0";
}
SessionData.setDoNotDisturb(true, minutes);
return "NOTIFICATION_MODAL_DND_SET_FOR_" + minutes + "_SUCCESS";
}
function enableDoNotDisturbUntil(timestampMs: string): string {
const ts = Number(timestampMs);
if (!ts || ts <= Date.now()) {
return "ERROR: timestamp must be a future epoch ms";
}
SessionData.setDoNotDisturbUntilTimestamp(ts);
return "NOTIFICATION_MODAL_DND_SET_UNTIL_SUCCESS";
}
function enableDoNotDisturbIndefinitely(): string {
SessionData.setDoNotDisturb(true, 0);
return "NOTIFICATION_MODAL_DND_INDEFINITE_SUCCESS";
}
function enableDoNotDisturbUntilTomorrowMorning(): string {
const now = new Date();
const target = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1, 8, 0, 0, 0);
SessionData.setDoNotDisturbUntilTimestamp(target.getTime());
return "NOTIFICATION_MODAL_DND_UNTIL_TOMORROW_SUCCESS";
}
function disableDoNotDisturb(): string {
SessionData.setDoNotDisturb(false);
return "NOTIFICATION_MODAL_DND_DISABLE_SUCCESS";
}
function getDoNotDisturb(): bool {
return SessionData.doNotDisturb;
}
function getDoNotDisturbUntil(): string {
return String(SessionData.doNotDisturbUntil);
}
function clearAll(): string {
notificationModal.clearAll();
return "NOTIFICATION_MODAL_CLEAR_ALL_SUCCESS";

View File

@@ -188,6 +188,9 @@ Item {
case "battery":
coreDetailLoader.sourceComponent = batteryDetailComponent;
break;
case "doNotDisturb":
coreDetailLoader.sourceComponent = doNotDisturbDetailComponent;
break;
default:
return;
}
@@ -230,6 +233,11 @@ Item {
BatteryDetail {}
}
Component {
id: doNotDisturbDetailComponent
DoNotDisturbDetail {}
}
Component {
id: diskUsageDetailComponent
DiskUsageDetail {

View File

@@ -192,6 +192,8 @@ Column {
return widgetWidth <= 25 ? smallDiskUsageComponent : diskUsagePillComponent;
} else if (id === "colorPicker") {
return colorPickerPillComponent;
} else if (id === "doNotDisturb") {
return widgetWidth <= 25 ? smallToggleComponent : dndPillComponent;
} else {
return widgetWidth <= 25 ? smallToggleComponent : toggleButtonComponent;
}
@@ -638,6 +640,22 @@ Column {
}
}
Component {
id: dndPillComponent
DndPill {
property var widgetData: parent.widgetData || {}
property int widgetIndex: parent.widgetIndex || 0
width: parent.width
height: 60
onExpandClicked: {
if (!root.editMode) {
root.expandClicked(widgetData, widgetIndex);
}
}
}
}
Component {
id: smallBatteryComponent
SmallBatteryButton {
@@ -668,8 +686,6 @@ Column {
return DisplayService.nightModeEnabled ? "nightlight" : "dark_mode";
case "darkMode":
return "contrast";
case "doNotDisturb":
return SessionData.doNotDisturb ? "do_not_disturb_on" : "do_not_disturb_off";
case "idleInhibitor":
return SessionService.idleInhibited ? "motion_sensor_active" : "motion_sensor_idle";
default:
@@ -683,8 +699,6 @@ Column {
return I18n.tr("Night Mode");
case "darkMode":
return I18n.tr("Dark Mode");
case "doNotDisturb":
return I18n.tr("Do Not Disturb");
case "idleInhibitor":
return SessionService.idleInhibited ? I18n.tr("Keeping Awake") : I18n.tr("Keep Awake");
default:
@@ -707,8 +721,6 @@ Column {
return DisplayService.nightModeEnabled || false;
case "darkMode":
return !SessionData.isLightMode;
case "doNotDisturb":
return SessionData.doNotDisturb || false;
case "idleInhibitor":
return SessionService.idleInhibited || false;
default:
@@ -735,11 +747,6 @@ Column {
Theme.setLightMode(newMode);
break;
}
case "doNotDisturb":
{
SessionData.setDoNotDisturb(!SessionData.doNotDisturb);
break;
}
case "idleInhibitor":
{
SessionService.toggleIdleInhibit();

View File

@@ -0,0 +1,258 @@
import QtQuick
import qs.Common
import qs.Widgets
Rectangle {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
implicitHeight: contentColumn.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 0
property real nowMs: Date.now()
Timer {
interval: 1000
repeat: true
running: root.visible && SessionData.doNotDisturb && SessionData.doNotDisturbUntil > 0
onTriggered: root.nowMs = Date.now()
}
function _pad2(n) {
return n < 10 ? "0" + n : "" + n;
}
function formatUntil(ts) {
if (!ts)
return "";
const d = new Date(ts);
const use24h = (typeof SettingsData !== "undefined") ? SettingsData.use24HourClock : true;
if (use24h)
return _pad2(d.getHours()) + ":" + _pad2(d.getMinutes());
const suffix = d.getHours() >= 12 ? "PM" : "AM";
const h12 = ((d.getHours() + 11) % 12) + 1;
return h12 + ":" + _pad2(d.getMinutes()) + " " + suffix;
}
function formatRemaining(ms) {
if (ms <= 0)
return "";
const totalMinutes = Math.ceil(ms / 60000);
if (totalMinutes < 60)
return I18n.tr("%1 min left").arg(totalMinutes);
const hours = Math.floor(totalMinutes / 60);
const mins = totalMinutes - hours * 60;
if (mins === 0)
return I18n.tr("%1 h left").arg(hours);
return I18n.tr("%1 h %2 m left").arg(hours).arg(mins);
}
function minutesUntilTomorrowMorning() {
const now = new Date();
const target = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1, 8, 0, 0, 0);
return Math.max(1, Math.round((target.getTime() - now.getTime()) / 60000));
}
readonly property var presets: [
{
"label": I18n.tr("15 min"),
"minutes": 15
},
{
"label": I18n.tr("30 min"),
"minutes": 30
},
{
"label": I18n.tr("1 hour"),
"minutes": 60
},
{
"label": I18n.tr("3 hours"),
"minutes": 180
},
{
"label": I18n.tr("8 hours"),
"minutes": 480
},
{
"label": I18n.tr("Until 8 AM"),
"minutesFn": true
}
]
Column {
id: contentColumn
width: parent.width - Theme.spacingL * 2
anchors.left: parent.left
anchors.top: parent.top
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: SessionData.doNotDisturb ? "do_not_disturb_on" : "notifications_paused"
size: Theme.iconSizeLarge
color: SessionData.doNotDisturb ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Column {
anchors.verticalCenter: parent.verticalCenter
width: parent.width - Theme.iconSizeLarge - Theme.spacingM
spacing: 2
StyledText {
text: I18n.tr("Silence notifications")
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
width: parent.width
elide: Text.ElideRight
}
StyledText {
text: {
if (!SessionData.doNotDisturb)
return I18n.tr("Pick how long to pause notifications");
if (SessionData.doNotDisturbUntil <= 0)
return I18n.tr("On indefinitely");
const remaining = Math.max(0, SessionData.doNotDisturbUntil - root.nowMs);
return root.formatRemaining(remaining) + " · " + I18n.tr("until %1").arg(root.formatUntil(SessionData.doNotDisturbUntil));
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: parent.width
elide: Text.ElideRight
}
}
}
Grid {
width: parent.width
columns: 3
columnSpacing: Theme.spacingS
rowSpacing: Theme.spacingS
Repeater {
model: root.presets
Rectangle {
required property var modelData
width: (contentColumn.width - Theme.spacingS * 2) / 3
height: 36
radius: Theme.cornerRadius
color: presetArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: 1
StyledText {
anchors.centerIn: parent
text: modelData.label
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
}
MouseArea {
id: presetArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
const minutes = modelData.minutesFn ? root.minutesUntilTomorrowMorning() : modelData.minutes;
SessionData.setDoNotDisturb(true, minutes);
}
}
}
}
}
Row {
width: parent.width
spacing: Theme.spacingS
Rectangle {
width: (contentColumn.width - Theme.spacingS) / 2
height: 36
radius: Theme.cornerRadius
color: foreverArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: 1
Row {
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
anchors.verticalCenter: parent.verticalCenter
name: "block"
size: Theme.iconSizeSmall
color: Theme.surfaceText
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: I18n.tr("Until I turn it off")
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
}
}
MouseArea {
id: foreverArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: SessionData.setDoNotDisturb(true, 0)
}
}
Rectangle {
width: (contentColumn.width - Theme.spacingS) / 2
height: 36
radius: Theme.cornerRadius
visible: SessionData.doNotDisturb
color: offArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.18) : Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: 1
Row {
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
anchors.verticalCenter: parent.verticalCenter
name: "notifications_active"
size: Theme.iconSizeSmall
color: offArea.containsMouse ? Theme.error : Theme.surfaceText
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: I18n.tr("Turn off")
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: offArea.containsMouse ? Theme.error : Theme.surfaceText
}
}
MouseArea {
id: offArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: SessionData.setDoNotDisturb(false)
}
}
}
}
}

View File

@@ -0,0 +1,29 @@
import QtQuick
import qs.Common
import qs.Modules.ControlCenter.Widgets
CompoundPill {
id: root
iconName: SessionData.doNotDisturb ? "do_not_disturb_on" : "do_not_disturb_off"
iconColor: SessionData.doNotDisturb ? Theme.primary : Theme.surfaceText
primaryText: I18n.tr("Do Not Disturb")
isActive: SessionData.doNotDisturb
secondaryText: {
if (!SessionData.doNotDisturb)
return I18n.tr("Off");
if (SessionData.doNotDisturbUntil <= 0)
return I18n.tr("On");
const d = new Date(SessionData.doNotDisturbUntil);
const use24h = (typeof SettingsData !== "undefined") ? SettingsData.use24HourClock : true;
const pad = n => n < 10 ? "0" + n : "" + n;
if (use24h)
return I18n.tr("Until %1").arg(pad(d.getHours()) + ":" + pad(d.getMinutes()));
const suffix = d.getHours() >= 12 ? "PM" : "AM";
const h12 = ((d.getHours() + 11) % 12) + 1;
return I18n.tr("Until %1").arg(h12 + ":" + pad(d.getMinutes()) + " " + suffix);
}
onToggled: SessionData.setDoNotDisturb(!SessionData.doNotDisturb)
}

View File

@@ -9,6 +9,15 @@ import qs.Widgets
PanelWindow {
id: root
WindowBlur {
targetWindow: root
blurX: menuContainer.x
blurY: menuContainer.y
blurWidth: root.visible ? menuContainer.width : 0
blurHeight: root.visible ? menuContainer.height : 0
blurRadius: Theme.cornerRadius
}
WlrLayershell.namespace: "dms:dock-context-menu"
property var appData: null
@@ -112,8 +121,8 @@ PanelWindow {
height: Math.max(60, menuColumn.implicitHeight + Theme.spacingS * 2)
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
radius: Theme.cornerRadius
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1
border.color: BlurService.enabled ? BlurService.borderColor : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: BlurService.enabled ? BlurService.borderWidth : 1
opacity: root.visible ? 1 : 0
visible: opacity > 0
@@ -165,7 +174,7 @@ PanelWindow {
width: parent.width
height: 28
radius: Theme.cornerRadius
color: windowArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
color: windowArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
StyledText {
anchors.left: parent.left
@@ -255,7 +264,7 @@ PanelWindow {
width: parent.width
height: 28
radius: Theme.cornerRadius
color: actionArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
color: actionArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
Row {
anchors.left: parent.left
@@ -330,7 +339,7 @@ PanelWindow {
width: parent.width
height: 28
radius: Theme.cornerRadius
color: pinArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
color: pinArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
StyledText {
anchors.left: parent.left
@@ -390,7 +399,7 @@ PanelWindow {
width: parent.width
height: 28
radius: Theme.cornerRadius
color: nvidiaArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
color: nvidiaArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
StyledText {
anchors.left: parent.left

View File

@@ -148,6 +148,15 @@ BasePill {
PanelWindow {
id: contextMenuWindow
WindowBlur {
targetWindow: contextMenuWindow
blurX: menuContainer.x
blurY: menuContainer.y
blurWidth: contextMenuWindow.visible ? menuContainer.width : 0
blurHeight: contextMenuWindow.visible ? menuContainer.height : 0
blurRadius: Theme.cornerRadius
}
WlrLayershell.namespace: "dms:notepad-context-menu"
property bool isVertical: false
@@ -244,8 +253,8 @@ BasePill {
height: Math.max(60, menuColumn.implicitHeight + Theme.spacingS * 2)
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
radius: Theme.cornerRadius
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1
border.color: BlurService.enabled ? BlurService.borderColor : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: BlurService.enabled ? BlurService.borderWidth : 1
opacity: contextMenuWindow.visible ? 1 : 0
visible: opacity > 0

View File

@@ -1,5 +1,6 @@
import QtQuick
import qs.Common
import qs.Modules.Notifications.Center
import qs.Modules.Plugins
import qs.Widgets
@@ -34,7 +35,49 @@ BasePill {
}
}
onRightClicked: {
SessionData.setDoNotDisturb(!SessionData.doNotDisturb);
onRightClicked: (rx, ry) => {
const screen = root.parentScreen || Screen;
if (!screen)
return;
const globalPos = root.visualContent.mapToItem(null, 0, 0);
const isVertical = root.axis?.isVertical ?? false;
const edge = root.axis?.edge ?? "top";
const gap = Math.max(Theme.spacingXS, root.barSpacing ?? Theme.spacingXS);
const barOffset = root.barThickness + root.barSpacing + gap;
let anchorX;
let anchorY;
let anchorEdge;
if (isVertical) {
anchorY = globalPos.y - (screen.y || 0) + root.visualContent.height / 2;
if (edge === "left") {
anchorX = barOffset;
anchorEdge = "top";
} else {
anchorX = screen.width - barOffset;
anchorEdge = "top";
}
} else {
anchorX = globalPos.x - (screen.x || 0) + root.visualContent.width / 2;
if (edge === "bottom") {
anchorY = screen.height - barOffset;
anchorEdge = "bottom";
} else {
anchorY = barOffset;
anchorEdge = "top";
}
}
dndPopupLoader.active = true;
const popup = dndPopupLoader.item;
if (!popup)
return;
popup.showAt(anchorX, anchorY, screen, anchorEdge);
}
Loader {
id: dndPopupLoader
active: false
sourceComponent: DndDurationPopup {}
}
}

View File

@@ -46,9 +46,11 @@ Variants {
readonly property string connectedBarSide: SettingsData.dockPosition === SettingsData.Position.Top ? "top" : SettingsData.dockPosition === SettingsData.Position.Bottom ? "bottom" : SettingsData.dockPosition === SettingsData.Position.Left ? "left" : "right"
readonly property bool connectedBarActiveOnEdge: Theme.isConnectedEffect && !!(dock.screen || modelData) && SettingsData.getActiveBarEdgesForScreen(dock.screen || modelData).includes(connectedBarSide)
readonly property real connectedJoinInset: {
if (!Theme.isConnectedEffect)
return 0;
return connectedBarActiveOnEdge ? SettingsData.frameBarSize : SettingsData.frameThickness;
if (Theme.isConnectedEffect)
return connectedBarActiveOnEdge ? SettingsData.frameBarSize : SettingsData.frameThickness;
if (SettingsData.frameEnabled)
return SettingsData.frameEdgeInsetForSide(dock.screen || modelData, dock.connectedBarSide);
return 0;
}
readonly property real surfaceRadius: Theme.connectedSurfaceRadius
readonly property color surfaceColor: Theme.isConnectedEffect ? Theme.connectedSurfaceColor : Theme.withAlpha(Theme.surfaceContainer, backgroundTransparency)
@@ -379,7 +381,16 @@ Variants {
onTriggered: dock.revealSticky = false
}
// Flip `reveal` false when a modal claims this edge; reuses the slide animation
readonly property bool _modalRetractActive: {
if (!dock._dockScreenName) return false;
return ConnectedModeState.dockRetractActiveForSide(dock._dockScreenName, dock.connectedBarSide);
}
property bool reveal: {
if (_modalRetractActive)
return false;
if (CompositorService.isNiri && NiriService.inOverview && SettingsData.dockOpenOnOverview) {
return true;
}

View File

@@ -230,7 +230,7 @@ PanelWindow {
width: parent.width
height: 28
radius: Theme.cornerRadius
color: windowArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
color: windowArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
StyledText {
anchors.left: parent.left
@@ -320,7 +320,7 @@ PanelWindow {
width: parent.width
height: 28
radius: Theme.cornerRadius
color: actionArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
color: actionArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
Row {
anchors.left: parent.left
@@ -395,7 +395,7 @@ PanelWindow {
width: parent.width
height: 28
radius: Theme.cornerRadius
color: pinArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
color: pinArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
Row {
anchors.left: parent.left
@@ -468,7 +468,7 @@ PanelWindow {
width: parent.width
height: 28
radius: Theme.cornerRadius
color: nvidiaArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
color: nvidiaArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
Row {
anchors.left: parent.left

View File

@@ -46,6 +46,7 @@ PanelWindow {
"y": 0
})
readonly property var _notifState: ConnectedModeState.notificationStates[win._screenName] || ConnectedModeState.emptyNotificationState
readonly property var _modalState: ConnectedModeState.modalStates[win._screenName] || ConnectedModeState.emptyModalState
// ─── Connected chrome convenience properties ──────────────────────────────
readonly property bool _connectedActive: win._frameActive && SettingsData.connectedFrameModeActive
@@ -94,6 +95,22 @@ PanelWindow {
readonly property real _effectiveNotifFarEndCcr: win._notifState.omitEndConnector ? win._effectiveNotifFarCcr : 0
readonly property real _effectiveNotifMaxCcr: Math.max(win._effectiveNotifStartCcr, win._effectiveNotifEndCcr)
readonly property real _effectiveNotifFarExtent: Math.max(win._effectiveNotifFarStartCcr, win._effectiveNotifFarEndCcr)
readonly property real _effectiveModalCcr: {
const isHoriz = win._modalState.barSide === "top" || win._modalState.barSide === "bottom";
const crossSize = isHoriz ? _modalBodyBlurAnchor.width : _modalBodyBlurAnchor.height;
const extent = isHoriz ? _modalBodyBlurAnchor.height : _modalBodyBlurAnchor.width;
return Theme.snap(Math.max(0, Math.min(win._ccr, win._surfaceRadius, extent, crossSize / 2)), win._dpr);
}
readonly property real _effectiveModalFarCcr: {
const isHoriz = win._modalState.barSide === "top" || win._modalState.barSide === "bottom";
const crossSize = isHoriz ? _modalBodyBlurAnchor.width : _modalBodyBlurAnchor.height;
return Theme.snap(Math.max(0, Math.min(win._ccr, win._surfaceRadius, crossSize / 2)), win._dpr);
}
readonly property real _effectiveModalStartCcr: win._modalState.omitStartConnector ? 0 : win._effectiveModalCcr
readonly property real _effectiveModalEndCcr: win._modalState.omitEndConnector ? 0 : win._effectiveModalCcr
readonly property real _effectiveModalFarStartCcr: win._modalState.omitStartConnector ? win._effectiveModalFarCcr : 0
readonly property real _effectiveModalFarEndCcr: win._modalState.omitEndConnector ? win._effectiveModalFarCcr : 0
readonly property real _effectiveModalFarExtent: Math.max(win._effectiveModalFarStartCcr, win._effectiveModalFarEndCcr)
readonly property color _surfaceColor: Theme.connectedSurfaceColor
readonly property real _surfaceOpacity: _surfaceColor.a
readonly property color _opaqueSurfaceColor: Qt.rgba(_surfaceColor.r, _surfaceColor.g, _surfaceColor.b, 1)
@@ -403,6 +420,180 @@ PanelWindow {
height: _active ? Theme.snap(win._notifState.bodyH, win._dpr) : 0
}
Item {
id: _modalBodyBlurAnchor
visible: false
readonly property bool _active: win._frameActive && win._modalState.visible && win._modalState.bodyW > 0 && win._modalState.bodyH > 0
// Clamp animX/Y so the blur body shrinks toward the bar edge (same as _popoutBodyBlurAnchor).
readonly property real _dyClamp: (win._modalState.barSide === "top" || win._modalState.barSide === "bottom") ? Math.max(-win._modalState.bodyH, Math.min(win._modalState.animY, win._modalState.bodyH)) : 0
readonly property real _dxClamp: (win._modalState.barSide === "left" || win._modalState.barSide === "right") ? Math.max(-win._modalState.bodyW, Math.min(win._modalState.animX, win._modalState.bodyW)) : 0
x: _active ? Theme.snap(win._modalState.bodyX + (win._modalState.barSide === "right" ? _dxClamp : 0), win._dpr) : 0
y: _active ? Theme.snap(win._modalState.bodyY + (win._modalState.barSide === "bottom" ? _dyClamp : 0), win._dpr) : 0
width: _active ? Theme.snap(Math.max(0, win._modalState.bodyW - Math.abs(_dxClamp)), win._dpr) : 0
height: _active ? Theme.snap(Math.max(0, win._modalState.bodyH - Math.abs(_dyClamp)), win._dpr) : 0
}
Item {
id: _modalBodyBlurCap
opacity: 0
readonly property string _side: win._modalState.barSide
readonly property real _capThickness: win._modalBlurCapThickness()
readonly property bool _active: _modalBodyBlurAnchor._active && _capThickness > 0 && _modalBodyBlurAnchor.width > 0 && _modalBodyBlurAnchor.height > 0
readonly property real _capWidth: (_side === "left" || _side === "right") ? Math.min(_capThickness, _modalBodyBlurAnchor.width) : _modalBodyBlurAnchor.width
readonly property real _capHeight: (_side === "top" || _side === "bottom") ? Math.min(_capThickness, _modalBodyBlurAnchor.height) : _modalBodyBlurAnchor.height
x: !_active ? 0 : (_side === "right" ? _modalBodyBlurAnchor.x + _modalBodyBlurAnchor.width - _capWidth : _modalBodyBlurAnchor.x)
y: !_active ? 0 : (_side === "bottom" ? _modalBodyBlurAnchor.y + _modalBodyBlurAnchor.height - _capHeight : _modalBodyBlurAnchor.y)
width: _active ? _capWidth : 0
height: _active ? _capHeight : 0
}
Item {
id: _modalLeftConnectorBlurAnchor
opacity: 0
readonly property real _radius: win._modalConnectorRadius("left")
readonly property bool _active: _modalBodyBlurAnchor._active && _radius > 0
readonly property real _w: win._modalConnectorWidth(0, "left")
readonly property real _h: win._modalConnectorHeight(0, "left")
x: _active ? Theme.snap(win._modalConnectorX(_modalBodyBlurAnchor.x, _modalBodyBlurAnchor.width, "left", 0), win._dpr) : 0
y: _active ? Theme.snap(win._modalConnectorY(_modalBodyBlurAnchor.y, _modalBodyBlurAnchor.height, "left", 0), win._dpr) : 0
width: _active ? _w : 0
height: _active ? _h : 0
}
Item {
id: _modalRightConnectorBlurAnchor
opacity: 0
readonly property real _radius: win._modalConnectorRadius("right")
readonly property bool _active: _modalBodyBlurAnchor._active && _radius > 0
readonly property real _w: win._modalConnectorWidth(0, "right")
readonly property real _h: win._modalConnectorHeight(0, "right")
x: _active ? Theme.snap(win._modalConnectorX(_modalBodyBlurAnchor.x, _modalBodyBlurAnchor.width, "right", 0), win._dpr) : 0
y: _active ? Theme.snap(win._modalConnectorY(_modalBodyBlurAnchor.y, _modalBodyBlurAnchor.height, "right", 0), win._dpr) : 0
width: _active ? _w : 0
height: _active ? _h : 0
}
Item {
id: _modalLeftConnectorCutout
opacity: 0
readonly property bool _active: _modalLeftConnectorBlurAnchor.width > 0 && _modalLeftConnectorBlurAnchor.height > 0
readonly property string _arcCorner: win._connectorArcCorner(win._modalState.barSide, "left")
readonly property real _radius: win._modalConnectorRadius("left")
x: _active ? win._connectorCutoutX(_modalLeftConnectorBlurAnchor.x, _modalLeftConnectorBlurAnchor.width, _arcCorner, _radius) : 0
y: _active ? win._connectorCutoutY(_modalLeftConnectorBlurAnchor.y, _modalLeftConnectorBlurAnchor.height, _arcCorner, _radius) : 0
width: _active ? _radius * 2 : 0
height: _active ? _radius * 2 : 0
}
Item {
id: _modalRightConnectorCutout
opacity: 0
readonly property bool _active: _modalRightConnectorBlurAnchor.width > 0 && _modalRightConnectorBlurAnchor.height > 0
readonly property string _arcCorner: win._connectorArcCorner(win._modalState.barSide, "right")
readonly property real _radius: win._modalConnectorRadius("right")
x: _active ? win._connectorCutoutX(_modalRightConnectorBlurAnchor.x, _modalRightConnectorBlurAnchor.width, _arcCorner, _radius) : 0
y: _active ? win._connectorCutoutY(_modalRightConnectorBlurAnchor.y, _modalRightConnectorBlurAnchor.height, _arcCorner, _radius) : 0
width: _active ? _radius * 2 : 0
height: _active ? _radius * 2 : 0
}
Item {
id: _modalFarStartConnectorBlurAnchor
opacity: 0
readonly property real _radius: win._effectiveModalFarStartCcr
readonly property bool _active: _modalBodyBlurAnchor._active && _radius > 0
x: _active ? Theme.snap(win._farConnectorX(_modalBodyBlurAnchor.x, _modalBodyBlurAnchor.y, _modalBodyBlurAnchor.width, _modalBodyBlurAnchor.height, win._modalState.barSide, "left", _radius), win._dpr) : 0
y: _active ? Theme.snap(win._farConnectorY(_modalBodyBlurAnchor.x, _modalBodyBlurAnchor.y, _modalBodyBlurAnchor.width, _modalBodyBlurAnchor.height, win._modalState.barSide, "left", _radius), win._dpr) : 0
width: _active ? _radius : 0
height: _active ? _radius : 0
}
Item {
id: _modalFarStartBodyBlurCap
opacity: 0
readonly property real _radius: win._effectiveModalFarStartCcr
readonly property bool _active: _modalBodyBlurAnchor._active && _radius > 0
x: _active ? Theme.snap(win._farBodyCapX(_modalBodyBlurAnchor.x, _modalBodyBlurAnchor.width, win._modalState.barSide, "left", _radius), win._dpr) : 0
y: _active ? Theme.snap(win._farBodyCapY(_modalBodyBlurAnchor.y, _modalBodyBlurAnchor.height, win._modalState.barSide, "left", _radius), win._dpr) : 0
width: _active ? _radius : 0
height: _active ? _radius : 0
}
Item {
id: _modalFarEndBodyBlurCap
opacity: 0
readonly property real _radius: win._effectiveModalFarEndCcr
readonly property bool _active: _modalBodyBlurAnchor._active && _radius > 0
x: _active ? Theme.snap(win._farBodyCapX(_modalBodyBlurAnchor.x, _modalBodyBlurAnchor.width, win._modalState.barSide, "right", _radius), win._dpr) : 0
y: _active ? Theme.snap(win._farBodyCapY(_modalBodyBlurAnchor.y, _modalBodyBlurAnchor.height, win._modalState.barSide, "right", _radius), win._dpr) : 0
width: _active ? _radius : 0
height: _active ? _radius : 0
}
Item {
id: _modalFarEndConnectorBlurAnchor
opacity: 0
readonly property real _radius: win._effectiveModalFarEndCcr
readonly property bool _active: _modalBodyBlurAnchor._active && _radius > 0
x: _active ? Theme.snap(win._farConnectorX(_modalBodyBlurAnchor.x, _modalBodyBlurAnchor.y, _modalBodyBlurAnchor.width, _modalBodyBlurAnchor.height, win._modalState.barSide, "right", _radius), win._dpr) : 0
y: _active ? Theme.snap(win._farConnectorY(_modalBodyBlurAnchor.x, _modalBodyBlurAnchor.y, _modalBodyBlurAnchor.width, _modalBodyBlurAnchor.height, win._modalState.barSide, "right", _radius), win._dpr) : 0
width: _active ? _radius : 0
height: _active ? _radius : 0
}
Item {
id: _modalFarStartConnectorCutout
opacity: 0
readonly property bool _active: _modalFarStartConnectorBlurAnchor.width > 0 && _modalFarStartConnectorBlurAnchor.height > 0
readonly property string _barSide: win._farConnectorBarSide(win._modalState.barSide, "left")
readonly property string _placement: win._farConnectorPlacement(win._modalState.barSide, "left")
readonly property string _arcCorner: win._connectorArcCorner(_barSide, _placement)
readonly property real _radius: win._effectiveModalFarStartCcr
x: _active ? win._connectorCutoutX(_modalFarStartConnectorBlurAnchor.x, _modalFarStartConnectorBlurAnchor.width, _arcCorner, _radius) : 0
y: _active ? win._connectorCutoutY(_modalFarStartConnectorBlurAnchor.y, _modalFarStartConnectorBlurAnchor.height, _arcCorner, _radius) : 0
width: _active ? _radius * 2 : 0
height: _active ? _radius * 2 : 0
}
Item {
id: _modalFarEndConnectorCutout
opacity: 0
readonly property bool _active: _modalFarEndConnectorBlurAnchor.width > 0 && _modalFarEndConnectorBlurAnchor.height > 0
readonly property string _barSide: win._farConnectorBarSide(win._modalState.barSide, "right")
readonly property string _placement: win._farConnectorPlacement(win._modalState.barSide, "right")
readonly property string _arcCorner: win._connectorArcCorner(_barSide, _placement)
readonly property real _radius: win._effectiveModalFarEndCcr
x: _active ? win._connectorCutoutX(_modalFarEndConnectorBlurAnchor.x, _modalFarEndConnectorBlurAnchor.width, _arcCorner, _radius) : 0
y: _active ? win._connectorCutoutY(_modalFarEndConnectorBlurAnchor.y, _modalFarEndConnectorBlurAnchor.height, _arcCorner, _radius) : 0
width: _active ? _radius * 2 : 0
height: _active ? _radius * 2 : 0
}
Item {
id: _notifBodySceneBlurAnchor
visible: false
@@ -704,6 +895,53 @@ PanelWindow {
radius: win._effectiveNotifFarEndCcr
}
}
// ── Connected modal blur regions ──
Region {
item: _modalBodyBlurAnchor
radius: win._surfaceRadius
}
Region {
item: _modalBodyBlurCap
}
Region {
item: _modalLeftConnectorBlurAnchor
Region {
item: _modalLeftConnectorCutout
intersection: Intersection.Subtract
radius: win._modalConnectorRadius("left")
}
}
Region {
item: _modalRightConnectorBlurAnchor
Region {
item: _modalRightConnectorCutout
intersection: Intersection.Subtract
radius: win._modalConnectorRadius("right")
}
}
Region {
item: _modalFarStartBodyBlurCap
}
Region {
item: _modalFarEndBodyBlurCap
}
Region {
item: _modalFarStartConnectorBlurAnchor
Region {
item: _modalFarStartConnectorCutout
intersection: Intersection.Subtract
radius: win._effectiveModalFarStartCcr
}
}
Region {
item: _modalFarEndConnectorBlurAnchor
Region {
item: _modalFarEndConnectorCutout
intersection: Intersection.Subtract
radius: win._effectiveModalFarEndCcr
}
}
}
// ─── Connector position helpers ────────────────────────────────────────
@@ -894,6 +1132,52 @@ PanelWindow {
return (win._dockState.barSide === "left" || win._dockState.barSide === "right") ? win._seamOverlap : 0;
}
function _modalArcExtent() {
return (win._modalState.barSide === "top" || win._modalState.barSide === "bottom") ? _modalBodyBlurAnchor.height : _modalBodyBlurAnchor.width;
}
function _modalBlurCapThickness() {
const extent = win._modalArcExtent();
return Math.max(0, Math.min(win._effectiveModalCcr, extent - win._surfaceRadius));
}
function _modalConnectorRadius(placement) {
return placement === "right" ? win._effectiveModalEndCcr : win._effectiveModalStartCcr;
}
function _modalConnectorWidth(spacing, placement) {
const isVert = win._modalState.barSide === "left" || win._modalState.barSide === "right";
const radius = win._modalConnectorRadius(placement);
return isVert ? (spacing + radius) : radius;
}
function _modalConnectorHeight(spacing, placement) {
const isVert = win._modalState.barSide === "left" || win._modalState.barSide === "right";
const radius = win._modalConnectorRadius(placement);
return isVert ? radius : (spacing + radius);
}
function _modalConnectorX(baseX, bodyWidth, placement, spacing) {
const barSide = win._modalState.barSide;
const isVert = barSide === "left" || barSide === "right";
const seamX = !isVert ? (placement === "left" ? baseX : baseX + bodyWidth) : (barSide === "left" ? baseX : baseX + bodyWidth);
const w = _modalConnectorWidth(spacing, placement);
if (!isVert)
return placement === "left" ? seamX - w : seamX;
return barSide === "left" ? seamX : seamX - w;
}
function _modalConnectorY(baseY, bodyHeight, placement, spacing) {
const barSide = win._modalState.barSide;
const seamY = barSide === "top" ? baseY : barSide === "bottom" ? baseY + bodyHeight : (placement === "left" ? baseY : baseY + bodyHeight);
const h = _modalConnectorHeight(spacing, placement);
if (barSide === "top")
return seamY;
if (barSide === "bottom")
return seamY - h;
return placement === "left" ? seamY - h : seamY;
}
function _popoutArcExtent() {
return (ConnectedModeState.popoutBarSide === "top" || ConnectedModeState.popoutBarSide === "bottom") ? _popoutBodyBlurAnchor.height : _popoutBodyBlurAnchor.width;
}
@@ -1343,5 +1627,61 @@ PanelWindow {
y: 0
}
}
// Bar-side-bounded clip so modal chrome retracts behind the bar on exit
// instead of sliding over bar widgets (mirrors the popout `_popoutClip`).
Item {
id: _modalClip
visible: _modalBodyBlurAnchor._active
z: 1
readonly property string _modalSide: win._modalState.barSide
readonly property real _inset: _modalBodyBlurAnchor._active && win.screen ? SettingsData.frameEdgeInsetForSide(win.screen, _modalSide) : 0
readonly property real _topBound: _modalSide === "top" ? _inset : 0
readonly property real _bottomBound: _modalSide === "bottom" ? (win.height - _inset) : win.height
readonly property real _leftBound: _modalSide === "left" ? _inset : 0
readonly property real _rightBound: _modalSide === "right" ? (win.width - _inset) : win.width
x: _leftBound
y: _topBound
width: Math.max(0, _rightBound - _leftBound)
height: Math.max(0, _bottomBound - _topBound)
clip: true
Item {
id: _modalChrome
readonly property string _modalSide: win._modalState.barSide
readonly property bool _isHoriz: _modalSide === "top" || _modalSide === "bottom"
readonly property real _startCcr: win._effectiveModalStartCcr
readonly property real _endCcr: win._effectiveModalEndCcr
readonly property real _farExtent: win._effectiveModalFarExtent
readonly property real _bodyOffsetX: _isHoriz ? _startCcr : (_modalSide === "right" ? _farExtent : 0)
readonly property real _bodyOffsetY: _isHoriz ? (_modalSide === "bottom" ? _farExtent : 0) : _startCcr
readonly property real _bodyW: Theme.snap(_modalBodyBlurAnchor.width, win._dpr)
readonly property real _bodyH: Theme.snap(_modalBodyBlurAnchor.height, win._dpr)
x: Theme.snap(_modalBodyBlurAnchor.x - _bodyOffsetX - _modalClip.x, win._dpr)
y: Theme.snap(_modalBodyBlurAnchor.y - _bodyOffsetY - _modalClip.y, win._dpr)
width: _isHoriz ? Theme.snap(_bodyW + _startCcr + _endCcr, win._dpr) : Theme.snap(_bodyW + _farExtent, win._dpr)
height: _isHoriz ? Theme.snap(_bodyH + _farExtent, win._dpr) : Theme.snap(_bodyH + _startCcr + _endCcr, win._dpr)
ConnectedShape {
visible: _modalBodyBlurAnchor._active && _modalChrome._bodyW > 0 && _modalChrome._bodyH > 0
barSide: _modalChrome._modalSide
bodyWidth: _modalChrome._bodyW
bodyHeight: _modalChrome._bodyH
connectorRadius: win._effectiveModalCcr
startConnectorRadius: _modalChrome._startCcr
endConnectorRadius: _modalChrome._endCcr
farStartConnectorRadius: win._effectiveModalFarStartCcr
farEndConnectorRadius: win._effectiveModalFarEndCcr
surfaceRadius: win._surfaceRadius
fillColor: win._opaqueSurfaceColor
x: 0
y: 0
}
}
}
}
}

View File

@@ -0,0 +1,250 @@
import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
Rectangle {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
signal dismissed
readonly property bool currentlyActive: SessionData.doNotDisturb
readonly property real currentRemainingMs: SessionData.doNotDisturbUntil > 0 ? Math.max(0, SessionData.doNotDisturbUntil - nowMs) : 0
property real nowMs: Date.now()
Timer {
interval: 1000
repeat: true
running: root.visible && root.currentlyActive && SessionData.doNotDisturbUntil > 0
onTriggered: root.nowMs = Date.now()
}
function _pad2(n) {
return n < 10 ? "0" + n : "" + n;
}
function formatRemaining(ms) {
if (ms <= 0)
return I18n.tr("Off");
const totalMinutes = Math.ceil(ms / 60000);
if (totalMinutes < 60)
return I18n.tr("%1 min left").arg(totalMinutes);
const hours = Math.floor(totalMinutes / 60);
const mins = totalMinutes - hours * 60;
if (mins === 0)
return I18n.tr("%1 h left").arg(hours);
return I18n.tr("%1 h %2 m left").arg(hours).arg(mins);
}
function formatUntilTimestamp(ts) {
if (!ts)
return "";
const d = new Date(ts);
const hours = d.getHours();
const minutes = d.getMinutes();
const use24h = (typeof SettingsData !== "undefined") ? SettingsData.use24HourClock : true;
if (use24h) {
return _pad2(hours) + ":" + _pad2(minutes);
}
const suffix = hours >= 12 ? "PM" : "AM";
const h12 = ((hours + 11) % 12) + 1;
return h12 + ":" + _pad2(minutes) + " " + suffix;
}
function minutesUntilTomorrowMorning() {
const now = new Date();
const target = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1, 8, 0, 0, 0);
return Math.max(1, Math.round((target.getTime() - now.getTime()) / 60000));
}
readonly property var presetOptions: [
{
"label": I18n.tr("For 15 minutes"),
"minutes": 15
},
{
"label": I18n.tr("For 30 minutes"),
"minutes": 30
},
{
"label": I18n.tr("For 1 hour"),
"minutes": 60
},
{
"label": I18n.tr("For 3 hours"),
"minutes": 180
},
{
"label": I18n.tr("For 8 hours"),
"minutes": 480
},
{
"label": I18n.tr("Until tomorrow, 8:00 AM"),
"minutesFn": true
},
{
"label": I18n.tr("Until I turn it off"),
"minutes": 0
}
]
function selectPreset(option) {
let minutes = option.minutes;
if (option.minutesFn) {
minutes = minutesUntilTomorrowMorning();
}
SessionData.setDoNotDisturb(true, minutes);
root.dismissed();
}
function turnOff() {
SessionData.setDoNotDisturb(false);
root.dismissed();
}
implicitWidth: Math.max(220, menuColumn.implicitWidth + Theme.spacingM * 2)
implicitHeight: menuColumn.implicitHeight + Theme.spacingM * 2
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
radius: Theme.cornerRadius
border.color: BlurService.enabled ? BlurService.borderColor : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: BlurService.enabled ? BlurService.borderWidth : 1
Column {
id: menuColumn
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: Theme.spacingM
spacing: Theme.spacingXS
Row {
width: parent.width
spacing: Theme.spacingS
DankIcon {
name: SessionData.doNotDisturb ? "notifications_off" : "notifications_paused"
size: Theme.iconSize - 2
color: SessionData.doNotDisturb ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Column {
width: parent.width - Theme.iconSize - parent.spacing
anchors.verticalCenter: parent.verticalCenter
spacing: 0
StyledText {
text: I18n.tr("Do Not Disturb")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
elide: Text.ElideRight
width: parent.width
}
StyledText {
visible: root.currentlyActive
text: {
if (SessionData.doNotDisturbUntil > 0) {
return root.formatRemaining(root.currentRemainingMs) + " · " + I18n.tr("until %1").arg(root.formatUntilTimestamp(SessionData.doNotDisturbUntil));
}
return I18n.tr("On indefinitely");
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
elide: Text.ElideRight
width: parent.width
}
}
}
Rectangle {
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.15)
}
Repeater {
model: root.presetOptions
Rectangle {
id: optionRect
required property var modelData
width: menuColumn.width
height: 32
radius: Theme.cornerRadius
color: optionArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
StyledText {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.right: parent.right
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
text: optionRect.modelData.label
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
elide: Text.ElideRight
}
MouseArea {
id: optionArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.selectPreset(optionRect.modelData)
}
}
}
Rectangle {
visible: root.currentlyActive
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.15)
}
Rectangle {
visible: root.currentlyActive
width: menuColumn.width
height: 32
radius: Theme.cornerRadius
color: offArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.14) : "transparent"
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.right: parent.right
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
DankIcon {
anchors.verticalCenter: parent.verticalCenter
name: "notifications_active"
size: Theme.iconSizeSmall
color: offArea.containsMouse ? Theme.error : Theme.surfaceText
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: I18n.tr("Turn off now")
font.pixelSize: Theme.fontSizeSmall
color: offArea.containsMouse ? Theme.error : Theme.surfaceText
font.weight: Font.Medium
}
}
MouseArea {
id: offArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.turnOff()
}
}
}
}

View File

@@ -0,0 +1,88 @@
import QtQuick
import Quickshell
import Quickshell.Wayland
import qs.Common
import qs.Services
import qs.Widgets
PanelWindow {
id: root
WindowBlur {
targetWindow: root
blurX: menu.x
blurY: menu.y
blurWidth: root.visible ? menu.width : 0
blurHeight: root.visible ? menu.height : 0
blurRadius: Theme.cornerRadius
}
WlrLayershell.namespace: "dms:dnd-duration-menu"
WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
color: "transparent"
anchors {
top: true
left: true
right: true
bottom: true
}
property point anchorPos: Qt.point(0, 0)
property string anchorEdge: "top"
visible: false
function showAt(x, y, targetScreen, edge) {
if (targetScreen)
root.screen = targetScreen;
anchorPos = Qt.point(x, y);
anchorEdge = edge || "top";
visible = true;
}
function closeMenu() {
visible = false;
}
Connections {
target: PopoutManager
function onPopoutOpening() {
root.closeMenu();
}
}
MouseArea {
anchors.fill: parent
z: 0
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onClicked: root.closeMenu()
}
DndDurationMenu {
id: menu
z: 1
visible: root.visible
x: {
const left = 10;
const right = root.width - width - 10;
const want = root.anchorPos.x - width / 2;
return Math.max(left, Math.min(right, want));
}
y: {
switch (root.anchorEdge) {
case "bottom":
return Math.max(10, root.anchorPos.y - height);
case "left":
case "right":
return Math.max(10, Math.min(root.height - height - 10, root.anchorPos.y - height / 2));
default:
return Math.min(root.height - height - 10, root.anchorPos.y);
}
}
onDismissed: root.closeMenu()
}
}

View File

@@ -9,12 +9,18 @@ Item {
property var keyboardController: null
property bool showSettings: false
property int currentTab: 0
property bool showDndMenu: false
onCurrentTabChanged: {
if (currentTab === 1 && !SettingsData.notificationHistoryEnabled)
currentTab = 0;
}
onShowSettingsChanged: {
if (showSettings)
showDndMenu = false;
}
Connections {
target: SettingsData
function onNotificationHistoryEnabledChanged() {
@@ -59,8 +65,31 @@ Item {
iconColor: SessionData.doNotDisturb ? Theme.error : Theme.surfaceText
buttonSize: Theme.iconSize + Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
onClicked: SessionData.setDoNotDisturb(!SessionData.doNotDisturb)
onEntered: sharedTooltip.show(I18n.tr("Do Not Disturb"), doNotDisturbButton, 0, 0, "bottom")
onClicked: {
if (SessionData.doNotDisturb) {
SessionData.setDoNotDisturb(false);
return;
}
root.showDndMenu = !root.showDndMenu;
if (root.showDndMenu)
root.showSettings = false;
}
onEntered: sharedTooltip.show(SessionData.doNotDisturb ? I18n.tr("Turn off Do Not Disturb") : I18n.tr("Do Not Disturb"), doNotDisturbButton, 0, 0, "bottom")
onExited: sharedTooltip.hide()
}
DankActionButton {
id: dndScheduleButton
iconName: root.showDndMenu ? "expand_less" : "schedule"
iconColor: root.showDndMenu ? Theme.primary : Theme.surfaceText
buttonSize: Theme.iconSize + Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
onClicked: {
root.showDndMenu = !root.showDndMenu;
if (root.showDndMenu)
root.showSettings = false;
}
onEntered: sharedTooltip.show(I18n.tr("Silence for a while"), dndScheduleButton, 0, 0, "bottom")
onExited: sharedTooltip.hide()
}
}
@@ -139,6 +168,13 @@ Item {
}
}
DndDurationMenu {
id: dndMenu
width: parent.width
visible: root.showDndMenu
onDismissed: root.showDndMenu = false
}
DankButtonGroup {
id: tabGroup
width: parent.width

View File

@@ -360,11 +360,18 @@ PanelWindow {
function _frameEdgeInset(side) {
if (!screen)
return 0;
const edges = SettingsData.getActiveBarEdgesForScreen(screen);
const raw = edges.includes(side) ? SettingsData.frameBarSize : SettingsData.frameThickness;
const raw = SettingsData.frameEdgeInsetForSide(screen, side);
return Math.max(0, Math.round(Theme.px(raw, dpr)));
}
readonly property bool frameOnlyNoConnected: SettingsData.frameEnabled && !connectedFrameMode && !!screen
&& SettingsData.isScreenInPreferences(screen, SettingsData.frameScreenPreferences)
// Frame ON + Connected OFF. frameEdgeInset is the full bar/frame inset
function _frameGapMargin(side) {
return _frameEdgeInset(side) + Theme.popupDistance;
}
function getTopMargin() {
const popupPos = SettingsData.notificationPopupPosition;
const isTop = isTopCenter || popupPos === SettingsData.Position.Top || popupPos === SettingsData.Position.Left;
@@ -377,6 +384,8 @@ PanelWindow {
: (Theme.px(SettingsData.frameRounding, dpr) + Theme.px(Theme.connectedCornerRadius, dpr));
return _frameEdgeInset("top") + cornerClear + screenY;
}
if (frameOnlyNoConnected)
return _frameGapMargin("top") + screenY;
const barInfo = getBarInfo();
const base = barInfo.topBar > 0 ? barInfo.topBar : Theme.popupDistance;
return base + screenY;
@@ -394,6 +403,8 @@ PanelWindow {
: (Theme.px(SettingsData.frameRounding, dpr) + Theme.px(Theme.connectedCornerRadius, dpr));
return _frameEdgeInset("bottom") + cornerClear + screenY;
}
if (frameOnlyNoConnected)
return _frameGapMargin("bottom") + screenY;
const barInfo = getBarInfo();
const base = barInfo.bottomBar > 0 ? barInfo.bottomBar : Theme.popupDistance;
return base + screenY;
@@ -410,6 +421,8 @@ PanelWindow {
if (connectedFrameMode)
return _frameEdgeInset("left");
if (frameOnlyNoConnected)
return _frameGapMargin("left");
const barInfo = getBarInfo();
return barInfo.leftBar > 0 ? barInfo.leftBar : Theme.popupDistance;
}
@@ -425,6 +438,8 @@ PanelWindow {
if (connectedFrameMode)
return _frameEdgeInset("right");
if (frameOnlyNoConnected)
return _frameGapMargin("right");
const barInfo = getBarInfo();
return barInfo.rightBar > 0 ? barInfo.rightBar : Theme.popupDistance;
}

View File

@@ -185,7 +185,7 @@ Popup {
}
contentItem: Rectangle {
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
color: BlurService.enabled ? Theme.surfaceContainer : Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
radius: Theme.cornerRadius
border.color: BlurService.enabled ? BlurService.borderColor : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: BlurService.enabled ? BlurService.borderWidth : 1
@@ -274,7 +274,7 @@ Popup {
}
if (isSelected)
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2);
return menuItemArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent";
return menuItemArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent";
}
opacity: modelData.enabled ? 1 : 0.5

View File

@@ -314,6 +314,22 @@ Item {
opacity: enabled ? 1.0 : 0.5
onToggled: checked => SettingsData.set("frameCloseGaps", checked)
}
SettingsButtonGroupRow {
visible: SettingsData.frameEnabled
settingKey: "frameLauncherEmergeSide"
tags: ["frame", "connected", "launcher", "modal", "emerge", "direction", "bottom", "top"]
text: I18n.tr("Launcher Emerge Side")
description: I18n.tr("Which frame edge the Launcher slides in from. Other modals emerge from the opposite side.")
model: [I18n.tr("Bottom"), I18n.tr("Top")]
currentIndex: SettingsData.frameLauncherEmergeSide === "top" ? 1 : 0
enabled: SettingsData.connectedFrameModeActive
opacity: enabled ? 1.0 : 0.5
onSelectionChanged: (index, selected) => {
if (!selected) return;
SettingsData.set("frameLauncherEmergeSide", index === 1 ? "top" : "bottom");
}
}
}
// ── Display Assignment ────────────────────────────────────────────

View File

@@ -2,6 +2,7 @@ import QtQuick
import Quickshell
import Quickshell.Wayland
import qs.Common
import qs.Services
PanelWindow {
id: root
@@ -71,10 +72,10 @@ PanelWindow {
Rectangle {
anchors.fill: parent
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
color: BlurService.enabled ? Theme.surfaceContainerHigh : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
radius: Theme.cornerRadius
border.width: 1
border.color: Theme.outlineMedium
border.width: BlurService.enabled ? BlurService.borderWidth : 1
border.color: BlurService.enabled ? BlurService.borderColor : Theme.outlineMedium
Text {
id: textContent

View File

@@ -1,6 +1,7 @@
import QtQuick
import QtQuick.Controls
import qs.Common
import qs.Services
Item {
id: root
@@ -111,10 +112,10 @@ Item {
dim: false
background: Rectangle {
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
color: BlurService.enabled ? Theme.surfaceContainerHigh : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
radius: Theme.cornerRadius
border.width: 1
border.color: Theme.outlineMedium
border.width: BlurService.enabled ? BlurService.borderWidth : 1
border.color: BlurService.enabled ? BlurService.borderColor : Theme.outlineMedium
}
contentItem: Text {