bar/workspaces: add special ws overlay
This commit is contained in:
parent
d8f7bac960
commit
0723153b30
3 changed files with 467 additions and 30 deletions
364
modules/bar/components/workspaces/SpecialWorkspaces.qml
Normal file
364
modules/bar/components/workspaces/SpecialWorkspaces.qml
Normal file
|
|
@ -0,0 +1,364 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import qs.components
|
||||
import qs.components.effects
|
||||
import qs.services
|
||||
import qs.utils
|
||||
import qs.config
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property ShellScreen screen
|
||||
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(screen)
|
||||
readonly property string activeSpecial: (Config.bar.workspaces.perMonitorWorkspaces ? monitor : Hyprland.focusedMonitor)?.lastIpcObject.specialWorkspace.name ?? ""
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: ShaderEffect {
|
||||
required property Item source
|
||||
readonly property Item maskSource: mask
|
||||
|
||||
fragmentShader: `file://${Quickshell.shellDir}/assets/shaders/opacitymask.frag.qsb`
|
||||
}
|
||||
|
||||
Item {
|
||||
id: mask
|
||||
|
||||
anchors.fill: parent
|
||||
layer.enabled: true
|
||||
visible: false
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Appearance.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: Appearance.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: Appearance.rounding.full
|
||||
implicitHeight: parent.height / 2
|
||||
opacity: view.contentY < view.contentHeight - parent.height + Appearance.padding.small ? 0 : 1
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: view
|
||||
|
||||
anchors.fill: parent
|
||||
spacing: Appearance.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: Hyprland.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?.size ?? 0
|
||||
|
||||
Behavior on y {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
|
||||
delegate: ColumnLayout {
|
||||
id: ws
|
||||
|
||||
required property HyprlandWorkspace modelData
|
||||
readonly property int size: label.Layout.preferredHeight + (hasWindows ? windows.implicitHeight + Appearance.padding.small : 0)
|
||||
property string icon
|
||||
property bool hasWindows
|
||||
|
||||
anchors.left: view.contentItem.left
|
||||
anchors.right: view.contentItem.right
|
||||
|
||||
spacing: 0
|
||||
|
||||
Component.onCompleted: {
|
||||
icon = Icons.getSpecialWsIcon(modelData.name);
|
||||
hasWindows = Config.bar.workspaces.showWindows && modelData.lastIpcObject.windows > 0;
|
||||
}
|
||||
|
||||
// Hacky thing cause modelData gets destroyed before the remove anim finishes
|
||||
Connections {
|
||||
target: ws.modelData
|
||||
|
||||
function onNameChanged(): void {
|
||||
if (ws.modelData)
|
||||
ws.icon = Icons.getSpecialWsIcon(ws.modelData.name);
|
||||
}
|
||||
|
||||
function onLastIpcObjectChanged(): void {
|
||||
if (ws.modelData)
|
||||
ws.hasWindows = Config.bar.workspaces.showWindows && ws.modelData.lastIpcObject.windows > 0;
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Config.bar.workspaces
|
||||
|
||||
function onShowWindowsChanged(): void {
|
||||
if (ws.modelData)
|
||||
ws.hasWindows = Config.bar.workspaces.showWindows && ws.modelData.lastIpcObject.windows > 0;
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: label
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
|
||||
Layout.preferredHeight: Config.bar.sizes.innerWidth - Appearance.padding.small * 2
|
||||
|
||||
asynchronous: true
|
||||
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
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredHeight: implicitHeight
|
||||
|
||||
visible: active
|
||||
active: ws.hasWindows
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: Column {
|
||||
spacing: 0
|
||||
|
||||
add: Transition {
|
||||
Anim {
|
||||
properties: "scale"
|
||||
from: 0
|
||||
to: 1
|
||||
easing.bezierCurve: Appearance.anim.curves.standardDecel
|
||||
}
|
||||
}
|
||||
|
||||
move: Transition {
|
||||
Anim {
|
||||
properties: "scale"
|
||||
to: 1
|
||||
easing.bezierCurve: Appearance.anim.curves.standardDecel
|
||||
}
|
||||
Anim {
|
||||
properties: "x,y"
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: Hyprland.toplevels.values.filter(c => c.workspace?.id === ws.modelData.id)
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
required property var modelData
|
||||
|
||||
grade: 0
|
||||
text: Icons.getAppCategoryIcon(modelData.lastIpcObject.class, "terminal")
|
||||
color: Colours.palette.m3onSurfaceVariant
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on Layout.preferredHeight {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
add: Transition {
|
||||
Anim {
|
||||
properties: "scale"
|
||||
from: 0
|
||||
to: 1
|
||||
easing.bezierCurve: Appearance.anim.curves.standardDecel
|
||||
}
|
||||
}
|
||||
|
||||
remove: Transition {
|
||||
Anim {
|
||||
property: "scale"
|
||||
to: 0.5
|
||||
duration: Appearance.anim.durations.small
|
||||
}
|
||||
Anim {
|
||||
property: "opacity"
|
||||
to: 0
|
||||
duration: Appearance.anim.durations.small
|
||||
}
|
||||
}
|
||||
|
||||
move: Transition {
|
||||
Anim {
|
||||
properties: "scale"
|
||||
to: 1
|
||||
easing.bezierCurve: Appearance.anim.curves.standardDecel
|
||||
}
|
||||
Anim {
|
||||
properties: "x,y"
|
||||
}
|
||||
}
|
||||
|
||||
displaced: Transition {
|
||||
Anim {
|
||||
properties: "scale"
|
||||
to: 1
|
||||
easing.bezierCurve: Appearance.anim.curves.standardDecel
|
||||
}
|
||||
Anim {
|
||||
properties: "x,y"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: Config.bar.workspaces.activeIndicator
|
||||
asynchronous: true
|
||||
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?.size ?? 0
|
||||
|
||||
color: Colours.palette.m3tertiary
|
||||
radius: Appearance.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.bezierCurve: Appearance.anim.curves.emphasized
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on implicitHeight {
|
||||
Anim {
|
||||
easing.bezierCurve: Appearance.anim.curves.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 - Appearance.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);
|
||||
if (ws?.modelData)
|
||||
Hyprland.dispatch(`togglespecialworkspace ${ws.modelData.name.slice(8)}`);
|
||||
else
|
||||
Hyprland.dispatch("togglespecialworkspace special");
|
||||
}
|
||||
}
|
||||
|
||||
component Anim: NumberAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
||||
|
|
@ -6,12 +6,14 @@ import qs.components
|
|||
import Quickshell
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Effects
|
||||
|
||||
StyledRect {
|
||||
StyledClippingRect {
|
||||
id: root
|
||||
|
||||
required property ShellScreen screen
|
||||
|
||||
readonly property bool onSpecial: (Config.bar.workspaces.perMonitorWorkspaces ? Hyprland.monitorFor(screen) : Hyprland.focusedMonitor)?.lastIpcObject.specialWorkspace.name !== ""
|
||||
readonly property int activeWsId: Config.bar.workspaces.perMonitorWorkspaces ? (Hyprland.monitorFor(screen).activeWorkspace?.id ?? 1) : Hyprland.activeWsId
|
||||
|
||||
readonly property var occupied: Hyprland.workspaces.values.reduce((acc, curr) => {
|
||||
|
|
@ -20,54 +22,125 @@ StyledRect {
|
|||
}, {})
|
||||
readonly property int groupOffset: Math.floor((activeWsId - 1) / Config.bar.workspaces.shown) * Config.bar.workspaces.shown
|
||||
|
||||
implicitHeight: layout.implicitHeight + Appearance.padding.small * 2
|
||||
property real blur: onSpecial ? 1 : 0
|
||||
|
||||
implicitWidth: Config.bar.sizes.innerWidth
|
||||
implicitHeight: layout.implicitHeight + Appearance.padding.small * 2
|
||||
|
||||
color: Colours.tPalette.m3surfaceContainer
|
||||
radius: Appearance.rounding.full
|
||||
|
||||
Loader {
|
||||
active: Config.bar.workspaces.occupiedBg
|
||||
asynchronous: true
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Appearance.padding.small
|
||||
scale: root.onSpecial ? 0.8 : 1
|
||||
opacity: root.onSpecial ? 0.5 : 1
|
||||
|
||||
sourceComponent: OccupiedBg {
|
||||
workspaces: workspaces
|
||||
occupied: root.occupied
|
||||
groupOffset: root.groupOffset
|
||||
layer.enabled: root.blur > 0
|
||||
layer.effect: MultiEffect {
|
||||
blurEnabled: true
|
||||
blur: root.blur
|
||||
blurMax: 32
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
Loader {
|
||||
active: Config.bar.workspaces.occupiedBg
|
||||
asynchronous: true
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: Math.floor(Appearance.spacing.small / 2)
|
||||
anchors.fill: parent
|
||||
anchors.margins: Appearance.padding.small
|
||||
|
||||
Repeater {
|
||||
id: workspaces
|
||||
|
||||
model: Config.bar.workspaces.shown
|
||||
|
||||
Workspace {
|
||||
activeWsId: root.activeWsId
|
||||
sourceComponent: OccupiedBg {
|
||||
workspaces: workspaces
|
||||
occupied: root.occupied
|
||||
groupOffset: root.groupOffset
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: Math.floor(Appearance.spacing.small / 2)
|
||||
|
||||
Repeater {
|
||||
id: workspaces
|
||||
|
||||
model: Config.bar.workspaces.shown
|
||||
|
||||
Workspace {
|
||||
activeWsId: root.activeWsId
|
||||
occupied: root.occupied
|
||||
groupOffset: root.groupOffset
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
active: Config.bar.workspaces.activeIndicator
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: ActiveIndicator {
|
||||
activeWsId: root.activeWsId
|
||||
workspaces: workspaces
|
||||
mask: layout
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: layout
|
||||
onClicked: event => {
|
||||
const ws = layout.childAt(event.x, event.y).index + root.groupOffset + 1;
|
||||
if (Hyprland.activeWsId !== ws)
|
||||
Hyprland.dispatch(`workspace ${ws}`);
|
||||
else
|
||||
Hyprland.dispatch("togglespecialworkspace special");
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
active: Config.bar.workspaces.activeIndicator
|
||||
id: specialWs
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Appearance.padding.small
|
||||
|
||||
active: opacity > 0
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: ActiveIndicator {
|
||||
activeWsId: root.activeWsId
|
||||
workspaces: workspaces
|
||||
mask: layout
|
||||
scale: root.onSpecial ? 1 : 0.5
|
||||
opacity: root.onSpecial ? 1 : 0
|
||||
|
||||
sourceComponent: SpecialWorkspaces {
|
||||
screen: root.screen
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on blur {
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.small
|
||||
}
|
||||
}
|
||||
|
||||
component Anim: NumberAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ Singleton {
|
|||
if (name === "communication")
|
||||
return "forum";
|
||||
if (name === "music")
|
||||
return "music_note";
|
||||
return "music_cast";
|
||||
if (name === "todo")
|
||||
return "checklist";
|
||||
if (name === "sysmon")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue