mirror of
https://github.com/belsabbagh/dotfiles.git
synced 2026-04-11 17:47:09 +00:00
327 lines
10 KiB
QML
327 lines
10 KiB
QML
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;
|
|
}
|
|
}
|
|
}
|
|
} |