import "./weather" import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland import Quickshell.Services.UPower import qs import qs.services import qs.modules.common import qs.modules.common.widgets Scope { id: bar readonly property int osdHideMouseMoveThreshold: 20 property bool showBarBackground: Config.options.bar.showBackground component VerticalBarSeparator: Rectangle { Layout.topMargin: Appearance.sizes.baseBarHeight / 3 Layout.bottomMargin: Appearance.sizes.baseBarHeight / 3 Layout.fillHeight: true implicitWidth: 1 color: Appearance.colors.colOutlineVariant } Variants { // For each monitor model: { const screens = Quickshell.screens; const list = Config.options.bar.screenList; if (!list || list.length === 0) return screens; return screens.filter(screen => list.includes(screen.name)); } LazyLoader { id: barLoader active: GlobalStates.barOpen && !GlobalStates.screenLocked required property ShellScreen modelData component: PanelWindow { // Bar window id: barRoot screen: barLoader.modelData property var brightnessMonitor: Brightness.getMonitorForScreen(barLoader.modelData) property real useShortenedForm: (Appearance.sizes.barHellaShortenScreenWidthThreshold >= screen.width) ? 2 : (Appearance.sizes.barShortenScreenWidthThreshold >= screen.width) ? 1 : 0 readonly property int centerSideModuleWidth: (useShortenedForm == 2) ? Appearance.sizes.barCenterSideModuleWidthHellaShortened : (useShortenedForm == 1) ? Appearance.sizes.barCenterSideModuleWidthShortened : Appearance.sizes.barCenterSideModuleWidth exclusionMode: ExclusionMode.Ignore exclusiveZone: Appearance.sizes.baseBarHeight + (Config.options.bar.cornerStyle === 1 ? Appearance.sizes.hyprlandGapsOut : 0) WlrLayershell.namespace: "quickshell:bar" implicitHeight: Appearance.sizes.barHeight + Appearance.rounding.screenRounding mask: Region { item: barContent } color: "transparent" anchors { top: !Config.options.bar.bottom bottom: Config.options.bar.bottom left: true right: true } Item { // Bar content region id: barContent anchors { right: parent.right left: parent.left top: parent.top bottom: undefined } implicitHeight: Appearance.sizes.barHeight height: Appearance.sizes.barHeight states: State { name: "bottom" when: Config.options.bar.bottom AnchorChanges { target: barContent anchors { right: parent.right left: parent.left top: undefined bottom: parent.bottom } } } // Background shadow Loader { active: showBarBackground && Config.options.bar.cornerStyle === 1 anchors.fill: barBackground sourceComponent: StyledRectangularShadow { anchors.fill: undefined // The loader's anchors act on this, and this should not have any anchor target: barBackground } } // Background Rectangle { id: barBackground anchors { fill: parent margins: Config.options.bar.cornerStyle === 1 ? (Appearance.sizes.hyprlandGapsOut) : 0 // idk why but +1 is needed } color: showBarBackground ? Appearance.colors.colLayer0 : "transparent" radius: Config.options.bar.cornerStyle === 1 ? Appearance.rounding.windowRounding : 0 border.width: Config.options.bar.cornerStyle === 1 ? 1 : 0 border.color: Appearance.colors.colLayer0Border } MouseArea { // Left side | scroll to change brightness id: barLeftSideMouseArea anchors.left: parent.left implicitHeight: Appearance.sizes.baseBarHeight height: Appearance.sizes.barHeight width: (barRoot.width - middleSection.width) / 2 property bool hovered: false property real lastScrollX: 0 property real lastScrollY: 0 property bool trackingScroll: false acceptedButtons: Qt.LeftButton hoverEnabled: true propagateComposedEvents: true onEntered: event => { barLeftSideMouseArea.hovered = true; } onExited: event => { barLeftSideMouseArea.hovered = false; barLeftSideMouseArea.trackingScroll = false; } onPressed: event => { if (event.button === Qt.LeftButton) { GlobalStates.sidebarLeftOpen = !GlobalStates.sidebarLeftOpen; } } // Scroll to change brightness WheelHandler { onWheel: event => { if (event.angleDelta.y < 0) barRoot.brightnessMonitor.setBrightness(barRoot.brightnessMonitor.brightness - 0.05); else if (event.angleDelta.y > 0) barRoot.brightnessMonitor.setBrightness(barRoot.brightnessMonitor.brightness + 0.05); // Store the mouse position and start tracking barLeftSideMouseArea.lastScrollX = event.x; barLeftSideMouseArea.lastScrollY = event.y; barLeftSideMouseArea.trackingScroll = true; } acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad } onPositionChanged: mouse => { if (barLeftSideMouseArea.trackingScroll) { const dx = mouse.x - barLeftSideMouseArea.lastScrollX; const dy = mouse.y - barLeftSideMouseArea.lastScrollY; if (Math.sqrt(dx * dx + dy * dy) > osdHideMouseMoveThreshold) { GlobalStates.osdBrightnessOpen = false; barLeftSideMouseArea.trackingScroll = false; } } } Item { // Left section anchors.fill: parent implicitHeight: leftSectionRowLayout.implicitHeight implicitWidth: leftSectionRowLayout.implicitWidth ScrollHint { reveal: barLeftSideMouseArea.hovered icon: "light_mode" tooltipText: Translation.tr("Scroll to change brightness") side: "left" anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter } RowLayout { // Content id: leftSectionRowLayout anchors.fill: parent spacing: 10 RippleButton { // Left sidebar button Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter Layout.leftMargin: Appearance.rounding.screenRounding Layout.fillWidth: false property real buttonPadding: 5 implicitWidth: distroIcon.width + buttonPadding * 2 implicitHeight: distroIcon.height + buttonPadding * 2 buttonRadius: Appearance.rounding.full colBackground: barLeftSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1) colBackgroundHover: Appearance.colors.colLayer1Hover colRipple: Appearance.colors.colLayer1Active colBackgroundToggled: Appearance.colors.colSecondaryContainer colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover colRippleToggled: Appearance.colors.colSecondaryContainerActive toggled: GlobalStates.sidebarLeftOpen property color colText: toggled ? Appearance.m3colors.m3onSecondaryContainer : Appearance.colors.colOnLayer0 onPressed: { GlobalStates.sidebarLeftOpen = !GlobalStates.sidebarLeftOpen; } CustomIcon { id: distroIcon anchors.centerIn: parent width: 19.5 height: 19.5 source: Config.options.bar.topLeftIcon == 'distro' ? SystemInfo.distroIcon : "spark-symbolic" colorize: true color: Appearance.colors.colOnLayer0 } } ActiveWindow { visible: barRoot.useShortenedForm === 0 Layout.rightMargin: Appearance.rounding.screenRounding Layout.fillWidth: true Layout.fillHeight: true bar: barRoot } } } } RowLayout { // Middle section id: middleSection anchors.centerIn: parent spacing: Config.options?.bar.borderless ? 4 : 8 BarGroup { id: leftCenterGroup Layout.preferredWidth: barRoot.centerSideModuleWidth Layout.fillHeight: true Resources { alwaysShowAllResources: barRoot.useShortenedForm === 2 Layout.fillWidth: barRoot.useShortenedForm === 2 } Media { visible: barRoot.useShortenedForm < 2 Layout.fillWidth: true } } VerticalBarSeparator { visible: Config.options?.bar.borderless } BarGroup { id: middleCenterGroup padding: workspacesWidget.widgetPadding Layout.fillHeight: true Workspaces { id: workspacesWidget bar: barRoot Layout.fillHeight: true MouseArea { // Right-click to toggle overview anchors.fill: parent acceptedButtons: Qt.RightButton onPressed: event => { if (event.button === Qt.RightButton) { GlobalStates.overviewOpen = !GlobalStates.overviewOpen; } } } } } VerticalBarSeparator { visible: Config.options?.bar.borderless } MouseArea { id: rightCenterGroup implicitWidth: rightCenterGroupContent.implicitWidth implicitHeight: rightCenterGroupContent.implicitHeight Layout.preferredWidth: barRoot.centerSideModuleWidth Layout.fillHeight: true onPressed: { GlobalStates.sidebarRightOpen = !GlobalStates.sidebarRightOpen; } BarGroup { id: rightCenterGroupContent anchors.fill: parent ClockWidget { showDate: (Config.options.bar.verbose && barRoot.useShortenedForm < 2) Layout.alignment: Qt.AlignVCenter Layout.fillWidth: true } UtilButtons { visible: (Config.options.bar.verbose && barRoot.useShortenedForm === 0) Layout.alignment: Qt.AlignVCenter } BatteryIndicator { visible: (barRoot.useShortenedForm < 2 && UPower.displayDevice.isLaptopBattery) Layout.alignment: Qt.AlignVCenter } } } VerticalBarSeparator { visible: Config.options.bar.borderless && Config.options.bar.weather.enable } } MouseArea { // Right side | scroll to change volume id: barRightSideMouseArea anchors.right: parent.right implicitHeight: Appearance.sizes.baseBarHeight height: Appearance.sizes.barHeight width: (barRoot.width - middleSection.width) / 2 property bool hovered: false property real lastScrollX: 0 property real lastScrollY: 0 property bool trackingScroll: false acceptedButtons: Qt.LeftButton hoverEnabled: true propagateComposedEvents: true onEntered: event => { barRightSideMouseArea.hovered = true; } onExited: event => { barRightSideMouseArea.hovered = false; barRightSideMouseArea.trackingScroll = false; } onPressed: event => { if (event.button === Qt.LeftButton) { GlobalStates.sidebarRightOpen = !GlobalStates.sidebarRightOpen; } else if (event.button === Qt.RightButton) { MprisController.activePlayer.next(); } } // Scroll to change volume WheelHandler { onWheel: event => { const currentVolume = Audio.value; const step = currentVolume < 0.1 ? 0.01 : 0.02 || 0.2; if (event.angleDelta.y < 0) Audio.sink.audio.volume -= step; else if (event.angleDelta.y > 0) Audio.sink.audio.volume = Math.min(1, Audio.sink.audio.volume + step); // Store the mouse position and start tracking barRightSideMouseArea.lastScrollX = event.x; barRightSideMouseArea.lastScrollY = event.y; barRightSideMouseArea.trackingScroll = true; } acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad } onPositionChanged: mouse => { if (barRightSideMouseArea.trackingScroll) { const dx = mouse.x - barRightSideMouseArea.lastScrollX; const dy = mouse.y - barRightSideMouseArea.lastScrollY; if (Math.sqrt(dx * dx + dy * dy) > osdHideMouseMoveThreshold) { GlobalStates.osdVolumeOpen = false; barRightSideMouseArea.trackingScroll = false; } } } Item { anchors.fill: parent implicitHeight: rightSectionRowLayout.implicitHeight implicitWidth: rightSectionRowLayout.implicitWidth ScrollHint { reveal: barRightSideMouseArea.hovered icon: "volume_up" tooltipText: Translation.tr("Scroll to change volume") side: "right" anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter } RowLayout { id: rightSectionRowLayout anchors.fill: parent spacing: 5 layoutDirection: Qt.RightToLeft RippleButton { // Right sidebar button id: rightSidebarButton Layout.alignment: Qt.AlignRight | Qt.AlignVCenter Layout.rightMargin: Appearance.rounding.screenRounding Layout.fillWidth: false implicitWidth: indicatorsRowLayout.implicitWidth + 10 * 2 implicitHeight: indicatorsRowLayout.implicitHeight + 5 * 2 buttonRadius: Appearance.rounding.full colBackground: barRightSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1) colBackgroundHover: Appearance.colors.colLayer1Hover colRipple: Appearance.colors.colLayer1Active colBackgroundToggled: Appearance.colors.colSecondaryContainer colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover colRippleToggled: Appearance.colors.colSecondaryContainerActive toggled: GlobalStates.sidebarRightOpen property color colText: toggled ? Appearance.m3colors.m3onSecondaryContainer : Appearance.colors.colOnLayer0 Behavior on colText { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } onPressed: { GlobalStates.sidebarRightOpen = !GlobalStates.sidebarRightOpen; } RowLayout { id: indicatorsRowLayout anchors.centerIn: parent property real realSpacing: 15 spacing: 0 Revealer { reveal: Audio.sink?.audio?.muted ?? false Layout.fillHeight: true Layout.rightMargin: reveal ? indicatorsRowLayout.realSpacing : 0 Behavior on Layout.rightMargin { NumberAnimation { duration: Appearance.animation.elementMoveFast.duration easing.type: Appearance.animation.elementMoveFast.type easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } MaterialSymbol { text: "volume_off" iconSize: Appearance.font.pixelSize.larger color: rightSidebarButton.colText } } Revealer { reveal: Audio.source?.audio?.muted ?? false Layout.fillHeight: true Layout.rightMargin: reveal ? indicatorsRowLayout.realSpacing : 0 Behavior on Layout.rightMargin { NumberAnimation { duration: Appearance.animation.elementMoveFast.duration easing.type: Appearance.animation.elementMoveFast.type easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } MaterialSymbol { text: "mic_off" iconSize: Appearance.font.pixelSize.larger color: rightSidebarButton.colText } } Loader { active: HyprlandXkb.layoutCodes.length > 1 visible: active Layout.rightMargin: indicatorsRowLayout.realSpacing sourceComponent: StyledText { text: HyprlandXkb.currentLayoutCode font.pixelSize: Appearance.font.pixelSize.small color: rightSidebarButton.colText } } MaterialSymbol { Layout.rightMargin: indicatorsRowLayout.realSpacing text: Network.materialSymbol iconSize: Appearance.font.pixelSize.larger color: rightSidebarButton.colText } MaterialSymbol { text: Bluetooth.bluetoothConnected ? "bluetooth_connected" : Bluetooth.bluetoothEnabled ? "bluetooth" : "bluetooth_disabled" iconSize: Appearance.font.pixelSize.larger color: rightSidebarButton.colText } } } SysTray { bar: barRoot visible: barRoot.useShortenedForm === 0 Layout.fillWidth: false Layout.fillHeight: true } Item { Layout.fillWidth: true Layout.fillHeight: true } // Weather Loader { Layout.leftMargin: 8 Layout.fillHeight: true active: Config.options.bar.weather.enable sourceComponent: BarGroup { implicitHeight: Appearance.sizes.baseBarHeight WeatherBar {} } } } } } } // Round decorators Loader { id: roundDecorators anchors { left: parent.left right: parent.right } y: Appearance.sizes.barHeight width: parent.width height: Appearance.rounding.screenRounding active: showBarBackground && Config.options.bar.cornerStyle === 0 // Hug states: State { name: "bottom" when: Config.options.bar.bottom PropertyChanges { roundDecorators.y: 0 } } sourceComponent: Item { implicitHeight: Appearance.rounding.screenRounding RoundCorner { id: leftCorner anchors { top: parent.top bottom: parent.bottom left: parent.left } size: Appearance.rounding.screenRounding color: showBarBackground ? Appearance.colors.colLayer0 : "transparent" corner: RoundCorner.CornerEnum.TopLeft states: State { name: "bottom" when: Config.options.bar.bottom PropertyChanges { leftCorner.corner: RoundCorner.CornerEnum.BottomLeft } } } RoundCorner { id: rightCorner anchors { right: parent.right top: !Config.options.bar.bottom ? parent.top : undefined bottom: Config.options.bar.bottom ? parent.bottom : undefined } size: Appearance.rounding.screenRounding color: showBarBackground ? Appearance.colors.colLayer0 : "transparent" corner: RoundCorner.CornerEnum.TopRight states: State { name: "bottom" when: Config.options.bar.bottom PropertyChanges { rightCorner.corner: RoundCorner.CornerEnum.BottomRight } } } } } } } } IpcHandler { target: "bar" function toggle(): void { GlobalStates.barOpen = !GlobalStates.barOpen } function close(): void { GlobalStates.barOpen = false } function open(): void { GlobalStates.barOpen = true } } GlobalShortcut { name: "barToggle" description: "Toggles bar on press" onPressed: { GlobalStates.barOpen = !GlobalStates.barOpen; } } GlobalShortcut { name: "barOpen" description: "Opens bar on press" onPressed: { GlobalStates.barOpen = true; } } GlobalShortcut { name: "barClose" description: "Closes bar on press" onPressed: { GlobalStates.barOpen = false; } } }