:Merge tag 'v2.11.0'

This commit is contained in:
Never Gude 2025-09-18 22:47:16 +02:00
commit 67fed691d1
54 changed files with 776 additions and 794 deletions

Binary file not shown.

View file

@ -121,6 +121,12 @@ Singleton {
}
}
}
// Upgrade the density of the bar so the look stay the same for people who upgrade.
if (adapter.settingsVersion == 2) {
adapter.bar.density = "comfortable"
adapter.settingsVersion++
}
}
// -----------------------------------------------------
@ -259,13 +265,15 @@ Singleton {
JsonAdapter {
id: adapter
property int settingsVersion: 2
property int settingsVersion: 3
// bar
property JsonObject bar: JsonObject {
property string position: "top" // "top", "bottom", "left", or "right"
property real backgroundOpacity: 1.0
property list<string> monitors: []
property string density: "default" // "compact", "default", "comfortable"
property bool showCapsule: true
// Floating bar settings
property bool floating: false
@ -320,7 +328,7 @@ Singleton {
// general
property JsonObject general: JsonObject {
property string avatarImage: defaultAvatar
property bool dimDesktop: false
property bool dimDesktop: true
property bool showScreenCorners: false
property bool forceBlackScreenCorners: false
property real radiusRatio: 1.0

View file

@ -65,14 +65,36 @@ Singleton {
property int animationSlow: Math.round(450 / Settings.data.general.animationSpeed)
property int animationSlowest: Math.round(750 / Settings.data.general.animationSpeed)
// Dimensions
property int barHeight: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 39 : 37
property int capsuleHeight: (barHeight * 0.73)
property int baseWidgetSize: (barHeight * 0.9)
property int sliderWidth: 200
// Delays
property int tooltipDelay: 300
property int tooltipDelayLong: 1200
property int pillDelay: 500
// Settings widgets base size
property real baseWidgetSize: 33
property real sliderWidth: 200
// Bar Dimensions
property real barHeight: {
if (Settings.data.bar.density === "compact") {
return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 27 : 25
}
if (Settings.data.bar.density === "default") {
return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 33 : 31
}
if (Settings.data.bar.density === "comfortable") {
return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 39 : 37
}
}
property real capsuleHeight: {
if (Settings.data.bar.density === "compact") {
return barHeight * 0.85
}
if (Settings.data.bar.density === "default") {
return barHeight * 0.82
}
if (Settings.data.bar.density === "comfortable") {
return barHeight * 0.73
}
}
}

View file

@ -68,23 +68,12 @@ Variants {
radius: Settings.data.bar.floating ? Style.radiusL : 0
}
// For vertical bars, use a single column layout
Loader {
id: verticalBarLayout
anchors.fill: parent
visible: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
sourceComponent: verticalBarComponent
sourceComponent: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? verticalBarComponent : horizontalBarComponent
}
// For horizontal bars, use the original three-section layout
Loader {
id: horizontalBarLayout
anchors.fill: parent
visible: Settings.data.bar.position === "top" || Settings.data.bar.position === "bottom"
sourceComponent: horizontalBarComponent
}
// Main layout components
// For vertical bars
Component {
id: verticalBarComponent
Item {
@ -163,6 +152,7 @@ Variants {
}
}
// For horizontal bars
Component {
id: horizontalBarComponent
Item {

View file

@ -2,6 +2,7 @@ import QtQuick
import QtQuick.Controls
import qs.Commons
import qs.Services
import qs.Widgets
Item {
id: root
@ -10,13 +11,13 @@ Item {
property string text: ""
property string suffix: ""
property string tooltipText: ""
property real sizeRatio: 0.8
property bool autoHide: false
property bool forceOpen: false
property bool forceClose: false
property bool disableOpen: false
property bool rightOpen: false
property bool hovered: false
property bool compact: false
readonly property string barPosition: Settings.data.bar.position
readonly property bool isVerticalBar: barPosition === "left" || barPosition === "right"
@ -41,18 +42,18 @@ Item {
Component {
id: verticalPillComponent
NPillVertical {
BarPillVertical {
icon: root.icon
text: root.text
suffix: root.suffix
tooltipText: root.tooltipText
sizeRatio: root.sizeRatio
autoHide: root.autoHide
forceOpen: root.forceOpen
forceClose: root.forceClose
disableOpen: root.disableOpen
rightOpen: root.rightOpen
hovered: root.hovered
compact: root.compact
onShown: root.shown()
onHidden: root.hidden()
onEntered: root.entered()
@ -66,18 +67,18 @@ Item {
Component {
id: horizontalPillComponent
NPillHorizontal {
BarPillHorizontal {
icon: root.icon
text: root.text
suffix: root.suffix
tooltipText: root.tooltipText
sizeRatio: root.sizeRatio
autoHide: root.autoHide
forceOpen: root.forceOpen
forceClose: root.forceClose
disableOpen: root.disableOpen
rightOpen: root.rightOpen
hovered: root.hovered
compact: root.compact
onShown: root.shown()
onHidden: root.hidden()
onEntered: root.entered()

View file

@ -2,6 +2,7 @@ import QtQuick
import QtQuick.Controls
import qs.Commons
import qs.Services
import qs.Widgets
Item {
id: root
@ -10,13 +11,13 @@ Item {
property string text: ""
property string suffix: ""
property string tooltipText: ""
property real sizeRatio: 0.8
property bool autoHide: false
property bool forceOpen: false
property bool forceClose: false
property bool disableOpen: false
property bool rightOpen: false
property bool hovered: false
property bool compact: false
// Effective shown state (true if hovered/animated open or forced)
readonly property bool revealed: forceOpen || showPill
@ -34,26 +35,27 @@ Item {
property bool showPill: false
property bool shouldAnimateHide: false
// Exposed width logic
readonly property int iconSize: Math.round(Style.baseWidgetSize * sizeRatio * scaling)
readonly property int pillHeight: iconSize
readonly property int pillPaddingHorizontal: 3 * 2 * scaling // Very precise adjustment don't replace by Style.margin
readonly property int pillOverlap: iconSize * 0.5
readonly property int maxPillWidth: Math.max(1, textItem.implicitWidth + pillPaddingHorizontal * 2 + pillOverlap)
readonly property int pillHeight: Math.round(Style.capsuleHeight * scaling)
readonly property int pillPaddingHorizontal: Math.round(Style.capsuleHeight * 0.2 * scaling)
readonly property int pillOverlap: Math.round(Style.capsuleHeight * 0.5 * scaling)
readonly property int pillMaxWidth: Math.max(1, textItem.implicitWidth + pillPaddingHorizontal * 2 + pillOverlap)
width: iconSize + Math.max(0, pill.width - pillOverlap)
readonly property real iconSize: Math.max(1, compact ? pillHeight * 0.65 : pillHeight * 0.48)
readonly property real textSize: Math.max(1, compact ? pillHeight * 0.45 : pillHeight * 0.33)
width: pillHeight + Math.max(0, pill.width - pillOverlap)
height: pillHeight
Rectangle {
id: pill
width: revealed ? maxPillWidth : 1
width: revealed ? pillMaxWidth : 1
height: pillHeight
x: rightOpen ? (iconCircle.x + iconCircle.width / 2) : // Opens right
(iconCircle.x + iconCircle.width / 2) - width // Opens left
opacity: revealed ? Style.opacityFull : Style.opacityNone
color: Color.mSurfaceVariant
color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
topLeftRadius: rightOpen ? 0 : pillHeight * 0.5
bottomLeftRadius: rightOpen ? 0 : pillHeight * 0.5
@ -76,7 +78,7 @@ Item {
}
text: root.text + root.suffix
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeXS * scaling
font.pointSize: textSize
font.weight: Style.fontWeightBold
color: forceOpen ? Color.mOnSurface : Color.mPrimary
visible: revealed
@ -100,10 +102,10 @@ Item {
Rectangle {
id: iconCircle
width: iconSize
height: iconSize
width: pillHeight
height: pillHeight
radius: width * 0.5
color: hovered ? Color.mTertiary : Color.mSurfaceVariant
color: hovered ? Color.mTertiary : Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
anchors.verticalCenter: parent.verticalCenter
x: rightOpen ? 0 : (parent.width - width)
@ -117,7 +119,7 @@ Item {
NIcon {
icon: root.icon
font.pointSize: Style.fontSizeM * scaling
font.pointSize: iconSize
color: hovered ? Color.mOnTertiary : Color.mOnSurface
// Center horizontally
x: (iconCircle.width - width) / 2
@ -133,7 +135,7 @@ Item {
target: pill
property: "width"
from: 1
to: maxPillWidth
to: pillMaxWidth
duration: Style.animationNormal
easing.type: Easing.OutCubic
}
@ -173,7 +175,7 @@ Item {
NumberAnimation {
target: pill
property: "width"
from: maxPillWidth
from: pillMaxWidth
to: 1
duration: Style.animationNormal
easing.type: Easing.InCubic

View file

@ -2,6 +2,7 @@ import QtQuick
import QtQuick.Controls
import qs.Commons
import qs.Services
import qs.Widgets
Item {
id: root
@ -10,13 +11,13 @@ Item {
property string text: ""
property string suffix: ""
property string tooltipText: ""
property real sizeRatio: 0.8
property bool autoHide: false
property bool forceOpen: false
property bool forceClose: false
property bool disableOpen: false
property bool rightOpen: false
property bool hovered: false
property bool compact: false
// Bar position detection for pill direction
readonly property string barPosition: Settings.data.bar.position
@ -43,16 +44,19 @@ Item {
property bool shouldAnimateHide: false
// Sizing logic for vertical bars
readonly property int iconSize: Math.round(Style.baseWidgetSize * sizeRatio * scaling)
readonly property int pillHeight: iconSize
readonly property int buttonSize: Math.round(Style.capsuleHeight * scaling)
readonly property int pillHeight: buttonSize
readonly property int pillPaddingVertical: 3 * 2 * scaling // Very precise adjustment don't replace by Style.margin
readonly property int pillOverlap: iconSize * 0.5
readonly property int maxPillWidth: iconSize
readonly property int pillOverlap: buttonSize * 0.5
readonly property int maxPillWidth: buttonSize
readonly property int maxPillHeight: Math.max(1, textItem.implicitHeight + pillPaddingVertical * 4)
readonly property real iconSize: Math.max(1, compact ? pillHeight * 0.65 : pillHeight * 0.48)
readonly property real textSize: Math.max(1, compact ? pillHeight * 0.38 : pillHeight * 0.33)
// For vertical bars: width is just icon size, height includes pill space
width: iconSize
height: revealed ? (iconSize + maxPillHeight - pillOverlap) : iconSize
width: buttonSize
height: revealed ? (buttonSize + maxPillHeight - pillOverlap) : buttonSize
Rectangle {
id: pill
@ -64,13 +68,13 @@ Item {
y: openUpward ? (iconCircle.y + iconCircle.height / 2 - height) : (iconCircle.y + iconCircle.height / 2)
opacity: revealed ? Style.opacityFull : Style.opacityNone
color: Color.mSurfaceVariant
color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
// Radius logic for vertical expansion - rounded on the side that connects to icon
topLeftRadius: openUpward ? iconSize * 0.5 : 0
bottomLeftRadius: openDownward ? iconSize * 0.5 : 0
topRightRadius: openUpward ? iconSize * 0.5 : 0
bottomRightRadius: openDownward ? iconSize * 0.5 : 0
topLeftRadius: openUpward ? buttonSize * 0.5 : 0
bottomLeftRadius: openDownward ? buttonSize * 0.5 : 0
topRightRadius: openUpward ? buttonSize * 0.5 : 0
bottomRightRadius: openDownward ? buttonSize * 0.5 : 0
anchors.horizontalCenter: parent.horizontalCenter
@ -88,7 +92,7 @@ Item {
}
text: root.text + root.suffix
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeXXS * scaling
font.pointSize: textSize
font.weight: Style.fontWeightMedium
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
@ -121,10 +125,10 @@ Item {
Rectangle {
id: iconCircle
width: iconSize
height: iconSize
width: buttonSize
height: buttonSize
radius: width * 0.5
color: hovered ? Color.mTertiary : Color.mSurfaceVariant
color: hovered ? Color.mTertiary : Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
// Icon positioning based on direction
x: 0
@ -140,7 +144,7 @@ Item {
NIcon {
icon: root.icon
font.pointSize: Style.fontSizeM * scaling
font.pointSize: iconSize
color: hovered ? Color.mOnTertiary : Color.mOnSurface
// Center horizontally
x: (iconCircle.width - width) / 2

View file

@ -37,8 +37,18 @@ Item {
readonly property real maxWidth: minWidth * 2
readonly property string barPosition: Settings.data.bar.position
readonly property bool isVertical: barPosition === "left" || barPosition === "right"
readonly property bool compact: (Settings.data.bar.density === "compact")
implicitHeight: (barPosition === "left" || barPosition === "right") ? calculatedVerticalHeight() : Math.round(Style.barHeight * scaling)
implicitWidth: (barPosition === "left" || barPosition === "right") ? Math.round(Style.baseWidgetSize * 0.8 * scaling) : (horizontalLayout.implicitWidth + Style.marginM * 2 * scaling)
implicitWidth: (barPosition === "left" || barPosition === "right") ? Math.round(Style.capsuleHeight * 0.8 * scaling) : (horizontalLayout.implicitWidth + Style.marginM * 2 * scaling)
readonly property real textSize: {
var base = isVertical ? width : height
return Math.max(1, compact ? base * 0.43 : base * 0.33)
}
readonly property real iconSize: textSize * 1.25
function getTitle() {
try {
@ -60,7 +70,7 @@ Item {
let total = Style.marginM * 2 * scaling // internal padding
if (showIcon) {
total += Style.baseWidgetSize * 0.5 * scaling + 2 * scaling // icon + spacing
total += Style.capsuleHeight * 0.5 * scaling + 2 * scaling // icon + spacing
}
// Calculate actual text width more accurately
@ -129,12 +139,14 @@ Item {
Rectangle {
id: windowTitleRect
visible: root.visible
anchors.left: parent.left
anchors.left: (barPosition === "top" || barPosition === "bottom") ? parent.left : undefined
anchors.top: (barPosition === "left" || barPosition === "right") ? parent.top : undefined
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
width: (barPosition === "left" || barPosition === "right") ? Math.round(Style.capsuleHeight * scaling) : (horizontalLayout.implicitWidth + Style.marginM * 2 * scaling)
height: (barPosition === "left" || barPosition === "right") ? Math.round(Style.capsuleHeight * scaling) : Math.round(Style.capsuleHeight * scaling)
radius: Math.round(Style.radiusM * scaling)
color: Color.mSurfaceVariant
radius: width / 2
color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
Item {
id: mainContainer
@ -152,8 +164,8 @@ Item {
// Window icon
Item {
Layout.preferredWidth: Style.baseWidgetSize * 0.5 * scaling
Layout.preferredHeight: Style.baseWidgetSize * 0.5 * scaling
Layout.preferredWidth: Style.capsuleHeight * 0.75 * scaling
Layout.preferredHeight: Style.capsuleHeight * 0.75 * scaling
Layout.alignment: Qt.AlignVCenter
visible: getTitle() !== "" && showIcon
@ -217,10 +229,9 @@ Item {
// Window icon
Item {
width: Style.baseWidgetSize * 0.5 * scaling
height: Style.baseWidgetSize * 0.5 * scaling
width: Style.capsuleHeight * 0.75 * scaling
height: Style.capsuleHeight * 0.75 * scaling
anchors.centerIn: parent
visible: getTitle() !== "" && showIcon
IconImage {
id: windowIconVertical

View file

@ -5,6 +5,7 @@ import QtQuick.Layouts
import qs.Commons
import qs.Services
import qs.Widgets
import qs.Modules.Bar.Extras
Item {
id: root
@ -81,10 +82,11 @@ Item {
}
}
NPill {
BarPill {
id: pill
rightOpen: BarWidgetRegistry.getNPillDirection(root)
compact: (Settings.data.bar.density === "compact")
rightOpen: BarWidgetRegistry.getPillDirection(root)
icon: testMode ? BatteryService.getIcon(testPercent, testCharging, true) : BatteryService.getIcon(percent, charging, isReady)
text: (isReady || testMode) ? Math.round(percent) : "-"
suffix: "%"

View file

@ -13,8 +13,9 @@ NIconButton {
property ShellScreen screen
property real scaling: 1.0
sizeRatio: 0.8
colorBg: Color.mSurfaceVariant
baseSize: Style.capsuleHeight
compact: (Settings.data.bar.density === "compact")
colorBg: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
colorFg: Color.mOnSurface
colorBorder: Color.transparent
colorBorderHover: Color.transparent

View file

@ -4,6 +4,7 @@ import qs.Commons
import qs.Modules.SettingsPanel
import qs.Services
import qs.Widgets
import qs.Modules.Bar.Extras
Item {
id: root
@ -73,10 +74,11 @@ Item {
onTriggered: pill.hide()
}
NPill {
BarPill {
id: pill
rightOpen: BarWidgetRegistry.getNPillDirection(root)
compact: (Settings.data.bar.density === "compact")
rightOpen: BarWidgetRegistry.getPillDirection(root)
icon: getIcon()
autoHide: false // Important to be false so we can hover as long as we want
text: {

View file

@ -29,6 +29,7 @@ Rectangle {
}
readonly property string barPosition: Settings.data.bar.position
readonly property bool compact: (Settings.data.bar.density === "compact")
// Resolve settings: try user settings or defaults from BarWidgetRegistry
readonly property bool use12h: widgetSettings.use12HourClock !== undefined ? widgetSettings.use12HourClock : widgetMetadata.use12HourClock
@ -39,15 +40,15 @@ Rectangle {
readonly property bool verticalMode: barPosition === "left" || barPosition === "right"
implicitWidth: verticalMode ? Math.round(Style.capsuleHeight * scaling) : Math.round(layout.implicitWidth + Style.marginM * 2 * scaling)
implicitHeight: verticalMode ? Math.round(Style.capsuleHeight * 2.5 * scaling) : Math.round(Style.baseWidgetSize * 0.8 * scaling) // Match NPill
implicitHeight: verticalMode ? Math.round(Style.capsuleHeight * 2.5 * scaling) : Math.round(Style.capsuleHeight * scaling) // Match BarPill
radius: Math.round(Style.radiusS * scaling)
color: Color.mSurfaceVariant
color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
Item {
id: clockContainer
anchors.fill: parent
anchors.margins: Style.marginXS * scaling
anchors.margins: compact ? 0 : Style.marginXS * scaling
ColumnLayout {
id: layout

View file

@ -6,6 +6,7 @@ import qs.Commons
import qs.Services
import qs.Widgets
import qs.Modules.SettingsPanel
import qs.Modules.Bar.Extras
Item {
id: root
@ -43,12 +44,13 @@ Item {
implicitWidth: pill.width
implicitHeight: pill.height
NPill {
BarPill {
id: pill
rightOpen: BarWidgetRegistry.getNPillDirection(root)
rightOpen: BarWidgetRegistry.getPillDirection(root)
icon: customIcon
text: _dynamicText
compact: (Settings.data.bar.density === "compact")
autoHide: false
forceOpen: _dynamicText !== ""
forceClose: false

View file

@ -10,10 +10,10 @@ NIconButton {
property real scaling: 1.0
icon: "dark-mode"
tooltipText: "Toggle light/dark mode"
sizeRatio: 0.8
colorBg: Settings.data.colorSchemes.darkMode ? Color.mSurfaceVariant : Color.mPrimary
tooltipText: "Toggle light/dark mode."
compact: (Settings.data.bar.density === "compact")
baseSize: Style.capsuleHeight
colorBg: Settings.data.colorSchemes.darkMode ? (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent) : Color.mPrimary
colorFg: Settings.data.colorSchemes.darkMode ? Color.mOnSurface : Color.mOnPrimary
colorBorder: Color.transparent
colorBorderHover: Color.transparent

View file

@ -11,14 +11,12 @@ NIconButton {
property ShellScreen screen
property real scaling: 1.0
sizeRatio: 0.8
baseSize: Style.capsuleHeight
compact: (Settings.data.bar.density === "compact")
icon: IdleInhibitorService.isInhibited ? "keep-awake-on" : "keep-awake-off"
tooltipText: IdleInhibitorService.isInhibited ? "Disable keep awake" : "Enable keep awake"
colorBg: IdleInhibitorService.isInhibited ? Color.mPrimary : Color.mSurfaceVariant
colorBg: IdleInhibitorService.isInhibited ? Color.mPrimary : (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent)
colorFg: IdleInhibitorService.isInhibited ? Color.mOnPrimary : Color.mOnSurface
colorBorder: Color.transparent
onClicked: {
IdleInhibitorService.manualToggle()
}
onClicked: IdleInhibitorService.manualToggle()
}

View file

@ -6,6 +6,7 @@ import Quickshell.Io
import qs.Commons
import qs.Services
import qs.Widgets
import qs.Modules.Bar.Extras
Item {
id: root
@ -38,11 +39,12 @@ Item {
implicitWidth: pill.width
implicitHeight: pill.height
NPill {
BarPill {
id: pill
anchors.verticalCenter: parent.verticalCenter
rightOpen: BarWidgetRegistry.getNPillDirection(root)
compact: (Settings.data.bar.density === "compact")
rightOpen: BarWidgetRegistry.getPillDirection(root)
icon: "keyboard"
autoHide: false // Important to be false so we can hover as long as we want
text: currentLayout.toUpperCase()

View file

@ -31,6 +31,7 @@ Item {
}
readonly property string barPosition: Settings.data.bar.position
readonly property bool compact: (Settings.data.bar.density === "compact")
readonly property bool showAlbumArt: (widgetSettings.showAlbumArt !== undefined) ? widgetSettings.showAlbumArt : widgetMetadata.showAlbumArt
readonly property bool showVisualizer: (widgetSettings.showVisualizer !== undefined) ? widgetSettings.showVisualizer : widgetMetadata.showVisualizer
@ -81,7 +82,7 @@ Item {
width: (barPosition === "left" || barPosition === "right") ? Math.round(Style.baseWidgetSize * 0.8 * scaling) : (rowLayout.implicitWidth + Style.marginM * 2 * scaling)
height: (barPosition === "left" || barPosition === "right") ? Math.round(Style.baseWidgetSize * 0.8 * scaling) : Math.round(Style.capsuleHeight * scaling)
radius: (barPosition === "left" || barPosition === "right") ? width / 2 : Math.round(Style.radiusM * scaling)
color: Color.mSurfaceVariant
color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
// Used to anchor the tooltip, so the tooltip does not move when the content expands
Item {

View file

@ -6,6 +6,7 @@ import qs.Commons
import qs.Modules.SettingsPanel
import qs.Services
import qs.Widgets
import qs.Modules.Bar.Extras
Item {
id: root
@ -86,10 +87,11 @@ Item {
}
}
NPill {
BarPill {
id: pill
rightOpen: BarWidgetRegistry.getNPillDirection(root)
rightOpen: BarWidgetRegistry.getPillDirection(root)
icon: getIcon()
compact: (Settings.data.bar.density === "compact")
autoHide: false // Important to be false so we can hover as long as we want
text: Math.floor(AudioService.inputVolume * 100)
suffix: "%"

View file

@ -14,8 +14,9 @@ NIconButton {
property ShellScreen screen
property real scaling: 1.0
sizeRatio: 0.8
colorBg: Settings.data.nightLight.forced ? Color.mPrimary : Color.mSurfaceVariant
compact: (Settings.data.bar.density === "compact")
baseSize: Style.capsuleHeight
colorBg: Settings.data.nightLight.forced ? Color.mPrimary : (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent)
colorFg: Settings.data.nightLight.forced ? Color.mOnPrimary : Color.mOnSurface
colorBorder: Color.transparent
colorBorderHover: Color.transparent

View file

@ -49,10 +49,11 @@ NIconButton {
return count
}
sizeRatio: 0.8
baseSize: Style.capsuleHeight
compact: (Settings.data.bar.density === "compact")
icon: Settings.data.notifications.doNotDisturb ? "bell-off" : "bell"
tooltipText: Settings.data.notifications.doNotDisturb ? "Notification history.\nRight-click to disable 'Do Not Disturb'." : "Notification history.\nRight-click to enable 'Do Not Disturb'."
colorBg: Color.mSurfaceVariant
colorBg: (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent)
colorFg: Color.mOnSurface
colorBorder: Color.transparent
colorBorderHover: Color.transparent
@ -68,29 +69,20 @@ NIconButton {
Loader {
anchors.right: parent.right
anchors.top: parent.top
anchors.rightMargin: -4 * scaling
anchors.topMargin: -4 * scaling
anchors.rightMargin: 2 * scaling
anchors.topMargin: 1 * scaling
z: 2
active: showUnreadBadge && (!hideWhenZero || computeUnreadCount() > 0)
sourceComponent: Rectangle {
id: badge
readonly property int count: computeUnreadCount()
readonly property string label: count <= 99 ? String(count) : "99+"
readonly property real pad: 8 * scaling
height: 16 * scaling
width: Math.max(height, textNode.implicitWidth + pad)
height: 8 * scaling
width: height
radius: height / 2
color: Color.mError
border.color: Color.mSurface
border.width: 1
visible: count > 0 || !hideWhenZero
NText {
id: textNode
anchors.centerIn: parent
text: badge.label
font.pointSize: Style.fontSizeXXS * scaling
color: Color.mOnError
}
}
}
}

View file

@ -13,7 +13,7 @@ NIconButton {
property real scaling: 1.0
readonly property bool hasPP: PowerProfileService.available
sizeRatio: 0.8
baseSize: Style.capsuleHeight
visible: hasPP
function profileIcon() {
@ -46,7 +46,8 @@ NIconButton {
icon: root.profileIcon()
tooltipText: root.profileName()
colorBg: (PowerProfileService.profile === PowerProfile.Balanced) ? Color.mSurfaceVariant : Color.mPrimary
compact: (Settings.data.bar.density === "compact")
colorBg: (PowerProfileService.profile === PowerProfile.Balanced) ? (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent) : Color.mPrimary
colorFg: (PowerProfileService.profile === PowerProfile.Balanced) ? Color.mOnSurface : Color.mOnPrimary
colorBorder: Color.transparent
colorBorderHover: Color.transparent

View file

@ -11,11 +11,11 @@ NIconButton {
property ShellScreen screen
property real scaling: 1.0
sizeRatio: 0.8
compact: (Settings.data.bar.density === "compact")
baseSize: Style.capsuleHeight
icon: "power"
tooltipText: "Power Settings"
colorBg: Color.mSurfaceVariant
colorBg: (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent)
colorFg: Color.mError
colorBorder: Color.transparent
colorBorderHover: Color.transparent

View file

@ -12,8 +12,9 @@ NIconButton {
visible: ScreenRecorderService.isRecording
icon: "camera-video"
tooltipText: "Screen recording is active\nClick to stop recording"
sizeRatio: 0.8
tooltipText: "Screen recording is active.\nClick to stop recording."
compact: (Settings.data.bar.density === "compact")
baseSize: Style.capsuleHeight
colorBg: Color.mPrimary
colorFg: Color.mOnPrimary
onClicked: ScreenRecorderService.toggleRecording()

View file

@ -33,9 +33,9 @@ NIconButton {
icon: useDistroLogo ? "" : "noctalia"
tooltipText: "Open side panel."
sizeRatio: 0.85
colorBg: Color.mSurfaceVariant
baseSize: Style.capsuleHeight
compact: (Settings.data.bar.density === "compact")
colorBg: (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent)
colorFg: Color.mOnSurface
colorBgHover: useDistroLogo ? Color.mSurfaceVariant : Color.mTertiary
colorBorder: Color.transparent
@ -46,10 +46,11 @@ NIconButton {
IconImage {
id: logo
anchors.centerIn: parent
width: root.width * 0.85
width: root.width * 0.8
height: width
source: useDistroLogo ? DistroLogoService.osLogo : ""
visible: useDistroLogo && source !== ""
smooth: true
asynchronous: true
}
}

View file

@ -29,6 +29,8 @@ Rectangle {
}
readonly property string barPosition: Settings.data.bar.position
readonly property bool isVertical: barPosition === "left" || barPosition === "right"
readonly property bool compact: (Settings.data.bar.density === "compact")
readonly property bool showCpuUsage: (widgetSettings.showCpuUsage !== undefined) ? widgetSettings.showCpuUsage : widgetMetadata.showCpuUsage
readonly property bool showCpuTemp: (widgetSettings.showCpuTemp !== undefined) ? widgetSettings.showCpuTemp : widgetMetadata.showCpuTemp
@ -37,410 +39,267 @@ Rectangle {
readonly property bool showNetworkStats: (widgetSettings.showNetworkStats !== undefined) ? widgetSettings.showNetworkStats : widgetMetadata.showNetworkStats
readonly property bool showDiskUsage: (widgetSettings.showDiskUsage !== undefined) ? widgetSettings.showDiskUsage : widgetMetadata.showDiskUsage
readonly property real textSize: {
var base = isVertical ? width * 0.82 : height
return Math.max(1, compact ? base * 0.43 : base * 0.33)
}
readonly property real iconSize: textSize * 1.25
anchors.centerIn: parent
implicitWidth: (barPosition === "left" || barPosition === "right") ? Math.round(Style.capsuleHeight * scaling) : Math.round(horizontalLayout.implicitWidth + Style.marginM * 2 * scaling)
implicitHeight: (barPosition === "left" || barPosition === "right") ? Math.round(verticalLayout.implicitHeight + Style.marginM * 2 * scaling) : Math.round(Style.capsuleHeight * scaling)
implicitWidth: isVertical ? Math.round(Style.capsuleHeight * scaling) : Math.round(mainGrid.implicitWidth + Style.marginM * 2 * scaling)
implicitHeight: isVertical ? Math.round(mainGrid.implicitHeight + Style.marginM * 2 * scaling) : Math.round(Style.capsuleHeight * scaling)
radius: Math.round(Style.radiusM * scaling)
color: Color.mSurfaceVariant
color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
// Compact speed formatter for vertical bar display
function formatCompactSpeed(bytesPerSecond) {
if (!bytesPerSecond || bytesPerSecond <= 0)
return "0"
const units = ["", "k", "M", "G"]
let value = bytesPerSecond
let unitIndex = 0
while (value >= 1024 && unitIndex < units.length - 1) {
value = value / 1024.0
unitIndex++
}
// Promote at ~100 of current unit (e.g., 100k -> ~0.1M shown as 0.1M or 0M if rounded)
if (unitIndex < units.length - 1 && value >= 100) {
value = value / 1024.0
unitIndex++
}
const display = (value >= 10) ? Math.round(value).toString() : value.toFixed(1)
return display + units[unitIndex]
}
// Horizontal layout for top/bottom bars
RowLayout {
id: horizontalLayout
GridLayout {
id: mainGrid
anchors.centerIn: parent
anchors.leftMargin: Style.marginM * scaling
anchors.rightMargin: Style.marginM * scaling
spacing: Style.marginXS * scaling
visible: barPosition === "top" || barPosition === "bottom"
// Dynamic layout based on bar orientation
flow: isVertical ? GridLayout.TopToBottom : GridLayout.LeftToRight
rows: isVertical ? -1 : 1
columns: isVertical ? 1 : -1
rowSpacing: isVertical ? (Style.marginS * scaling) : (Style.marginXS * scaling)
columnSpacing: isVertical ? (Style.marginXS * scaling) : (Style.marginXS * scaling)
// CPU Usage Component
Item {
Layout.preferredWidth: cpuUsageRow.implicitWidth
Layout.preferredWidth: cpuUsageContent.implicitWidth
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.alignment: Qt.AlignVCenter
Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter
visible: showCpuUsage
RowLayout {
id: cpuUsageRow
GridLayout {
id: cpuUsageContent
anchors.centerIn: parent
spacing: Style.marginXXS * scaling
flow: isVertical ? GridLayout.TopToBottom : GridLayout.LeftToRight
rows: isVertical ? 2 : 1
columns: isVertical ? 1 : 2
rowSpacing: Style.marginXXS * scaling
columnSpacing: Style.marginXXS * scaling
NText {
text: isVertical ? `${Math.round(SystemStatService.cpuUsage)}%` : `${SystemStatService.cpuUsage}%`
font.family: Settings.data.ui.fontFixed
font.pointSize: textSize
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignCenter
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: Color.mPrimary
Layout.row: isVertical ? 0 : 0
Layout.column: isVertical ? 0 : 1
}
NIcon {
icon: "cpu-usage"
font.pointSize: Style.fontSizeM * scaling
Layout.alignment: Qt.AlignVCenter
}
NText {
text: `${SystemStatService.cpuUsage}%`
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeXS * scaling
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignVCenter
verticalAlignment: Text.AlignVCenter
color: Color.mPrimary
font.pointSize: iconSize
Layout.alignment: Qt.AlignCenter
Layout.row: isVertical ? 1 : 0
Layout.column: 0
}
}
}
// CPU Temperature Component
Item {
Layout.preferredWidth: cpuTempRow.implicitWidth
Layout.preferredWidth: cpuTempContent.implicitWidth
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.alignment: Qt.AlignVCenter
Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter
visible: showCpuTemp
RowLayout {
id: cpuTempRow
GridLayout {
id: cpuTempContent
anchors.centerIn: parent
spacing: Style.marginXXS * scaling
flow: isVertical ? GridLayout.TopToBottom : GridLayout.LeftToRight
rows: isVertical ? 2 : 1
columns: isVertical ? 1 : 2
rowSpacing: Style.marginXXS * scaling
columnSpacing: Style.marginXXS * scaling
NText {
text: isVertical ? `${SystemStatService.cpuTemp}°` : `${SystemStatService.cpuTemp}°C`
font.family: Settings.data.ui.fontFixed
font.pointSize: textSize
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignCenter
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: Color.mPrimary
Layout.row: isVertical ? 0 : 0
Layout.column: isVertical ? 0 : 1
}
NIcon {
icon: "cpu-temperature"
// Fire is so tall, we need to make it smaller
font.pointSize: Style.fontSizeS * scaling
Layout.alignment: Qt.AlignVCenter
}
NText {
text: `${SystemStatService.cpuTemp}°C`
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeXS * scaling
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignVCenter
verticalAlignment: Text.AlignVCenter
color: Color.mPrimary
font.pointSize: iconSize
Layout.alignment: Qt.AlignCenter
Layout.row: isVertical ? 1 : 0
Layout.column: 0
}
}
}
// Memory Usage Component
Item {
Layout.preferredWidth: memoryUsageRow.implicitWidth
Layout.preferredWidth: memoryContent.implicitWidth
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.alignment: Qt.AlignVCenter
Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter
visible: showMemoryUsage
RowLayout {
id: memoryUsageRow
GridLayout {
id: memoryContent
anchors.centerIn: parent
spacing: Style.marginXXS * scaling
flow: isVertical ? GridLayout.TopToBottom : GridLayout.LeftToRight
rows: isVertical ? 2 : 1
columns: isVertical ? 1 : 2
rowSpacing: Style.marginXXS * scaling
columnSpacing: Style.marginXXS * scaling
NText {
text: {
if (showMemoryAsPercent) {
return `${SystemStatService.memPercent}%`
} else {
return isVertical ? `${Math.round(SystemStatService.memGb)}G` : `${SystemStatService.memGb}G`
}
}
font.family: Settings.data.ui.fontFixed
font.pointSize: textSize
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignCenter
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: Color.mPrimary
Layout.row: isVertical ? 0 : 0
Layout.column: isVertical ? 0 : 1
}
NIcon {
icon: "memory"
font.pointSize: Style.fontSizeM * scaling
Layout.alignment: Qt.AlignVCenter
}
NText {
text: showMemoryAsPercent ? `${SystemStatService.memPercent}%` : `${SystemStatService.memGb}G`
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeXS * scaling
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignVCenter
verticalAlignment: Text.AlignVCenter
color: Color.mPrimary
font.pointSize: iconSize
Layout.alignment: Qt.AlignCenter
Layout.row: isVertical ? 1 : 0
Layout.column: 0
}
}
}
// Network Download Speed Component
Item {
Layout.preferredWidth: networkDownloadRow.implicitWidth
Layout.preferredWidth: downloadContent.implicitWidth
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.alignment: Qt.AlignVCenter
Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter
visible: showNetworkStats
RowLayout {
id: networkDownloadRow
GridLayout {
id: downloadContent
anchors.centerIn: parent
spacing: Style.marginXS * scaling
flow: isVertical ? GridLayout.TopToBottom : GridLayout.LeftToRight
rows: isVertical ? 2 : 1
columns: isVertical ? 1 : 2
rowSpacing: Style.marginXXS * scaling
columnSpacing: isVertical ? (Style.marginXXS * scaling) : (Style.marginXS * scaling)
NText {
text: isVertical ? SystemStatService.formatCompactSpeed(SystemStatService.rxSpeed) : SystemStatService.formatSpeed(SystemStatService.rxSpeed)
font.family: Settings.data.ui.fontFixed
font.pointSize: textSize
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignCenter
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: Color.mPrimary
Layout.row: isVertical ? 0 : 0
Layout.column: isVertical ? 0 : 1
}
NIcon {
icon: "download-speed"
font.pointSize: Style.fontSizeM * scaling
Layout.alignment: Qt.AlignVCenter
}
NText {
text: SystemStatService.formatSpeed(SystemStatService.rxSpeed)
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeXS * scaling
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignVCenter
verticalAlignment: Text.AlignVCenter
color: Color.mPrimary
font.pointSize: iconSize
Layout.alignment: Qt.AlignCenter
Layout.row: isVertical ? 1 : 0
Layout.column: 0
}
}
}
// Network Upload Speed Component
Item {
Layout.preferredWidth: networkUploadRow.implicitWidth
Layout.preferredWidth: uploadContent.implicitWidth
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.alignment: Qt.AlignVCenter
Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter
visible: showNetworkStats
RowLayout {
id: networkUploadRow
GridLayout {
id: uploadContent
anchors.centerIn: parent
spacing: Style.marginXS * scaling
flow: isVertical ? GridLayout.TopToBottom : GridLayout.LeftToRight
rows: isVertical ? 2 : 1
columns: isVertical ? 1 : 2
rowSpacing: Style.marginXXS * scaling
columnSpacing: isVertical ? (Style.marginXXS * scaling) : (Style.marginXS * scaling)
NText {
text: isVertical ? SystemStatService.formatCompactSpeed(SystemStatService.txSpeed) : SystemStatService.formatSpeed(SystemStatService.txSpeed)
font.family: Settings.data.ui.fontFixed
font.pointSize: textSize
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignCenter
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: Color.mPrimary
Layout.row: isVertical ? 0 : 0
Layout.column: isVertical ? 0 : 1
}
NIcon {
icon: "upload-speed"
font.pointSize: Style.fontSizeM * scaling
Layout.alignment: Qt.AlignVCenter
}
NText {
text: SystemStatService.formatSpeed(SystemStatService.txSpeed)
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeXS * scaling
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignVCenter
verticalAlignment: Text.AlignVCenter
color: Color.mPrimary
font.pointSize: iconSize
Layout.alignment: Qt.AlignCenter
Layout.row: isVertical ? 1 : 0
Layout.column: 0
}
}
}
// Disk Usage Component (primary drive)
Item {
Layout.preferredWidth: diskUsageRow.implicitWidth
Layout.preferredWidth: diskContent.implicitWidth
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.alignment: Qt.AlignVCenter
Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter
visible: showDiskUsage
RowLayout {
id: diskUsageRow
GridLayout {
id: diskContent
anchors.centerIn: parent
spacing: Style.marginXS * scaling
NIcon {
icon: "storage"
font.pointSize: Style.fontSizeM * scaling
Layout.alignment: Qt.AlignVCenter
}
flow: isVertical ? GridLayout.TopToBottom : GridLayout.LeftToRight
rows: isVertical ? 2 : 1
columns: isVertical ? 1 : 2
rowSpacing: Style.marginXXS * scaling
columnSpacing: isVertical ? (Style.marginXXS * scaling) : (Style.marginXS * scaling)
NText {
text: `${SystemStatService.diskPercent}%`
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeXS * scaling
font.pointSize: textSize
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignVCenter
Layout.alignment: Qt.AlignCenter
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: Color.mPrimary
}
}
}
}
// Vertical layout for left/right bars
ColumnLayout {
id: verticalLayout
anchors.centerIn: parent
anchors.topMargin: Style.marginS * scaling
anchors.bottomMargin: Style.marginS * scaling
width: Math.round(28 * scaling)
spacing: Style.marginS * scaling
visible: barPosition === "left" || barPosition === "right"
// CPU Usage Component
Item {
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.preferredWidth: Math.round(28 * scaling)
Layout.alignment: Qt.AlignHCenter
visible: showCpuUsage
Column {
id: cpuUsageRowVertical
anchors.centerIn: parent
spacing: Style.marginXXS * scaling
NText {
text: `${Math.round(SystemStatService.cpuUsage)}%`
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeXXS * scaling
font.weight: Style.fontWeightMedium
anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: Text.AlignHCenter
color: Color.mPrimary
}
NIcon {
icon: "cpu-usage"
font.pointSize: Style.fontSizeS * scaling
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
// CPU Temperature Component
Item {
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.preferredWidth: Math.round(28 * scaling)
Layout.alignment: Qt.AlignHCenter
visible: showCpuTemp
Column {
id: cpuTempRowVertical
anchors.centerIn: parent
spacing: Style.marginXXS * scaling
NText {
text: `${SystemStatService.cpuTemp}°`
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeXXS * scaling
font.weight: Style.fontWeightMedium
anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: Text.AlignHCenter
color: Color.mPrimary
}
NIcon {
icon: "cpu-temperature"
// Fire is so tall, we need to make it smaller
font.pointSize: Style.fontSizeXS * scaling
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
// Memory Usage Component
Item {
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.preferredWidth: Math.round(28 * scaling)
Layout.alignment: Qt.AlignHCenter
visible: showMemoryUsage
Column {
id: memoryUsageRowVertical
anchors.centerIn: parent
spacing: Style.marginXXS * scaling
NText {
text: showMemoryAsPercent ? `${SystemStatService.memPercent}%` : `${Math.round(SystemStatService.memGb)}G`
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeXXS * scaling
font.weight: Style.fontWeightMedium
anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: Text.AlignHCenter
color: Color.mPrimary
}
NIcon {
icon: "memory"
font.pointSize: Style.fontSizeS * scaling
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
// Network Download Speed Component
Item {
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.preferredWidth: Math.round(28 * scaling)
Layout.alignment: Qt.AlignHCenter
visible: showNetworkStats
Column {
id: networkDownloadRowVertical
anchors.centerIn: parent
spacing: Style.marginXXS * scaling
NText {
anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: Text.AlignHCenter
text: formatCompactSpeed(SystemStatService.rxSpeed)
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeXXS * scaling
font.weight: Style.fontWeightMedium
color: Color.mPrimary
}
NIcon {
icon: "download-speed"
font.pointSize: Style.fontSizeS * scaling
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
// Network Upload Speed Component
Item {
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.preferredWidth: Math.round(28 * scaling)
Layout.alignment: Qt.AlignHCenter
visible: showNetworkStats
Column {
id: networkUploadRowVertical
anchors.centerIn: parent
spacing: Style.marginXXS * scaling
NText {
anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: Text.AlignHCenter
text: formatCompactSpeed(SystemStatService.txSpeed)
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeXXS * scaling
font.weight: Style.fontWeightMedium
color: Color.mPrimary
}
NIcon {
icon: "upload-speed"
font.pointSize: Style.fontSizeS * scaling
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
// Disk Usage Component (primary drive)
Item {
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.preferredWidth: Math.round(28 * scaling)
Layout.alignment: Qt.AlignHCenter
visible: showDiskUsage
ColumnLayout {
id: diskUsageRowVertical
anchors.centerIn: parent
spacing: Style.marginXXS * scaling
NText {
text: `${SystemStatService.diskPercent}%`
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeXXS * scaling
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Text.AlignHCenter
color: Color.mPrimary
Layout.row: isVertical ? 0 : 0
Layout.column: isVertical ? 0 : 1
}
NIcon {
icon: "storage"
font.pointSize: Style.fontSizeS * scaling
Layout.alignment: Qt.AlignHCenter
font.pointSize: iconSize
Layout.alignment: Qt.AlignCenter
Layout.row: isVertical ? 1 : 0
Layout.column: 0
}
}
}

View file

@ -1,5 +1,3 @@
pragma ComponentBehavior
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
@ -15,18 +13,32 @@ Rectangle {
property ShellScreen screen
property real scaling: 1.0
readonly property real itemSize: Style.baseWidgetSize * 0.8 * scaling
readonly property bool isVerticalBar: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
readonly property bool compact: (Settings.data.bar.density === "compact")
readonly property real itemSize: compact ? Style.capsuleHeight * 0.9 * scaling : Style.capsuleHeight * 0.8 * scaling
// Always visible when there are toplevels
implicitWidth: taskbarLayout.implicitWidth + Style.marginM * scaling * 2
implicitHeight: Math.round(Style.capsuleHeight * scaling)
implicitWidth: isVerticalBar ? Math.round(Style.capsuleHeight * scaling) : taskbarLayout.implicitWidth + Style.marginM * scaling * 2
implicitHeight: isVerticalBar ? taskbarLayout.implicitHeight + Style.marginM * scaling * 2 : Math.round(Style.capsuleHeight * scaling)
radius: Math.round(Style.radiusM * scaling)
color: Color.mSurfaceVariant
color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
RowLayout {
GridLayout {
id: taskbarLayout
anchors.centerIn: parent
spacing: Style.marginXXS * root.scaling
anchors.fill: parent
anchors {
leftMargin: isVerticalBar ? undefined : Style.marginM * scaling
rightMargin: isVerticalBar ? undefined : Style.marginM * scaling
topMargin: compact ? 0 : isVerticalBar ? Style.marginM * scaling : undefined
bottomMargin: compact ? 0 : isVerticalBar ? Style.marginM * scaling : undefined
}
// Configure GridLayout to behave like RowLayout or ColumnLayout
rows: isVerticalBar ? -1 : 1 // -1 means unlimited
columns: isVerticalBar ? 1 : -1 // -1 means unlimited
rowSpacing: isVerticalBar ? Style.marginXXS * root.scaling : 0
columnSpacing: isVerticalBar ? 0 : Style.marginXXS * root.scaling
Repeater {
model: ToplevelManager && ToplevelManager.toplevels ? ToplevelManager.toplevels : []
@ -43,8 +55,8 @@ Rectangle {
Rectangle {
id: iconBackground
anchors.centerIn: parent
width: root.itemSize * 0.75
height: root.itemSize * 0.75
width: parent.width
height: parent.height
color: taskbarItem.isActive ? Color.mPrimary : root.color
border.width: 0
radius: Math.round(Style.radiusXS * root.scaling)
@ -54,10 +66,11 @@ Rectangle {
IconImage {
id: appIcon
anchors.centerIn: parent
width: Style.marginL * root.scaling
height: Style.marginL * root.scaling
width: parent.width
height: parent.height
source: AppIcons.iconForAppId(taskbarItem.modelData.appId)
smooth: true
asynchronous: true
}
}

View file

@ -16,9 +16,10 @@ Rectangle {
property ShellScreen screen
property real scaling: 1.0
readonly property real itemSize: 24 * scaling
readonly property string barPosition: Settings.data.bar.position
readonly property bool isVertical: barPosition === "left" || barPosition === "right"
readonly property bool compact: (Settings.data.bar.density === "compact")
readonly property real itemSize: isVertical ? width * 0.75 : height * 0.85
function onLoaded() {
// When the widget is fully initialized with its props set the screen for the trayMenu
@ -31,7 +32,7 @@ Rectangle {
implicitWidth: isVertical ? Math.round(Style.capsuleHeight * scaling) : (trayFlow.implicitWidth + Style.marginS * scaling * 2)
implicitHeight: isVertical ? (trayFlow.implicitHeight + Style.marginS * scaling * 2) : Math.round(Style.capsuleHeight * scaling)
radius: Math.round(Style.radiusM * scaling)
color: Color.mSurfaceVariant
color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
Layout.alignment: Qt.AlignVCenter

View file

@ -6,6 +6,7 @@ import qs.Commons
import qs.Modules.SettingsPanel
import qs.Services
import qs.Widgets
import qs.Modules.Bar.Extras
Item {
id: root
@ -71,10 +72,11 @@ Item {
}
}
NPill {
BarPill {
id: pill
rightOpen: BarWidgetRegistry.getNPillDirection(root)
compact: (Settings.data.bar.density === "compact")
rightOpen: BarWidgetRegistry.getPillDirection(root)
icon: getIcon()
autoHide: false // Important to be false so we can hover as long as we want
text: Math.floor(AudioService.volume * 100)

View file

@ -13,9 +13,9 @@ NIconButton {
property ShellScreen screen
property real scaling: 1.0
sizeRatio: 0.8
colorBg: Color.mSurfaceVariant
compact: (Settings.data.bar.density === "compact")
baseSize: Style.capsuleHeight
colorBg: (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent)
colorFg: Color.mOnSurface
colorBorder: Color.transparent
colorBorderHover: Color.transparent

View file

@ -32,6 +32,15 @@ Item {
}
readonly property string barPosition: Settings.data.bar.position
readonly property bool isVertical: barPosition === "left" || barPosition === "right"
readonly property bool compact: (Settings.data.bar.density === "compact")
readonly property real baseDimensionRatio: {
const b = compact ? 0.85 : 0.65
if (widgetSettings.labelMode === "none") {
return b * 0.75
}
return b
}
readonly property string labelMode: (widgetSettings.labelMode !== undefined) ? widgetSettings.labelMode : widgetMetadata.labelMode
readonly property bool hideUnoccupied: (widgetSettings.hideUnoccupied !== undefined) ? widgetSettings.hideUnoccupied : widgetMetadata.hideUnoccupied
@ -49,47 +58,45 @@ Item {
signal workspaceChanged(int workspaceId, color accentColor)
implicitHeight: (barPosition === "left" || barPosition === "right") ? calculatedVerticalHeight() : Math.round(Style.barHeight * scaling)
implicitWidth: (barPosition === "left" || barPosition === "right") ? Math.round(Style.barHeight * scaling) : calculatedHorizontalWidth()
implicitWidth: isVertical ? Math.round(Style.barHeight * scaling) : computeWidth()
implicitHeight: isVertical ? computeHeight() : Math.round(Style.barHeight * scaling)
function calculatedWsWidth(ws) {
function getWorkspaceWidth(ws) {
const d = Style.capsuleHeight * root.baseDimensionRatio
if (ws.isFocused)
return Math.round(44 * scaling)
else if (ws.isActive)
return Math.round(28 * scaling)
return d * 2.5
else
return Math.round(20 * scaling)
return d
}
function calculatedWsHeight(ws) {
function getWorkspaceHeight(ws) {
const d = Style.capsuleHeight * root.baseDimensionRatio
if (ws.isFocused)
return Math.round(44 * scaling)
else if (ws.isActive)
return Math.round(28 * scaling)
return d * 3
else
return Math.round(20 * scaling)
return d
}
function calculatedVerticalHeight() {
function computeWidth() {
let total = 0
for (var i = 0; i < localWorkspaces.count; i++) {
const ws = localWorkspaces.get(i)
total += calculatedWsHeight(ws)
total += getWorkspaceWidth(ws)
}
total += Math.max(localWorkspaces.count - 1, 0) * spacingBetweenPills
total += horizontalPadding * 2
return total
return Math.round(total)
}
function calculatedHorizontalWidth() {
function computeHeight() {
let total = 0
for (var i = 0; i < localWorkspaces.count; i++) {
const ws = localWorkspaces.get(i)
total += calculatedWsWidth(ws)
total += getWorkspaceHeight(ws)
}
total += Math.max(localWorkspaces.count - 1, 0) * spacingBetweenPills
total += horizontalPadding * 2
return total
return Math.round(total)
}
Component.onCompleted: {
@ -173,10 +180,10 @@ Item {
Rectangle {
id: workspaceBackground
width: (barPosition === "left" || barPosition === "right") ? Math.round(Style.capsuleHeight * scaling) : parent.width
height: (barPosition === "left" || barPosition === "right") ? parent.height : Math.round(Style.capsuleHeight * scaling)
width: isVertical ? Math.round(Style.capsuleHeight * scaling) : parent.width
height: isVertical ? parent.height : Math.round(Style.capsuleHeight * scaling)
radius: Math.round(Style.radiusM * scaling)
color: Color.mSurfaceVariant
color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
@ -187,17 +194,16 @@ Item {
id: pillRow
spacing: spacingBetweenPills
anchors.verticalCenter: workspaceBackground.verticalCenter
width: root.width - horizontalPadding * 2
x: horizontalPadding
visible: barPosition === "top" || barPosition === "bottom"
visible: !isVertical
Repeater {
id: workspaceRepeaterHorizontal
model: localWorkspaces
Item {
id: workspacePillContainer
height: (labelMode !== "none") ? Math.round(18 * scaling) : Math.round(14 * scaling)
width: root.calculatedWsWidth(model)
width: root.getWorkspaceWidth(model)
height: Style.capsuleHeight * root.baseDimensionRatio
Rectangle {
id: pill
@ -216,7 +222,7 @@ Item {
return model.idx.toString()
}
}
font.pointSize: model.isFocused ? Style.fontSizeXS * scaling : Style.fontSizeXXS * scaling
font.pointSize: model.isFocused ? workspacePillContainer.height * 0.45 : workspacePillContainer.height * 0.42
font.capitalization: Font.AllUppercase
font.family: Settings.data.ui.fontFixed
font.weight: Style.fontWeightBold
@ -332,17 +338,16 @@ Item {
id: pillColumn
spacing: spacingBetweenPills
anchors.horizontalCenter: workspaceBackground.horizontalCenter
height: root.height - horizontalPadding * 2
y: horizontalPadding
visible: barPosition === "left" || barPosition === "right"
visible: isVertical
Repeater {
id: workspaceRepeaterVertical
model: localWorkspaces
Item {
id: workspacePillContainerVertical
width: (labelMode !== "none") ? Math.round(18 * scaling) : Math.round(14 * scaling)
height: root.calculatedWsHeight(model)
width: Style.capsuleHeight * root.baseDimensionRatio
height: root.getWorkspaceHeight(model)
Rectangle {
id: pillVertical
@ -361,7 +366,7 @@ Item {
return model.idx.toString()
}
}
font.pointSize: model.isFocused ? Style.fontSizeXS * scaling : Style.fontSizeXXS * scaling
font.pointSize: model.isFocused ? workspacePillContainerVertical.width * 0.45 : workspacePillContainerVertical.width * 0.42
font.capitalization: Font.AllUppercase
font.family: Settings.data.ui.fontFixed
font.weight: Style.fontWeightBold

View file

@ -53,7 +53,7 @@ NPanel {
enabled: Settings.data.network.bluetoothEnabled
icon: BluetoothService.adapter && BluetoothService.adapter.discovering ? "stop" : "refresh"
tooltipText: "Refresh Devices"
sizeRatio: 0.8
baseSize: Style.baseWidgetSize * 0.8
onClicked: {
if (BluetoothService.adapter) {
BluetoothService.adapter.discovering = !BluetoothService.adapter.discovering
@ -64,7 +64,7 @@ NPanel {
NIconButton {
icon: "close"
tooltipText: "Close."
sizeRatio: 0.8
baseSize: Style.baseWidgetSize * 0.8
onClicked: {
root.close()
}

View file

@ -12,7 +12,6 @@ NPanel {
preferredWidth: 340
preferredHeight: 320
panelAnchorRight: Settings.data.bar.position === "right"
// Main Column
panelContent: ColumnLayout {

View file

@ -79,8 +79,11 @@ Item {
"icon": app.icon || "application-x-executable",
"isImage": false,
"onActivate": function () {
Logger.log("ApplicationsPlugin", `Launching: ${app.name}`)
// Close the launcher/NPanel immediately without any animations.
// Ensures we are not preventing the future focusing of the app
launcher.closeCompleted()
Logger.log("ApplicationsPlugin", `Launching: ${app.name}`)
if (Settings.data.appLauncher.useApp2Unit && app.id) {
Logger.log("ApplicationsPlugin", `Using app2unit for: ${app.id}`)
if (app.runInTerminal)
@ -89,11 +92,9 @@ Item {
Quickshell.execDetached(["app2unit", "--"].concat(app.command))
} else if (app.execute) {
app.execute()
} else if (app.exec) {
// Fallback to manual execution
Process.execute(app.exec)
} else {
Logger.log("ApplicationsPlugin", `Could not launch: ${app.name}`)
}
launcher.close()
}
}
}

View file

@ -326,7 +326,7 @@ Variants {
NIconButton {
icon: "close"
tooltipText: "Close."
sizeRatio: 0.6
baseSize: Style.baseWidgetSize * 0.6
anchors.top: parent.top
anchors.topMargin: Style.marginM * scaling
anchors.right: parent.right

View file

@ -14,7 +14,6 @@ NPanel {
preferredWidth: 380
preferredHeight: 500
panelAnchorRight: Settings.data.bar.position === "right"
panelKeyboardFocus: true
panelContent: Rectangle {
@ -48,15 +47,15 @@ NPanel {
NIconButton {
icon: Settings.data.notifications.doNotDisturb ? "bell-off" : "bell"
tooltipText: Settings.data.notifications.doNotDisturb ? "'Do Not Disturb' is enabled." : "'Do Not Disturb' is disabled."
sizeRatio: 0.8
baseSize: Style.baseWidgetSize * 0.8
onClicked: Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb
onRightClicked: Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb
}
NIconButton {
icon: "trash"
tooltipText: "Clear history"
sizeRatio: 0.8
tooltipText: "Clear history."
baseSize: Style.baseWidgetSize * 0.8
onClicked: {
NotificationService.clearHistory()
root.close()
@ -66,7 +65,7 @@ NPanel {
NIconButton {
icon: "close"
tooltipText: "Close."
sizeRatio: 0.8
baseSize: Style.baseWidgetSize * 0.8
onClicked: {
root.close()
}
@ -136,7 +135,7 @@ NPanel {
width: notificationList.width
height: notificationLayout.implicitHeight + (Style.marginM * scaling * 2)
radius: Style.radiusM * scaling
color: notificationMouseArea.containsMouse ? Color.mTertiary : Color.mSurfaceVariant
color: Color.mSurfaceVariant
border.color: Qt.alpha(Color.mOutline, Style.opacityMedium)
border.width: Math.max(1, Style.borderS * scaling)
@ -169,7 +168,7 @@ NPanel {
text: (summary || "No summary").substring(0, 100)
font.pointSize: Style.fontSizeM * scaling
font.weight: Font.Medium
color: notificationMouseArea.containsMouse ? Color.mOnTertiary : Color.mPrimary
color: Color.mPrimary
wrapMode: Text.Wrap
Layout.fillWidth: true
maximumLineCount: 2
@ -179,7 +178,7 @@ NPanel {
NText {
text: (body || "").substring(0, 150)
font.pointSize: Style.fontSizeXS * scaling
color: notificationMouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface
color: Color.mOnSurface
wrapMode: Text.Wrap
Layout.fillWidth: true
maximumLineCount: 3
@ -190,7 +189,7 @@ NPanel {
NText {
text: NotificationService.formatTimestamp(timestamp)
font.pointSize: Style.fontSizeXS * scaling
color: notificationMouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface
color: Color.mOnSurface
Layout.fillWidth: true
}
}
@ -198,8 +197,8 @@ NPanel {
// Delete button
NIconButton {
icon: "trash"
tooltipText: "Delete notification"
sizeRatio: 0.7
tooltipText: "Delete notification."
baseSize: Style.baseWidgetSize * 0.7
Layout.alignment: Qt.AlignTop
onClicked: {

View file

@ -14,6 +14,8 @@ NBox {
property var widgetModel: []
property var availableWidgets: []
readonly property real miniButtonSize: Style.baseWidgetSize * 0.65
signal addWidget(string widgetId, string section)
signal removeWidget(string section, int index)
signal reorderWidget(string section, int fromIndex, int toIndex)
@ -178,7 +180,7 @@ NBox {
active: BarWidgetRegistry.widgetHasUserSettings(modelData.id)
sourceComponent: NIconButton {
icon: "settings"
sizeRatio: 0.6
baseSize: miniButtonSize
colorBorder: Qt.alpha(Color.mOutline, Style.opacityLight)
colorBg: Color.mOnSurface
colorFg: Color.mOnPrimary
@ -218,7 +220,7 @@ NBox {
NIconButton {
icon: "close"
sizeRatio: 0.6
baseSize: miniButtonSize
colorBorder: Qt.alpha(Color.mOutline, Style.opacityLight)
colorBg: Color.mOnSurface
colorFg: Color.mOnPrimary

View file

@ -298,7 +298,7 @@ ColumnLayout {
NIconButton {
icon: "close"
sizeRatio: 0.8
baseSize: Style.baseWidgetSize * 0.8
Layout.alignment: Qt.AlignVCenter
Layout.rightMargin: Style.marginXS * scaling
onClicked: {

View file

@ -45,32 +45,52 @@ ColumnLayout {
description: "Configure bar appearance and positioning."
}
RowLayout {
NComboBox {
Layout.fillWidth: true
label: "Bar Position"
description: "Choose where to place the bar on the screen."
model: ListModel {
ListElement {
key: "top"
name: "Top"
}
ListElement {
key: "bottom"
name: "Bottom"
}
ListElement {
key: "left"
name: "Left"
}
ListElement {
key: "right"
name: "Right"
}
NComboBox {
Layout.fillWidth: true
label: "Bar Position"
description: "Choose where to place the bar on the screen."
model: ListModel {
ListElement {
key: "top"
name: "Top"
}
ListElement {
key: "bottom"
name: "Bottom"
}
ListElement {
key: "left"
name: "Left"
}
ListElement {
key: "right"
name: "Right"
}
currentKey: Settings.data.bar.position
onSelected: key => Settings.data.bar.position = key
}
currentKey: Settings.data.bar.position
onSelected: key => Settings.data.bar.position = key
}
NComboBox {
Layout.fillWidth: true
label: "Bar Density"
description: "Choose the density of the bar."
model: ListModel {
ListElement {
key: "compact"
name: "Compact"
}
ListElement {
key: "default"
name: "Default"
}
ListElement {
key: "comfortable"
name: "Comfortable"
}
}
currentKey: Settings.data.bar.density
onSelected: key => Settings.data.bar.density = key
}
ColumnLayout {
@ -92,6 +112,15 @@ ColumnLayout {
text: Math.floor(Settings.data.bar.backgroundOpacity * 100) + "%"
}
}
NToggle {
Layout.fillWidth: true
label: "Show Capsule"
description: "Adds a capsule behind each widget to improve readability on transparent bars."
checked: Settings.data.bar.showCapsule
onToggled: checked => Settings.data.bar.showCapsule = checked
}
NToggle {
Layout.fillWidth: true
label: "Floating Bar"
@ -163,40 +192,6 @@ ColumnLayout {
Layout.bottomMargin: Style.marginXL * scaling
}
// Monitor Configuration
ColumnLayout {
spacing: Style.marginM * scaling
Layout.fillWidth: true
NHeader {
label: "Monitors Configuration"
description: "Choose which monitors should display the bar."
}
Repeater {
model: Quickshell.screens || []
delegate: NCheckbox {
Layout.fillWidth: true
label: `${modelData.name || "Unknown"}${modelData.model ? `: ${modelData.model}` : ""}`
description: `${modelData.width}x${modelData.height} at (${modelData.x}, ${modelData.y})`
checked: (Settings.data.bar.monitors || []).indexOf(modelData.name) !== -1
onToggled: checked => {
if (checked) {
Settings.data.bar.monitors = addMonitor(Settings.data.bar.monitors, modelData.name)
} else {
Settings.data.bar.monitors = removeMonitor(Settings.data.bar.monitors, modelData.name)
}
}
}
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
// Widgets Management Section
ColumnLayout {
spacing: Style.marginXXS * scaling
@ -264,6 +259,40 @@ ColumnLayout {
Layout.bottomMargin: Style.marginXL * scaling
}
// Monitor Configuration
ColumnLayout {
spacing: Style.marginM * scaling
Layout.fillWidth: true
NHeader {
label: "Monitors Configuration"
description: "Show bar on specific monitors. Defaults to all if none are chosen."
}
Repeater {
model: Quickshell.screens || []
delegate: NCheckbox {
Layout.fillWidth: true
label: modelData.name || "Unknown"
description: `${modelData.model} - ${modelData.width}x${modelData.height} [x:${modelData.x} y:${modelData.y}]`
checked: (Settings.data.bar.monitors || []).indexOf(modelData.name) !== -1
onToggled: checked => {
if (checked) {
Settings.data.bar.monitors = addMonitor(Settings.data.bar.monitors, modelData.name)
} else {
Settings.data.bar.monitors = removeMonitor(Settings.data.bar.monitors, modelData.name)
}
}
}
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
// ---------------------------------
// Signal functions
// ---------------------------------

View file

@ -87,19 +87,9 @@ ColumnLayout {
anchors.margins: Style.marginL * scaling
spacing: Style.marginXXS * scaling
NText {
text: (`${modelData.name}: ${modelData.model}` || "Unknown")
font.pointSize: Style.fontSizeL * scaling
font.weight: Style.fontWeightBold
color: Color.mPrimary
}
NText {
text: `Resolution: ${modelData.width}x${modelData.height} at (${modelData.x}, ${modelData.y})`
font.pointSize: Style.fontSizeXS * scaling
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true
NLabel {
label: modelData.name || "Unknown"
description: `${modelData.model} - ${modelData.width}x${modelData.height} [x:${modelData.x} y:${modelData.y}]`
}
// Scale
@ -134,8 +124,8 @@ ColumnLayout {
NIconButton {
icon: "refresh"
sizeRatio: 0.8
tooltipText: "Reset scaling"
baseSize: Style.baseWidgetSize * 0.9
tooltipText: "Reset scaling."
onClicked: ScalingService.setScreenScale(modelData, 1.0)
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter

View file

@ -93,15 +93,15 @@ ColumnLayout {
NHeader {
label: "Monitors Configuration"
description: "Choose which monitors should display the dock."
description: "Show dock on specific monitors."
}
Repeater {
model: Quickshell.screens || []
delegate: NCheckbox {
Layout.fillWidth: true
label: `${modelData.name || "Unknown"}${modelData.model ? `: ${modelData.model}` : ""}`
description: `${modelData.width}x${modelData.height} at (${modelData.x}, ${modelData.y})`
label: modelData.name || "Unknown"
description: `${modelData.model} - ${modelData.width}x${modelData.height} [x:${modelData.x} y:${modelData.y}]`
checked: (Settings.data.dock.monitors || []).indexOf(modelData.name) !== -1
onToggled: checked => {
if (checked) {

View file

@ -46,40 +46,6 @@ ColumnLayout {
Layout.bottomMargin: Style.marginXL * scaling
}
// Monitor Configuration
ColumnLayout {
spacing: Style.marginM * scaling
Layout.fillWidth: true
NHeader {
label: "Monitors Configuration"
description: "Choose which monitors should display notifications."
}
Repeater {
model: Quickshell.screens || []
delegate: NCheckbox {
Layout.fillWidth: true
label: `${modelData.name || "Unknown"}${modelData.model ? `: ${modelData.model}` : ""}`
description: `${modelData.width}x${modelData.height} at (${modelData.x}, ${modelData.y})`
checked: (Settings.data.notifications.monitors || []).indexOf(modelData.name) !== -1
onToggled: checked => {
if (checked) {
Settings.data.notifications.monitors = addMonitor(Settings.data.notifications.monitors, modelData.name)
} else {
Settings.data.notifications.monitors = removeMonitor(Settings.data.notifications.monitors, modelData.name)
}
}
}
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
// Notification Duration Settings
ColumnLayout {
spacing: Style.marginL * scaling
@ -159,4 +125,38 @@ ColumnLayout {
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
// Monitor Configuration
ColumnLayout {
spacing: Style.marginM * scaling
Layout.fillWidth: true
NHeader {
label: "Monitors Configuration"
description: "Show bar on specific monitors. Defaults to all if none are chosen."
}
Repeater {
model: Quickshell.screens || []
delegate: NCheckbox {
Layout.fillWidth: true
label: modelData.name || "Unknown"
description: `${modelData.model} - ${modelData.width}x${modelData.height} [x:${modelData.x} y:${modelData.y}]`
checked: (Settings.data.notifications.monitors || []).indexOf(modelData.name) !== -1
onToggled: checked => {
if (checked) {
Settings.data.notifications.monitors = addMonitor(Settings.data.notifications.monitors, modelData.name)
} else {
Settings.data.notifications.monitors = removeMonitor(Settings.data.notifications.monitors, modelData.name)
}
}
}
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
}

View file

@ -64,7 +64,7 @@ ColumnLayout {
// Source
NComboBox {
label: "Video Source"
description: "Portal is recommend, if you get artifacts try Screen."
description: "Portal is recommended, if you get artifacts try Screen."
model: ListModel {
ListElement {
key: "portal"

View file

@ -136,7 +136,7 @@ Rectangle {
colorBorder: Color.transparent
colorBorderHover: Color.mOutline
sizeRatio: 0.8
baseSize: Style.baseWidgetSize * 0.8
Layout.alignment: Qt.AlignTop
onClicked: root.hide()

View file

@ -57,7 +57,7 @@ NPanel {
NIconButton {
icon: "refresh"
tooltipText: "Refresh"
sizeRatio: 0.8
baseSize: Style.baseWidgetSize * 0.8
enabled: Settings.data.network.wifiEnabled && !NetworkService.scanning
onClicked: NetworkService.scan()
}
@ -65,7 +65,7 @@ NPanel {
NIconButton {
icon: "close"
tooltipText: "Close."
sizeRatio: 0.8
baseSize: Style.baseWidgetSize * 0.8
onClicked: root.close()
}
}
@ -106,7 +106,7 @@ NPanel {
NIconButton {
icon: "close"
sizeRatio: 0.6
baseSize: Style.baseWidgetSize * 0.6
onClicked: NetworkService.lastError = ""
}
}
@ -368,7 +368,7 @@ NPanel {
visible: (modelData.existing || modelData.cached) && !modelData.connected && NetworkService.connectingTo !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid
icon: "trash"
tooltipText: "Forget network"
sizeRatio: 0.7
baseSize: Style.baseWidgetSize * 0.8
onClicked: expandedSsid = expandedSsid === modelData.ssid ? "" : modelData.ssid
}
@ -478,7 +478,7 @@ NPanel {
NIconButton {
icon: "close"
sizeRatio: 0.8
baseSize: Style.baseWidgetSize * 0.8
onClicked: {
passwordSsid = ""
passwordInput = ""
@ -532,7 +532,7 @@ NPanel {
NIconButton {
icon: "close"
sizeRatio: 0.8
baseSize: Style.baseWidgetSize * 0.8
onClicked: expandedSsid = ""
}
}

View file

@ -230,6 +230,23 @@ Start the Shell with: `qs -c noctalia-shell`
Access settings through the side panel (top right button) to configure weather, wallpapers, screen recording, audio, network, and theme options.
Configuration is usually stored in ~/.config/noctalia.
### Some of my app icons are missing!
The issue is most likely that you did not set up your environment variables properly.
Example environment variables that you can use one of the following:
If you already have an icon theme set for GTK then you can use this one:
- `QT_QPA_PLATFORMTHEME=gtk3`
You can also use Qt6ct to set your icon theme, for that you can use:
- `QT_QPA_PLATFORMTHEME=qt6ct`
If you don't have either of those set then you can just use:
- `QS_ICON_THEME="youricontheme"`
**Any of these environment variables should go into `/etc/environment` (you need to reboot afterwards). For NixOS you can use `environment.variables` or `home.sessionVariables`.**
### Application Launcher
The launcher supports special commands for enhanced functionality:
@ -278,10 +295,6 @@ window-rule {
clip-to-geometry true
}
layer-rule {
match namespace="^quickshell-wallpaper$"
}
layer-rule {
match namespace="^quickshell-overview$"
place-within-backdrop true

View file

@ -207,7 +207,7 @@ Singleton {
return (widgetMetadata[id] !== undefined) && (widgetMetadata[id].allowUserSettings === true)
}
function getNPillDirection(widget) {
function getPillDirection(widget) {
try {
if (widget.section === "left") {
return true

View file

@ -16,6 +16,9 @@ Singleton {
property var registeredPanels: ({})
signal willOpen
signal willClose
// Register this panel
function registerPanel(panel) {
registeredPanels[panel.objectName] = panel
@ -38,6 +41,14 @@ Singleton {
openedPanel.close()
}
openedPanel = panel
// emit signal
willOpen()
}
function willClosePanel(panel) {
// emit signal
willClose()
}
function closedPanel(panel) {

View file

@ -333,6 +333,26 @@ Singleton {
}
}
// Compact speed formatter for vertical bar display
function formatCompactSpeed(bytesPerSecond) {
if (!bytesPerSecond || bytesPerSecond <= 0)
return "0"
const units = ["", "K", "M", "G"]
let value = bytesPerSecond
let unitIndex = 0
while (value >= 1024 && unitIndex < units.length - 1) {
value = value / 1024.0
unitIndex++
}
// Promote at ~100 of current unit (e.g., 100k -> ~0.1M shown as 0.1M or 0M if rounded)
if (unitIndex < units.length - 1 && value >= 100) {
value = value / 1024.0
unitIndex++
}
const display = Math.round(value).toString()
return display + units[unitIndex]
}
// -------------------------------------------------------
// Function to start fetching and computing the cpu temperature
function updateCpuTemperature() {

View file

@ -8,7 +8,7 @@ Singleton {
id: root
// Public properties
property string baseVersion: "2.9.2"
property string baseVersion: "2.11.0"
property bool isDevelopment: false
property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + "-dev"}`

View file

@ -13,7 +13,7 @@ Rectangle {
signal colorSelected(color color)
implicitWidth: 150 * scaling
implicitHeight: 40 * scaling
implicitHeight: Math.round(Style.baseWidgetSize * 1.1 * scaling)
radius: Style.radiusM * scaling
color: Color.mSurface
@ -40,12 +40,16 @@ Rectangle {
RowLayout {
anchors.fill: parent
anchors.margins: Style.marginS * scaling
anchors {
leftMargin: Style.marginL * scaling
rightMargin: Style.marginL * scaling
}
spacing: Style.marginS * scaling
// Color preview circle
Rectangle {
Layout.preferredWidth: 24 * scaling
Layout.preferredHeight: 24 * scaling
Layout.preferredWidth: root.height * 0.6 * scaling
Layout.preferredHeight: root.height * 0.6 * scaling
radius: Layout.preferredWidth * 0.5
color: root.selectedColor
border.color: Color.mOutline
@ -56,11 +60,14 @@ Rectangle {
text: root.selectedColor.toString().toUpperCase()
font.family: Settings.data.ui.fontFixed
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
}
NIcon {
icon: "color-picker"
color: Color.mOnSurfaceVariant
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
}
}
}

View file

@ -7,14 +7,14 @@ import qs.Services
Rectangle {
id: root
// Multiplier to control how large the button container is relative to Style.baseWidgetSize
property real sizeRatio: 1.0
property real baseSize: Style.baseWidgetSize
property string icon
property string tooltipText
property bool enabled: true
property bool allowClickWhenDisabled: false
property bool hovering: false
property bool compact: false
property color colorBg: Color.mSurfaceVariant
property color colorFg: Color.mPrimary
@ -29,8 +29,8 @@ Rectangle {
signal rightClicked
signal middleClicked
implicitWidth: Math.round(Style.baseWidgetSize * scaling * sizeRatio)
implicitHeight: Math.round(Style.baseWidgetSize * scaling * sizeRatio)
implicitWidth: Math.round(baseSize * scaling)
implicitHeight: Math.round(baseSize * scaling)
opacity: root.enabled ? Style.opacityFull : Style.opacityMedium
color: root.enabled && root.hovering ? colorBgHover : colorBg
@ -47,7 +47,7 @@ Rectangle {
NIcon {
icon: root.icon
font.pointSize: Math.max(1, root.width * 0.47)
font.pointSize: Math.max(1, root.compact ? root.width * 0.65 : root.width * 0.48)
color: root.enabled && root.hovering ? colorFgHover : colorFg
// Center horizontally
x: (root.width - width) / 2

View file

@ -38,9 +38,9 @@ Loader {
readonly property real originalOpacity: 0.0
property real scaleValue: originalScale
property real opacityValue: originalOpacity
property real dimmingOpacity: 0
property alias isClosing: hideTimer.running
readonly property string barPosition: Settings.data.bar.position
signal opened
signal closed
@ -109,9 +109,11 @@ Loader {
// -----------------------------------------
function close() {
dimmingOpacity = 0
scaleValue = originalScale
opacityValue = originalOpacity
hideTimer.start()
PanelService.willClosePanel(root)
}
// -----------------------------------------
@ -141,10 +143,16 @@ Loader {
// PanelWindow has its own screen property inherited of QsWindow
property real scaling: ScalingService.getScreenScale(screen)
readonly property real barHeight: Math.round(Style.barHeight * scaling)
readonly property real barWidth: Math.round(Style.barHeight * scaling)
readonly property bool barAtBottom: Settings.data.bar.position === "bottom"
readonly property string barPosition: Settings.data.bar.position
readonly property bool isVertical: barPosition === "left" || barPosition === "right"
readonly property bool barIsVisible: (screen !== null) && (Settings.data.bar.monitors.includes(screen.name) || (Settings.data.bar.monitors.length === 0))
readonly property real verticalBarWidth: Math.round(Style.barHeight * scaling)
Component.onCompleted: {
Logger.log("NPanel", "Opened", root.objectName)
dimmingOpacity = Style.opacityHeavy
}
Connections {
target: ScalingService
@ -169,16 +177,14 @@ Loader {
visible: true
// Dim desktop if required
color: (root.active && !root.isClosing && Settings.data.general.dimDesktop) ? Qt.alpha(Color.mShadow, Style.opacityHeavy) : Color.transparent
color: Settings.data.general.dimDesktop ? Qt.alpha(Color.mShadow, dimmingOpacity) : Color.transparent
WlrLayershell.exclusionMode: ExclusionMode.Ignore
WlrLayershell.namespace: "noctalia-panel"
WlrLayershell.keyboardFocus: root.panelKeyboardFocus ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
Behavior on color {
ColorAnimation {
duration: Style.animationNormal
duration: Style.animationSlow
}
}
@ -186,29 +192,6 @@ Loader {
anchors.left: true
anchors.right: true
anchors.bottom: true
margins.top: {
if (!barIsVisible || barAtBottom) {
return 0
}
switch (Settings.data.bar.position) {
case "top":
return (Style.barHeight + Style.marginM) * scaling + (Settings.data.bar.floating && !panelAnchorVerticalCenter ? Settings.data.bar.marginVertical * Style.marginXL * scaling : 0)
default:
return Style.marginM * scaling
}
}
margins.bottom: {
if (!barIsVisible || !barAtBottom) {
return 0
}
switch (Settings.data.bar.position) {
case "bottom":
return (Style.barHeight + Style.marginM) * scaling + (Settings.data.bar.floating && !panelAnchorVerticalCenter ? Settings.data.bar.marginVertical * Style.marginXL * scaling : 0)
default:
return 0
}
}
// Close any panel with Esc without requiring focus
Shortcut {
@ -225,6 +208,7 @@ Loader {
onClicked: root.close()
}
// The actual panel's content
Rectangle {
id: panelBackground
color: panelBackgroundColor
@ -255,130 +239,131 @@ Loader {
scale: root.scaleValue
opacity: root.opacityValue
x: calculatedX
y: calculatedY
property int calculatedX: {
var barPosition = Settings.data.bar.position
// Check anchor properties first, even when using button positioning
if (!panelAnchorHorizontalCenter && panelAnchorLeft) {
return Math.round(Style.marginS * scaling)
} else if (!panelAnchorHorizontalCenter && panelAnchorRight) {
// For right anchor, consider bar position
if (barPosition === "right") {
// If bar is on right, position panel to the left of the bar
var maxX = panelWindow.width - barWidth - panelBackground.width - (Style.marginS * scaling)
// If we have button position, position close to the button like working panels
if (root.useButtonPosition) {
// Use the same logic as working panels - position at edge of bar with spacing
var maxXWithSpacing = panelWindow.width - barWidth - panelBackground.width
// Add spacing - more if screen corners are disabled, less if enabled
if (!Settings.data.general.showScreenCorners || Settings.data.bar.floating) {
maxXWithSpacing -= Style.marginL * scaling
} else {
maxXWithSpacing -= Style.marginM * scaling
}
return Math.round(maxXWithSpacing)
} else {
return Math.round(maxX)
}
} else {
// Default right positioning
var rightX = panelWindow.width - panelBackground.width - (Style.marginS * scaling)
return Math.round(rightX)
}
} else if (root.useButtonPosition) {
// Position panel relative to button (only if no explicit anchoring)
var targetX
// For vertical bars, position panel close to the button
if (barPosition === "left") {
// Position panel to the right of the left bar, close to the button
var minX = barWidth
// Add spacing - more if screen corners are disabled, less if enabled
if (!Settings.data.general.showScreenCorners || Settings.data.bar.floating) {
minX += Style.marginL * scaling
} else {
minX += Style.marginM * scaling
}
targetX = minX
} else if (barPosition === "right") {
// Position panel to the left of the right bar, close to the button
var maxX = panelWindow.width - barWidth - panelBackground.width
// Add spacing - more if screen corners are disabled, less if enabled
if (!Settings.data.general.showScreenCorners || Settings.data.bar.floating) {
maxX -= Style.marginL * scaling
} else {
maxX -= Style.marginM * scaling
}
targetX = maxX
} else {
// For horizontal bars, center panel on button
targetX = root.buttonPosition.x + (root.buttonWidth / 2) - (panelBackground.width / 2)
}
// Keep panel within screen bounds
var maxScreenX = panelWindow.width - panelBackground.width - (Style.marginS * scaling)
var minScreenX = Style.marginS * scaling
return Math.round(Math.max(minScreenX, Math.min(targetX, maxScreenX)))
} else {
// For vertical bars, center but avoid bar overlap
var centerX = (panelWindow.width - panelBackground.width) / 2
if (barPosition === "left") {
var minX = barWidth
// Add spacing - more if screen corners are disabled, less if enabled
if (!Settings.data.general.showScreenCorners || Settings.data.bar.floating) {
minX += Style.marginL * scaling
} else {
minX += Style.marginM * scaling
}
centerX = Math.max(centerX, minX)
} else if (barPosition === "right") {
// For right bar, center but ensure it doesn't overlap with the bar
var maxX = panelWindow.width - barWidth - panelBackground.width
// Add spacing - more if screen corners are disabled, less if enabled
if (!Settings.data.general.showScreenCorners || Settings.data.bar.floating) {
maxX -= Style.marginL * scaling
} else {
maxX -= Style.marginM * scaling
}
centerX = Math.min(centerX, maxX)
}
return Math.round(centerX)
// ---------------------------------------------
// Does not account for corners are they are negligible and helps keep the code clean.
// ---------------------------------------------
property real marginTop: {
if (!barIsVisible) {
return 0
}
switch (barPosition || panelAnchorVerticalCenter) {
case "top":
return (Style.barHeight + Style.marginS) * scaling + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * 2 * Style.marginXL * scaling : 0)
default:
return Style.marginS * scaling
}
}
property int calculatedY: {
var barPosition = Settings.data.bar.position
property real marginBottom: {
if (!barIsVisible || panelAnchorVerticalCenter) {
return 0
}
switch (barPosition) {
case "bottom":
return (Style.barHeight + Style.marginS) * scaling + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * 2 * Style.marginXL * scaling : 0)
default:
return Style.marginS * scaling
}
}
if (root.useButtonPosition) {
// Position panel relative to button
var targetY = root.buttonPosition.y + (root.buttonHeight / 2) - (panelBackground.height / 2)
property real marginLeft: {
if (!barIsVisible || panelAnchorHorizontalCenter) {
return 0
}
switch (barPosition) {
case "left":
return (Style.barHeight + Style.marginS) * scaling + (Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * 2 * Style.marginXL * scaling : 0)
default:
return Style.marginS * scaling
}
}
// Keep panel within screen bounds
var maxY = panelWindow.height - panelBackground.height - (Style.marginS * scaling)
var minY = Style.marginS * scaling
property real marginRight: {
if (!barIsVisible || panelAnchorHorizontalCenter) {
return 0
}
switch (barPosition) {
case "right":
return (Style.barHeight + Style.marginS) * scaling + (Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * 2 * Style.marginXL * scaling : 0)
default:
return Style.marginS * scaling
}
}
return Math.round(Math.max(minY, Math.min(targetY, maxY)))
} else if (panelAnchorVerticalCenter) {
return Math.round((panelWindow.height - panelBackground.height) / 2)
} else if (panelAnchorBottom) {
return Math.round(panelWindow.height - panelBackground.height - (Style.marginS * scaling))
} else if (panelAnchorTop) {
return Math.round(Style.marginS * scaling)
} else if (barPosition === "left" || barPosition === "right") {
// For vertical bars, center vertically
return Math.round((panelWindow.height - panelBackground.height) / 2)
} else if (!barAtBottom) {
// Below the top bar
return Math.round(Style.marginS * scaling)
// ---------------------------------------------
property int calculatedX: {
// Priority to fixed anchoring
if (panelAnchorHorizontalCenter) {
return Math.round((panelWindow.width - panelBackground.width) / 2)
} else if (panelAnchorLeft) {
return marginLeft
} else if (panelAnchorRight) {
return Math.round(panelWindow.width - panelBackground.width - marginRight)
}
// No fixed anchoring
if (isVertical) {
// Vertical bar
if (barPosition === "right") {
// To the left of the right bar
return Math.round(panelWindow.width - panelBackground.width - marginRight)
} else {
// To the right of the left bar
return marginLeft
}
} else {
// Above the bottom bar
return Math.round(panelWindow.height - panelBackground.height - (Style.marginS * scaling))
// Horizontal bar
if (root.useButtonPosition) {
// Position panel relative to button
var targetX = buttonPosition.x + (buttonWidth / 2) - (panelBackground.width / 2)
// Keep panel within screen bounds
var maxX = panelWindow.width - panelBackground.width - marginRight
var minX = marginLeft
return Math.round(Math.max(minX, Math.min(targetX, maxX)))
} else {
// Fallback to center horizontally
return Math.round((panelWindow.width - panelBackground.width) / 2)
}
}
}
// ---------------------------------------------
property int calculatedY: {
// Priority to fixed anchoring
if (panelAnchorVerticalCenter) {
return Math.round((panelWindow.height - panelBackground.height) / 2)
} else if (panelAnchorTop) {
return marginTop
} else if (panelAnchorBottom) {
return Math.round(panelWindow.height - panelBackground.height - marginBottom)
}
// No fixed anchoring
if (isVertical) {
// Vertical bar
if (useButtonPosition) {
// Position panel relative to button
var targetY = buttonPosition.y + (buttonHeight / 2) - (panelBackground.height / 2)
// Keep panel within screen bounds
var maxY = panelWindow.height - panelBackground.height - marginBottom
var minY = marginTop
return Math.round(Math.max(minY, Math.min(targetY, maxY)))
} else {
// Fallback to center vertically
return Math.round((panelWindow.height - panelBackground.height) / 2)
}
} else {
// Horizontal bar
if (barPosition === "bottom") {
// Above the bottom bar
return Math.round(panelWindow.height - panelBackground.height - marginBottom)
} else {
// Below the top bar
return marginTop
}
}
}

View file

@ -173,6 +173,7 @@ RowLayout {
NText {
anchors.centerIn: parent
text: root.prefix + spinBox.value + root.suffix
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightMedium
color: Color.mOnSurface

View file

@ -49,7 +49,7 @@ Item {
item.onLoaded()
}
//Logger.log("NWidgetLoader", "Loaded", widgetId, "on screen", item.screen.name)
Logger.log("NWidgetLoader", "Loaded", widgetId, "on screen", item.screen.name)
}
}