Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions modules/bar/BatteryIndicator.qml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Item {
property bool vertical: bar.orientation === "vertical"
property bool isHovered: false
property bool layerEnabled: true

property real radius: 0
property real startRadius: radius
property real endRadius: radius
Expand Down Expand Up @@ -248,7 +248,7 @@ Item {
}

Text {
text: Battery.isPluggedIn ? (Battery.timeToFull !== "" ? "Full in " + Battery.timeToFull : "") : (Battery.timeToEmpty !== "" ? Battery.timeToEmpty + " remaining" : "")
text: Battery.isPluggedIn ? (Battery.timeToFull !== "" ? "Full in " + Battery.timeToFull : "Fully charged") : (Battery.timeToEmpty !== "" ? Battery.timeToEmpty + " remaining" : "")
font.family: Styling.defaultFont
font.pixelSize: Styling.fontSize(-1)
color: Colors.overBackground
Expand Down
18 changes: 16 additions & 2 deletions modules/bar/ControlsButton.qml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.modules.services
import qs.modules.components
import qs.modules.theme
Expand All @@ -14,7 +15,7 @@ Item {
property bool vertical: bar.orientation === "vertical"
property bool isHovered: false
property bool layerEnabled: true

property real radius: 0
property real startRadius: radius
property real endRadius: radius
Expand All @@ -27,6 +28,11 @@ Item {
Layout.fillWidth: vertical
Layout.fillHeight: !vertical

StyledToolTip {
show: root.isHovered && !root.popupOpen
tooltipText: "Audio & Brightness Controls"
}

HoverHandler {
onHoveredChanged: root.isHovered = hovered
}
Expand Down Expand Up @@ -69,7 +75,15 @@ Item {
anchors.fill: parent
hoverEnabled: false
cursorShape: Qt.PointingHandCursor
onClicked: controlsPopup.toggle()
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: mouse => {
if (mouse.button === Qt.RightButton) {
Quickshell.execDetached(["pavucontrol"]);
return;
} else if (mouse.button === Qt.LeftButton) {
controlsPopup.toggle();
}
}
}
}

Expand Down
106 changes: 85 additions & 21 deletions modules/bar/systray/SysTrayItem.qml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ MouseArea {
required property var bar
required property SystemTrayItem item
property int trayItemSize: 20
property bool isHovered: false

acceptedButtons: Qt.LeftButton | Qt.RightButton
Layout.fillHeight: bar.orientation === "horizontal"
Layout.fillWidth: bar.orientation === "vertical"
implicitWidth: trayItemSize
implicitHeight: trayItemSize

onClicked: event => {
switch (event.button) {
case Qt.LeftButton:
Expand All @@ -40,13 +41,13 @@ MouseArea {
id: systrayPopup
anchorItem: root
bar: root.bar

// Use a reasonable width for the menu
contentWidth: 220
// Height adapts to content, with a max limit if needed.
// Must include vertical padding (8 top + 8 bottom = 16)
contentHeight: Math.min(itemsColumn.implicitHeight + 16, 400)

popupPadding: 8
// 8px standard margin + 8px SysTray container padding to ensure correct offset from the main bar
visualMargin: 16
Expand All @@ -61,35 +62,88 @@ MouseArea {
anchors.fill: parent
contentWidth: availableWidth
clip: true

ScrollBar.horizontal.policy: ScrollBar.AlwaysOff

ColumnLayout {
id: itemsColumn
width: parent.width
spacing: 2

Repeater {
model: menuOpener.children ? menuOpener.children.values : []
delegate: SystrayMenuItem {

delegate: ColumnLayout {
required property var modelData

Layout.fillWidth: true

textStr: modelData.text || ""
iconSource: modelData.icon || ""
// Simple heuristic for image icon
isImageIcon: iconSource.indexOf("/") !== -1 || iconSource.indexOf(".") !== -1
isSeparator: modelData.isSeparator || false

onClicked: {
if (modelData.triggered) {
modelData.triggered();
} else if (modelData.activate) {
modelData.activate();
spacing: 2

property bool submenuExpanded: false

SystrayMenuItem {
Layout.fillWidth: true

textStr: modelData.text || ""
iconSource: modelData.icon || ""
isImageIcon: iconSource.indexOf("/") !== -1 || iconSource.indexOf(".") !== -1
isSeparator: modelData.isSeparator || false
hasSubmenu: modelData.hasChildren || false
expanded: parent.submenuExpanded
buttonType: modelData.buttonType || 0
checkState: modelData.checkState || 0

onClicked: {
if (modelData.hasChildren) {
parent.submenuExpanded = !parent.submenuExpanded;
} else {
if (modelData.triggered) {
modelData.triggered();
} else if (modelData.activate) {
modelData.activate();
}
systrayPopup.close();
}
}
}

// Submenu children — uses its own QsMenuOpener to trigger lazy loading
ColumnLayout {
visible: submenuExpanded && modelData.hasChildren
Layout.fillWidth: true
spacing: 2

QsMenuOpener {
id: subMenuOpener
menu: modelData.hasChildren ? modelData : null
}

Repeater {
model: subMenuOpener.children ? subMenuOpener.children.values : []

delegate: SystrayMenuItem {
required property var modelData

Layout.fillWidth: true
depth: 1

textStr: modelData.text || ""
iconSource: modelData.icon || ""
isImageIcon: iconSource.indexOf("/") !== -1 || iconSource.indexOf(".") !== -1
isSeparator: modelData.isSeparator || false
buttonType: modelData.buttonType || 0
checkState: modelData.checkState || 0

onClicked: {
if (modelData.triggered) {
modelData.triggered();
} else if (modelData.activate) {
modelData.activate();
}
systrayPopup.close();
}
}
}
systrayPopup.close();
}
}
}
Expand All @@ -116,4 +170,14 @@ MouseArea {
sourceItem: trayIcon
anchors.fill: trayIcon
}

StyledToolTip {
show: root.isHovered
tooltipText: root.item.tooltipTitle || root.item.title
desciription: root.item.tooltipDescription || ""
}

HoverHandler {
onHoveredChanged: root.isHovered = hovered
}
}
85 changes: 75 additions & 10 deletions modules/bar/systray/SystrayMenuItem.qml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Button {
id: root

property string textStr: ""

// Clean text logic from ContextMenu.qml
readonly property string cleanText: {
let t = textStr;
Expand All @@ -23,7 +23,14 @@ Button {
property var iconSource: ""
property bool isImageIcon: false
property bool isSeparator: false

property bool hasSubmenu: false
property bool expanded: false
property int depth: 0
// 0 = None, 1 = CheckBox, 2 = RadioButton
property int buttonType: 0
// Qt.Unchecked = 0, Qt.PartiallyChecked = 1, Qt.Checked = 2
property int checkState: 0

implicitWidth: 200
implicitHeight: isSeparator ? 10 : 36
enabled: !isSeparator
Expand All @@ -36,7 +43,7 @@ Button {
return root.hovered ? Styling.srItem("overprimary") : "transparent"
}
radius: Styling.radius(0)

// Separator line
Rectangle {
visible: root.isSeparator
Expand All @@ -50,19 +57,68 @@ Button {
contentItem: RowLayout {
spacing: 8
visible: !root.isSeparator

// Add margins for content
anchors.fill: parent
anchors.leftMargin: 8
anchors.leftMargin: 8 + root.depth * 12
anchors.rightMargin: 8


// Check/Radio indicator
Item {
visible: root.buttonType > 0
Layout.preferredWidth: 16
Layout.preferredHeight: 16

// Checkbox
Rectangle {
visible: root.buttonType === 1
anchors.centerIn: parent
width: 14
height: 14
radius: 3
color: root.checkState === Qt.Unchecked ? "transparent" : Colors.primary
border.color: root.checkState === Qt.Unchecked ? Colors.outline : Colors.primary
border.width: 1.5

Text {
anchors.centerIn: parent
visible: root.checkState !== Qt.Unchecked
text: root.checkState === Qt.PartiallyChecked ? "\u2212" : "\u2713"
color: Colors.overPrimary
font.pixelSize: 10
font.bold: true
}
}

// RadioButton
Rectangle {
visible: root.buttonType === 2
anchors.centerIn: parent
width: 14
height: 14
radius: 7
color: "transparent"
border.color: root.checkState === Qt.Checked ? Colors.primary : Colors.outline
border.width: 1.5

Rectangle {
anchors.centerIn: parent
visible: root.checkState === Qt.Checked
width: 7
height: 7
radius: 4
color: Colors.primary
}
}
}

// Icon
Loader {
Layout.preferredWidth: 16
Layout.preferredHeight: 16
visible: root.iconSource !== ""
visible: root.iconSource !== "" && root.buttonType === 0
sourceComponent: root.isImageIcon ? imageIcon : fontIcon

Component {
id: fontIcon
Text {
Expand All @@ -74,7 +130,7 @@ Button {
verticalAlignment: Text.AlignVCenter
}
}

Component {
id: imageIcon
Image {
Expand All @@ -84,7 +140,7 @@ Button {
}
}
}

// Text
Text {
Layout.fillWidth: true
Expand All @@ -95,5 +151,14 @@ Button {
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}

// Submenu chevron
Text {
visible: root.hasSubmenu
text: root.expanded ? "\u25BE" : "\u25B8"
color: root.hovered ? Colors.overPrimary : Colors.overBackground
font.pixelSize: Styling.fontSize(0)
verticalAlignment: Text.AlignVCenter
}
}
}
Loading