diff --git a/Panes/Battery.qml b/Panes/Battery.qml new file mode 100644 index 0000000..f9cb2aa --- /dev/null +++ b/Panes/Battery.qml @@ -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} +} diff --git a/Launcher.qml b/Panes/Launcher.qml similarity index 96% rename from Launcher.qml rename to Panes/Launcher.qml index 112bac3..1f5e42d 100644 --- a/Launcher.qml +++ b/Panes/Launcher.qml @@ -3,7 +3,7 @@ import Quickshell.Io import Quickshell.Wayland import QtQuick import QtQuick.Effects -import "./emojis.mjs" as Emojis +import "../emojis.mjs" as Emojis Item { height: currentItems.length ? 180 : 60 @@ -24,6 +24,11 @@ Item { .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 { diff --git a/Panes/Status.qml b/Panes/Status.qml new file mode 100644 index 0000000..f3351f4 --- /dev/null +++ b/Panes/Status.qml @@ -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") + } + } +} diff --git a/Status.qml b/Status.qml deleted file mode 100644 index d9cf0dd..0000000 --- a/Status.qml +++ /dev/null @@ -1,63 +0,0 @@ -import Quickshell -import QtQuick -import "./Widgets" as Widgets - -Item { - width: 800 - height: 36 - Row { - anchors { - top: parent.top - left: parent.left - bottom: parent.bottom - } - Item { - anchors { - top: parent.top - bottom: parent.bottom - } - width: height - Rectangle { - anchors { - fill: parent - margins: 4 - } - radius: 12 - color: parent.hover.hovered ? "#333333" : "#222222" - Image { - anchors.centerIn: parent - width: 20 - height: width - source: Qt.resolvedUrl("./arch.svg") - sourceSize {width: width; height: height} - } - } - property var hover: HoverHandler {} - property var click: TapHandler { - onTapped: launcher.open() - } - } - } - 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") - } - } -} diff --git a/TopBar.qml b/TopBar.qml index 5987916..81956bc 100644 --- a/TopBar.qml +++ b/TopBar.qml @@ -7,18 +7,22 @@ import QtQuick.Controls import QtQuick.Effects import QtQuick.Shapes import "." as Shell +import "./Panes" as Panes PanelWindow { id: root anchors { top: true } + property var font: {family: "0xProto Nerd Font"} + implicitWidth: 1024 implicitHeight: 200 - WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand + WlrLayershell.keyboardFocus: (up && !shortcut.pressed) ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None WlrLayershell.layer: WlrLayer.Overlay exclusionMode: ExclusionMode.Ignore color: "transparent" + visible: false property bool up: false IpcHandler { @@ -29,11 +33,35 @@ PanelWindow { root.up ? close() : open() } } + + GlobalShortcut { + id: shortcut + name: "topbar" + description: "Hold to peek, tap to toggle topbar" + onPressed: { + root.open() + } + onReleased: { + if (!hover.hovered) + root.close() + } + } + function open() { - background.index = 0 + root.visible = true + background.exit.stop() + if (!root.up) background.height = 0 + root.up = true + for (const child of background.children) { + child.entry.start() + } background.entry.start() } function close() { + background.entry.stop() + for (const child of background.children) { + child.exit.start() + } background.exit.start() } @@ -55,9 +83,6 @@ PanelWindow { DefaultTransition on width { } DefaultTransition on height { } property int index: 0 - HoverHandler { onHoveredChanged: if (!hovered) { - root.close() - }} onIndexChanged: { width = children[index].width + radius*2 height = children[index].height @@ -73,8 +98,8 @@ PanelWindow { } } - component Display: Item { - id: display + component Pane: Item { + id: pane anchors { top: parent.top horizontalCenter: parent.horizontalCenter @@ -85,7 +110,7 @@ PanelWindow { property var fadeIn: SequentialAnimation { PauseAnimation {duration: 200} NumberAnimation { - target: display + target: pane property: "opacity" to: 1 duration: 300 @@ -94,23 +119,27 @@ PanelWindow { onStarted: fadeOut.stop() } property var fadeOut: NumberAnimation { - target: display + target: pane property: "opacity" to: 0 duration: 300 onStarted: fadeIn.stop() easing.type: Easing.OutQuint } - property var entry: NumberAnimation { - target: display - property: "anchors.topMargin" - to: 0 - duration: 300 - easing.type: Easing.OutQuint - onStarted: exit.stop() + property var entry: + SequentialAnimation { + PauseAnimation {duration: 2} + NumberAnimation { + target: pane + property: "anchors.topMargin" + to: 0 + duration: 300 + easing.type: Easing.OutQuint + onStarted: exit.stop() + } } property var exit: NumberAnimation { - target: display + target: pane property: "anchors.topMargin" to: -height duration: 300 @@ -118,33 +147,22 @@ PanelWindow { onStarted: entry.stop() } } - Display { opacity: 1; Shell.Status { - anchors { - top: parent.top - horizontalCenter: parent.horizontalCenter - } - } } - Display { Shell.Launcher { + Pane { opacity: 1; Panes.Status { }} + Pane { Panes.Launcher { + id: launcher onShowChanged: if (show) background.index = 1; else background.index = 0 - anchors { - top: parent.top - horizontalCenter: parent.horizontalCenter - } - } } + }} + Pane { Panes.Battery { + }} - property var entry: NumberAnimation { - target: background - property: "height" - to: background.children[background.index].height - duration: 300 - easing.type: Easing.OutQuint - onStarted: { - root.implicitHeight = 200 - background.exit.stop() - root.up = true - for (const child of background.children) { - child.entry.start() - } + property var entry: SequentialAnimation { + PauseAnimation {duration: 2} + NumberAnimation { + target: background + property: "height" + to: background.children[background.index].height + duration: 300 + easing.type: Easing.OutQuint } } property var exit: NumberAnimation { @@ -153,20 +171,13 @@ PanelWindow { to: 0 duration: 300 easing.type: Easing.InQuint - onStarted: { - background.entry.stop() - for (const child of background.children) { - child.exit.start() - } - } onFinished: { - root.implicitHeight = 1 + root.visible = false root.up = false background.index = 0 + launcher.clear() } } + HoverHandler {id: hover} } - HoverHandler { onHoveredChanged: if (hovered) { - root.open() - }} } diff --git a/Widgets/Battery.qml b/Widgets/Battery.qml new file mode 100644 index 0000000..9cc2cd0 --- /dev/null +++ b/Widgets/Battery.qml @@ -0,0 +1,60 @@ +import Quickshell.Services.UPower +import Quickshell.Widgets +import QtQuick +import Quickshell +import "." as Widgets + +Item { + anchors { + top: parent.top + bottom: parent.bottom + } + width: children[1].width + 20 + Rectangle { + anchors.fill: parent + anchors.margins: 4 + color: hover.hovered ? "#11ffffff" : "#00ffffff" + radius: 8 + bottomLeftRadius: 12 + } + Row { + anchors { + verticalCenter: parent.verticalCenter + } + x: 8 + spacing: 4 + Image { + anchors.verticalCenter: parent.verticalCenter + width: 20 + height: width + sourceSize { width: width; height: height } + source: { + if (UPower.displayDevice.state === UPowerDeviceState.Charging) + return Quickshell.shellPath("assets/battery-charging-symbolic.svg") + switch (PowerProfiles.profile) { + case PowerProfile.PowerSaver: + return Quickshell.shellPath("assets/power-saver-symbolic.svg") + break; + case PowerProfile.Balanced: + return Quickshell.shellPath("assets/balanced-symbolic.svg") + break; + case PowerProfile.Performance: + return Quickshell.shellPath("assets/performance-symbolic.svg") + break; + } + return Quickshell.shellPath("assets/battery-performance-symbolic.svg") + } + } + Text { + id: content + anchors.verticalCenter: parent.verticalCenter + x: 10 + color: "white" + text: `${Math.round(UPower.displayDevice.percentage * 100)}% · ${Math.round(UPower.displayDevice.changeRate)}W` + } + } + HoverHandler {id: hover} + TapHandler {onTapped: { + background.index = 2 + }} +} diff --git a/Widgets/Workspaces.qml b/Widgets/Workspaces.qml index 58a4f0e..0bdbc75 100644 --- a/Widgets/Workspaces.qml +++ b/Widgets/Workspaces.qml @@ -1,36 +1,60 @@ import QtQuick import QtQuick.Shapes +import Quickshell.Widgets import Quickshell.Hyprland +import Quickshell -Row { - Repeater { - model: 9 - delegate: Shape { - height: 14 - width: 24 - property var workspace: Hyprland.workspaces.values.find(ws => ws.id === modelData+1) - preferredRendererType: Shape.CurveRenderer - anchors.verticalCenter: parent.verticalCenter - ShapePath { - strokeWidth: workspace ? 7 : 4 - Behavior on strokeWidth {NumberAnimation { - duration: 150 - }} - strokeColor: "white" - fillColor: "transparent" - PathAngleArc { - moveToStart: true - centerX: width/2 - centerY: height/2 - startAngle: 0 - sweepAngle: 360 - radiusX: workspace ? 3.5 : 5 - Behavior on radiusX {NumberAnimation { - duration: 150 - }} - radiusY: radiusX +Item { + width: children[0].width + Row { + id: workspaceRow + height: parent.height + anchors.centerIn: parent + spacing: 4 + Repeater { + model: 9 + delegate: Item { + height: parent.height + width: 24 + property var workspace: Hyprland.workspaces.values.find(ws => ws.id === modelData+1) + property var icon: { + const appId = workspace?.toplevels.values[0]?.wayland?.appId + return DesktopEntries.applications.values.find( + app => app.startupClass === appId || app.id === appId + )?.icon; + } + Rectangle { + color: "transparent" + width: workspace?.toplevels.values.length ? (icon ? 0 : 16) : 4 + height: width + anchors.centerIn: parent + radius: width/2 + border.color: "white" + border.width: workspace?.toplevels.values.length ? (icon ? width/2 : width/4) : width/2 + Behavior on width {NumberAnimation {duration: 300; easing.type: Easing.OutQuint}} + } + Image { + width: workspace?.toplevels.values.length && icon ? 24 : 0 + Behavior on width {NumberAnimation {duration: 300; easing.type: Easing.InOutQuint}} + height: width + anchors.centerIn: parent + anchors.verticalCenterOffset: 0 + source: icon ? Quickshell.iconPath(icon) : source + sourceSize.width: width + sourceSize.height: height } } } } + Rectangle { + x: (Hyprland.focusedWorkspace.id-1) * (workspaceRow.children[0].width + workspaceRow.spacing) + 2 + y: 2 + Behavior on x {NumberAnimation { + duration: 300 + easing.type: Easing.OutCubic + }} + width: 20 + height: 4 + radius: height/2 + } } diff --git a/Workspaces.qml b/Workspaces.qml deleted file mode 100644 index fa65f6d..0000000 --- a/Workspaces.qml +++ /dev/null @@ -1,30 +0,0 @@ -import QtQuick -import QtQuick.Shapes -import Quickshell.Hyprland - -Row { - Repeater { - model: 9 - delegate: Shape { - height: 14 - width: 24 - property var workspace: Hyprland.workspaces.values.find(ws => ws.id === modelData) - preferredRendererType: Shape.CurveRenderer - anchors.verticalCenter: parent.verticalCenter - ShapePath { - strokeWidth: workspace ? 12 : 4 - strokeColor: "white" - fillColor: "transparent" - PathAngleArc { - moveToStart: true - centerX: width/2 - centerY: height/2 - startAngle: 0 - sweepAngle: 360 - radiusX: workspace ? 2 : 6 - radiusY: radiusX - } - } - } - } -} diff --git a/arch.svg b/arch.svg deleted file mode 100644 index ca3bd88..0000000 --- a/arch.svg +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/archlinux.png b/assets/archlinux.png new file mode 100644 index 0000000..3c5fb38 Binary files /dev/null and b/assets/archlinux.png differ diff --git a/assets/archlinux.svg b/assets/archlinux.svg new file mode 100644 index 0000000..db54283 --- /dev/null +++ b/assets/archlinux.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/balanced-symbolic.svg b/assets/balanced-symbolic.svg new file mode 100644 index 0000000..559d881 --- /dev/null +++ b/assets/balanced-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/battery-charging-symbolic.svg b/assets/battery-charging-symbolic.svg new file mode 100644 index 0000000..a72cea9 --- /dev/null +++ b/assets/battery-charging-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/brightness-symbolic.svg b/assets/brightness-symbolic.svg new file mode 100644 index 0000000..4864f28 --- /dev/null +++ b/assets/brightness-symbolic.svg @@ -0,0 +1,6 @@ + + + +brightness + + diff --git a/assets/hyprland.svg b/assets/hyprland.svg new file mode 100644 index 0000000..a183412 --- /dev/null +++ b/assets/hyprland.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + diff --git a/assets/lazer-bluetooth-symbolic.svg b/assets/lazer-bluetooth-symbolic.svg new file mode 100644 index 0000000..dbaa014 --- /dev/null +++ b/assets/lazer-bluetooth-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/lazer-next-symbolic.svg b/assets/lazer-next-symbolic.svg new file mode 100644 index 0000000..e266cbe --- /dev/null +++ b/assets/lazer-next-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/lazer-pause-symbolic.svg b/assets/lazer-pause-symbolic.svg new file mode 100644 index 0000000..6ce1074 --- /dev/null +++ b/assets/lazer-pause-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/lazer-play-symbolic.svg b/assets/lazer-play-symbolic.svg new file mode 100644 index 0000000..195881d --- /dev/null +++ b/assets/lazer-play-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/lazer-previous-symbolic.svg b/assets/lazer-previous-symbolic.svg new file mode 100644 index 0000000..9b0b4d0 --- /dev/null +++ b/assets/lazer-previous-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/logout.svg b/assets/logout.svg new file mode 100644 index 0000000..7a1d737 --- /dev/null +++ b/assets/logout.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/myvolume-symbolic.svg b/assets/myvolume-symbolic.svg new file mode 100644 index 0000000..0fc151d --- /dev/null +++ b/assets/myvolume-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/nobluetooth-symbolic.svg b/assets/nobluetooth-symbolic.svg new file mode 100644 index 0000000..fc87d43 --- /dev/null +++ b/assets/nobluetooth-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/noplayer-symbolic.svg b/assets/noplayer-symbolic.svg new file mode 100644 index 0000000..8989d8b --- /dev/null +++ b/assets/noplayer-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/assets/notif.svg b/assets/notif.svg new file mode 100644 index 0000000..4143ab4 --- /dev/null +++ b/assets/notif.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/performance-symbolic.svg b/assets/performance-symbolic.svg new file mode 100644 index 0000000..1a2f5c8 --- /dev/null +++ b/assets/performance-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/player-symbolic.svg b/assets/player-symbolic.svg new file mode 100644 index 0000000..5fe8cd6 --- /dev/null +++ b/assets/player-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/assets/power-saver-symbolic.svg b/assets/power-saver-symbolic.svg new file mode 100644 index 0000000..dde83ee --- /dev/null +++ b/assets/power-saver-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/reboot.svg b/assets/reboot.svg new file mode 100644 index 0000000..2dff6d1 --- /dev/null +++ b/assets/reboot.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/screenshots/battery.png b/assets/screenshots/battery.png new file mode 100644 index 0000000..25fdcda Binary files /dev/null and b/assets/screenshots/battery.png differ diff --git a/assets/screenshots/bluetooth.png b/assets/screenshots/bluetooth.png new file mode 100644 index 0000000..9a4b63a Binary files /dev/null and b/assets/screenshots/bluetooth.png differ diff --git a/assets/screenshots/brightness.png b/assets/screenshots/brightness.png new file mode 100644 index 0000000..6b91274 Binary files /dev/null and b/assets/screenshots/brightness.png differ diff --git a/assets/screenshots/media.png b/assets/screenshots/media.png new file mode 100644 index 0000000..53761a4 Binary files /dev/null and b/assets/screenshots/media.png differ diff --git a/assets/screenshots/notification.png b/assets/screenshots/notification.png new file mode 100644 index 0000000..6eb6fdb Binary files /dev/null and b/assets/screenshots/notification.png differ diff --git a/assets/screenshots/notifications.png b/assets/screenshots/notifications.png new file mode 100644 index 0000000..15ef75e Binary files /dev/null and b/assets/screenshots/notifications.png differ diff --git a/assets/screenshots/power.png b/assets/screenshots/power.png new file mode 100644 index 0000000..7263cf8 Binary files /dev/null and b/assets/screenshots/power.png differ diff --git a/assets/screenshots/volume.png b/assets/screenshots/volume.png new file mode 100644 index 0000000..843cb59 Binary files /dev/null and b/assets/screenshots/volume.png differ diff --git a/assets/screenshots/workspaces.png b/assets/screenshots/workspaces.png new file mode 100644 index 0000000..331f408 Binary files /dev/null and b/assets/screenshots/workspaces.png differ diff --git a/assets/shutdown.svg b/assets/shutdown.svg new file mode 100644 index 0000000..2b1bd5c --- /dev/null +++ b/assets/shutdown.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/suspend.svg b/assets/suspend.svg new file mode 100644 index 0000000..0e861af --- /dev/null +++ b/assets/suspend.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/shell.qml b/shell.qml index b1a8897..1888d2c 100644 --- a/shell.qml +++ b/shell.qml @@ -4,7 +4,6 @@ import "." as Shell ShellRoot { Shell.Wall {} - Shell.Launcher {} Shell.TopBar {} Shell.Boateye {} Shell.Lock {