Merge status bar and launcher into morphing top bar

This commit is contained in:
2026-02-25 03:09:27 -08:00
parent ea9f96dab7
commit 86cf291663
7 changed files with 436 additions and 340 deletions

43
BarShape.qml Normal file
View File

@@ -0,0 +1,43 @@
import QtQuick
import QtQuick.Effects
import QtQuick.Shapes
Shape {
id: background
preferredRendererType: Shape.CurveRenderer
required property var color
required property var radius
ShapePath {
strokeWidth: 0
fillColor: color
startX: 0
startY: 0
PathArc {
x: radius; y: Math.min(radius, background.height/2)
radiusX: radius; radiusY: y
direction: PathArc.Clockwise
}
PathLine { x: radius; y: background.height - Math.min(radius, background.height/2) }
PathArc {
x: radius * 2; y: background.height
radiusX: radius; radiusY: Math.min(radius, background.height/2)
direction: PathArc.Counterclockwise
}
PathLine { x: background.width - radius * 2; y: background.height }
PathArc {
x: background.width - radius; y: background.height - Math.min(radius, background.height/2)
radiusX: radius; radiusY: Math.min(radius, background.height/2)
direction: PathArc.Counterclockwise
}
PathLine { x: background.width - radius; y: Math.min(radius, background.height/2) }
PathArc {
x: background.width; y: 0
radiusX: radius; radiusY: Math.min(radius, background.height/2)
direction: PathArc.Clockwise
}
}
layer.enabled: true
layer.effect: MultiEffect {
shadowEnabled: true
}
}

View File

@@ -5,37 +5,10 @@ import QtQuick
import QtQuick.Effects import QtQuick.Effects
import "./emojis.mjs" as Emojis import "./emojis.mjs" as Emojis
PanelWindow { Item {
id: root height: currentItems.length ? 180 : 60
anchors { width: 700
top: true property var show: input.text.length
}
visible: false
implicitHeight: 400
implicitWidth: 1000
exclusionMode: ExclusionMode.Ignore
color: "transparent"
WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
IpcHandler {
target: "launcher"
function open() {root.open()}
function clone() {root.close()}
function toggle() {root.visible ? root.close() : root.open()}
}
function open() {
input.text = ""
root.visible = true
exit.stop();
entry.start();
}
function close() {
input.text = ""
entry.stop();
exit.start();
}
property var currentItems: { property var currentItems: {
if (input.text.length === 0 || input.text === ":") { if (input.text.length === 0 || input.text === ":") {
return [] return []
@@ -52,28 +25,6 @@ PanelWindow {
.sort((a, b) => a.searchPos - b.searchPos) .sort((a, b) => a.searchPos - b.searchPos)
} }
Rectangle {
anchors {
top: parent.top
horizontalCenter: parent.horizontalCenter
topMargin: 100
}
color: "#181818"
height: currentItems.length ? 180 : 60
Behavior on height { NumberAnimation {
duration: 300
easing.type: Easing.OutQuint
}}
width: 700
radius: 12
RectangularShadow {
z: -1
anchors.fill: parent
radius: 12
blur: 10
spread: 2
}
Rectangle { Rectangle {
anchors { anchors {
top: parent.top top: parent.top
@@ -82,7 +33,7 @@ PanelWindow {
margins: 6 margins: 6
} }
height: 48 height: 48
color: "#222222" color: "#181818"
radius: 10 radius: 10
TextInput { TextInput {
id: input id: input
@@ -121,6 +72,7 @@ PanelWindow {
} }
height: 114 height: 114
highlightFollowsCurrentItem: true highlightFollowsCurrentItem: true
onCurrentIndexChanged: background.indexChanged()
orientation: ListView.Horizontal orientation: ListView.Horizontal
spacing: 6 spacing: 6
@@ -214,35 +166,4 @@ PanelWindow {
} }
} }
} }
transform: Translate {
y: -50
NumberAnimation on y {
id: entry
to: 0
duration: 300
easing.type: Easing.OutQuint
}
NumberAnimation on y {
id: exit
to: -50
duration: 300
easing.type: Easing.InQuint
onFinished: root.visible = false
}
}
opacity: 0
NumberAnimation on opacity {
running: entry.running
to: 1
duration: entry.duration/2
}
SequentialAnimation on opacity {
running: exit.running
PauseAnimation {duration: exit.duration/2}
NumberAnimation {
to: 0
duration: exit.duration/2
}
}
}
} }

63
Status.qml Normal file
View File

@@ -0,0 +1,63 @@
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")
}
}
}

View File

