mirror of
https://github.com/belsabbagh/dotfiles.git
synced 2026-04-11 09:36:46 +00:00
quickshell and hyprland additions
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user