mirror of
https://github.com/end-4/dots-hyprland.git
synced 2026-06-05 23:09:26 -05:00
waffles: taskview: show windows
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname=".svg"
|
||||
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="7.75"
|
||||
inkscape:cx="12"
|
||||
inkscape:cy="12"
|
||||
inkscape:window-width="1173"
|
||||
inkscape:window-height="790"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1007 B |
@@ -0,0 +1 @@
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m8.5 16.586-3.793-3.793a1 1 0 0 0-1.414 1.414l4.5 4.5a1 1 0 0 0 1.414 0l11-11a1 1 0 0 0-1.414-1.414L8.5 16.586Z" fill="#212121"/></svg>
|
||||
|
After Width: | Height: | Size: 239 B |
@@ -0,0 +1 @@
|
||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M4.53 12.97a.75.75 0 0 0-1.06 1.06l4.5 4.5a.75.75 0 0 0 1.06 0l11-11a.75.75 0 0 0-1.06-1.06L8.5 16.94l-3.97-3.97Z" fill="#212121"/></svg>
|
||||
|
After Width: | Height: | Size: 241 B |
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname=".svg"
|
||||
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="7.75"
|
||||
inkscape:cx="12"
|
||||
inkscape:cy="12"
|
||||
inkscape:window-width="1173"
|
||||
inkscape:window-height="790"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1007 B |
@@ -0,0 +1,6 @@
|
||||
import Quickshell
|
||||
|
||||
ScriptModel {
|
||||
required property int count
|
||||
values: Array(count).map((_, i) => i)
|
||||
}
|
||||
@@ -177,7 +177,7 @@ Singleton {
|
||||
|
||||
property Component color: Component {
|
||||
ColorAnimation {
|
||||
duration: 70
|
||||
duration: 80
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: transition.easing.bezierCurve.easeIn
|
||||
}
|
||||
|
||||
@@ -6,5 +6,7 @@ import QtQuick.Controls
|
||||
ListView {
|
||||
id: root
|
||||
|
||||
boundsBehavior: Flickable.DragOverBounds
|
||||
|
||||
ScrollBar.vertical: WScrollBar {}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
import qs
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.functions
|
||||
import qs.modules.common.models
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.waffle.looks
|
||||
import "window-layout.js" as WindowLayout
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
@@ -36,6 +41,90 @@ Rectangle {
|
||||
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn
|
||||
}
|
||||
|
||||
// Windows
|
||||
property real maxWindowHeight: 290
|
||||
property real maxWindowWidth: 738
|
||||
property real padding: 52
|
||||
property real spacing: 25
|
||||
readonly property list<var> toplevels: ToplevelManager.toplevels.values.filter(t => {
|
||||
const client = HyprlandData.clientForToplevel(t);
|
||||
return client && client.workspace.id === HyprlandData.activeWorkspace?.id;
|
||||
})
|
||||
readonly property list<var> arrangedToplevels: {
|
||||
const maxRowWidth = width - padding * 2;
|
||||
const count = toplevels.length;
|
||||
const resultLayout = [];
|
||||
|
||||
var i = 0;
|
||||
while (i < count) {
|
||||
var row = [];
|
||||
var rowWidth = 0;
|
||||
var j = i;
|
||||
|
||||
while (j < count) {
|
||||
const toplevel = toplevels[j];
|
||||
const client = HyprlandData.clientForToplevel(toplevel);
|
||||
const scaledSize = WindowLayout.scaleWindow(client, maxWindowWidth, maxWindowHeight);
|
||||
|
||||
if (rowWidth + scaledSize.width <= maxRowWidth || row.length === 0) {
|
||||
row.push(toplevel);
|
||||
rowWidth += scaledSize.width;
|
||||
j++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
resultLayout.push(row);
|
||||
i = j;
|
||||
}
|
||||
return resultLayout;
|
||||
}
|
||||
|
||||
// Windows
|
||||
WListView {
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
topMargin: (root.height - (wsBorder.height + 16) - height) / 2
|
||||
}
|
||||
spacing: root.spacing
|
||||
topMargin: root.padding
|
||||
bottomMargin: root.padding
|
||||
leftMargin: root.padding
|
||||
rightMargin: root.padding
|
||||
height: Math.min(contentHeight + topMargin + bottomMargin, root.height - (wsBorder.height + 16))
|
||||
|
||||
interactive: height < contentHeight
|
||||
|
||||
clip: true
|
||||
|
||||
model: IndexModel {
|
||||
count: arrangedToplevels.length
|
||||
}
|
||||
delegate: RowLayout {
|
||||
id: clientRow
|
||||
required property int index
|
||||
spacing: root.spacing
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
Repeater {
|
||||
model: IndexModel {
|
||||
count: root.arrangedToplevels[clientRow.index].length
|
||||
}
|
||||
delegate: TaskViewWindow {
|
||||
id: client
|
||||
required property int index
|
||||
Layout.alignment: Qt.AlignTop
|
||||
maxHeight: root.maxWindowHeight
|
||||
maxWidth: root.maxWindowWidth
|
||||
toplevel: root.arrangedToplevels[clientRow.index][index]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Workspaces
|
||||
Rectangle {
|
||||
id: wsBorder
|
||||
@@ -65,7 +154,7 @@ Rectangle {
|
||||
|
||||
implicitHeight: 174
|
||||
|
||||
ListView {
|
||||
WListView {
|
||||
id: workspaceListView
|
||||
anchors {
|
||||
top: parent.top
|
||||
@@ -83,15 +172,27 @@ Rectangle {
|
||||
clip: true
|
||||
spacing: 4
|
||||
|
||||
model: ScriptModel {
|
||||
values: {
|
||||
function reposition() {
|
||||
positionViewAtIndex(HyprlandData.activeWorkspace.id - 1, ListView.Contain);
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: HyprlandData
|
||||
function onActiveWorkspaceChanged() {
|
||||
workspaceListView.reposition();
|
||||
}
|
||||
}
|
||||
model: IndexModel {
|
||||
id: workspaceIndexModel
|
||||
count: {
|
||||
const maxWorkspaceId = Math.max.apply(null, HyprlandData.workspaces.map(ws => ws.id));
|
||||
return Array(Math.max(maxWorkspaceId, 1));
|
||||
return Math.max(maxWorkspaceId, 1) + 1;
|
||||
}
|
||||
}
|
||||
delegate: TaskViewWorkspace {
|
||||
required property int index
|
||||
workspace: index + 1
|
||||
newWorkspace: index == workspaceIndexModel.count - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
import qs
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.functions
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.waffle.looks
|
||||
import "window-layout.js" as WindowLayout
|
||||
|
||||
WMouseAreaButton {
|
||||
id: root
|
||||
|
||||
required property var toplevel
|
||||
required property int maxHeight
|
||||
required property int maxWidth
|
||||
|
||||
property var hyprlandClient: HyprlandData.clientForToplevel(root.toplevel)
|
||||
|
||||
property string iconName: AppSearch.guessIcon(hyprlandClient?.class)
|
||||
|
||||
color: containsMouse ? Looks.colors.bg1Base : Looks.colors.bgPanelFooterBase
|
||||
borderColor: Looks.colors.bg2Border
|
||||
radius: Looks.radius.xLarge
|
||||
|
||||
property size size: WindowLayout.scaleWindow(hyprlandClient, maxWidth, maxHeight)
|
||||
implicitWidth: Math.max(Math.round(contentItem.implicitWidth), 138)
|
||||
implicitHeight: Math.round(contentItem.implicitHeight)
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: root.background
|
||||
}
|
||||
scale: (root.pressedButtons & Qt.LeftButton) ? 0.95 : 1
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
id: scaleAnim
|
||||
duration: 300
|
||||
easing.type: Easing.OutExpo
|
||||
}
|
||||
}
|
||||
|
||||
function closeWindow() {
|
||||
Hyprland.dispatch(`closewindow address:${root.hyprlandClient?.address}`)
|
||||
}
|
||||
|
||||
acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
|
||||
onClicked: (event) => {
|
||||
if (event.button === Qt.LeftButton) {
|
||||
GlobalStates.overviewOpen = false
|
||||
Hyprland.dispatch(`focuswindow address:${root.hyprlandClient?.address}`)
|
||||
GlobalStates.overviewOpen = false;
|
||||
} else if (event.button === Qt.MiddleButton) {
|
||||
root.closeWindow();
|
||||
event.accepted = true;
|
||||
} else if (event.button === Qt.RightButton) {
|
||||
if (!windowMenu.visible) windowMenu.popup();
|
||||
else windowMenu.close();
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: contentItem
|
||||
z: 2
|
||||
anchors.fill: parent
|
||||
anchors.margins: 1
|
||||
spacing: 0
|
||||
|
||||
RowLayout {
|
||||
spacing: 8
|
||||
WAppIcon {
|
||||
Layout.leftMargin: 10
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
iconName: root.iconName
|
||||
implicitSize: 16
|
||||
tryCustomIcon: false
|
||||
}
|
||||
WText {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
text: root.hyprlandClient?.title ?? ""
|
||||
}
|
||||
CloseButton {
|
||||
implicitWidth: 38
|
||||
implicitHeight: 38
|
||||
padding: 8
|
||||
onClicked: root.closeWindow()
|
||||
}
|
||||
}
|
||||
|
||||
ScreencopyView {
|
||||
Layout.fillHeight: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
implicitWidth: Math.round(root.size.width)
|
||||
implicitHeight: Math.round(root.size.height)
|
||||
|
||||
captureSource: root.toplevel ?? null
|
||||
live: true
|
||||
}
|
||||
}
|
||||
|
||||
WMenu {
|
||||
id: windowMenu
|
||||
downDirection: true
|
||||
|
||||
Action {
|
||||
enabled: root.hyprlandClient?.floating
|
||||
property bool isPinned: root.hyprlandClient?.pinned
|
||||
icon.name: isPinned ? "checkmark" : "empty"
|
||||
text: Translation.tr("Show this window on all desktops")
|
||||
onTriggered: {
|
||||
Hyprland.dispatch(`pin address:${root.hyprlandClient?.address}`)
|
||||
}
|
||||
}
|
||||
Action {
|
||||
icon.name: "empty"
|
||||
text: Translation.tr("Close")
|
||||
onTriggered: root.closeWindow()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,13 +15,15 @@ WMouseAreaButton {
|
||||
id: root
|
||||
|
||||
required property int workspace
|
||||
property bool newWorkspace: false
|
||||
|
||||
readonly property bool isActiveWorkspace: HyprlandData.activeWorkspace?.id === root.workspace
|
||||
readonly property real screenWidth: QsWindow.window.width
|
||||
readonly property real screenHeight: QsWindow.window.height
|
||||
readonly property real screenAspectRatio: screenWidth / screenHeight
|
||||
readonly property real screenScale: QsWindow.window.devicePixelRatio
|
||||
readonly property real scale: 0.1148148148
|
||||
readonly property real windowScale: wallpaperHeight / screenHeight
|
||||
|
||||
property real wallpaperHeight: 124
|
||||
|
||||
height: ListView.view.height
|
||||
implicitWidth: 244 // for now
|
||||
@@ -36,8 +38,18 @@ WMouseAreaButton {
|
||||
animation: Looks.transition.color.createObject(this)
|
||||
}
|
||||
|
||||
scale: root.containsPress ? 0.95 : 1
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
id: scaleAnim
|
||||
duration: 300
|
||||
easing.type: Easing.OutExpo
|
||||
}
|
||||
}
|
||||
|
||||
// Content
|
||||
ColumnLayout {
|
||||
id: contentItem
|
||||
anchors {
|
||||
fill: parent
|
||||
leftMargin: 12
|
||||
@@ -52,15 +64,15 @@ WMouseAreaButton {
|
||||
Layout.fillHeight: false
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
elide: Text.ElideRight
|
||||
text: Translation.tr("Desktop %1").arg(root.workspace)
|
||||
text: root.newWorkspace ? Translation.tr("New desktop") : Translation.tr("Desktop %1").arg(root.workspace)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: wsBg
|
||||
height: 124
|
||||
height: root.wallpaperHeight
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
color: Looks.colors.bg1Base
|
||||
color: Looks.colors.bg1
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
@@ -71,34 +83,42 @@ WMouseAreaButton {
|
||||
}
|
||||
}
|
||||
|
||||
StyledImage {
|
||||
// Workspace content
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
cache: true
|
||||
sourceSize: Qt.size(root.screenAspectRatio * 124, 124)
|
||||
source: Config.options.background.wallpaperPath
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
active: !root.newWorkspace
|
||||
sourceComponent: StyledImage {
|
||||
cache: true
|
||||
sourceSize: Qt.size(root.screenAspectRatio * root.wallpaperHeight, root.wallpaperHeight)
|
||||
source: Config.options.background.wallpaperPath
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: ToplevelManager.toplevels.values.filter(toplevel => {
|
||||
const address = `0x${toplevel.HyprlandToplevel?.address}`;
|
||||
var win = HyprlandData.windowByAddress[address];
|
||||
const inWorkspace = win?.workspace?.id === root.workspace;
|
||||
return inWorkspace;
|
||||
})
|
||||
}
|
||||
delegate: ScreencopyView {
|
||||
required property var modelData
|
||||
readonly property var hyprlandWindowData: HyprlandData.windowByAddress[`0x${modelData.HyprlandToplevel?.address}`]
|
||||
captureSource: modelData
|
||||
live: true
|
||||
width: hyprlandWindowData?.size[0] * root.scale
|
||||
height: hyprlandWindowData?.size[1] * root.scale
|
||||
x: hyprlandWindowData?.at[0] * root.scale
|
||||
y: hyprlandWindowData?.at[1] * root.scale
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: HyprlandData.toplevelsForWorkspace(root.workspace)
|
||||
}
|
||||
delegate: ScreencopyView {
|
||||
required property var modelData
|
||||
readonly property var hyprlandWindowData: HyprlandData.windowByAddress[`0x${modelData.HyprlandToplevel?.address}`]
|
||||
captureSource: modelData
|
||||
live: true
|
||||
width: hyprlandWindowData?.size[0] * root.windowScale
|
||||
height: hyprlandWindowData?.size[1] * root.windowScale
|
||||
x: hyprlandWindowData?.at[0] * root.windowScale
|
||||
y: hyprlandWindowData?.at[1] * root.windowScale
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// New plus icon
|
||||
Loader {
|
||||
anchors.centerIn: parent
|
||||
active: root.newWorkspace
|
||||
sourceComponent: FluentIcon {
|
||||
icon: "add"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +129,7 @@ WMouseAreaButton {
|
||||
bottom: parent.bottom
|
||||
}
|
||||
shown: root.isActiveWorkspace
|
||||
|
||||
|
||||
sourceComponent: Rectangle {
|
||||
id: activeIndicator
|
||||
implicitWidth: 32
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
function scaleWindow(hyprlandClient, maxWindowWidth, maxWindowHeight) {
|
||||
const [width, height] = hyprlandClient.size;
|
||||
const [xScale, yScale] = [maxWindowWidth / width, maxWindowHeight / height];
|
||||
const scale = Math.min(xScale, yScale);
|
||||
return Qt.size(width * scale, height * scale)
|
||||
}
|
||||
|
||||
function arrangedClients(hyprlandClients, maxRowWidth, maxWindowWidth, maxWindowHeight) {
|
||||
const count = hyprlandClients.length;
|
||||
const resultLayout = [];
|
||||
|
||||
var i = 0;
|
||||
while (i < count) {
|
||||
var row = [];
|
||||
var rowWidth = 0;
|
||||
var j = i;
|
||||
|
||||
while (j < count) {
|
||||
const client = hyprlandClients[j];
|
||||
const scaledSize = scaleWindow(client, maxWindowWidth, maxWindowHeight);
|
||||
|
||||
if (rowWidth + scaledSize.width <= maxRowWidth || row.length === 0) {
|
||||
row.push(client);
|
||||
rowWidth += scaledSize.width;
|
||||
j++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
resultLayout.push(row);
|
||||
i = j;
|
||||
}
|
||||
|
||||
return resultLayout;
|
||||
}
|
||||
@@ -4,6 +4,7 @@ pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
|
||||
/**
|
||||
@@ -21,6 +22,30 @@ Singleton {
|
||||
property var monitors: []
|
||||
property var layers: ({})
|
||||
|
||||
// Convenient stuff
|
||||
|
||||
function toplevelsForWorkspace(workspace) {
|
||||
return ToplevelManager.toplevels.values.filter(toplevel => {
|
||||
const address = `0x${toplevel.HyprlandToplevel?.address}`;
|
||||
var win = HyprlandData.windowByAddress[address];
|
||||
return win?.workspace?.id === workspace;
|
||||
})
|
||||
}
|
||||
|
||||
function hyprlandClientsForWorkspace(workspace) {
|
||||
return root.windowList.filter(win => win.workspace.id === workspace);
|
||||
}
|
||||
|
||||
function clientForToplevel(toplevel) {
|
||||
if (!toplevel || !toplevel.HyprlandToplevel) {
|
||||
return null;
|
||||
}
|
||||
const address = `0x${toplevel?.HyprlandToplevel?.address}`;
|
||||
return root.windowByAddress[address];
|
||||
}
|
||||
|
||||
// Internals
|
||||
|
||||
function updateWindowList() {
|
||||
getClients.running = true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user