forked from Shinonome/dots-hyprland
242 lines
11 KiB
QML
242 lines
11 KiB
QML
pragma ComponentBehavior: Bound
|
|
import QtQuick
|
|
import Quickshell
|
|
|
|
import qs.modules.common as C
|
|
import qs.modules.common.widgets as W
|
|
import qs.modules.common.widgets.shapes as Shapes
|
|
import "../../../common/widgets/shapes/material-shapes.js" as MaterialShapes
|
|
|
|
// Notes
|
|
// vertical + atBottom = right side
|
|
// start radius = top or left, end radius = bottom or right
|
|
Shapes.ShapeCanvas {
|
|
id: bgShape
|
|
|
|
// Stuff fed from outside
|
|
required property bool vertical
|
|
required property bool atBottom
|
|
required property bool showPopup
|
|
required property real backgroundWidth
|
|
required property real backgroundHeight
|
|
property real popupContentWidth: 400
|
|
property real popupContentHeight: 500
|
|
property real popupPadding: 10
|
|
property real popupWidth: popupContentWidth + popupPadding * 2
|
|
property real popupHeight: popupContentHeight + popupPadding * 2
|
|
required property real startRadius
|
|
required property real endRadius
|
|
property real baseMargin: {
|
|
if (!vertical)
|
|
return (parent.height - containerShape.height) / 2;
|
|
else
|
|
return (parent.width - containerShape.width) / 2;
|
|
}
|
|
|
|
property alias containerShape: containerShape
|
|
property alias popupShape: popupShape
|
|
|
|
// Popup constraints
|
|
// mapToGlobal is not reactive so we gotta hook manually
|
|
property real xInGlobal
|
|
property real yInGlobal
|
|
function updateXInGlobal() {
|
|
xInGlobal = mapToGlobal(0, 0).x + xOffset;
|
|
}
|
|
function updateYInGlobal() {
|
|
yInGlobal = mapToGlobal(0, 0).y + yOffset;
|
|
}
|
|
function updatePosInGlobal() {
|
|
updateXInGlobal()
|
|
updateYInGlobal()
|
|
}
|
|
Component.onCompleted: updatePosInGlobal()
|
|
onXChanged: updatePosInGlobal()
|
|
onYChanged: updatePosInGlobal()
|
|
readonly property real minPopupXOffset: -xInGlobal + baseMargin
|
|
readonly property real minPopupYOffset: -yInGlobal + baseMargin
|
|
readonly property real maxPopupXOffset: {
|
|
const maxPopupX = QsWindow.window.screen.width - popupWidth - baseMargin;
|
|
const maxOffset = maxPopupX - xInGlobal;
|
|
return maxOffset;
|
|
}
|
|
readonly property real maxPopupYOffset: {
|
|
const maxPopupY = QsWindow.window.screen.height - popupHeight - baseMargin;
|
|
const maxOffset = maxPopupY - yInGlobal;
|
|
return maxOffset;
|
|
}
|
|
readonly property real popupXOffset: {
|
|
if (!vertical) return Math.min(Math.max(-(popupWidth - containerShape.width) / 2, minPopupXOffset), maxPopupXOffset);
|
|
else return atBottom ? -(popupShape.width + spacing) : (containerShape.width + spacing);
|
|
}
|
|
readonly property real popupYOffset: {
|
|
if (!vertical) return atBottom ? -(popupShape.height + spacing) : (containerShape.height + spacing);
|
|
else return Math.min(Math.max(-(popupHeight - containerShape.height) / 2, minPopupYOffset), maxPopupYOffset)
|
|
}
|
|
|
|
// Positioning
|
|
readonly property real popupContentOffsetBase: -baseMargin + popupPadding
|
|
readonly property real paddedContainerHeight: containerShape.height + baseMargin * 2
|
|
readonly property real paddedContainerWidth: containerShape.width + baseMargin * 2
|
|
readonly property real popupContentOffsetY: {
|
|
if (!vertical) return paddedContainerHeight + spacing + popupContentOffsetBase + (atBottom ? -(popupHeight + backgroundHeight + spacing * 2) : 0)
|
|
else return popupYOffset + popupContentOffsetBase;
|
|
}
|
|
readonly property real popupContentOffsetX: {
|
|
if (!vertical) return popupXOffset + popupContentOffsetBase;
|
|
else return paddedContainerWidth + spacing + popupContentOffsetBase + (atBottom ? -(popupWidth + backgroundWidth + spacing * 2) : 0);
|
|
}
|
|
|
|
anchors {
|
|
left: parent.left
|
|
leftMargin: {
|
|
if (!vertical) return -xOffset;
|
|
if (!bgShape.atBottom || !bgShape.showPopup) return baseMargin;
|
|
return baseMargin - popupShape.width - bgShape.spacing;
|
|
}
|
|
top: parent.top
|
|
topMargin: {
|
|
if (vertical) return -yOffset;
|
|
if (!bgShape.atBottom || !bgShape.showPopup) return baseMargin;
|
|
return baseMargin - popupShape.height - bgShape.spacing;
|
|
}
|
|
}
|
|
width: {
|
|
if (!vertical) return bgShape.showPopup ? Math.max(backgroundWidth, popupWidth) : backgroundWidth;
|
|
else return bgShape.showPopup ? (containerShape.width + popupShape.width + bgShape.spacing) : containerShape.width;
|
|
}
|
|
height: {
|
|
if (!vertical) return bgShape.showPopup ? (containerShape.height + popupShape.height + bgShape.spacing) : containerShape.height;
|
|
else return bgShape.showPopup ? Math.max(backgroundHeight, popupHeight) : backgroundHeight;
|
|
}
|
|
property bool popupHiding: false
|
|
onShowPopupChanged: popupHiding = true
|
|
onProgressChanged: if (progress == 1) popupHiding = false
|
|
color: showPopup || popupHiding ? C.Appearance.colors.colLayer3Base : C.Appearance.colors.colLayer1
|
|
xOffset: {
|
|
if (!vertical) return showPopup ? Math.max(-popupXOffset, 0) : 0;
|
|
else return bgShape.atBottom ? (width - containerShape.width) : 0;
|
|
}
|
|
yOffset: {
|
|
if (!vertical) return bgShape.atBottom ? (height - containerShape.height) : 0;
|
|
else return showPopup ? Math.max(-popupYOffset, 0) : 0;
|
|
}
|
|
animation: Anim {}
|
|
|
|
Behavior on width {
|
|
Anim {}
|
|
}
|
|
Behavior on height {
|
|
Anim {}
|
|
}
|
|
Behavior on anchors.topMargin {
|
|
animation: !bgShape.vertical ? animComp.createObject(this) : undefined
|
|
}
|
|
Behavior on anchors.leftMargin {
|
|
animation: bgShape.vertical ? animComp.createObject(this) : undefined
|
|
}
|
|
Behavior on xOffset {
|
|
animation: !bgShape.vertical ? animComp.createObject(this) : undefined
|
|
}
|
|
Behavior on yOffset {
|
|
animation: bgShape.vertical ? animComp.createObject(this) : undefined
|
|
}
|
|
|
|
polygonIsNormalized: false
|
|
property real spacing: baseMargin * 2
|
|
W.AxisRectangularContainerShape {
|
|
id: containerShape
|
|
width: bgShape.backgroundWidth
|
|
height: bgShape.backgroundHeight
|
|
startRadius: bgShape.startRadius
|
|
endRadius: bgShape.endRadius
|
|
vertical: bgShape.vertical
|
|
}
|
|
W.RectangularContainerShape {
|
|
id: popupShape
|
|
width: bgShape.popupWidth
|
|
height: bgShape.popupHeight
|
|
radius: C.Appearance.rounding.large
|
|
xOffset: bgShape.popupXOffset
|
|
yOffset: bgShape.popupYOffset
|
|
}
|
|
|
|
roundedPolygon: {
|
|
var points = [];
|
|
if (!bgShape.showPopup) return containerShape.getFullShape();
|
|
|
|
const joinRadiusOverride = containerShape.radiusLimit;
|
|
if (!bgShape.vertical) {
|
|
const popupBigger = bgShape.popupWidth > bgShape.backgroundWidth;
|
|
// Inline comment spam to mitigate qmlls' sabotaging of the (code) layout
|
|
points = [
|
|
...(bgShape.atBottom ? containerShape.getFirstBottomPoints() : [ //
|
|
...popupShape.getFirstBottomPoints(), popupShape.getBottomLeftPoint(), //
|
|
...popupShape.leftPoints, //
|
|
popupShape.getTopLeftPoint(0, -bgShape.spacing * (!popupBigger), joinRadiusOverride), //
|
|
]), //
|
|
containerShape.getBottomLeftPoint(0, bgShape.spacing * (!bgShape.atBottom && popupBigger), joinRadiusOverride), //
|
|
containerShape.getTopLeftPoint(0, -bgShape.spacing * (bgShape.atBottom && popupBigger), joinRadiusOverride), //
|
|
...(!bgShape.atBottom ? containerShape.topPoints : [ //
|
|
popupShape.getBottomLeftPoint(0, bgShape.spacing * (!popupBigger), joinRadiusOverride), //
|
|
...popupShape.leftPoints, //
|
|
popupShape.getTopLeftPoint(), //
|
|
...popupShape.topPoints, //
|
|
popupShape.getTopRightPoint(), //
|
|
...popupShape.rightPoints, //
|
|
popupShape.getBottomRightPoint(0, bgShape.spacing * (!popupBigger), joinRadiusOverride), //
|
|
]), //
|
|
containerShape.getTopRightPoint(0, -bgShape.spacing * (bgShape.atBottom && popupBigger), joinRadiusOverride), //
|
|
containerShape.getBottomRightPoint(0, bgShape.spacing * (!bgShape.atBottom && popupBigger), joinRadiusOverride), //
|
|
...(bgShape.atBottom ? containerShape.getLastBottomPoints() : [ //
|
|
popupShape.getTopRightPoint(0, -bgShape.spacing * (!popupBigger), joinRadiusOverride), //
|
|
...popupShape.rightPoints, //
|
|
popupShape.getBottomRightPoint(), //
|
|
...popupShape.getLastBottomPoints(), //
|
|
]),
|
|
];
|
|
} else {
|
|
const popupBigger = bgShape.popupHeight > bgShape.backgroundHeight;
|
|
points = [ //
|
|
...containerShape.getFirstBottomPoints(), //
|
|
containerShape.getBottomLeftPoint(-bgShape.spacing * (popupBigger && bgShape.atBottom), 0, joinRadiusOverride), //
|
|
...(!bgShape.atBottom ? containerShape.leftPoints : [ //
|
|
popupShape.getBottomRightPoint(bgShape.spacing * (!popupBigger)), //
|
|
...popupShape.bottomPoints, //
|
|
popupShape.getBottomLeftPoint(), //
|
|
...popupShape.leftPoints, //
|
|
popupShape.getTopLeftPoint(), //
|
|
...popupShape.topPoints, //
|
|
popupShape.getTopRightPoint(bgShape.spacing * (!popupBigger)), //
|
|
]), //
|
|
containerShape.getTopLeftPoint(-bgShape.spacing * (popupBigger && bgShape.atBottom), 0, joinRadiusOverride), //
|
|
...containerShape.topPoints, //
|
|
containerShape.getTopRightPoint(bgShape.spacing * (popupBigger && !bgShape.atBottom), 0, joinRadiusOverride), //
|
|
...(bgShape.atBottom ? containerShape.rightPoints : [ //
|
|
popupShape.getTopLeftPoint(-bgShape.spacing * (!popupBigger), 0, joinRadiusOverride), //
|
|
...popupShape.topPoints, //
|
|
popupShape.getTopRightPoint(), //
|
|
...popupShape.rightPoints, //
|
|
popupShape.getBottomRightPoint(), //
|
|
...popupShape.bottomPoints, //
|
|
popupShape.getBottomLeftPoint(-bgShape.spacing * (!popupBigger), 0, joinRadiusOverride), //
|
|
]), //
|
|
containerShape.getBottomRightPoint(bgShape.spacing * (popupBigger && !bgShape.atBottom), 0, joinRadiusOverride), //
|
|
...containerShape.getLastBottomPoints(), //
|
|
];
|
|
}
|
|
return MaterialShapes.customPolygon(points);
|
|
}
|
|
|
|
component Anim: NumberAnimation {
|
|
duration: C.Appearance.animation.elementMove.duration
|
|
easing.type: C.Appearance.animation.elementMove.type
|
|
easing.bezierCurve: C.Appearance.animation.elementMove.bezierCurve
|
|
}
|
|
|
|
Component {
|
|
id: animComp
|
|
Anim {}
|
|
}
|
|
}
|