Improve panes system and add battery pane

This commit is contained in:
2026-02-26 02:50:28 -08:00
parent 86cf291663
commit a3ffbc80c5
41 changed files with 528 additions and 273 deletions

157
Panes/Battery.qml Normal file
View File

@@ -0,0 +1,157 @@
import QtQuick
import QtQuick.Shapes
import Quickshell.Services.UPower
Column {
anchors {
top: parent.top
horizontalCenter: parent.horizontalCenter
}
width: childrenRect.width
height: childrenRect.height
anchors.topMargin: 6
Rectangle {
radius: 12
height: 40
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: 6
color: "#181818"
Rectangle {
color: "white"
width: parent.width/3 - anchors.margins
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.margins: 4
x: PowerProfiles.profile * (width + anchors.margins) + anchors.margins
Behavior on x {NumberAnimation {duration: 300; easing.type: Easing.OutQuint}}
radius: 8
}
Row {
id: profilesRow
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.right: parent.right
component Profile: Rectangle {
id: profile
color: "transparent"
required property var powerProfile
width: parent.width/3
height: 40
Text {
parent: profile
anchors.centerIn: parent
text: PowerProfile.toString(powerProfile)
color: PowerProfiles.profile === powerProfile ? "black" : "white"
Behavior on color { ColorAnimation {duration: 50}}
}
TapHandler {onTapped: PowerProfiles.profile = powerProfile}
}
Profile {powerProfile: PowerProfile.PowerSaver}
Profile {powerProfile: PowerProfile.Balanced}
Profile {powerProfile: PowerProfile.Performance}
}
}
Repeater {
model: UPower.devices.values.filter(dev => dev.model)
delegate: Row {
spacing: 6
Shape {
id: meter
anchors.verticalCenter: parent.verticalCenter
width: 120
height: 120
preferredRendererType: Shape.CurveRenderer
ShapePath {
fillColor: "transparent"
strokeColor: "#444"
strokeWidth: 6
capStyle: ShapePath.RoundCap
PathAngleArc {
centerX: meter.width/2
centerY: meter.height/2
moveToStart: true
radiusX: meter.width/2 - 12
radiusY: meter.height/2 - 12
startAngle: 90
sweepAngle: 270
}
}
ShapePath {
fillColor: "transparent"
strokeColor: "white"
strokeWidth: 6
capStyle: ShapePath.RoundCap
PathAngleArc {
centerX: meter.width/2
centerY: meter.height/2
moveToStart: true
radiusX: meter.width/2 - 12
radiusY: meter.height/2 - 12
startAngle: 90
sweepAngle: modelData.percentage * 270
}
}
Text {
anchors.centerIn: parent
font.pixelSize: 20
color: "white"
text: Math.round(modelData.percentage * 100) + "%"
}
}
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: 6
Text {
x: 4
text: UPowerDeviceType.toString(modelData.type)
font.pixelSize: 20
color: "white"
}
Row {
spacing: 6
component InfoCard: Rectangle {
id: card
required property var data
required property var label
color: "#181818"
height: 60
width: 100
radius: 12
Column {
parent: card
anchors.centerIn: parent
Text {
anchors.horizontalCenter: parent.horizontalCenter
color: "white"
font.pixelSize: 16
text: card.data
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
color: "#aaa"
text: card.label
font.pixelSize: 12
}
}
}
InfoCard {
data: modelData.changeRate + "W"
label: UPowerDeviceState.toString(modelData.state)
}
InfoCard {
property int time: (modelData.timeToEmpty ? modelData.timeToEmpty : modelData.timeToFull)
data: `${Math.floor(time / 60 / 60)}H ${Math.floor((time / 60) % 60)}M`
label: modelData.timeToEmpty ? "to empty" : "to full"
}
InfoCard {
data: Math.round(modelData.healthPercentage) + "%"
label: "healthy"
}
Item {width: 6; height: 1}
}
}
}
}
Item {width: 1; height: 6}
}

174
Panes/Launcher.qml Normal file
View File