@@ -1,139 +0,0 @@
import Quickshell
import Quickshell.Hyprland
import Quickshell.Wayland
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import QtQuick.Shapes
import "." as Shell
PanelWindow {
id: root
anchors {
top: true
}
implicitWidth: 1024
implicitHeight: 1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
WlrLayershell.layer: WlrLayer.Overlay
exclusionMode: ExclusionMode.Ignore
property real radius: 16
property var bgColor: "#222222"
property int fullHeight: 48
color: "transparent"
Shape {
id: background
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
width: 1000
height: 0
property real radius: 12
property var color: "white"
preferredRendererType: Shape.CurveRenderer
NumberAnimation on height {
id: entry
running: false
to: fullHeight
duration: 300
easing.type: Easing.OutBack
easing.overshoot: 1.5
onStarted: root.implicitHeight = 60
}
NumberAnimation on height {
id: exit
running: false
to: 0
duration: 300
easing.type: Easing.InCubic
onFinished: root.implicitHeight = 1
}
ShapePath {
strokeWidth: 0
fillColor: root.bgColor
startX: 0
startY: 0
PathArc {
x: root.radius; y: Math.min(root.radius, background.height/2)
radiusX: root.radius; radiusY: y
direction: PathArc.Clockwise
}
PathLine { x: root.radius; y: background.height - Math.min(root.radius, background.height/2) }
PathArc {
x: root.radius * 2; y: background.height
radiusX: root.radius; radiusY: Math.min(root.radius, background.height/2)
direction: PathArc.Counterclockwise
}
PathLine { x: background.width - root.radius * 2; y: background.height }
PathArc {
x: background.width - root.radius; y: background.height - Math.min(root.radius, background.height/2)
radiusX: root.radius; radiusY: Math.min(root.radius, background.height/2)
direction: PathArc.Counterclockwise
}
PathLine { x: background.width - root.radius; y: Math.min(root.radius, background.height/2) }
PathArc {
x: background.width; y: 0
radiusX: root.radius; radiusY: Math.min(root.radius, background.height/2)
direction: PathArc.Clockwise
}
}
layer.enabled: true
layer.effect: MultiEffect {
shadowEnabled: true
}
}
Row {
anchors {
left: background.left
bottom: background.bottom
leftMargin: root.radius * 2
}
height: fullHeight
Button {
anchors {
top: parent.top
bottom: parent.bottom
}
width: 20
background: Image {
anchors.centerIn: parent
width: 20
height: width
source: Qt.resolvedUrl("./arch.svg")
sourceSize {width: width; height: height}
}
}
}
Shell.Workspaces {
height: fullHeight
anchors {
horizontalCenter: background.horizontalCenter
bottom: background.bottom
}
}
Row {
anchors {
right: background.right
bottom: background.bottom
rightMargin: root.radius * 2
}
height: fullHeight
Text {
property var clock: SystemClock {}
anchors.verticalCenter: parent.verticalCenter
color: "white"
text: Qt.formatDateTime(clock.date, "ddd MMM dd · hh:mm")
}
}
MouseArea {
anchors.fill: background
hoverEnabled: true
onExited: {
entry.stop()
exit.start()
}
}
HoverHandler { onHoveredChanged: if (hovered) {
exit.stop()
entry.start()
}}
}

172
TopBar.qml Normal file
View File

@@ -0,0 +1,172 @@
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
import Quickshell.Wayland
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import QtQuick.Shapes
import "." as Shell
PanelWindow {
id: root
anchors {
top: true
}
implicitWidth: 1024
implicitHeight: 200
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
WlrLayershell.layer: WlrLayer.Overlay
exclusionMode: ExclusionMode.Ignore
color: "transparent"
property bool up: false
IpcHandler {
target: "topbar"
function open() { root.open() }
function close() { root.close() }
function toggle() {
root.up ? close() : open()
}
}
function open() {
background.index = 0
background.entry.start()
}
function close() {
background.exit.start()
}
Shell.BarShape {
id: background
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
color: "#222222"
radius: 16
width: children[0].width + radius*2
height: children[0].height
component DefaultTransition: Behavior {
enabled: root.up
NumberAnimation {
duration: 500
easing.type: Easing.OutQuint
}
}
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
for (const [i, child] of children.entries()) {
if (i === index) {
if (root.up) child.fadeIn.start()
else child.opacity = 1
}
else {
if (root.up) child.fadeOut.start()
else child.opacity = 0
}
}
}
component Display: Item {
id: display
anchors {
top: parent.top
horizontalCenter: parent.horizontalCenter
}
width: children[0].width
height: children[0].height
opacity: 0
property var fadeIn: SequentialAnimation {
PauseAnimation {duration: 200}
NumberAnimation {
target: display
property: "opacity"
to: 1
duration: 300
easing.type: Easing.OutQuint
}
onStarted: fadeOut.stop()
}
property var fadeOut: NumberAnimation {
target: display
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 exit: NumberAnimation {
target: display
property: "anchors.topMargin"
to: -height
duration: 300
easing.type: Easing.InQuint
onStarted: entry.stop()
}
}
Display { opacity: 1; Shell.Status {
anchors {
top: parent.top
horizontalCenter: parent.horizontalCenter
}
} }
Display { Shell.Launcher {
onShowChanged: if (show) background.index = 1; else background.index = 0
anchors {
top: parent.top
horizontalCenter: parent.horizontalCenter
}
} }
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 exit: NumberAnimation {
target: background
property: "height"
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.up = false
background.index = 0
}
}
}
HoverHandler { onHoveredChanged: if (hovered) {
root.open()
}}
}

36
Widgets/Workspaces.qml Normal file
View File

@@ -0,0 +1,36 @@
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+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
}
}
}
}
}

View File

@@ -5,7 +5,7 @@ import "." as Shell
ShellRoot { ShellRoot {
Shell.Wall {} Shell.Wall {}
Shell.Launcher {} Shell.Launcher {}
Shell.StatusBar {} Shell.TopBar {}
Shell.Boateye {} Shell.Boateye {}
Shell.Lock { Shell.Lock {
id: lock id: lock