major updates

This commit is contained in:
Zacharias-Brohn
2025-11-24 22:00:50 +01:00
parent 5593cce7ca
commit 91a4f95fd0
26 changed files with 1287 additions and 447 deletions
+65 -70
View File
@@ -1,11 +1,15 @@
pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import Quickshell.Wayland import Quickshell.Wayland
import qs.Modules import qs.Modules
import qs.Modules.Bar
import qs.Config import qs.Config
import qs.Helpers import qs.Helpers
import qs.Drawers
Scope { Scope {
Variants { Variants {
@@ -16,11 +20,12 @@ Scope {
required property var modelData required property var modelData
property bool trayMenuVisible: false property bool trayMenuVisible: false
screen: modelData screen: modelData
color: "transparent"
property var root: Quickshell.shellDir property var root: Quickshell.shellDir
WlrLayershell.exclusionMode: ExclusionMode.Ignore WlrLayershell.exclusionMode: ExclusionMode.Ignore
PanelWindow { PanelWindow {
id: wrapper id: exclusionZone
screen: bar.screen screen: bar.screen
WlrLayershell.layer: WlrLayer.Bottom WlrLayershell.layer: WlrLayer.Bottom
anchors { anchors {
@@ -49,12 +54,65 @@ Scope {
bottom: true bottom: true
} }
mask: Region { item: backgroundRect } mask: Region {
x: 0
y: 34
color: "transparent" width: bar.width
height: bar.screen.height - backgroundRect.implicitHeight
intersection: Intersection.Xor
regions: popoutRegions.instances
}
Variants {
id: popoutRegions
model: panels.children
Region {
required property Item modelData
x: modelData.x
y: modelData.y + backgroundRect.implicitHeight
width: modelData.width
height: modelData.height
intersection: Intersection.Subtract
}
}
Item {
anchors.fill: parent
Backgrounds {
panels: panels
bar: backgroundRect
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onContainsMouseChanged: {
if ( !containsMouse ) {
panels.popouts.hasCurrent = false;
}
}
onPositionChanged: event => {
if ( mouseY < backgroundRect.implicitHeight ) {
barLoader.checkPopout(mouseX);
}
}
Panels {
id: panels
screen: bar.modelData
bar: backgroundRect
}
Rectangle { Rectangle {
id: backgroundRect id: backgroundRect
property Wrapper popouts: panels.popouts
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
@@ -66,77 +124,13 @@ Scope {
CAnim {} CAnim {}
} }
RowLayout { BarLoader {
id: barLoader
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: 5 popouts: panels.popouts
anchors.rightMargin: 5
RowLayout {
id: leftSection
Layout.fillHeight: true
Layout.preferredWidth: leftSection.childrenRect.width
Workspaces {
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
bar: bar bar: bar
} }
AudioWidget {
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
}
Resources {
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
}
UpdatesWidget {
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
countUpdates: Updates.availableUpdates
}
}
RowLayout {
id: centerSection
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
}
RowLayout {
id: rightSection
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
TrayWidget {
id: systemTrayModule
bar: bar
Layout.alignment: Qt.AlignVCenter
}
Clock {
Layout.alignment: Qt.AlignVCenter
}
Text {
id: notificationCenterIcon
property color iconColor: Config.useDynamicColors ? DynamicColors.palette.m3tertiaryFixed : "white"
Layout.alignment: Qt.AlignVCenter
text: HasNotifications.hasNotifications ? "\uf4fe" : "\ue7f4"
font.family: "Material Symbols Rounded"
font.pixelSize: 20
color: iconColor
Behavior on color {
CAnim {}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
ncProcess.running = true
}
}
}
}
}
WindowTitle { WindowTitle {
anchors.centerIn: parent anchors.centerIn: parent
width: Math.min( 300, parent.width * 0.4 ) width: Math.min( 300, parent.width * 0.4 )
@@ -147,3 +141,4 @@ Scope {
} }
} }
} }
}
+55
View File
@@ -0,0 +1,55 @@
import Quickshell.Io
JsonObject {
property Popouts popouts: Popouts {}
property list<var> entries: [
{
id: "workspaces",
enabled: true
},
{
id: "audio",
enabled: true
},
{
id: "resources",
enabled: true
},
{
id: "updates",
enabled: true
},
{
id: "spacer",
enabled: true
},
{
id: "activeWindow",
enabled: true
},
{
id: "spacer",
enabled: true
},
{
id: "tray",
enabled: true
},
{
id: "clock",
enabled: true
},
{
id: "notifBell",
enabled: true
},
]
component Popouts: JsonObject {
property bool tray: true
property bool audio: true
property bool activeWindow: false
property bool resources: true
}
}
+2
View File
@@ -18,6 +18,7 @@ Singleton {
property alias gpuType: adapter.gpuType property alias gpuType: adapter.gpuType
property alias background: adapter.background property alias background: adapter.background
property alias useDynamicColors: adapter.useDynamicColors property alias useDynamicColors: adapter.useDynamicColors
property alias barConfig: adapter.barConfig
FileView { FileView {
id: root id: root
@@ -44,6 +45,7 @@ Singleton {
property string gpuType: "" property string gpuType: ""
property BackgroundConfig background: BackgroundConfig {} property BackgroundConfig background: BackgroundConfig {}
property bool useDynamicColors: false property bool useDynamicColors: false
property BarConfig barConfig: BarConfig {}
} }
} }
} }
+22
View File
@@ -0,0 +1,22 @@
import QtQuick
import QtQuick.Shapes
import qs.Modules as Modules
Shape {
id: root
required property Panels panels
required property Item bar
anchors.fill: parent
anchors.margins: 8
anchors.topMargin: bar.implicitHeight
preferredRendererType: Shape.CurveRenderer
Modules.Background {
wrapper: root.panels.popouts
startX: Math.floor(wrapper.x - rounding)
startY: wrapper.y
}
}
+33
View File
@@ -0,0 +1,33 @@
import Quickshell
import QtQuick
import QtQuick.Shapes
import qs.Modules as Modules
import qs.Config
Item {
id: root
required property ShellScreen screen
required property Item bar
readonly property alias popouts: popouts
anchors.fill: parent
anchors.margins: 8
anchors.topMargin: bar.implicitHeight
Modules.Wrapper {
id: popouts
screen: root.screen
anchors.top: parent.top
x: {
const off = currentCenter - 8 - nonAnimWidth / 2;
const diff = root.width - Math.floor(off + nonAnimWidth);
if (diff < 0)
return off + diff;
return Math.max(off, 0);
}
}
}
+31 -35
View File
@@ -9,34 +9,13 @@ import qs.Config
import qs.Components import qs.Components
import qs.Daemons import qs.Daemons
CustomRect { Item {
id: root id: root
implicitWidth: layout.implicitWidth + 10 * 2 implicitWidth: layout.implicitWidth + 10 * 2
implicitHeight: 0 implicitHeight: layout.implicitHeight + 10 * 2
color: Config.useDynamicColors ? DynamicColors.tPalette.m3surface : "#40000000"
clip: true
property alias expanded: root.isExpanded required property var wrapper
property bool isExpanded: false
Anim {
id: expandAnim
running: root.isExpanded
target: root
property: "implicitHeight"
to: layout.implicitHeight + 10 * 2
duration: MaterialEasing.standardTime
}
Anim {
id: collapseAnim
running: !root.isExpanded
target: root
property: "implicitHeight"
to: 0
duration: MaterialEasing.standardTime
}
ButtonGroup { ButtonGroup {
id: sinks id: sinks
@@ -49,8 +28,8 @@ CustomRect {
ColumnLayout { ColumnLayout {
id: layout id: layout
anchors.left: parent.left anchors.top: parent.top
anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter
spacing: 12 spacing: 12
CustomText { CustomText {
@@ -95,7 +74,7 @@ CustomRect {
CustomText { CustomText {
Layout.topMargin: 10 Layout.topMargin: 10
Layout.bottomMargin: -7 / 2 Layout.bottomMargin: -7 / 2
text: qsTr("Volume (%1)").arg(Audio.muted ? qsTr("Muted") : `${Math.round(Audio.volume * 100)}%`) text: qsTr("Output Volume (%1)").arg(Audio.muted ? qsTr("Muted") : `${Math.round(Audio.volume * 100)}%`)
font.weight: 500 font.weight: 500
} }
@@ -103,13 +82,6 @@ CustomRect {
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: 10 * 3 implicitHeight: 10 * 3
onWheel: event => {
if (event.angleDelta.y > 0)
Audio.incrementVolume();
else if (event.angleDelta.y < 0)
Audio.decrementVolume();
}
CustomSlider { CustomSlider {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
@@ -124,6 +96,31 @@ CustomRect {
} }
} }
CustomText {
Layout.topMargin: 10
Layout.bottomMargin: -7 / 2
text: qsTr("Input Volume (%1)").arg(Audio.sourceMuted ? qsTr("Muted") : `${Math.round(Audio.sourceVolume * 100)}%`)
font.weight: 500
}
CustomMouseArea {
Layout.fillWidth: true
implicitHeight: 10 * 3
CustomSlider {
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: parent.implicitHeight
value: Audio.sourceVolume
onMoved: Audio.setSourceVolume(value)
Behavior on value {
Anim {}
}
}
}
CustomRect { CustomRect {
Layout.topMargin: 12 Layout.topMargin: 12
visible: true visible: true
@@ -138,7 +135,6 @@ CustomRect {
color: DynamicColors.palette.m3onPrimaryContainer color: DynamicColors.palette.m3onPrimaryContainer
function onClicked(): void { function onClicked(): void {
root.isExpanded = !root.isExpanded;
Quickshell.execDetached(["app2unit", "--", "pavucontrol"]); Quickshell.execDetached(["app2unit", "--", "pavucontrol"]);
} }
} }
-8
View File
@@ -53,19 +53,11 @@ Item {
border.width: 0 border.width: 0
} }
AudioPopup {
id: audioPopup
anchors.left: parent.left
anchors.top: parent.bottom
}
MouseArea { MouseArea {
id: mouseArea id: mouseArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onEntered: audioPopup.expanded = true
onExited: audioPopup.expanded = false
RowLayout { RowLayout {
anchors { anchors {
+41 -56
View File
@@ -1,79 +1,64 @@
pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import qs.Helpers import QtQuick.Shapes
import qs.Config import qs.Config
Item { ShapePath {
id: root id: root
property string source: SearchWallpapers.current required property Wrapper wrapper
property Image current: one readonly property real rounding: 8
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
anchors.fill: parent strokeWidth: -1
fillColor: DynamicColors.tPalette.m3surface
onSourceChanged: { PathArc {
if (!source) { relativeX: root.rounding
current = null; relativeY: root.roundingY
} else if (current === one) { radiusX: root.rounding
two.update(); radiusY: Math.min(root.rounding, root.wrapper.height)
} else {
one.update();
}
} }
Component.onCompleted: { PathLine {
console.log(root.source) relativeX: 0
if (source) relativeY: root.wrapper.height - root.roundingY * 2
Qt.callLater(() => one.update());
} }
Img { PathArc {
id: one relativeX: root.rounding
relativeY: root.roundingY
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
direction: PathArc.Counterclockwise
} }
Img { PathLine {
id: two relativeX: root.wrapper.width - root.rounding * 2
relativeY: 0
} }
component Img: CachingImage { PathArc {
id: img relativeX: root.rounding
relativeY: -root.roundingY
function update(): void { radiusX: root.rounding
if (path === root.source) { radiusY: Math.min(root.rounding, root.wrapper.height)
root.current = this; direction: PathArc.Counterclockwise
} else {
path = root.source;
}
} }
anchors.fill: parent PathLine {
relativeX: 0
opacity: 0 relativeY: -(root.wrapper.height - root.roundingY * 2)
scale: SearchWallpapers.showPreview ? 1 : 0.8
asynchronous: true
onStatusChanged: {
if (status === Image.Ready) {
root.current = this;
}
} }
states: State { PathArc {
name: "visible" relativeX: root.rounding
when: root.current === img relativeY: -root.roundingY
radiusX: root.rounding
PropertyChanges { radiusY: Math.min(root.rounding, root.wrapper.height)
img.opacity: 1
img.scale: 1
}
} }
transitions: Transition { Behavior on fillColor {
Anim { CAnim {}
target: img
properties: "opacity,scale"
duration: Config.background.wallFadeDuration
}
}
} }
} }
+165
View File
@@ -0,0 +1,165 @@
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import QtQuick.Layouts
import qs.Modules
import qs.Config
import qs.Helpers
import qs.Daemons
RowLayout {
id: root
anchors.fill: parent
anchors.leftMargin: 5
anchors.rightMargin: 5
readonly property int vPadding: 6
required property Wrapper popouts
required property PanelWindow bar
function checkPopout(x: real): void {
const ch = childAt(x, height / 2) as WrappedLoader;
if (!ch) {
popouts.hasCurrent = false;
return;
}
const id = ch.id;
const top = ch.x;
const item = ch.item;
const itemWidth = item.implicitWidth;
if (id === "audio" && Config.barConfig.popouts.audio) {
popouts.currentName = "audio";
popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x);
popouts.hasCurrent = true;
} else if ( id === "resources" && Config.barConfig.popouts.resources ) {
popouts.currentName = "resources";
popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x);
popouts.hasCurrent = true;
} else if (id === "tray" && Config.barConfig.popouts.tray) {
const index = Math.floor(((x - top - 6) / item.implicitWidth) * item.items.count);
const trayItem = item.items.itemAt(index);
if (trayItem) {
popouts.currentName = `traymenu${index}`;
popouts.currentCenter = Qt.binding(() => trayItem.mapToItem(root, trayItem.implicitWidth / 2, 0).x);
popouts.hasCurrent = true;
} else {
popouts.hasCurrent = false;
}
} else if (id === "activeWindow" && Config.barConfig.popouts.activeWindow) {
popouts.currentName = id.toLowerCase();
popouts.currentCenter = item.mapToItem(root, 0, itemHeight / 2).y;
popouts.hasCurrent = true;
}
}
Repeater {
id: repeater
model: Config.barConfig.entries
DelegateChooser {
role: "id"
DelegateChoice {
roleValue: "spacer"
delegate: WrappedLoader {
Layout.fillWidth: true
}
}
DelegateChoice {
roleValue: "workspaces"
delegate: WrappedLoader {
sourceComponent: Workspaces {
bar: root.bar
}
}
}
DelegateChoice {
roleValue: "audio"
delegate: WrappedLoader {
sourceComponent: AudioWidget {}
}
}
DelegateChoice {
roleValue: "tray"
delegate: WrappedLoader {
sourceComponent: TrayWidget {
bar: root.bar
}
}
}
DelegateChoice {
roleValue: "resources"
delegate: WrappedLoader {
sourceComponent: Resources {}
}
}
DelegateChoice {
roleValue: "updates"
delegate: WrappedLoader {
sourceComponent: UpdatesWidget {}
}
}
DelegateChoice {
roleValue: "notifBell"
delegate: WrappedLoader {
sourceComponent: NotifBell {
Layout.alignment: Qt.AlignHCenter
}
}
}
DelegateChoice {
roleValue: "clock"
delegate: WrappedLoader {
sourceComponent: Clock {
Layout.alignment: Qt.AlignHCenter
}
}
}
DelegateChoice {
roleValue: "activeWindow"
delegate: WrappedLoader {
sourceComponent: WindowTitle {}
}
}
}
}
component WrappedLoader: Loader {
required property bool enabled
required property string id
required property int index
function findFirstEnabled(): Item {
const count = repeater.count;
for (let i = 0; i < count; i++) {
const item = repeater.itemAt(i);
if (item?.enabled)
return item;
}
return null;
}
function findLastEnabled(): Item {
for (let i = repeater.count - 1; i >= 0; i--) {
const item = repeater.itemAt(i);
if (item?.enabled)
return item;
}
return null;
}
Layout.alignment: Qt.AlignHCenter
// Cursed ahh thing to add padding to first and last enabled components
Layout.topMargin: findFirstEnabled() === this ? root.vPadding : 0
Layout.bottomMargin: findLastEnabled() === this ? root.vPadding : 0
visible: enabled
active: enabled
}
}
+17
View File
@@ -0,0 +1,17 @@
import QtQuick
import qs.Config
import qs.Modules
Item {
implicitWidth: timeText.contentWidth
implicitHeight: timeText.contentHeight
Text {
id: timeText
text: Time.time
color: Config.useDynamicColors ? DynamicColors.palette.m3tertiary : "white"
Behavior on color {
CAnim {}
}
}
}
+133
View File
@@ -0,0 +1,133 @@
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Services.SystemTray
import QtQuick
import qs.Config
Item {
id: root
required property Item wrapper
readonly property Popout currentPopout: content.children.find(c => c.shouldBeActive) ?? null
readonly property Item current: currentPopout?.item ?? null
anchors.centerIn: parent
implicitWidth: (currentPopout?.implicitWidth ?? 0) + 10 * 2
implicitHeight: (currentPopout?.implicitHeight ?? 0) + 10 * 2
Item {
id: content
anchors.fill: parent
anchors.margins: 10
Popout {
name: "audio"
sourceComponent: AudioPopup {
wrapper: root.wrapper
}
}
Popout {
name: "resources"
sourceComponent: ResourcePopout {
wrapper: root.wrapper
}
}
Repeater {
model: ScriptModel {
values: [ ...SystemTray.items.values ]
}
Popout {
id: trayMenu
required property SystemTrayItem modelData
required property int index
name: `traymenu${index}`
sourceComponent: trayMenuComponent
Connections {
target: root.wrapper
function onHasCurrentChanged(): void {
if ( root.wrapper.hasCurrent && trayMenu.shouldBeActive ) {
trayMenu.sourceComponent = null;
trayMenu.sourceComponent = trayMenuComponent;
}
}
}
Component {
id: trayMenuComponent
TrayMenuPopout {
popouts: root.wrapper
trayItem: trayMenu.modelData.menu
}
}
}
}
}
component Popout: Loader {
id: popout
required property string name
readonly property bool shouldBeActive: root.wrapper.currentName === name
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
opacity: 0
scale: 0.8
active: false
states: State {
name: "active"
when: popout.shouldBeActive
PropertyChanges {
popout.active: true
popout.opacity: 1
popout.scale: 1
}
}
transitions: [
Transition {
from: "active"
to: ""
SequentialAnimation {
Anim {
properties: "opacity,scale"
duration: MaterialEasing.expressiveEffectsTime
}
PropertyAction {
target: popout
property: "active"
}
}
},
Transition {
from: ""
to: "active"
SequentialAnimation {
PropertyAction {
target: popout
property: "active"
}
Anim {
properties: "opacity,scale"
}
}
}
]
}
}
-2
View File
@@ -4,8 +4,6 @@ import Quickshell
Singleton { Singleton {
id: root id: root
// thanks to Soramane :>
// expressive curves => thanks end cutie ;)
readonly property list<real> emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1] readonly property list<real> emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1]
readonly property list<real> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1] readonly property list<real> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1]
readonly property int emphasizedAccelTime: 200 readonly property int emphasizedAccelTime: 200
+28
View File
@@ -0,0 +1,28 @@
import QtQuick
import qs.Config
import qs.Helpers
Item {
implicitWidth: 20
implicitHeight: 18
Text {
id: notificationCenterIcon
property color iconColor: Config.useDynamicColors ? DynamicColors.palette.m3tertiaryFixed : "white"
text: HasNotifications.hasNotifications ? "\uf4fe" : "\ue7f4"
font.family: "Material Symbols Rounded"
font.pixelSize: 20
color: iconColor
Behavior on color {
CAnim {}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
ncProcess.running = true
}
}
}
}
+2 -3
View File
@@ -14,9 +14,8 @@ Item {
property color warningBarColor: Config.useDynamicColors ? DynamicColors.palette.m3error : Config.accentColor.accents.warning property color warningBarColor: Config.useDynamicColors ? DynamicColors.palette.m3error : Config.accentColor.accents.warning
property color textColor: Config.useDynamicColors ? DynamicColors.palette.m3onSurface : "#ffffff" property color textColor: Config.useDynamicColors ? DynamicColors.palette.m3onSurface : "#ffffff"
height: columnLayout.childrenRect.height Layout.preferredWidth: 158
anchors.left: parent.left Layout.preferredHeight: columnLayout.implicitHeight
anchors.right: parent.right
ColumnLayout { ColumnLayout {
id: columnLayout id: columnLayout
+61
View File
@@ -0,0 +1,61 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Config
Item {
id: popoutWindow
implicitWidth: contentColumn.implicitWidth + 10 * 2
implicitHeight: contentColumn.implicitHeight + 10
required property var wrapper
// ShadowRect {
// anchors.fill: contentRect
// radius: 8
// }
ColumnLayout {
id: contentColumn
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
spacing: 10
ResourceDetail {
resourceName: qsTr( "Memory Usage" )
iconString: "\uf7a3"
percentage: ResourceUsage.memoryUsedPercentage
warningThreshold: 95
details: qsTr( "%1 of %2 MB used" )
.arg( Math.round( ResourceUsage.memoryUsed * 0.001 ))
.arg( Math.round( ResourceUsage.memoryTotal * 0.001 ))
}
ResourceDetail {
resourceName: qsTr( "CPU Usage" )
iconString: "\ue322"
percentage: ResourceUsage.cpuUsage
warningThreshold: 95
details: qsTr( "%1% used" )
.arg( Math.round( ResourceUsage.cpuUsage * 100 ))
}
ResourceDetail {
resourceName: qsTr( "GPU Usage" )
iconString: "\ue30f"
percentage: ResourceUsage.gpuUsage
warningThreshold: 95
details: qsTr( "%1% used" )
.arg( Math.round( ResourceUsage.gpuUsage * 100 ))
}
ResourceDetail {
resourceName: qsTr( "VRAM Usage" )
iconString: "\ue30d"
percentage: ResourceUsage.gpuMemUsage
warningThreshold: 95
details: qsTr( "%1% used" )
.arg( Math.round( ResourceUsage.gpuMemUsage * 100 ))
}
}
}
-107
View File
@@ -94,111 +94,4 @@ Item {
} }
} }
} }
Item {
id: popoutWindow
z: 0
property int rectHeight: contentRect.implicitHeight
anchors.fill: parent
visible: true
// ShadowRect {
// anchors.fill: contentRect
// radius: 8
// }
ParallelAnimation {
id: openAnim
Anim {
target: contentRect
property: "implicitHeight"
to: contentColumn.childrenRect.height + 20
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
ParallelAnimation {
id: closeAnim
Anim {
target: contentRect
property: "implicitHeight"
to: 0
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
Rectangle {
id: contentRect
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.bottom
color: Config.useDynamicColors ? DynamicColors.tPalette.m3surface : Config.baseBgColor
border.color: Config.useDynamicColors ? "transparent" : Config.baseBorderColor
border.width: 1
bottomLeftRadius: 8
bottomRightRadius: 8
clip: true
Column {
id: contentColumn
anchors.fill: parent
anchors.margins: 10
spacing: 10
ResourceDetail {
resourceName: qsTr( "Memory Usage" )
iconString: "\uf7a3"
percentage: ResourceUsage.memoryUsedPercentage
warningThreshold: 95
details: qsTr( "%1 of %2 MB used" )
.arg( Math.round( ResourceUsage.memoryUsed * 0.001 ))
.arg( Math.round( ResourceUsage.memoryTotal * 0.001 ))
}
ResourceDetail {
resourceName: qsTr( "CPU Usage" )
iconString: "\ue322"
percentage: ResourceUsage.cpuUsage
warningThreshold: 95
details: qsTr( "%1% used" )
.arg( Math.round( ResourceUsage.cpuUsage * 100 ))
}
ResourceDetail {
resourceName: qsTr( "GPU Usage" )
iconString: "\ue30f"
percentage: ResourceUsage.gpuUsage
warningThreshold: 95
details: qsTr( "%1% used" )
.arg( Math.round( ResourceUsage.gpuUsage * 100 ))
}
ResourceDetail {
resourceName: qsTr( "VRAM Usage" )
iconString: "\ue30d"
percentage: ResourceUsage.gpuMemUsage
warningThreshold: 95
details: qsTr( "%1% used" )
.arg( Math.round( ResourceUsage.gpuMemUsage * 100 ))
}
}
}
MouseArea {
id: widgetMouseArea
z: 1
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: contentRect.bottom
hoverEnabled: true
onEntered: {
openAnim.start();
}
onExited: {
closeAnim.start();
}
}
}
} }
View File
+1 -44
View File
@@ -8,39 +8,13 @@ import qs.Config
import Caelestia import Caelestia
import QtQuick.Effects import QtQuick.Effects
MouseArea { Item {
id: root id: root
required property SystemTrayItem item required property SystemTrayItem item
required property PanelWindow bar required property PanelWindow bar
property point globalPos
property bool hasLoaded: false property bool hasLoaded: false
implicitWidth: 24
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton
onPositionChanged: {
globalPos = root.mapToItem(root.bar.backgroundRect, 0, 0);
}
Rectangle {
anchors.centerIn: parent
implicitHeight: 28
implicitWidth: 28
radius: 6
anchors.verticalCenter: parent.verticalCenter
color: root.containsMouse ? Config.colors.backgrounds.hover : "transparent"
Behavior on color {
ColorAnimation {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
}
Image { Image {
id: icon id: icon
@@ -58,22 +32,5 @@ MouseArea {
sourceSize.height: ( batteryHDPI || nmHDPI ) ? 16 : 22 sourceSize.height: ( batteryHDPI || nmHDPI ) ? 16 : 22
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
TrayMenu {
id: trayMenu
trayMenu: root.item?.menu
trayItemRect: root.globalPos
bar: root.bar
}
}
onClicked: {
if ( mouse.button === Qt.LeftButton ) {
root.item.activate();
} else if ( mouse.button === Qt.RightButton ) {
trayMenu.trayMenu = null;
trayMenu.trayMenu = root.item?.menu;
trayMenu.visible = !trayMenu.visible;
trayMenu.focusGrab = true;
}
} }
} }
+19 -4
View File
@@ -8,6 +8,8 @@ import Qt5Compat.GraphicalEffects
import Quickshell.Hyprland import Quickshell.Hyprland
import QtQml import QtQml
import qs.Effects import qs.Effects
import qs.Config
import qs.Modules
PanelWindow { PanelWindow {
id: root id: root
@@ -24,6 +26,12 @@ PanelWindow {
property int biggestWidth: 0 property int biggestWidth: 0
property int menuItemCount: menuOpener.children.values.length property int menuItemCount: menuOpener.children.values.length
property color backgroundColor: Config.useDynamicColors ? DynamicColors.tPalette.m3surface : Config.baseBgColor
property color highlightColor: Config.useDynamicColors ? DynamicColors.tPalette.m3primaryContainer : "#15FFFFFF"
property color textColor: Config.useDynamicColors ? DynamicColors.palette.m3onSurface : "white"
property color disabledHighlightColor: Config.useDynamicColors ? DynamicColors.layer(DynamicColors.palette.m3primaryContainer, 0) : "#08FFFFFF"
property color disabledTextColor: Config.useDynamicColors ? DynamicColors.layer(DynamicColors.palette.m3onSurface, 0) : "#80FFFFFF"
QsMenuOpener { QsMenuOpener {
id: menuOpener id: menuOpener
menu: root.trayMenu menu: root.trayMenu
@@ -183,8 +191,9 @@ PanelWindow {
y: Math.round( root.trayItemRect.y - 5 ) y: Math.round( root.trayItemRect.y - 5 )
implicitWidth: listLayout.contentWidth + 10 implicitWidth: listLayout.contentWidth + 10
implicitHeight: listLayout.contentHeight + ( root.menuStack.length > 0 ? root.entryHeight + 10 : 10 ) implicitHeight: listLayout.contentHeight + ( root.menuStack.length > 0 ? root.entryHeight + 10 : 10 )
color: "#80151515" color: root.backgroundColor
radius: 8 radius: 8
border.width: Config.useDynamicColors ? 0 : 1
border.color: "#40FFFFFF" border.color: "#40FFFFFF"
clip: true clip: true
@@ -218,7 +227,7 @@ PanelWindow {
id: listLayout id: listLayout
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: contentHeight Layout.preferredHeight: contentHeight
spacing: 2 spacing: 0
contentWidth: root.biggestWidth contentWidth: root.biggestWidth
contentHeight: contentItem.childrenRect.height contentHeight: contentItem.childrenRect.height
model: menuOpener.children model: menuOpener.children
@@ -236,10 +245,16 @@ PanelWindow {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
height: menuItem.modelData.isSeparator ? 1 : root.entryHeight height: menuItem.modelData.isSeparator ? 1 : root.entryHeight
color: menuItem.modelData.isSeparator ? "#20FFFFFF" : containsMouseAndEnabled ? "#15FFFFFF" : containsMouseAndNotEnabled ? "#08FFFFFF" : "transparent" color: menuItem.modelData.isSeparator ? "#20FFFFFF" : containsMouseAndEnabled ? root.highlightColor : containsMouseAndNotEnabled ? root.disabledHighlightColor : "transparent"
radius: 4 radius: 4
visible: true visible: true
Behavior on color {
CAnim {
duration: 150
}
}
Component.onCompleted: { Component.onCompleted: {
var biggestWidth = root.biggestWidth; var biggestWidth = root.biggestWidth;
var currentWidth = widthMetrics.width + (menuItem.modelData.icon ?? "" ? 30 : 0) + (menuItem.modelData.hasChildren ? 30 : 0) + 20; var currentWidth = widthMetrics.width + (menuItem.modelData.icon ?? "" ? 30 : 0) + (menuItem.modelData.hasChildren ? 30 : 0) + 20;
@@ -284,7 +299,7 @@ PanelWindow {
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.leftMargin: 10 Layout.leftMargin: 10
text: menuItem.modelData.text text: menuItem.modelData.text
color: menuItem.modelData.enabled ? "white" : "gray" color: menuItem.modelData.enabled ? root.textColor : root.disabledTextColor
} }
Image { Image {
id: iconImage id: iconImage
+230
View File
@@ -0,0 +1,230 @@
pragma ComponentBehavior: Bound
import qs.Components
import qs.Config
import Quickshell
import Quickshell.Widgets
import QtQuick
import QtQuick.Controls
StackView {
id: root
required property Item popouts
required property QsMenuHandle trayItem
property int biggestWidth: 0
implicitWidth: currentItem.implicitWidth
implicitHeight: currentItem.implicitHeight
initialItem: SubMenu {
handle: root.trayItem
}
pushEnter: NoAnim {}
pushExit: NoAnim {}
popEnter: NoAnim {}
popExit: NoAnim {}
component NoAnim: Transition {
NumberAnimation {
duration: 0
}
}
component SubMenu: Column {
id: menu
required property QsMenuHandle handle
property bool isSubMenu
property bool shown
padding: 0
spacing: 4
opacity: shown ? 1 : 0
scale: shown ? 1 : 0.8
Component.onCompleted: shown = true
StackView.onActivating: shown = true
StackView.onDeactivating: shown = false
StackView.onRemoved: destroy()
Behavior on opacity {
Anim {}
}
Behavior on scale {
Anim {}
}
QsMenuOpener {
id: menuOpener
menu: menu.handle
}
Repeater {
model: menuOpener.children
CustomRect {
id: item
required property QsMenuEntry modelData
implicitWidth: root.biggestWidth
implicitHeight: modelData.isSeparator ? 1 : children.implicitHeight
radius: 4
color: modelData.isSeparator ? DynamicColors.palette.m3outlineVariant : "transparent"
Loader {
id: children
anchors.left: parent.left
anchors.right: parent.right
active: !item.modelData.isSeparator
asynchronous: true
sourceComponent: Item {
implicitHeight: 30
StateLayer {
radius: item.radius
disabled: !item.modelData.enabled
function onClicked(): void {
const entry = item.modelData;
if (entry.hasChildren)
root.push(subMenuComp.createObject(null, {
handle: entry,
isSubMenu: true
}));
else {
item.modelData.triggered();
root.popouts.hasCurrent = false;
}
}
}
Loader {
id: icon
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: 10
active: item.modelData.icon !== ""
asynchronous: true
sourceComponent: IconImage {
implicitSize: label.implicitHeight
source: item.modelData.icon
}
}
CustomText {
id: label
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 10
text: labelMetrics.elidedText
color: item.modelData.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
}
TextMetrics {
id: labelMetrics
text: item.modelData.text
font.pointSize: label.font.pointSize
font.family: label.font.family
Component.onCompleted: {
var biggestWidth = root.biggestWidth;
var currentWidth = labelMetrics.width + (item.modelData.icon ?? "" ? 30 : 0) + (item.modelData.hasChildren ? 30 : 0) + 20;
if ( currentWidth > biggestWidth ) {
root.biggestWidth = currentWidth;
}
}
}
Loader {
id: expand
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
active: item.modelData.hasChildren
asynchronous: true
sourceComponent: MaterialIcon {
text: "chevron_right"
color: item.modelData.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
}
}
}
}
}
}
Loader {
active: menu.isSubMenu
asynchronous: true
sourceComponent: Item {
implicitWidth: back.implicitWidth
implicitHeight: back.implicitHeight + 2 / 2
Item {
anchors.bottom: parent.bottom
implicitWidth: back.implicitWidth
implicitHeight: back.implicitHeight
CustomRect {
anchors.fill: parent
radius: 1000
color: DynamicColors.palette.m3secondaryContainer
StateLayer {
radius: parent.radius
color: DynamicColors.palette.m3onSecondaryContainer
function onClicked(): void {
root.pop();
}
}
}
Row {
id: back
anchors.verticalCenter: parent.verticalCenter
MaterialIcon {
anchors.verticalCenter: parent.verticalCenter
text: "chevron_left"
color: DynamicColors.palette.m3onSecondaryContainer
}
CustomText {
anchors.verticalCenter: parent.verticalCenter
text: qsTr("Back")
color: DynamicColors.palette.m3onSecondaryContainer
}
}
}
}
}
}
Component {
id: subMenuComp
SubMenu {}
}
}
+5 -11
View File
@@ -5,27 +5,21 @@ import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Services.SystemTray import Quickshell.Services.SystemTray
Rectangle { Row {
id: root id: root
required property PanelWindow bar required property PanelWindow bar
implicitHeight: parent.height readonly property alias items: repeater
implicitWidth: rowL.implicitWidth + 10 spacing: 0
color: "transparent"
RowLayout {
spacing: 5
id: rowL
anchors.centerIn: parent
Repeater { Repeater {
id: repeater id: repeater
model: SystemTray.items model: SystemTray.items
TrayItem { TrayItem {
id: trayItem id: trayItem
required property SystemTrayItem modelData required property SystemTrayItem modelData
implicitHeight: root.implicitHeight implicitHeight: 34
implicitWidth: 28
item: modelData item: modelData
bar: root.bar bar: root.bar
} }
} }
} }
}
+1 -1
View File
@@ -5,7 +5,7 @@ import qs.Config
Item { Item {
id: root id: root
required property int countUpdates property int countUpdates: Updates.availableUpdates
implicitWidth: contentRow.childrenRect.width + 10 implicitWidth: contentRow.childrenRect.width + 10
implicitHeight: 22 implicitHeight: 22
property color textColor: Config.useDynamicColors ? DynamicColors.palette.m3tertiaryFixed : "#ffffff" property color textColor: Config.useDynamicColors ? DynamicColors.palette.m3tertiaryFixed : "#ffffff"
+79
View File
@@ -0,0 +1,79 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.Helpers
import qs.Config
Item {
id: root
property string source: SearchWallpapers.current
property Image current: one
anchors.fill: parent
onSourceChanged: {
if (!source) {
current = null;
} else if (current === one) {
two.update();
} else {
one.update();
}
}
Component.onCompleted: {
console.log(root.source)
if (source)
Qt.callLater(() => one.update());
}
Img {
id: one
}
Img {
id: two
}
component Img: CachingImage {
id: img
function update(): void {
if (path === root.source) {
root.current = this;
} else {
path = root.source;
}
}
anchors.fill: parent
opacity: 0
scale: SearchWallpapers.showPreview ? 1 : 0.8
asynchronous: true
onStatusChanged: {
if (status === Image.Ready) {
root.current = this;
}
}
states: State {
name: "visible"
when: root.current === img
PropertyChanges {
img.opacity: 1
img.scale: 1
}
}
transitions: Transition {
Anim {
target: img
properties: "opacity,scale"
duration: Config.background.wallFadeDuration
}
}
}
}
+7 -2
View File
@@ -8,11 +8,15 @@ import Quickshell
import Quickshell.Hyprland import Quickshell.Hyprland
import qs.Config import qs.Config
Item {
id: itemRoot
required property PanelWindow bar
implicitHeight: 28
implicitWidth: root.implicitWidth
Rectangle { Rectangle {
id: root id: root
required property PanelWindow bar property HyprlandMonitor monitor: Hyprland.monitorFor( itemRoot.bar?.screen )
property HyprlandMonitor monitor: Hyprland.monitorFor( root.bar?.screen )
implicitWidth: workspacesRow.implicitWidth + 6 implicitWidth: workspacesRow.implicitWidth + 6
implicitHeight: 22 implicitHeight: 22
@@ -114,3 +118,4 @@ Rectangle {
} }
} }
} }
}
+186
View File
@@ -0,0 +1,186 @@
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
import QtQuick
import qs.Config
import qs.Helpers
Item {
id: root
required property ShellScreen screen
readonly property real nonAnimWidth: children.find(c => c.shouldBeActive)?.implicitWidth ?? content.implicitWidth
readonly property real nonAnimHeight: hasCurrent ? children.find(c => c.shouldBeActive)?.implicitHeight ?? content.implicitHeight : 0
readonly property Item current: content.item?.current ?? null
property string currentName
property real currentCenter
property bool hasCurrent
property string detachedMode
property string queuedMode
readonly property bool isDetached: detachedMode.length > 0
property int animLength: 400
property list<real> animCurve: MaterialEasing.emphasized
function detach(mode: string): void {
animLength = 600;
if (mode === "winfo") {
detachedMode = mode;
} else {
detachedMode = "any";
queuedMode = mode;
}
focus = true;
}
function close(): void {
hasCurrent = false;
animCurve = MaterialEasing.emphasizedDecel;
animLength = 400;
detachedMode = "";
animCurve = MaterialEasing.emphasized;
}
visible: width > 0 && height > 0
clip: true
implicitWidth: nonAnimWidth
implicitHeight: nonAnimHeight
Keys.onEscapePressed: close()
HyprlandFocusGrab {
active: root.isDetached
windows: [QsWindow.window]
onCleared: root.close()
}
Binding {
when: root.isDetached
target: QsWindow.window
property: "WlrLayershell.keyboardFocus"
value: WlrKeyboardFocus.OnDemand
}
Comp {
id: content
shouldBeActive: root.hasCurrent
asynchronous: true
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
sourceComponent: Content {
wrapper: root
}
}
// Comp {
// shouldBeActive: root.detachedMode === "winfo"
// asynchronous: true
// anchors.centerIn: parent
//
// sourceComponent: WindowInfo {
// screen: root.screen
// client: Hypr.activeToplevel
// }
// }
// Comp {
// shouldBeActive: root.detachedMode === "any"
// asynchronous: true
// anchors.centerIn: parent
//
// sourceComponent: ControlCenter {
// screen: root.screen
// active: root.queuedMode
//
// function close(): void {
// root.close();
// }
// }
// }
Behavior on x {
enabled: root.implicitHeight > 0
Anim {
duration: root.animLength
easing.bezierCurve: root.animCurve
}
}
Behavior on y {
Anim {
duration: root.animLength
easing.bezierCurve: root.animCurve
}
}
Behavior on implicitWidth {
enabled: root.implicitHeight > 0
Anim {
duration: root.animLength
easing.bezierCurve: root.animCurve
}
}
Behavior on implicitHeight {
Anim {
duration: root.animLength
easing.bezierCurve: root.animCurve
}
}
component Comp: Loader {
id: comp
property bool shouldBeActive
asynchronous: true
active: false
opacity: 0
states: State {
name: "active"
when: comp.shouldBeActive
PropertyChanges {
comp.opacity: 1
comp.active: true
}
}
transitions: [
Transition {
from: ""
to: "active"
SequentialAnimation {
PropertyAction {
property: "active"
}
Anim {
property: "opacity"
}
}
},
Transition {
from: "active"
to: ""
SequentialAnimation {
Anim {
property: "opacity"
}
PropertyAction {
property: "active"
}
}
}
]
}
}
+1 -1
View File
@@ -26,7 +26,7 @@ Loader {
right: true right: true
bottom: true bottom: true
} }
Background {} WallBackground {}
} }
} }
} }