370 lines
10 KiB
QML
370 lines
10 KiB
QML
pragma ComponentBehavior: Bound
|
|
|
|
import QtQuick
|
|
import QtQuick.Layouts
|
|
import Quickshell
|
|
import Quickshell.Hyprland
|
|
import qs.components
|
|
import qs.components.effects
|
|
import qs.services
|
|
import Caelestia.Config
|
|
import qs.utils
|
|
|
|
Item {
|
|
id: root
|
|
|
|
required property ShellScreen screen
|
|
readonly property HyprlandMonitor monitor: Hypr.monitorFor(screen)
|
|
readonly property string activeSpecial: (Config.bar.workspaces.perMonitorWorkspaces ? monitor : Hypr.focusedMonitor)?.lastIpcObject.specialWorkspace?.name ?? ""
|
|
|
|
layer.enabled: true
|
|
layer.effect: OpacityMask {
|
|
maskSource: mask
|
|
}
|
|
|
|
Item {
|
|
id: mask
|
|
|
|
anchors.fill: parent
|
|
layer.enabled: true
|
|
visible: false
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
radius: Tokens.rounding.full
|
|
|
|
gradient: Gradient {
|
|
orientation: Gradient.Vertical
|
|
|
|
GradientStop {
|
|
position: 0
|
|
color: Qt.rgba(0, 0, 0, 0)
|
|
}
|
|
GradientStop {
|
|
position: 0.3
|
|
color: Qt.rgba(0, 0, 0, 1)
|
|
}
|
|
GradientStop {
|
|
position: 0.7
|
|
color: Qt.rgba(0, 0, 0, 1)
|
|
}
|
|
GradientStop {
|
|
position: 1
|
|
color: Qt.rgba(0, 0, 0, 0)
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.top: parent.top
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
|
|
radius: Tokens.rounding.full
|
|
implicitHeight: parent.height / 2
|
|
opacity: view.contentY > 0 ? 0 : 1
|
|
|
|
Behavior on opacity {
|
|
Anim {}
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.bottom: parent.bottom
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
|
|
radius: Tokens.rounding.full
|
|
implicitHeight: parent.height / 2
|
|
opacity: view.contentY < view.contentHeight - parent.height + Tokens.padding.small ? 0 : 1
|
|
|
|
Behavior on opacity {
|
|
Anim {}
|
|
}
|
|
}
|
|
}
|
|
|
|
ListView {
|
|
id: view
|
|
|
|
anchors.fill: parent
|
|
spacing: Tokens.spacing.normal
|
|
interactive: false
|
|
|
|
currentIndex: model.values.findIndex(w => w.name === root.activeSpecial)
|
|
onCurrentIndexChanged: currentIndex = Qt.binding(() => model.values.findIndex(w => w.name === root.activeSpecial))
|
|
|
|
model: ScriptModel {
|
|
values: Hypr.workspaces.values.filter(w => w.name.startsWith("special:") && (!Config.bar.workspaces.perMonitorWorkspaces || w.monitor === root.monitor))
|
|
}
|
|
|
|
preferredHighlightBegin: 0
|
|
preferredHighlightEnd: height
|
|
highlightRangeMode: ListView.StrictlyEnforceRange
|
|
|
|
highlightFollowsCurrentItem: false
|
|
highlight: Item {
|
|
y: view.currentItem?.y ?? 0
|
|
implicitHeight: (view.currentItem as SpecialWsDelegate)?.size ?? 0
|
|
|
|
Behavior on y {
|
|
Anim {}
|
|
}
|
|
}
|
|
|
|
delegate: SpecialWsDelegate {}
|
|
|
|
add: Transition {
|
|
Anim {
|
|
properties: "scale"
|
|
from: 0
|
|
to: 1
|
|
easing: Tokens.anim.standardDecel
|
|
}
|
|
}
|
|
|
|
remove: Transition {
|
|
Anim {
|
|
property: "scale"
|
|
to: 0.5
|
|
duration: Tokens.anim.durations.small
|
|
}
|
|
Anim {
|
|
property: "opacity"
|
|
to: 0
|
|
duration: Tokens.anim.durations.small
|
|
}
|
|
}
|
|
|
|
move: Transition {
|
|
Anim {
|
|
properties: "scale"
|
|
to: 1
|
|
easing: Tokens.anim.standardDecel
|
|
}
|
|
Anim {
|
|
properties: "x,y"
|
|
}
|
|
}
|
|
|
|
displaced: Transition {
|
|
Anim {
|
|
properties: "scale"
|
|
to: 1
|
|
easing: Tokens.anim.standardDecel
|
|
}
|
|
Anim {
|
|
properties: "x,y"
|
|
}
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
asynchronous: true
|
|
active: Config.bar.workspaces.activeIndicator
|
|
anchors.fill: parent
|
|
|
|
sourceComponent: Item {
|
|
StyledClippingRect {
|
|
id: indicator
|
|
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
|
|
y: (view.currentItem?.y ?? 0) - view.contentY
|
|
implicitHeight: (view.currentItem as SpecialWsDelegate)?.size ?? 0
|
|
|
|
color: Colours.palette.m3tertiary
|
|
radius: Tokens.rounding.full
|
|
|
|
Colouriser {
|
|
source: view
|
|
sourceColor: Colours.palette.m3onSurface
|
|
colorizationColor: Colours.palette.m3onTertiary
|
|
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
|
|
x: 0
|
|
y: -indicator.y
|
|
implicitWidth: view.width
|
|
implicitHeight: view.height
|
|
}
|
|
|
|
Behavior on y {
|
|
Anim {
|
|
easing: Tokens.anim.emphasized
|
|
}
|
|
}
|
|
|
|
Behavior on implicitHeight {
|
|
Anim {
|
|
easing: Tokens.anim.emphasized
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
property real startY
|
|
|
|
anchors.fill: view
|
|
|
|
drag.target: view.contentItem
|
|
drag.axis: Drag.YAxis
|
|
drag.maximumY: 0
|
|
drag.minimumY: Math.min(0, view.height - view.contentHeight - Tokens.padding.small)
|
|
|
|
onPressed: event => startY = event.y
|
|
|
|
onClicked: event => {
|
|
if (Math.abs(event.y - startY) > drag.threshold)
|
|
return;
|
|
|
|
const ws = view.itemAt(event.x, event.y) as SpecialWsDelegate;
|
|
if (ws?.modelData)
|
|
Hypr.dispatch(`togglespecialworkspace ${ws.modelData.name.slice(8)}`);
|
|
else
|
|
Hypr.dispatch("togglespecialworkspace special");
|
|
}
|
|
}
|
|
|
|
component SpecialWsDelegate: ColumnLayout {
|
|
id: ws
|
|
|
|
required property HyprlandWorkspace modelData
|
|
readonly property int size: label.Layout.preferredHeight + (hasWindows ? windows.implicitHeight + Tokens.padding.small : 0)
|
|
property int wsId
|
|
property string icon
|
|
property bool hasWindows
|
|
|
|
anchors.left: view.contentItem.left
|
|
anchors.right: view.contentItem.right
|
|
|
|
spacing: 0
|
|
|
|
Component.onCompleted: {
|
|
wsId = modelData.id;
|
|
icon = Icons.getSpecialWsIcon(modelData.name);
|
|
hasWindows = Config.bar.workspaces.showWindowsOnSpecialWorkspaces && modelData.lastIpcObject.windows > 0;
|
|
}
|
|
|
|
// Hacky thing cause modelData gets destroyed before the remove anim finishes
|
|
Connections {
|
|
function onIdChanged(): void {
|
|
if (ws.modelData)
|
|
ws.wsId = ws.modelData.id;
|
|
}
|
|
|
|
function onNameChanged(): void {
|
|
if (ws.modelData)
|
|
ws.icon = Icons.getSpecialWsIcon(ws.modelData.name);
|
|
}
|
|
|
|
function onLastIpcObjectChanged(): void {
|
|
if (ws.modelData)
|
|
ws.hasWindows = Config.bar.workspaces.showWindowsOnSpecialWorkspaces && ws.modelData.lastIpcObject.windows > 0;
|
|
}
|
|
|
|
target: ws.modelData
|
|
}
|
|
|
|
Connections {
|
|
function onShowWindowsOnSpecialWorkspacesChanged(): void {
|
|
if (ws.modelData)
|
|
ws.hasWindows = Config.bar.workspaces.showWindowsOnSpecialWorkspaces && ws.modelData.lastIpcObject.windows > 0;
|
|
}
|
|
|
|
target: Config.bar.workspaces
|
|
}
|
|
|
|
Loader {
|
|
id: label
|
|
|
|
asynchronous: true
|
|
|
|
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
|
|
Layout.preferredHeight: Tokens.sizes.bar.innerWidth - Tokens.padding.small * 2
|
|
|
|
sourceComponent: ws.icon.length === 1 ? letterComp : iconComp
|
|
|
|
Component {
|
|
id: iconComp
|
|
|
|
MaterialIcon {
|
|
fill: 1
|
|
text: ws.icon
|
|
verticalAlignment: Qt.AlignVCenter
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: letterComp
|
|
|
|
StyledText {
|
|
text: ws.icon
|
|
verticalAlignment: Qt.AlignVCenter
|
|
}
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
id: windows
|
|
|
|
asynchronous: true
|
|
|
|
Layout.alignment: Qt.AlignHCenter
|
|
Layout.fillHeight: true
|
|
Layout.preferredHeight: implicitHeight
|
|
|
|
visible: active
|
|
active: ws.hasWindows
|
|
|
|
sourceComponent: Column {
|
|
spacing: 0
|
|
|
|
add: Transition {
|
|
Anim {
|
|
properties: "scale"
|
|
from: 0
|
|
to: 1
|
|
easing: Tokens.anim.standardDecel
|
|
}
|
|
}
|
|
|
|
move: Transition {
|
|
Anim {
|
|
properties: "scale"
|
|
to: 1
|
|
easing: Tokens.anim.standardDecel
|
|
}
|
|
Anim {
|
|
properties: "x,y"
|
|
}
|
|
}
|
|
|
|
Repeater {
|
|
model: ScriptModel {
|
|
values: {
|
|
const windows = Hypr.toplevels.values.filter(c => c.workspace?.id === ws.wsId);
|
|
const maxIcons = Config.bar.workspaces.maxWindowIcons;
|
|
return maxIcons > 0 ? windows.slice(0, maxIcons) : windows;
|
|
}
|
|
}
|
|
|
|
MaterialIcon {
|
|
required property var modelData
|
|
|
|
grade: 0
|
|
text: Icons.getAppCategoryIcon(modelData.lastIpcObject.class, "terminal")
|
|
color: Colours.palette.m3onSurfaceVariant
|
|
}
|
|
}
|
|
}
|
|
|
|
Behavior on Layout.preferredHeight {
|
|
Anim {}
|
|
}
|
|
}
|
|
}
|
|
}
|