1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-29 07:52:50 -05:00

Fractional scaling fixes + bar border settings

This commit is contained in:
bbedward
2025-10-10 12:25:00 -04:00
parent 27885c8ac3
commit 3014fd8095
8 changed files with 512 additions and 106 deletions

View File

@@ -155,6 +155,14 @@ Singleton {
property bool dankBarNoBackground: false property bool dankBarNoBackground: false
property bool dankBarGothCornersEnabled: false property bool dankBarGothCornersEnabled: false
property bool dankBarBorderEnabled: false property bool dankBarBorderEnabled: false
property string dankBarBorderColor: "surfaceText"
property real dankBarBorderOpacity: 1.0
property real dankBarBorderThickness: 1
onDankBarBorderColorChanged: saveSettings()
onDankBarBorderOpacityChanged: saveSettings()
onDankBarBorderThicknessChanged: saveSettings()
property int dankBarPosition: SettingsData.Position.Top property int dankBarPosition: SettingsData.Position.Top
property bool dankBarIsVertical: dankBarPosition === SettingsData.Position.Left || dankBarPosition === SettingsData.Position.Right property bool dankBarIsVertical: dankBarPosition === SettingsData.Position.Left || dankBarPosition === SettingsData.Position.Right
property bool lockScreenShowPowerActions: true property bool lockScreenShowPowerActions: true
@@ -407,6 +415,9 @@ Singleton {
dankBarNoBackground = settings.dankBarNoBackground !== undefined ? settings.dankBarNoBackground : (settings.topBarNoBackground !== undefined ? settings.topBarNoBackground : false) dankBarNoBackground = settings.dankBarNoBackground !== undefined ? settings.dankBarNoBackground : (settings.topBarNoBackground !== undefined ? settings.topBarNoBackground : false)
dankBarGothCornersEnabled = settings.dankBarGothCornersEnabled !== undefined ? settings.dankBarGothCornersEnabled : (settings.topBarGothCornersEnabled !== undefined ? settings.topBarGothCornersEnabled : false) dankBarGothCornersEnabled = settings.dankBarGothCornersEnabled !== undefined ? settings.dankBarGothCornersEnabled : (settings.topBarGothCornersEnabled !== undefined ? settings.topBarGothCornersEnabled : false)
dankBarBorderEnabled = settings.dankBarBorderEnabled !== undefined ? settings.dankBarBorderEnabled : false dankBarBorderEnabled = settings.dankBarBorderEnabled !== undefined ? settings.dankBarBorderEnabled : false
dankBarBorderColor = settings.dankBarBorderColor !== undefined ? settings.dankBarBorderColor : "surfaceText"
dankBarBorderOpacity = settings.dankBarBorderOpacity !== undefined ? settings.dankBarBorderOpacity : 1.0
dankBarBorderThickness = settings.dankBarBorderThickness !== undefined ? settings.dankBarBorderThickness : 1
dankBarPosition = settings.dankBarPosition !== undefined ? settings.dankBarPosition : (settings.dankBarAtBottom !== undefined ? (settings.dankBarAtBottom ? SettingsData.Position.Bottom : SettingsData.Position.Top) : (settings.topBarAtBottom !== undefined ? (settings.topBarAtBottom ? SettingsData.Position.Bottom : SettingsData.Position.Top) : SettingsData.Position.Top)) dankBarPosition = settings.dankBarPosition !== undefined ? settings.dankBarPosition : (settings.dankBarAtBottom !== undefined ? (settings.dankBarAtBottom ? SettingsData.Position.Bottom : SettingsData.Position.Top) : (settings.topBarAtBottom !== undefined ? (settings.topBarAtBottom ? SettingsData.Position.Bottom : SettingsData.Position.Top) : SettingsData.Position.Top))
lockScreenShowPowerActions = settings.lockScreenShowPowerActions !== undefined ? settings.lockScreenShowPowerActions : true lockScreenShowPowerActions = settings.lockScreenShowPowerActions !== undefined ? settings.lockScreenShowPowerActions : true
hideBrightnessSlider = settings.hideBrightnessSlider !== undefined ? settings.hideBrightnessSlider : false hideBrightnessSlider = settings.hideBrightnessSlider !== undefined ? settings.hideBrightnessSlider : false
@@ -533,6 +544,9 @@ Singleton {
"dankBarNoBackground": dankBarNoBackground, "dankBarNoBackground": dankBarNoBackground,
"dankBarGothCornersEnabled": dankBarGothCornersEnabled, "dankBarGothCornersEnabled": dankBarGothCornersEnabled,
"dankBarBorderEnabled": dankBarBorderEnabled, "dankBarBorderEnabled": dankBarBorderEnabled,
"dankBarBorderColor": dankBarBorderColor,
"dankBarBorderOpacity": dankBarBorderOpacity,
"dankBarBorderThickness": dankBarBorderThickness,
"dankBarPosition": dankBarPosition, "dankBarPosition": dankBarPosition,
"lockScreenShowPowerActions": lockScreenShowPowerActions, "lockScreenShowPowerActions": lockScreenShowPowerActions,
"hideBrightnessSlider": hideBrightnessSlider, "hideBrightnessSlider": hideBrightnessSlider,

View File

@@ -685,7 +685,17 @@ Singleton {
function withAlpha(c, a) { return Qt.rgba(c.r, c.g, c.b, a); } function withAlpha(c, a) { return Qt.rgba(c.r, c.g, c.b, a); }
function snap(value, dpr) { function snap(value, dpr) {
return Math.round(value * dpr) / dpr const s = dpr || 1
return Math.round(value * s) / s
}
function px(value, dpr) {
const s = dpr || 1
return Math.round(value * s) / s
}
function hairline(dpr) {
return 1 / (dpr || 1)
} }
function invertHex(hex) { function invertHex(hex) {

View File

@@ -1,7 +1,9 @@
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Hyprland
import Quickshell.Wayland import Quickshell.Wayland
import qs.Common import qs.Common
import qs.Services
PanelWindow { PanelWindow {
id: root id: root
@@ -14,14 +16,16 @@ PanelWindow {
property real height: 300 property real height: 300
readonly property real screenWidth: screen ? screen.width : 1920 readonly property real screenWidth: screen ? screen.width : 1920
readonly property real screenHeight: screen ? screen.height : 1080 readonly property real screenHeight: screen ? screen.height : 1080
readonly property real dpr: (screen && screen.devicePixelRatio) || 1 readonly property real dpr: {
if (CompositorService.isNiri && screen) {
function snap(v) { const niriScale = NiriService.displayScales[screen.name]
return Math.round(v * dpr) / dpr if (niriScale !== undefined) return niriScale
} }
if (CompositorService.isHyprland && screen) {
function px(v) { const hyprlandMonitor = Hyprland.monitors.values.find(m => m.name === screen.name)
return Math.round(v) if (hyprlandMonitor?.scale !== undefined) return hyprlandMonitor.scale
}
return (screen?.devicePixelRatio) || 1
} }
property bool showBackground: true property bool showBackground: true
property real backgroundOpacity: 0.5 property real backgroundOpacity: 0.5
@@ -142,26 +146,26 @@ PanelWindow {
Rectangle { Rectangle {
id: contentContainer id: contentContainer
width: px(root.width) width: Theme.px(root.width, dpr)
height: px(root.height) height: Theme.px(root.height, dpr)
anchors.centerIn: undefined anchors.centerIn: undefined
x: { x: {
if (positioning === "center") { if (positioning === "center") {
return snap((root.screenWidth - width) / 2) return Theme.snap((root.screenWidth - width) / 2, dpr)
} else if (positioning === "top-right") { } else if (positioning === "top-right") {
return px(Math.max(Theme.spacingL, root.screenWidth - width - Theme.spacingL)) return Theme.px(Math.max(Theme.spacingL, root.screenWidth - width - Theme.spacingL), dpr)
} else if (positioning === "custom") { } else if (positioning === "custom") {
return snap(root.customPosition.x) return Theme.snap(root.customPosition.x, dpr)
} }
return 0 return 0
} }
y: { y: {
if (positioning === "center") { if (positioning === "center") {
return snap((root.screenHeight - height) / 2) return Theme.snap((root.screenHeight - height) / 2, dpr)
} else if (positioning === "top-right") { } else if (positioning === "top-right") {
return px(Theme.barHeight + Theme.spacingXS) return Theme.px(Theme.barHeight + Theme.spacingXS, dpr)
} else if (positioning === "custom") { } else if (positioning === "custom") {
return snap(root.customPosition.y) return Theme.snap(root.customPosition.y, dpr)
} }
return 0 return 0
} }
@@ -170,6 +174,7 @@ PanelWindow {
border.color: root.borderColor border.color: root.borderColor
border.width: root.borderWidth border.width: root.borderWidth
clip: false clip: false
layer.enabled: true
opacity: root.shouldBeVisible ? 1 : 0 opacity: root.shouldBeVisible ? 1 : 0
transform: root.animationType === "slide" ? slideTransform : null transform: root.animationType === "slide" ? slideTransform : null
@@ -179,8 +184,8 @@ PanelWindow {
readonly property real rawX: root.shouldBeVisible ? 0 : 15 readonly property real rawX: root.shouldBeVisible ? 0 : 15
readonly property real rawY: root.shouldBeVisible ? 0 : -30 readonly property real rawY: root.shouldBeVisible ? 0 : -30
x: snap(rawX) x: Theme.snap(rawX, root.dpr)
y: snap(rawY) y: Theme.snap(rawY, root.dpr)
} }
Behavior on opacity { Behavior on opacity {

View File

@@ -25,7 +25,7 @@ Item {
readonly property real correctWidth: root.width readonly property real correctWidth: root.width
readonly property real correctHeight: root.height readonly property real correctHeight: root.height
canvasSize: Qt.size(barWindow.px(correctWidth), barWindow.px(correctHeight)) canvasSize: Qt.size(Math.ceil(correctWidth), Math.ceil(correctHeight))
property real wing: SettingsData.dankBarGothCornersEnabled ? barWindow._wingR : 0 property real wing: SettingsData.dankBarGothCornersEnabled ? barWindow._wingR : 0
property real rt: SettingsData.dankBarSquareCorners ? 0 : Theme.cornerRadius property real rt: SettingsData.dankBarSquareCorners ? 0 : Theme.cornerRadius
@@ -40,7 +40,6 @@ Item {
Connections { Connections {
target: barWindow target: barWindow
function on_BgColorChanged() { barShape.requestPaint() } function on_BgColorChanged() { barShape.requestPaint() }
function on_DprChanged() { barShape.requestPaint() }
} }
Connections { Connections {
@@ -50,19 +49,16 @@ Item {
onPaint: { onPaint: {
const ctx = getContext("2d") const ctx = getContext("2d")
const scale = barWindow._dpr const W = barWindow.isVertical ? correctHeight : correctWidth
const W = barWindow.px(barWindow.isVertical ? correctHeight : correctWidth) const H_raw = barWindow.isVertical ? correctWidth : correctHeight
const H_raw = barWindow.px(barWindow.isVertical ? correctWidth : correctHeight) const R = wing
const R = barWindow.px(wing) const RT = rt
const RT = barWindow.px(rt)
const H = H_raw - (R > 0 ? R : 0) const H = H_raw - (R > 0 ? R : 0)
const isTop = SettingsData.dankBarPosition === SettingsData.Position.Top const isTop = SettingsData.dankBarPosition === SettingsData.Position.Top
const isBottom = SettingsData.dankBarPosition === SettingsData.Position.Bottom const isBottom = SettingsData.dankBarPosition === SettingsData.Position.Bottom
const isLeft = SettingsData.dankBarPosition === SettingsData.Position.Left const isLeft = SettingsData.dankBarPosition === SettingsData.Position.Left
const isRight = SettingsData.dankBarPosition === SettingsData.Position.Right const isRight = SettingsData.dankBarPosition === SettingsData.Position.Right
ctx.scale(scale, scale)
function drawTopPath() { function drawTopPath() {
ctx.beginPath() ctx.beginPath()
ctx.moveTo(RT, 0) ctx.moveTo(RT, 0)
@@ -89,7 +85,7 @@ Item {
} }
ctx.reset() ctx.reset()
ctx.clearRect(0, 0, W, H_raw) ctx.clearRect(0, 0, Math.ceil(W), Math.ceil(H_raw))
ctx.save() ctx.save()
if (isBottom) { if (isBottom) {
@@ -120,7 +116,7 @@ Item {
readonly property real correctWidth: root.width readonly property real correctWidth: root.width
readonly property real correctHeight: root.height readonly property real correctHeight: root.height
canvasSize: Qt.size(barWindow.px(correctWidth), barWindow.px(correctHeight)) canvasSize: Qt.size(Math.ceil(correctWidth), Math.ceil(correctHeight))
property real wing: SettingsData.dankBarGothCornersEnabled ? barWindow._wingR : 0 property real wing: SettingsData.dankBarGothCornersEnabled ? barWindow._wingR : 0
property real rt: SettingsData.dankBarSquareCorners ? 0 : Theme.cornerRadius property real rt: SettingsData.dankBarSquareCorners ? 0 : Theme.cornerRadius
@@ -137,7 +133,6 @@ Item {
Connections { Connections {
target: barWindow target: barWindow
function on_BgColorChanged() { barTint.requestPaint() } function on_BgColorChanged() { barTint.requestPaint() }
function on_DprChanged() { barTint.requestPaint() }
} }
Connections { Connections {
@@ -147,19 +142,16 @@ Item {
onPaint: { onPaint: {
const ctx = getContext("2d") const ctx = getContext("2d")
const scale = barWindow._dpr const W = barWindow.isVertical ? correctHeight : correctWidth
const W = barWindow.px(barWindow.isVertical ? correctHeight : correctWidth) const H_raw = barWindow.isVertical ? correctWidth : correctHeight
const H_raw = barWindow.px(barWindow.isVertical ? correctWidth : correctHeight) const R = wing
const R = barWindow.px(wing) const RT = rt
const RT = barWindow.px(rt)
const H = H_raw - (R > 0 ? R : 0) const H = H_raw - (R > 0 ? R : 0)
const isTop = SettingsData.dankBarPosition === SettingsData.Position.Top const isTop = SettingsData.dankBarPosition === SettingsData.Position.Top
const isBottom = SettingsData.dankBarPosition === SettingsData.Position.Bottom const isBottom = SettingsData.dankBarPosition === SettingsData.Position.Bottom
const isLeft = SettingsData.dankBarPosition === SettingsData.Position.Left const isLeft = SettingsData.dankBarPosition === SettingsData.Position.Left
const isRight = SettingsData.dankBarPosition === SettingsData.Position.Right const isRight = SettingsData.dankBarPosition === SettingsData.Position.Right
ctx.scale(scale, scale)
function drawTopPath() { function drawTopPath() {
ctx.beginPath() ctx.beginPath()
ctx.moveTo(RT, 0) ctx.moveTo(RT, 0)
@@ -186,7 +178,7 @@ Item {
} }
ctx.reset() ctx.reset()
ctx.clearRect(0, 0, W, H_raw) ctx.clearRect(0, 0, Math.ceil(W), Math.ceil(H_raw))
ctx.save() ctx.save()
if (isBottom) { if (isBottom) {
@@ -211,14 +203,14 @@ Item {
Canvas { Canvas {
id: barBorder id: barBorder
anchors.fill: parent anchors.fill: parent
antialiasing: true antialiasing: false
visible: SettingsData.dankBarBorderEnabled visible: SettingsData.dankBarBorderEnabled
renderTarget: Canvas.FramebufferObject renderTarget: Canvas.FramebufferObject
renderStrategy: Canvas.Cooperative renderStrategy: Canvas.Cooperative
readonly property real correctWidth: root.width readonly property real correctWidth: root.width
readonly property real correctHeight: root.height readonly property real correctHeight: root.height
canvasSize: Qt.size(barWindow.px(correctWidth), barWindow.px(correctHeight)) canvasSize: Qt.size(Math.ceil(correctWidth), Math.ceil(correctHeight))
property real wing: SettingsData.dankBarGothCornersEnabled ? barWindow._wingR : 0 property real wing: SettingsData.dankBarGothCornersEnabled ? barWindow._wingR : 0
property real rt: SettingsData.dankBarSquareCorners ? 0 : Theme.cornerRadius property real rt: SettingsData.dankBarSquareCorners ? 0 : Theme.cornerRadius
@@ -232,32 +224,28 @@ Item {
onVisibleChanged: if (visible) requestPaint() onVisibleChanged: if (visible) requestPaint()
Component.onCompleted: requestPaint() Component.onCompleted: requestPaint()
Connections {
target: barWindow
function on_DprChanged() { barBorder.requestPaint() }
}
Connections { Connections {
target: Theme target: Theme
function onSecondaryChanged() { barBorder.requestPaint() } function onIsLightModeChanged() { barBorder.requestPaint() }
} }
Connections { Connections {
target: SettingsData target: SettingsData
function onDankBarBorderColorChanged() { barBorder.requestPaint() }
function onDankBarBorderOpacityChanged() { barBorder.requestPaint() }
function onDankBarBorderThicknessChanged() { barBorder.requestPaint() }
function onDankBarSpacingChanged() { barBorder.requestPaint() } function onDankBarSpacingChanged() { barBorder.requestPaint() }
function onDankBarSquareCornersChanged() { barBorder.requestPaint() } function onDankBarSquareCornersChanged() { barBorder.requestPaint() }
function onCornerRadiusChanged() { barBorder.requestPaint() }
} }
onPaint: { onPaint: {
if (!borderEnabled) return if (!borderEnabled) return
const ctx = getContext("2d") const ctx = getContext("2d")
const scale = barWindow._dpr const W = barWindow.isVertical ? correctHeight : correctWidth
const W = barWindow.px(barWindow.isVertical ? correctHeight : correctWidth) const H_raw = barWindow.isVertical ? correctWidth : correctHeight
const H_raw = barWindow.px(barWindow.isVertical ? correctWidth : correctHeight) const R = wing
const R = barWindow.px(wing) const RT = rt
const RT = barWindow.px(rt)
const H = H_raw - (R > 0 ? R : 0) const H = H_raw - (R > 0 ? R : 0)
const isTop = SettingsData.dankBarPosition === SettingsData.Position.Top const isTop = SettingsData.dankBarPosition === SettingsData.Position.Top
const isBottom = SettingsData.dankBarPosition === SettingsData.Position.Bottom const isBottom = SettingsData.dankBarPosition === SettingsData.Position.Bottom
@@ -267,8 +255,6 @@ Item {
const spacing = SettingsData.dankBarSpacing const spacing = SettingsData.dankBarSpacing
const hasEdgeGap = spacing > 0 || RT > 0 const hasEdgeGap = spacing > 0 || RT > 0
ctx.scale(scale, scale)
function drawTopBorder() { function drawTopBorder() {
ctx.beginPath() ctx.beginPath()
@@ -302,7 +288,7 @@ Item {
} }
ctx.reset() ctx.reset()
ctx.clearRect(0, 0, W, H_raw) ctx.clearRect(0, 0, Math.ceil(W), Math.ceil(H_raw))
ctx.save() ctx.save()
if (isBottom) { if (isBottom) {
@@ -319,8 +305,16 @@ Item {
drawTopBorder() drawTopBorder()
ctx.restore() ctx.restore()
ctx.lineWidth = 1 const key = SettingsData.dankBarBorderColor || "surfaceText"
ctx.strokeStyle = Theme.secondary const base = (key === "surfaceText") ? Theme.surfaceText
: (key === "primary") ? Theme.primary
: Theme.secondary
const color = Theme.withAlpha(base, SettingsData.dankBarBorderOpacity ?? 1.0)
const thickness = Math.max(1, SettingsData.dankBarBorderThickness ?? 1)
ctx.globalCompositeOperation = "source-over"
ctx.lineWidth = thickness
ctx.strokeStyle = color
ctx.stroke() ctx.stroke()
} }
} }

View File

@@ -3,6 +3,7 @@ import QtQuick.Controls
import QtQuick.Effects import QtQuick.Effects
import QtQuick.Shapes import QtQuick.Shapes
import Quickshell import Quickshell
import Quickshell.Hyprland
import Quickshell.Io import Quickshell.Io
import Quickshell.Services.Mpris import Quickshell.Services.Mpris
import Quickshell.Services.Notifications import Quickshell.Services.Notifications
@@ -61,8 +62,17 @@ Item {
property real wingtipsRadius: Theme.cornerRadius property real wingtipsRadius: Theme.cornerRadius
readonly property real _wingR: Math.max(0, wingtipsRadius) readonly property real _wingR: Math.max(0, wingtipsRadius)
readonly property color _bgColor: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, topBarCore?.backgroundTransparency ?? SettingsData.dankBarTransparency) readonly property color _bgColor: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, topBarCore?.backgroundTransparency ?? SettingsData.dankBarTransparency)
readonly property real _dpr: (barWindow.screen && barWindow.screen.devicePixelRatio) ? barWindow.screen.devicePixelRatio : 1 readonly property real _dpr: {
function px(v) { return Math.round(v * _dpr) / _dpr } if (CompositorService.isNiri && barWindow.screen) {
const niriScale = NiriService.displayScales[barWindow.screen.name]
if (niriScale !== undefined) return niriScale
}
if (CompositorService.isHyprland && barWindow.screen) {
const hyprlandMonitor = Hyprland.monitors.values.find(m => m.name === barWindow.screen.name)
if (hyprlandMonitor?.scale !== undefined) return hyprlandMonitor.scale
}
return (barWindow.screen?.devicePixelRatio) || 1
}
property string screenName: modelData.name property string screenName: modelData.name
readonly property int notificationCount: NotificationService.notifications.length readonly property int notificationCount: NotificationService.notifications.length
@@ -70,8 +80,8 @@ Item {
readonly property real widgetThickness: Math.max(20, 26 + SettingsData.dankBarInnerPadding * 0.6) readonly property real widgetThickness: Math.max(20, 26 + SettingsData.dankBarInnerPadding * 0.6)
screen: modelData screen: modelData
implicitHeight: !isVertical ? px(effectiveBarThickness + SettingsData.dankBarSpacing + (SettingsData.dankBarGothCornersEnabled ? _wingR : 0)) : 0 implicitHeight: !isVertical ? Theme.px(effectiveBarThickness + SettingsData.dankBarSpacing + (SettingsData.dankBarGothCornersEnabled ? _wingR : 0), _dpr) : 0
implicitWidth: isVertical ? px(effectiveBarThickness + SettingsData.dankBarSpacing + (SettingsData.dankBarGothCornersEnabled ? _wingR : 0)) : 0 implicitWidth: isVertical ? Theme.px(effectiveBarThickness + SettingsData.dankBarSpacing + (SettingsData.dankBarGothCornersEnabled ? _wingR : 0), _dpr) : 0
color: "transparent" color: "transparent"
property var nativeInhibitor: null property var nativeInhibitor: null
@@ -234,7 +244,7 @@ Item {
Item { Item {
id: inputMask id: inputMask
readonly property int barThickness: px(barWindow.effectiveBarThickness + SettingsData.dankBarSpacing) readonly property int barThickness: Theme.px(barWindow.effectiveBarThickness + SettingsData.dankBarSpacing, barWindow._dpr)
readonly property bool inOverviewWithShow: CompositorService.isNiri && NiriService.inOverview && SettingsData.dankBarOpenOnOverview readonly property bool inOverviewWithShow: CompositorService.isNiri && NiriService.inOverview && SettingsData.dankBarOpenOnOverview
readonly property bool effectiveVisible: SettingsData.dankBarVisible || inOverviewWithShow readonly property bool effectiveVisible: SettingsData.dankBarVisible || inOverviewWithShow
@@ -367,8 +377,8 @@ Item {
id: topBarMouseArea id: topBarMouseArea
y: !barWindow.isVertical ? (SettingsData.dankBarPosition === SettingsData.Position.Bottom ? parent.height - height : 0) : 0 y: !barWindow.isVertical ? (SettingsData.dankBarPosition === SettingsData.Position.Bottom ? parent.height - height : 0) : 0
x: barWindow.isVertical ? (SettingsData.dankBarPosition === SettingsData.Position.Right ? parent.width - width : 0) : 0 x: barWindow.isVertical ? (SettingsData.dankBarPosition === SettingsData.Position.Right ? parent.width - width : 0) : 0
height: !barWindow.isVertical ? px(barWindow.effectiveBarThickness + SettingsData.dankBarSpacing) : undefined height: !barWindow.isVertical ? Theme.px(barWindow.effectiveBarThickness + SettingsData.dankBarSpacing, barWindow._dpr) : undefined
width: barWindow.isVertical ? px(barWindow.effectiveBarThickness + SettingsData.dankBarSpacing) : undefined width: barWindow.isVertical ? Theme.px(barWindow.effectiveBarThickness + SettingsData.dankBarSpacing, barWindow._dpr) : undefined
anchors { anchors {
left: !barWindow.isVertical ? parent.left : (SettingsData.dankBarPosition === SettingsData.Position.Left ? parent.left : undefined) left: !barWindow.isVertical ? parent.left : (SettingsData.dankBarPosition === SettingsData.Position.Left ? parent.left : undefined)
right: !barWindow.isVertical ? parent.right : (SettingsData.dankBarPosition === SettingsData.Position.Right ? parent.right : undefined) right: !barWindow.isVertical ? parent.right : (SettingsData.dankBarPosition === SettingsData.Position.Right ? parent.right : undefined)
@@ -387,8 +397,8 @@ Item {
transform: Translate { transform: Translate {
id: topBarSlide id: topBarSlide
x: barWindow.isVertical ? px(topBarCore.reveal ? 0 : (SettingsData.dankBarPosition === SettingsData.Position.Right ? barWindow.implicitWidth : -barWindow.implicitWidth)) : 0 x: barWindow.isVertical ? Theme.snap(topBarCore.reveal ? 0 : (SettingsData.dankBarPosition === SettingsData.Position.Right ? barWindow.implicitWidth : -barWindow.implicitWidth), barWindow._dpr) : 0
y: !barWindow.isVertical ? px(topBarCore.reveal ? 0 : (SettingsData.dankBarPosition === SettingsData.Position.Bottom ? barWindow.implicitHeight : -barWindow.implicitHeight)) : 0 y: !barWindow.isVertical ? Theme.snap(topBarCore.reveal ? 0 : (SettingsData.dankBarPosition === SettingsData.Position.Bottom ? barWindow.implicitHeight : -barWindow.implicitHeight), barWindow._dpr) : 0
Behavior on x { Behavior on x {
NumberAnimation { NumberAnimation {
@@ -408,10 +418,10 @@ Item {
Item { Item {
id: barUnitInset id: barUnitInset
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: !barWindow.isVertical ? px(SettingsData.dankBarSpacing) : (axis.edge === "left" ? px(SettingsData.dankBarSpacing) : 0) anchors.leftMargin: !barWindow.isVertical ? Theme.px(SettingsData.dankBarSpacing, barWindow._dpr) : (axis.edge === "left" ? Theme.px(SettingsData.dankBarSpacing, barWindow._dpr) : 0)
anchors.rightMargin: !barWindow.isVertical ? px(SettingsData.dankBarSpacing) : (axis.edge === "right" ? px(SettingsData.dankBarSpacing) : 0) anchors.rightMargin: !barWindow.isVertical ? Theme.px(SettingsData.dankBarSpacing, barWindow._dpr) : (axis.edge === "right" ? Theme.px(SettingsData.dankBarSpacing, barWindow._dpr) : 0)
anchors.topMargin: barWindow.isVertical ? px(SettingsData.dankBarSpacing) : (axis.outerVisualEdge() === "bottom" ? 0 : px(SettingsData.dankBarSpacing)) anchors.topMargin: barWindow.isVertical ? Theme.px(SettingsData.dankBarSpacing, barWindow._dpr) : (axis.outerVisualEdge() === "bottom" ? 0 : Theme.px(SettingsData.dankBarSpacing, barWindow._dpr))
anchors.bottomMargin: barWindow.isVertical ? px(SettingsData.dankBarSpacing) : (axis.outerVisualEdge() === "bottom" ? px(SettingsData.dankBarSpacing) : 0) anchors.bottomMargin: barWindow.isVertical ? Theme.px(SettingsData.dankBarSpacing, barWindow._dpr) : (axis.outerVisualEdge() === "bottom" ? Theme.px(SettingsData.dankBarSpacing, barWindow._dpr) : 0)
BarCanvas { BarCanvas {
id: barBackground id: barBackground

View File

@@ -942,14 +942,51 @@ Item {
width: parent.width width: parent.width
spacing: Theme.spacingS spacing: Theme.spacingS
StyledText { Row {
text: I18n.tr("Edge Spacing (0 = edge-to-edge)") width: parent.width
font.pixelSize: Theme.fontSizeSmall spacing: Theme.spacingS
color: Theme.surfaceText
font.weight: Font.Medium StyledText {
text: I18n.tr("Edge Spacing (0 = edge-to-edge)")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
Item {
width: parent.width - edgeSpacingText.implicitWidth - resetEdgeSpacingBtn.width - Theme.spacingS - Theme.spacingM
height: 1
StyledText {
id: edgeSpacingText
visible: false
text: I18n.tr("Edge Spacing (0 = edge-to-edge)")
font.pixelSize: Theme.fontSizeSmall
}
}
DankActionButton {
id: resetEdgeSpacingBtn
buttonSize: 20
iconName: "refresh"
iconSize: 12
backgroundColor: Theme.surfaceContainerHigh
iconColor: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
onClicked: {
SettingsData.setDankBarSpacing(4)
}
}
Item {
width: Theme.spacingS
height: 1
}
} }
DankSlider { DankSlider {
id: edgeSpacingSlider
width: parent.width width: parent.width
height: 24 height: 24
value: SettingsData.dankBarSpacing value: SettingsData.dankBarSpacing
@@ -963,6 +1000,13 @@ Item {
SettingsData.setDankBarSpacing( SettingsData.setDankBarSpacing(
newValue) newValue)
} }
Binding {
target: edgeSpacingSlider
property: "value"
value: SettingsData.dankBarSpacing
restoreMode: Binding.RestoreBinding
}
} }
} }
@@ -970,19 +1014,56 @@ Item {
width: parent.width width: parent.width
spacing: Theme.spacingS spacing: Theme.spacingS
StyledText { Row {
text: I18n.tr("Exclusive Zone Offset") width: parent.width
font.pixelSize: Theme.fontSizeSmall spacing: Theme.spacingS
color: Theme.surfaceText
font.weight: Font.Medium StyledText {
text: I18n.tr("Exclusive Zone Offset")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
Item {
width: parent.width - exclusiveZoneText.implicitWidth - resetExclusiveZoneBtn.width - Theme.spacingS - Theme.spacingM
height: 1
StyledText {
id: exclusiveZoneText
visible: false
text: I18n.tr("Exclusive Zone Offset")
font.pixelSize: Theme.fontSizeSmall
}
}
DankActionButton {
id: resetExclusiveZoneBtn
buttonSize: 20
iconName: "refresh"
iconSize: 12
backgroundColor: Theme.surfaceContainerHigh
iconColor: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
onClicked: {
SettingsData.setDankBarBottomGap(0)
}
}
Item {
width: Theme.spacingS
height: 1
}
} }
DankSlider { DankSlider {
id: exclusiveZoneSlider
width: parent.width width: parent.width
height: 24 height: 24
value: SettingsData.dankBarBottomGap value: SettingsData.dankBarBottomGap
minimum: -100 minimum: -50
maximum: 100 maximum: 50
unit: "" unit: ""
showValue: true showValue: true
wheelEnabled: false wheelEnabled: false
@@ -991,6 +1072,13 @@ Item {
SettingsData.setDankBarBottomGap( SettingsData.setDankBarBottomGap(
newValue) newValue)
} }
Binding {
target: exclusiveZoneSlider
property: "value"
value: SettingsData.dankBarBottomGap
restoreMode: Binding.RestoreBinding
}
} }
} }
@@ -998,14 +1086,51 @@ Item {
width: parent.width width: parent.width
spacing: Theme.spacingS spacing: Theme.spacingS
StyledText { Row {
text: I18n.tr("Size") width: parent.width
font.pixelSize: Theme.fontSizeSmall spacing: Theme.spacingS
color: Theme.surfaceText
font.weight: Font.Medium StyledText {
text: I18n.tr("Size")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
Item {
width: parent.width - sizeText.implicitWidth - resetSizeBtn.width - Theme.spacingS - Theme.spacingM
height: 1
StyledText {
id: sizeText
visible: false
text: I18n.tr("Size")
font.pixelSize: Theme.fontSizeSmall
}
}
DankActionButton {
id: resetSizeBtn
buttonSize: 20
iconName: "refresh"
iconSize: 12
backgroundColor: Theme.surfaceContainerHigh
iconColor: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
onClicked: {
SettingsData.setDankBarInnerPadding(4)
}
}
Item {
width: Theme.spacingS
height: 1
}
} }
DankSlider { DankSlider {
id: sizeSlider
width: parent.width width: parent.width
height: 24 height: 24
value: SettingsData.dankBarInnerPadding value: SettingsData.dankBarInnerPadding
@@ -1019,6 +1144,13 @@ Item {
SettingsData.setDankBarInnerPadding( SettingsData.setDankBarInnerPadding(
newValue) newValue)
} }
Binding {
target: sizeSlider
property: "value"
value: SettingsData.dankBarInnerPadding
restoreMode: Binding.RestoreBinding
}
} }
} }
@@ -1056,14 +1188,227 @@ Item {
} }
} }
DankToggle { Column {
width: parent.width width: parent.width
text: I18n.tr("Border") spacing: Theme.spacingM
description: "Add a 1px border to the bar. Smart edge detection only shows border on exposed sides."
checked: SettingsData.dankBarBorderEnabled DankToggle {
onToggled: checked => { width: parent.width
SettingsData.setDankBarBorderEnabled(checked) text: I18n.tr("Border")
} description: "Add a 1px border to the bar. Smart edge detection only shows border on exposed sides."
checked: SettingsData.dankBarBorderEnabled
onToggled: checked => {
SettingsData.setDankBarBorderEnabled(checked)
}
}
Column {
width: parent.width
leftPadding: Theme.spacingM
spacing: Theme.spacingM
visible: SettingsData.dankBarBorderEnabled
Rectangle {
width: parent.width - parent.leftPadding
height: 1
color: Theme.outline
opacity: 0.2
}
Row {
width: parent.width - parent.leftPadding
spacing: Theme.spacingM
Column {
width: parent.width - borderColorGroup.width - Theme.spacingM
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Border Color")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
}
StyledText {
text: I18n.tr("Choose the border accent color")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: parent.width
}
}
DankButtonGroup {
id: borderColorGroup
anchors.verticalCenter: parent.verticalCenter
model: ["Surface", "Secondary", "Primary"]
currentIndex: {
const colorOption = SettingsData.dankBarBorderColor || "surfaceText"
switch (colorOption) {
case "surfaceText": return 0
case "secondary": return 1
case "primary": return 2
default: return 0
}
}
onSelectionChanged: (index, selected) => {
if (selected) {
let newColor = "surfaceText"
switch (index) {
case 0: newColor = "surfaceText"; break
case 1: newColor = "secondary"; break
case 2: newColor = "primary"; break
}
if (SettingsData.dankBarBorderColor !== newColor) {
SettingsData.dankBarBorderColor = newColor
}
}
}
}
}
Column {
width: parent.width - parent.leftPadding
spacing: Theme.spacingS
Row {
width: parent.width
spacing: Theme.spacingS
StyledText {
text: I18n.tr("Border Opacity")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
Item {
width: parent.width - borderOpacityText.implicitWidth - resetBorderOpacityBtn.width - Theme.spacingS - Theme.spacingM
height: 1
StyledText {
id: borderOpacityText
visible: false
text: I18n.tr("Border Opacity")
font.pixelSize: Theme.fontSizeSmall
}
}
DankActionButton {
id: resetBorderOpacityBtn
buttonSize: 20
iconName: "refresh"
iconSize: 12
backgroundColor: Theme.surfaceContainerHigh
iconColor: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
onClicked: {
SettingsData.dankBarBorderOpacity = 1.0
}
}
Item {
width: Theme.spacingS
height: 1
}
}
DankSlider {
id: borderOpacitySlider
width: parent.width
height: 24
value: (SettingsData.dankBarBorderOpacity ?? 1.0) * 100
minimum: 0
maximum: 100
unit: "%"
showValue: true
wheelEnabled: false
thumbOutlineColor: Theme.surfaceContainerHigh
onSliderValueChanged: newValue => {
SettingsData.dankBarBorderOpacity = newValue / 100
}
Binding {
target: borderOpacitySlider
property: "value"
value: (SettingsData.dankBarBorderOpacity ?? 1.0) * 100
restoreMode: Binding.RestoreBinding
}
}
}
Column {
width: parent.width - parent.leftPadding
spacing: Theme.spacingS
Row {
width: parent.width
spacing: Theme.spacingS
StyledText {
text: I18n.tr("Border Thickness")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
Item {
width: parent.width - borderThicknessText.implicitWidth - resetBorderThicknessBtn.width - Theme.spacingS - Theme.spacingM
height: 1
StyledText {
id: borderThicknessText
visible: false
text: I18n.tr("Border Thickness")
font.pixelSize: Theme.fontSizeSmall
}
}
DankActionButton {
id: resetBorderThicknessBtn
buttonSize: 20
iconName: "refresh"
iconSize: 12
backgroundColor: Theme.surfaceContainerHigh
iconColor: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
onClicked: {
SettingsData.dankBarBorderThickness = 1
}
}
Item {
width: Theme.spacingS
height: 1
}
}
DankSlider {
id: borderThicknessSlider
width: parent.width
height: 24
value: SettingsData.dankBarBorderThickness ?? 1
minimum: 1
maximum: 10
unit: "px"
showValue: true
wheelEnabled: false
thumbOutlineColor: Theme.surfaceContainerHigh
onSliderValueChanged: newValue => {
SettingsData.dankBarBorderThickness = newValue
}
Binding {
target: borderThicknessSlider
property: "value"
value: SettingsData.dankBarBorderThickness ?? 1
restoreMode: Binding.RestoreBinding
}
}
}
}
} }
Rectangle { Rectangle {

View File

@@ -23,6 +23,7 @@ Singleton {
property var outputs: ({}) property var outputs: ({})
property var windows: [] property var windows: []
property var displayScales: ({})
property bool inOverview: false property bool inOverview: false
@@ -68,6 +69,7 @@ Singleton {
const outputsData = JSON.parse(text) const outputsData = JSON.parse(text)
outputs = outputsData outputs = outputsData
console.log("NiriService: Loaded", Object.keys(outputsData).length, "outputs") console.log("NiriService: Loaded", Object.keys(outputsData).length, "outputs")
updateDisplayScales()
if (windows.length > 0) { if (windows.length > 0) {
windows = sortWindowsByLayout(windows) windows = sortWindowsByLayout(windows)
} }
@@ -169,6 +171,20 @@ Singleton {
outputsProcess.running = true outputsProcess.running = true
} }
function updateDisplayScales() {
if (!outputs || Object.keys(outputs).length === 0) return
const scales = {}
for (const outputName in outputs) {
const output = outputs[outputName]
if (output.logical && output.logical.scale !== undefined) {
scales[outputName] = output.logical.scale
}
}
displayScales = scales
}
function sortWindowsByLayout(windowList) { function sortWindowsByLayout(windowList) {
return [...windowList].sort((a, b) => { return [...windowList].sort((a, b) => {
const aWorkspace = workspaces[a.workspace_id] const aWorkspace = workspaces[a.workspace_id]
@@ -395,6 +411,7 @@ Singleton {
function handleOutputsChanged(data) { function handleOutputsChanged(data) {
if (!data.outputs) return if (!data.outputs) return
outputs = data.outputs outputs = data.outputs
updateDisplayScales()
windows = sortWindowsByLayout(windows) windows = sortWindowsByLayout(windows)
} }
@@ -410,6 +427,7 @@ Singleton {
if (ToastService.toastVisible && ToastService.currentLevel === ToastService.levelError) { if (ToastService.toastVisible && ToastService.currentLevel === ToastService.levelError) {
ToastService.hideToast() ToastService.hideToast()
} }
fetchOutputs()
if (hasInitialConnection && !suppressConfigToast && !suppressNextConfigToast && !matugenSuppression) { if (hasInitialConnection && !suppressConfigToast && !suppressNextConfigToast && !matugenSuppression) {
ToastService.showInfo("niri: config reloaded") ToastService.showInfo("niri: config reloaded")
} else if (suppressNextConfigToast) { } else if (suppressNextConfigToast) {

View File

@@ -1,7 +1,9 @@
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Hyprland
import Quickshell.Wayland import Quickshell.Wayland
import qs.Common import qs.Common
import qs.Services
PanelWindow { PanelWindow {
id: root id: root
@@ -69,9 +71,21 @@ PanelWindow {
readonly property real screenWidth: root.screen.width readonly property real screenWidth: root.screen.width
readonly property real screenHeight: root.screen.height readonly property real screenHeight: root.screen.height
readonly property real dpr: root.screen.devicePixelRatio readonly property real dpr: {
if (CompositorService.isNiri && root.screen) {
const niriScale = NiriService.displayScales[root.screen.name]
if (niriScale !== undefined) return niriScale
}
if (CompositorService.isHyprland && root.screen) {
const hyprlandMonitor = Hyprland.monitors.values.find(m => m.name === root.screen.name)
if (hyprlandMonitor?.scale !== undefined) return hyprlandMonitor.scale
}
return root.screen?.devicePixelRatio || 1
}
readonly property real calculatedX: { readonly property real alignedWidth: Theme.px(popupWidth, dpr)
readonly property real alignedHeight: Theme.px(popupHeight, dpr)
readonly property real alignedX: Theme.snap((() => {
if (SettingsData.dankBarPosition === SettingsData.Position.Left) { if (SettingsData.dankBarPosition === SettingsData.Position.Left) {
return triggerY return triggerY
} else if (SettingsData.dankBarPosition === SettingsData.Position.Right) { } else if (SettingsData.dankBarPosition === SettingsData.Position.Right) {
@@ -80,8 +94,8 @@ PanelWindow {
const centerX = triggerX + (triggerWidth / 2) - (popupWidth / 2) const centerX = triggerX + (triggerWidth / 2) - (popupWidth / 2)
return Math.max(Theme.popupDistance, Math.min(screenWidth - popupWidth - Theme.popupDistance, centerX)) return Math.max(Theme.popupDistance, Math.min(screenWidth - popupWidth - Theme.popupDistance, centerX))
} }
} })(), dpr)
readonly property real calculatedY: { readonly property real alignedY: Theme.snap((() => {
if (SettingsData.dankBarPosition === SettingsData.Position.Left || SettingsData.dankBarPosition === SettingsData.Position.Right) { if (SettingsData.dankBarPosition === SettingsData.Position.Left || SettingsData.dankBarPosition === SettingsData.Position.Right) {
const centerY = triggerX + (triggerWidth / 2) - (popupHeight / 2) const centerY = triggerX + (triggerWidth / 2) - (popupHeight / 2)
return Math.max(Theme.popupDistance, Math.min(screenHeight - popupHeight - Theme.popupDistance, centerY)) return Math.max(Theme.popupDistance, Math.min(screenHeight - popupHeight - Theme.popupDistance, centerY))
@@ -90,12 +104,7 @@ PanelWindow {
} else { } else {
return Math.max(Theme.popupDistance, Math.min(screenHeight - popupHeight - Theme.popupDistance, triggerY + Theme.popupDistance)) return Math.max(Theme.popupDistance, Math.min(screenHeight - popupHeight - Theme.popupDistance, triggerY + Theme.popupDistance))
} }
} })(), dpr)
readonly property real alignedWidth: Theme.snap(popupWidth, dpr)
readonly property real alignedHeight: Theme.snap(popupHeight, dpr)
readonly property real alignedX: Theme.snap(calculatedX, dpr)
readonly property real alignedY: Theme.snap(calculatedY, dpr)
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
@@ -117,6 +126,7 @@ PanelWindow {
height: alignedHeight height: alignedHeight
active: root.visible active: root.visible
asynchronous: false asynchronous: false
layer.enabled: Quickshell.env("DMS_DISABLE_LAYER") !== "true"
opacity: shouldBeVisible ? 1 : 0 opacity: shouldBeVisible ? 1 : 0
Behavior on opacity { Behavior on opacity {