496 lines
21 KiB
QML
496 lines
21 KiB
QML
import qs.modules.bar.popouts as BarPopouts
|
|
import qs.config
|
|
import qs.custom
|
|
import qs.services
|
|
import qs.util
|
|
import Quickshell
|
|
import Quickshell.Wayland
|
|
import Quickshell.Hyprland
|
|
import Quickshell.Widgets
|
|
import QtQuick
|
|
import QtQuick.Controls
|
|
import QtQuick.Effects
|
|
import QtQuick.Layouts
|
|
|
|
Item {
|
|
id: root
|
|
|
|
required property PersistentProperties uiState
|
|
required property BarPopouts.Wrapper popouts
|
|
|
|
width: Config.dashboard.workspaceWidth + 32
|
|
height: 750
|
|
|
|
property HyprlandWorkspace dragSourceWorkspace: null
|
|
property HyprlandWorkspace dragTargetWorkspace: null
|
|
|
|
// Controls scrolling at edge of panel while dragging
|
|
property real dragScrollVelocity: 0
|
|
|
|
CustomListView {
|
|
id: list
|
|
anchors.fill: parent
|
|
anchors.margins: 12
|
|
anchors.rightMargin: 0
|
|
spacing: 12
|
|
|
|
clip: true
|
|
|
|
CustomScrollBar.vertical: CustomScrollBar {
|
|
flickable: list
|
|
}
|
|
|
|
model: Hypr.monitors
|
|
|
|
delegate: Item {
|
|
id: monitor
|
|
|
|
required property int index
|
|
required property HyprlandMonitor modelData
|
|
readonly property ListModel workspaces: States.screens.get(modelData).workspaces
|
|
|
|
width: list.width - 20
|
|
height: childrenRect.height
|
|
|
|
Item {
|
|
id: monitorHeader
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
height: monIcon.height
|
|
|
|
MaterialIcon {
|
|
id: monIcon
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
anchors.left: parent.left
|
|
text: "desktop_windows"
|
|
color: Config.colors.tertiary
|
|
}
|
|
|
|
CustomText {
|
|
id: monText
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
anchors.left: monIcon.right
|
|
anchors.leftMargin: 4
|
|
text: monitor.modelData.name
|
|
color: Config.colors.tertiary
|
|
font.family: Config.font.family.mono
|
|
font.pointSize: Config.font.size.smaller
|
|
}
|
|
|
|
CustomRect {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
anchors.left: monText.right
|
|
anchors.right: parent.right
|
|
anchors.leftMargin: 8
|
|
anchors.rightMargin: 18
|
|
height: 1
|
|
color: Config.colors.inactive
|
|
}
|
|
}
|
|
|
|
ListView {
|
|
id: workspaceList
|
|
|
|
anchors.top: monitorHeader.bottom
|
|
anchors.topMargin: 8
|
|
width: list.width - 20
|
|
spacing: 22
|
|
|
|
height: contentHeight
|
|
acceptedButtons: Qt.NoButton
|
|
boundsBehavior: Flickable.StopAtBounds
|
|
|
|
model: monitor.workspaces
|
|
|
|
delegate: Column {
|
|
id: entry
|
|
|
|
required property int index
|
|
required property HyprlandWorkspace workspace
|
|
|
|
width: list.width - 20
|
|
|
|
spacing: 5
|
|
|
|
property bool hoveredWhileDragging: false
|
|
z: root.dragSourceWorkspace === workspace ? 10 : 0
|
|
|
|
Item {
|
|
id: header
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
implicitHeight: labelState.height
|
|
|
|
property color nonAnimColor: entry.hoveredWhileDragging ? Config.colors.workspaceMove :
|
|
entry.workspace?.active ? Config.colors.workspaces : Config.colors.primary
|
|
property color color: nonAnimColor
|
|
|
|
Behavior on color {
|
|
CAnim {}
|
|
}
|
|
|
|
StateLayer {
|
|
id: labelState
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
anchors.left: parent.left
|
|
implicitWidth: label.width + 20
|
|
implicitHeight: label.height + 8
|
|
color: Config.colors.secondary
|
|
|
|
function onClicked(): void {
|
|
Hypr.dispatch(`workspace ${entry.workspace.id}`)
|
|
}
|
|
|
|
Row {
|
|
id: label
|
|
|
|
anchors.centerIn: parent
|
|
spacing: 7
|
|
|
|
MaterialIcon {
|
|
text: Icons.getWorkspaceIcon(entry.workspace)
|
|
color: header.color
|
|
font.pointSize: Config.font.size.larger
|
|
animate: true
|
|
animateDuration: Config.anim.durations.small
|
|
}
|
|
|
|
CustomText {
|
|
anchors.top: parent.top
|
|
anchors.topMargin: 1
|
|
text: qsTr("Workspace %1").arg(index + 1)
|
|
font.family: Config.font.family.mono
|
|
font.pointSize: Config.font.size.normal
|
|
color: header.color
|
|
}
|
|
}
|
|
}
|
|
|
|
StateLayer {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
anchors.right: downArrow.left
|
|
anchors.rightMargin: 2
|
|
|
|
implicitWidth: implicitHeight
|
|
implicitHeight: upIcon.height + 6
|
|
|
|
disabled: entry.index === 0 && monitor.index === 0
|
|
function onClicked(): void {
|
|
if (entry.index !== 0) {
|
|
monitor.workspaces.move(entry.index, entry.index - 1, 1)
|
|
} else {
|
|
const workspace = entry.workspace;
|
|
monitor.workspaces.remove(entry.index, 1);
|
|
const otherWorkspaces = list.itemAtIndex(monitor.index - 1).workspaces;
|
|
otherWorkspaces.insert(0, {"workspace": workspace});
|
|
}
|
|
}
|
|
|
|
MaterialIcon {
|
|
id: upIcon
|
|
|
|
anchors.centerIn: parent
|
|
|
|
text: "keyboard_arrow_up"
|
|
color: parent.disabled ? Config.colors.inactive : header.nonAnimColor
|
|
font.pointSize: Config.font.size.larger
|
|
|
|
Behavior on color {
|
|
CAnim {}
|
|
}
|
|
}
|
|
}
|
|
|
|
StateLayer {
|
|
id: downArrow
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
anchors.right: parent.right
|
|
|
|
implicitWidth: implicitHeight
|
|
implicitHeight: downIcon.height + 6
|
|
|
|
disabled: entry.index === monitor.workspaces.count - 1 && monitor.index === list.count - 1
|
|
function onClicked(): void {
|
|
if (entry.index !== monitor.workspaces.count - 1) {
|
|
root.uiState.workspaces.move(entry.index, entry.index + 1, 1)
|
|
} else {
|
|
const workspace = entry.workspace;
|
|
monitor.workspaces.remove(entry.index, 1);
|
|
const otherWorkspaces = list.itemAtIndex(monitor.index + 1).workspaces;
|
|
otherWorkspaces.append({"workspace": workspace});
|
|
}
|
|
}
|
|
|
|
MaterialIcon {
|
|
id: downIcon
|
|
anchors.centerIn: parent
|
|
|
|
text: "keyboard_arrow_down"
|
|
color: parent.disabled ? Config.colors.inactive : header.nonAnimColor
|
|
font.pointSize: Config.font.size.larger
|
|
|
|
Behavior on color {
|
|
CAnim {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CustomRect {
|
|
id: preview
|
|
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
|
|
readonly property HyprlandMonitor mon: entry.workspace.monitor
|
|
|
|
// Exclude UI border and apply monitor scale
|
|
readonly property real monX: Config.border.thickness
|
|
readonly property real monY: Config.bar.height
|
|
readonly property real monWidth: (mon.width / mon.scale) - 2 * Config.border.thickness
|
|
readonly property real monHeight: (mon.height / mon.scale) - Config.bar.height - Config.border.thickness
|
|
|
|
readonly property real aspectRatio: monHeight / monWidth
|
|
readonly property real sizeRatio: Config.dashboard.workspaceWidth / monWidth
|
|
|
|
width: Config.dashboard.workspaceWidth
|
|
height: aspectRatio * width
|
|
|
|
radius: 7
|
|
color: entry.hoveredWhileDragging ? Config.colors.workspaceMove :
|
|
entry.workspace?.active ? Color.mute(Config.colors.workspaces, 1.5, 1.1) : Config.colors.container
|
|
|
|
Behavior on color {
|
|
CAnim {}
|
|
}
|
|
|
|
DropArea {
|
|
anchors.fill: parent
|
|
onEntered: {
|
|
root.dragTargetWorkspace = entry.workspace;
|
|
entry.hoveredWhileDragging = true;
|
|
}
|
|
onExited: {
|
|
if (root.draggingTargetWorkspace === entry.workspace)
|
|
root.draggingTargetWorkspace = null;
|
|
entry.hoveredWhileDragging = false;
|
|
}
|
|
}
|
|
|
|
Repeater {
|
|
anchors.fill: parent
|
|
|
|
model: entry.workspace?.toplevels
|
|
|
|
delegate: Item {
|
|
id: window
|
|
|
|
required property HyprlandToplevel modelData
|
|
|
|
property var ipc: modelData.lastIpcObject
|
|
|
|
opacity: ipc && ipc.at && !ipc.hidden ? 1 : 0
|
|
|
|
property real nonAnimX: ipc?.at ? (ipc.at[0] - preview.monX) * preview.sizeRatio : 0
|
|
property real nonAnimY: ipc?.at ? (ipc.at[1] - preview.monY) * preview.sizeRatio : 0
|
|
property real nonAnimWidth: ipc?.size ? ipc.size[0] * preview.sizeRatio : 0
|
|
property real nonAnimHeight: ipc?.size ? ipc.size[1] * preview.sizeRatio : 0
|
|
|
|
x: nonAnimX
|
|
y: nonAnimY
|
|
width: nonAnimWidth
|
|
height: nonAnimHeight
|
|
|
|
z: nonAnimX !== x || nonAnimY !== y ? 10 : 0
|
|
|
|
Behavior on x {
|
|
enabled: window.x !== 0
|
|
Anim {}
|
|
}
|
|
Behavior on y {
|
|
enabled: window.y !== 0
|
|
Anim {}
|
|
}
|
|
Behavior on width {
|
|
enabled: window.width !== 0
|
|
Anim {}
|
|
}
|
|
Behavior on height {
|
|
enabled: window.height !== 0
|
|
Anim {}
|
|
}
|
|
Behavior on opacity { Anim {} }
|
|
|
|
ScreencopyView {
|
|
id: view
|
|
visible: false
|
|
anchors.left: parent.left
|
|
anchors.top: parent.top
|
|
layer.enabled: true
|
|
|
|
// NOTE: Simulates cropping fill (which ScreencopyView does not natively support)
|
|
readonly property real aspectRatio: sourceSize.height / sourceSize.width
|
|
width: Math.max(window.nonAnimWidth, window.nonAnimHeight / aspectRatio)
|
|
height: Math.max(window.nonAnimHeight, window.nonAnimWidth * aspectRatio)
|
|
|
|
captureSource: window.modelData.wayland
|
|
live: true
|
|
|
|
CustomRect {
|
|
anchors.fill: parent
|
|
color: mouse.drag.active ?
|
|
Qt.tint(Qt.alpha(Config.colors.overlay, 0.7), Qt.alpha(Config.colors.workspaceMove, 0.2)) :
|
|
mouse.pressed ?
|
|
Qt.tint(Qt.alpha(Config.colors.overlay, 0.7), Qt.alpha(Config.colors.workspaces, 0.2)) :
|
|
mouse.containsMouse ?
|
|
Qt.tint(Qt.alpha(Config.colors.overlay, 0.5), Qt.alpha(Config.colors.workspaces, 0.1)) :
|
|
Qt.alpha(Config.colors.overlay, 0.5)
|
|
|
|
Behavior on color {
|
|
CAnim { duration: Config.anim.durations.small }
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
id: mask
|
|
visible: false
|
|
anchors.fill: view
|
|
layer.enabled: true
|
|
Rectangle {
|
|
width: window.width
|
|
height: window.height
|
|
radius: 8
|
|
}
|
|
}
|
|
|
|
MultiEffect {
|
|
anchors.fill: view
|
|
source: view
|
|
maskEnabled: true
|
|
maskSource: mask
|
|
}
|
|
|
|
IconImage {
|
|
id: icon
|
|
|
|
anchors.centerIn: parent
|
|
implicitSize: Math.min(48, window.width * 0.5, window.height * 0.5)
|
|
source: Icons.getAppIcon(window.ipc?.class ?? "", "")
|
|
}
|
|
|
|
// Interactions
|
|
|
|
Drag.active: mouse.drag.active
|
|
Drag.source: window
|
|
Drag.hotSpot.x: nonAnimWidth / 2
|
|
Drag.hotSpot.y: nonAnimHeight / 2
|
|
|
|
MouseArea {
|
|
id: mouse
|
|
|
|
anchors.fill: parent
|
|
|
|
hoverEnabled: true
|
|
acceptedButtons: Qt.AllButtons
|
|
|
|
drag.target: parent
|
|
onPressed: event => {
|
|
root.dragSourceWorkspace = entry.workspace;
|
|
window.Drag.hotSpot.x = event.x;
|
|
window.Drag.hotSpot.y = event.y;
|
|
}
|
|
onReleased: event => {
|
|
const targetWorkspace = root.dragTargetWorkspace;
|
|
root.dragSourceWorkspace = null;
|
|
root.dragScrollVelocity = 0;
|
|
if (targetWorkspace !== null && targetWorkspace !== entry.workspace) {
|
|
Hyprland.dispatch(`movetoworkspacesilent ${targetWorkspace.id}, address:0x${window.modelData.address}`)
|
|
} else {
|
|
window.x = Qt.binding(() => window.nonAnimX);
|
|
window.y = Qt.binding(() => window.nonAnimY);
|
|
}
|
|
}
|
|
|
|
onClicked: event => {
|
|
if (event.button === Qt.LeftButton) {
|
|
root.uiState.dashboard = false;
|
|
Hypr.dispatch(`focuswindow address:0x${window.modelData.address}`);
|
|
} else if (event.button === Qt.MiddleButton) {
|
|
Hypr.dispatch(`closewindow address:0x${window.modelData.address}`);
|
|
} else if (event.button === Qt.RightButton) {
|
|
root.uiState.dashboard = false;
|
|
popouts.currentName = "activewindow";
|
|
popouts.currentCenter = QsWindow.window.width / 2;
|
|
popouts.window = window.modelData;
|
|
popouts.hasCurrent = true;
|
|
}
|
|
}
|
|
|
|
onPositionChanged: event => {
|
|
if (!drag.active) return;
|
|
const y = root.mapFromItem(this, 0, event.y).y;
|
|
if (y < 100) {
|
|
root.dragScrollVelocity = (y - 100) / 25;
|
|
} else if (y > root.height - 100) {
|
|
root.dragScrollVelocity = (y - root.height + 100) / 25;
|
|
} else {
|
|
root.dragScrollVelocity = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
add: Transition {
|
|
Anim {
|
|
property: "opacity"
|
|
from: 0
|
|
to: 1
|
|
}
|
|
}
|
|
|
|
remove: Transition {
|
|
Anim {
|
|
property: "opacity"
|
|
from: 1
|
|
to: 0
|
|
}
|
|
}
|
|
|
|
move: Transition {
|
|
Anim {
|
|
property: "y"
|
|
}
|
|
Anim {
|
|
properties: "opacity"
|
|
to: 1
|
|
}
|
|
}
|
|
|
|
displaced: Transition {
|
|
Anim {
|
|
property: "y"
|
|
}
|
|
Anim {
|
|
properties: "opacity"
|
|
to: 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Timer {
|
|
running: root.dragScrollVelocity !== 0
|
|
repeat: true
|
|
interval: 10
|
|
|
|
onTriggered: {
|
|
list.contentY += root.dragScrollVelocity;
|
|
list.returnToBounds();
|
|
}
|
|
}
|
|
}
|