quickshell and hyprland additions

This commit is contained in:
2026-03-15 13:56:00 +02:00
parent c9c27d1554
commit 1ad06b82a6
509 changed files with 68371 additions and 19 deletions

View File

@@ -0,0 +1,327 @@
import qs.config
import QtQuick
import QtQuick.Effects
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import Quickshell.Wayland
LazyLoader {
id: root
property string displayName: screen?.name ?? ""
property PanelWindow instance: null
property HoverHandler hoverTarget
property real margin: Metrics.margin(10)
default property list<Component> content
property bool startAnim: false
property bool isVisible: false
property bool keepAlive: false
property bool interactable: false
property bool hasHitbox: true
property bool hCenterOnItem: false
property bool followMouse: false
property list<StyledPopout> childPopouts: []
property bool requiresHover: true
property bool _manualControl: false
property int hoverDelay: Metrics.chronoDuration(250)
property bool targetHovered: hoverTarget && hoverTarget.hovered
property bool containerHovered: interactable && root.item && root.item.containerHovered
property bool selfHovered: targetHovered || containerHovered
property bool childrenHovered: {
for (let i = 0; i < childPopouts.length; i++) {
if (childPopouts[i].selfHovered)
return true;
}
return false;
}
property bool hoverActive: selfHovered || childrenHovered
property Timer showDelayTimer: Timer {
interval: root.hoverDelay
repeat: false
onTriggered: {
root.keepAlive = true;
root.isVisible = true;
root.startAnim = true;
}
}
property Timer hangTimer: Timer {
interval: Metrics.chronoDuration(200)
repeat: false
onTriggered: {
root.startAnim = false;
cleanupTimer.restart();
}
}
property Timer cleanupTimer: Timer {
interval: Metrics.chronoDuration("small")
repeat: false
onTriggered: {
root.isVisible = false;
root.keepAlive = false;
root._manualControl = false;
root.instance = null;
}
}
onHoverActiveChanged: {
if (_manualControl)
return;
if (!requiresHover)
return;
if (hoverActive) {
hangTimer.stop();
cleanupTimer.stop();
if (hoverDelay > 0) {
showDelayTimer.restart();
} else {
root.keepAlive = true;
root.isVisible = true;
root.startAnim = true;
}
} else {
showDelayTimer.stop();
hangTimer.restart();
}
}
function show() {
hangTimer.stop();
cleanupTimer.stop();
showDelayTimer.stop();
_manualControl = true;
keepAlive = true;
isVisible = true;
startAnim = true;
}
function hide() {
_manualControl = true;
showDelayTimer.stop();
startAnim = false;
hangTimer.stop();
cleanupTimer.restart();
}
active: keepAlive
component: PanelWindow {
id: popoutWindow
color: "transparent"
visible: root.isVisible
WlrLayershell.namespace: "whisker:popout"
WlrLayershell.layer: WlrLayer.Overlay
exclusionMode: ExclusionMode.Ignore
exclusiveZone: 0
anchors {
left: true
top: true
right: true
bottom: true
}
property bool exceedingHalf: false
property var parentPopoutWindow: null
property point mousePos: Qt.point(0, 0)
property bool containerHovered: root.interactable && containerHoverHandler.hovered
HoverHandler {
id: windowHover
onPointChanged: point => {
if (root.followMouse)
popoutWindow.mousePos = point.position;
}
}
mask: Region {
x: !root.hasHitbox ? 0 : !requiresHover ? 0 : container.x
y: !root.hasHitbox ? 0 : !requiresHover ? 0 : container.y
width: !root.hasHitbox ? 0 : !requiresHover ? popoutWindow.width : container.implicitWidth
height: !root.hasHitbox ? 0 : !requiresHover ? popoutWindow.height : container.implicitHeight
}
MouseArea {
id: mouseArea
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
hoverEnabled: false
onPressed: mouse => {
if (!containerHoverHandler.containsMouse && root.isVisible) {
root.hide();
}
}
}
Item {
id: container
implicitWidth: contentArea.implicitWidth + root.margin * 2
implicitHeight: contentArea.implicitHeight + root.margin * 2
x: {
let xValue;
if (root.followMouse)
xValue = mousePos.x + 10;
else {
let targetItem = hoverTarget?.parent;
if (!targetItem)
xValue = 0;
else {
let baseX = targetItem.mapToGlobal(Qt.point(0, 0)).x;
if (parentPopoutWindow)
baseX += parentPopoutWindow.x;
let targetWidth = targetItem.width;
let popupWidth = container.implicitWidth;
if (root.hCenterOnItem) {
let centeredX = baseX + (targetWidth - popupWidth) / 2;
if (centeredX + popupWidth > screen.width)
centeredX = screen.width - popupWidth - 10;
if (centeredX < 10)
centeredX = 10;
xValue = centeredX;
} else {
let xPos = baseX - ((ConfigResolver.bar(root.displayName).position === "top" || ConfigResolver.bar(root.displayName).position === "top") ? 20 : -40);
if (xPos + popupWidth > screen.width) {
exceedingHalf = true;
xValue = baseX - popupWidth;
} else {
exceedingHalf = false;
xValue = xPos;
}
}
}
}
return root.cleanupTimer.running ? xValue : Math.round(xValue);
}
y: {
let yValue;
if (root.followMouse)
yValue = mousePos.y + 10;
else {
let targetItem = hoverTarget?.parent;
if (!targetItem)
yValue = 0;
else {
let baseY = targetItem.mapToGlobal(Qt.point(0, 0)).y;
if (parentPopoutWindow)
baseY += parentPopoutWindow.y;
let targetHeight = targetItem.height;
let popupHeight = container.implicitHeight;
let yPos = baseY + ((ConfigResolver.bar(root.displayName).position === "top" || ConfigResolver.bar(root.displayName).position === "top") ? targetHeight : 0);
if (yPos > screen.height / 2)
yPos = baseY - popupHeight;
if (yPos + popupHeight > screen.height)
yPos = screen.height - popupHeight - 10;
if (yPos < 10)
yPos = 10;
yValue = yPos;
}
}
return root.cleanupTimer.running ? yValue : Math.round(yValue);
}
opacity: root.startAnim ? 1 : 0
scale: root.interactable ? 1 : root.startAnim ? 1 : 0.9
layer.enabled: true
layer.effect: MultiEffect {
shadowEnabled: true
shadowOpacity: 1
shadowColor: Appearance.m3colors.m3shadow
shadowBlur: 1
shadowScale: 1
}
Behavior on opacity {
NumberAnimation {
duration: Metrics.chronoDuration("small")
easing.type: Appearance.animation.easing
}
}
Behavior on scale {
enabled: !root.interactable
NumberAnimation {
duration: Metrics.chronoDuration("small")
easing.type: Appearance.animation.easing
}
}
Behavior on implicitWidth {
enabled: root.interactable
NumberAnimation {
duration: Metrics.chronoDuration("small")
easing.type: Appearance.animation.easing
}
}
Behavior on implicitHeight {
enabled: root.interactable
NumberAnimation {
duration: Metrics.chronoDuration("small")
easing.type: Appearance.animation.easing
}
}
ClippingRectangle {
id: popupBackground
anchors.fill: parent
color: Appearance.m3colors.m3surface
radius: Appearance.rounding.normal
ColumnLayout {
id: contentArea
anchors.fill: parent
anchors.margins: root.margin
}
}
HoverHandler {
id: containerHoverHandler
enabled: root.interactable
}
}
Component.onCompleted: {
root.instance = popoutWindow;
for (let i = 0; i < root.content.length; i++) {
const comp = root.content[i];
if (comp && comp.createObject) {
comp.createObject(contentArea);
} else {
console.warn("StyledPopout: invalid content:", comp);
}
}
let parentPopout = root.parent;
while (parentPopout && !parentPopout.childPopouts)
parentPopout = parentPopout.parent;
if (parentPopout) {
parentPopout.childPopouts.push(root);
if (parentPopout.item)
popoutWindow.parentPopoutWindow = parentPopout.item;
}
}
}
}