@@ -0,0 +1,174 @@
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import QtQuick
import QtQuick.Effects
import "../emojis.mjs" as Emojis
Item {
height: currentItems.length ? 180 : 60
width: 700
property var show: input.text.length
property var currentItems: {
if (input.text.length === 0 || input.text === ":") {
return []
}
const searchEmojis = (input.text[0] === ':')
const items = searchEmojis
? Emojis.emojis.emojis
: Array.from(DesktopEntries.applications.values)
for (let item of items) {
item.searchPos = item.name.toLowerCase().search(input.text.slice(searchEmojis))
}
return items
.filter(item => item.searchPos !== -1)
.sort((a, b) => a.searchPos - b.searchPos)
}
function clear() { input.text = "" }
anchors {
top: parent.top
horizontalCenter: parent.horizontalCenter
}
Rectangle {
anchors {
top: parent.top
left: parent.left
right: parent.right
margins: 6
}
height: 48
color: "#181818"
radius: 10
TextInput {
id: input
anchors.verticalCenter: parent.verticalCenter
x: parent.width/2 - this.width/2
Behavior on x {NumberAnimation {duration: 300; easing.type: Easing.OutQuint}}
focus: true
font.pixelSize: 20
color: "white"
cursorDelegate: Item {}
onCursorPositionChanged: cursorPosition = text.length
Keys.onPressed: (event) => { switch (event.key) {
case Qt.Key_Escape:
root.close();
break;
case Qt.Key_Return:
list.currentItem.execute();
root.close();
break;
case Qt.Key_Right:
list.incrementCurrentIndex();
break;
case Qt.Key_Left:
list.decrementCurrentIndex();
break;
}}
}
}
ListView {
id: list
anchors {
top: input.parent.bottom
left: parent.left
right: parent.right
margins: 6
}
height: 114
highlightFollowsCurrentItem: true
onCurrentIndexChanged: background.indexChanged()
orientation: ListView.Horizontal
spacing: 6
add: Transition { NumberAnimation {
property: "opacity"
from: 0
to: 1
duration: 150
}}
remove: Transition { NumberAnimation {
property: "opacity"
from: 1
to: 0
duration: 150
}}
displaced: Transition {
NumberAnimation {
property: "x"
duration: 300
easing.type: Easing.OutQuint
onStarted: console.log("displaced")
}
NumberAnimation {
property: "opacity"
to: 1
duration: 150
}
}
model: ScriptModel {values: currentItems.slice(0, 5)}
delegate: Rectangle {
id: item
anchors {
top: parent?.top
bottom: parent?.bottom
}
width: (700 - 6*6)/5
color: "#20181818"
ListView.onIsCurrentItemChanged: ListView.isCurrentItem ? selectAnim.start() : deselectAnim.start()
property var selectAnim: ColorAnimation {
target: item
property: "color"
to: "#10ffffff"
duration: 200
easing.type: Easing.OutQuint
}
property var deselectAnim: ColorAnimation {
target: item
onStarted: selectAnim.stop()
property: "color"
to: "#00181818"
duration: 200
easing.type: Easing.InQuint
}
radius: 10
function execute() {
modelData.emoji
? Quickshell.clipboardText = modelData.emoji
: modelData.execute()
}
Column {
anchors {
left: parent.left
right: parent.right
verticalCenter: parent.verticalCenter
}
Image {
visible: !modelData.emoji
anchors.horizontalCenter: parent.horizontalCenter
width: 64
height: width
source: Quickshell.iconPath(modelData.icon)
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
font.pixelSize: 64
text: modelData.emoji ?? ""
}
Text {
anchors {
left: parent.left
right: parent.right
}
width: parent.width - 12
text: modelData.name
color: "white"
font.pixelSize: 16
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
}
}
}
}
}

43
Panes/Status.qml Normal file
View File

@@ -0,0 +1,43 @@
import Quickshell
import QtQuick
import "../Widgets" as Widgets
Item {
width: 800
height: 42
anchors {
top: parent.top
horizontalCenter: parent.horizontalCenter
}
Row {
anchors {
top: parent.top
left: parent.left
bottom: parent.bottom
leftMargin: 0
}
Widgets.Battery {}
}
Widgets.Workspaces {
height: parent.height
anchors {
top: parent.top
bottom: parent.bottom
horizontalCenter: parent.horizontalCenter
}
}
Row {
anchors {
top: parent.top
right: parent.right
bottom: parent.bottom
rightMargin: 12
}
Text {
anchors.verticalCenter: parent.verticalCenter
property var clock: SystemClock {}
color: "white"
text: Qt.formatDateTime(clock.date, "ddd MMM dd · hh:mm")
}
}
}