init: working version
This commit is contained in:
commit
7d8d7dacae
109 changed files with 15066 additions and 0 deletions
496
modules/dashboard/Workspaces.qml
Normal file
496
modules/dashboard/Workspaces.qml
Normal file
|
|
@ -0,0 +1,496 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue