mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-16 16:24:00 -04:00
Revives PR 2710 using in-process PopoutService toggles per review, extended with a right click action and a follow-mouse option for anchored popouts.
302 lines
14 KiB
QML
302 lines
14 KiB
QML
import QtQuick
|
|
import QtQuick.Shapes
|
|
import qs.Common
|
|
import qs.Services
|
|
import qs.Modules.SurfaceWidgets
|
|
import "BarOutline.js" as BarOutline
|
|
|
|
Item {
|
|
id: root
|
|
|
|
required property var barWindow
|
|
required property var axis
|
|
required property var barConfig
|
|
|
|
readonly property bool frameShapesBar: FrameTransitionState.effectiveFrameEnabled && barWindow.usesFrameBarChrome
|
|
readonly property bool gothEnabled: (barConfig?.gothCornersEnabled ?? false) && !(barWindow.flattenForMaximizedWindow && barWindow.hasMaximizedToplevel)
|
|
readonly property int barPos: barConfig?.position ?? 0
|
|
readonly property bool isTop: barPos === SettingsData.Position.Top
|
|
readonly property bool isBottom: barPos === SettingsData.Position.Bottom
|
|
readonly property bool isLeft: barPos === SettingsData.Position.Left
|
|
readonly property bool isRight: barPos === SettingsData.Position.Right
|
|
readonly property bool farEdge: isBottom || isRight
|
|
readonly property bool edgeAttached: (barConfig?.attachToScreenEdge ?? false) && !frameShapesBar
|
|
readonly property bool alongWings: !(barWindow.spansEdge ?? true)
|
|
readonly property real crossSize: axis.isVertical ? (parent?.width ?? 0) : (parent?.height ?? 0)
|
|
readonly property real wingCrossCap: Math.max(0, crossSize - Math.min(rt, crossSize / 2))
|
|
readonly property real wing: gothEnabled ? barWindow._wingR : 0
|
|
readonly property real wingAlong: Math.max(0, motion.currentOffsetAlong)
|
|
readonly property real wingCross: alongWings ? Math.min(wingAlong, wingCrossCap) : wingAlong
|
|
readonly property real windowLength: axis.isVertical ? barWindow.height : barWindow.width
|
|
readonly property string startSide: axis.isVertical ? "top" : "left"
|
|
readonly property string endSide: axis.isVertical ? "bottom" : "right"
|
|
readonly property var startNeighbour: alongWings ? null : ShellLayout.adjacentCover(barWindow.screen, startSide, barConfig, windowLength)
|
|
readonly property var endNeighbour: alongWings ? null : ShellLayout.adjacentCover(barWindow.screen, endSide, barConfig, windowLength)
|
|
readonly property real alongOrigin: axis.isVertical ? 0 : barWindow.renderedSpacing
|
|
readonly property real startCover: coveredBy(startNeighbour)
|
|
readonly property real endCover: coveredBy(endNeighbour)
|
|
readonly property real alongWing: alongWings ? wing : 0
|
|
readonly property real crossWing: alongWings ? 0 : wing
|
|
readonly property real rt: {
|
|
if (frameShapesBar)
|
|
return SettingsData.frameRounding;
|
|
if (barConfig?.squareCorners ?? false)
|
|
return 0;
|
|
if (barWindow.flattenForMaximizedWindow && barWindow.hasMaximizedToplevel)
|
|
return 0;
|
|
return Theme.windowRadius;
|
|
}
|
|
readonly property bool motionRunning: motion.running
|
|
readonly property alias leadingWing: leadingWing
|
|
readonly property alias trailingWing: trailingWing
|
|
|
|
readonly property bool hasPerBarOverride: (barConfig?.shadowIntensity ?? 0) > 0
|
|
readonly property var elevLevel: BarMetrics.elevationLevel
|
|
readonly property bool shadowEnabled: Theme.elevationEnabled && (SettingsData.barElevationEnabled ?? true)
|
|
readonly property string autoBarShadowDirection: isTop ? "top" : (isBottom ? "bottom" : (isLeft ? "left" : "right"))
|
|
readonly property string globalShadowDirection: Theme.elevationLightDirection === "autoBar" ? autoBarShadowDirection : Theme.elevationLightDirection
|
|
readonly property string perBarShadowDirectionMode: barConfig?.shadowDirectionMode ?? "inherit"
|
|
readonly property string perBarManualShadowDirection: {
|
|
switch (barConfig?.shadowDirection) {
|
|
case "top":
|
|
case "topLeft":
|
|
case "topRight":
|
|
case "bottom":
|
|
return barConfig.shadowDirection;
|
|
default:
|
|
return "top";
|
|
}
|
|
}
|
|
readonly property string effectiveShadowDirection: {
|
|
if (!hasPerBarOverride)
|
|
return globalShadowDirection;
|
|
switch (perBarShadowDirectionMode) {
|
|
case "autoBar":
|
|
return autoBarShadowDirection;
|
|
case "manual":
|
|
return perBarManualShadowDirection === "autoBar" ? autoBarShadowDirection : perBarManualShadowDirection;
|
|
default:
|
|
return globalShadowDirection;
|
|
}
|
|
}
|
|
readonly property real overrideBlurRatio: 0.2
|
|
readonly property real overrideOffsetRatio: 0.5
|
|
readonly property real overrideDefaultOpacityPercent: 60
|
|
readonly property real overrideBlurPx: (barConfig?.shadowIntensity ?? 0) * overrideBlurRatio
|
|
readonly property real overrideOpacity: (barConfig?.shadowOpacity ?? overrideDefaultOpacityPercent) / 100
|
|
readonly property color overrideBaseColor: {
|
|
switch (barConfig?.shadowColorMode ?? "default") {
|
|
case "surface":
|
|
return Theme.surface;
|
|
case "primary":
|
|
return Theme.primary;
|
|
case "secondary":
|
|
return Theme.secondary;
|
|
case "custom":
|
|
return barConfig?.shadowCustomColor ?? Theme.scrimColor;
|
|
default:
|
|
return Theme.scrimColor;
|
|
}
|
|
}
|
|
readonly property real shadowBlurPx: hasPerBarOverride ? overrideBlurPx : (elevLevel.blurPx ?? 0)
|
|
readonly property color shadowColor: hasPerBarOverride ? Theme.withAlpha(overrideBaseColor, overrideOpacity) : Theme.elevationShadowColor(elevLevel)
|
|
readonly property real shadowOffsetMagnitude: hasPerBarOverride ? (overrideBlurPx * overrideOffsetRatio) : Theme.elevationOffsetMagnitude(elevLevel, Theme.spacingXS, effectiveShadowDirection)
|
|
readonly property real shadowOffsetX: Theme.elevationOffsetXFor(hasPerBarOverride ? null : elevLevel, effectiveShadowDirection, shadowOffsetMagnitude)
|
|
readonly property real shadowOffsetY: Theme.elevationOffsetYFor(hasPerBarOverride ? null : elevLevel, effectiveShadowDirection, shadowOffsetMagnitude)
|
|
|
|
readonly property real attachedRadius: edgeAttached || frameShapesBar ? 0 : rt
|
|
readonly property real wingRootRadius: gothEnabled ? 0 : rt
|
|
readonly property real attachedCornerRadius: alongWings && gothEnabled ? 0 : attachedRadius
|
|
readonly property real freeCornerRadius: alongWings ? rt : wingRootRadius
|
|
readonly property var shapeTarget: ({
|
|
width: Math.max(0, axis.isVertical ? width - crossWing : width - alongWing * 2),
|
|
height: Math.max(0, axis.isVertical ? height - alongWing * 2 : height - crossWing),
|
|
offsetAlong: wing,
|
|
offsetCross: farEdge ? crossWing : 0,
|
|
topLeftRadius: isTop || isLeft ? attachedCornerRadius : freeCornerRadius,
|
|
topRightRadius: isTop || isRight ? attachedCornerRadius : freeCornerRadius,
|
|
bottomLeftRadius: isBottom || isLeft ? attachedCornerRadius : freeCornerRadius,
|
|
bottomRightRadius: isBottom || isRight ? attachedCornerRadius : freeCornerRadius
|
|
})
|
|
|
|
visible: !frameShapesBar
|
|
anchors.fill: parent
|
|
anchors.leftMargin: -(axis.isVertical ? (isRight ? crossWing : 0) : alongWing)
|
|
anchors.rightMargin: -(axis.isVertical ? (isLeft ? crossWing : 0) : alongWing)
|
|
anchors.topMargin: -(axis.isVertical ? alongWing : (isBottom ? crossWing : 0))
|
|
anchors.bottomMargin: -(axis.isVertical ? alongWing : (isTop ? crossWing : 0))
|
|
|
|
function coveredBy(neighbour) {
|
|
if (!neighbour || neighbour.tucked)
|
|
return 0;
|
|
return Math.max(0, neighbour.reach - alongOrigin) + neighbour.wing;
|
|
}
|
|
|
|
function alongWingCorner(leading) {
|
|
switch (barPos) {
|
|
case SettingsData.Position.Bottom:
|
|
return leading ? "topLeft" : "topRight";
|
|
case SettingsData.Position.Left:
|
|
return leading ? "topRight" : "bottomRight";
|
|
case SettingsData.Position.Right:
|
|
return leading ? "topLeft" : "bottomLeft";
|
|
default:
|
|
return leading ? "bottomLeft" : "bottomRight";
|
|
}
|
|
}
|
|
|
|
function applyTarget() {
|
|
if (width <= 0 || height <= 0 || !visible) {
|
|
motion.snapTo(shapeTarget);
|
|
return;
|
|
}
|
|
motion.setTarget(shapeTarget);
|
|
}
|
|
|
|
onShapeTargetChanged: applyTarget()
|
|
Component.onCompleted: motion.snapTo(shapeTarget)
|
|
|
|
VectorSpringMotion {
|
|
id: motion
|
|
|
|
readonly property var preset: Theme.springPreset("default", Theme.shortDuration)
|
|
reducedMotion: Theme.springMotionDisabled || SettingsData.reduceMotion
|
|
stiffness: preset.stiffness
|
|
damping: preset.damping
|
|
mass: preset.mass
|
|
onRunningChanged: {
|
|
if (!running)
|
|
snapTo(root.shapeTarget);
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
id: emptySurfaceClickArea
|
|
anchors.fill: parent
|
|
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
|
z: -999
|
|
onClicked: mouse => {
|
|
if (root.barWindow.handleEmptyBarClick(mouse.button, emptySurfaceClickArea, mouse.x, mouse.y))
|
|
return;
|
|
const activePopout = PopoutManager.getActivePopout(root.barWindow.screen);
|
|
if (activePopout) {
|
|
if (activePopout.dashVisible !== undefined) {
|
|
activePopout.dashVisible = false;
|
|
} else if (activePopout.notificationHistoryVisible !== undefined) {
|
|
activePopout.notificationHistoryVisible = false;
|
|
} else {
|
|
activePopout.close();
|
|
}
|
|
}
|
|
TrayMenuManager.closeAllMenus();
|
|
}
|
|
}
|
|
|
|
ElevationShadow {
|
|
visible: root.shadowEnabled && root.width > 0 && root.height > 0
|
|
x: body.x
|
|
y: body.y
|
|
width: body.width
|
|
height: body.height
|
|
shadowEnabled: root.shadowEnabled
|
|
level: root.hasPerBarOverride ? null : root.elevLevel
|
|
direction: root.effectiveShadowDirection
|
|
fallbackOffset: Theme.spacingXS
|
|
topLeftRadius: body.topLeftRadius
|
|
topRightRadius: body.topRightRadius
|
|
bottomLeftRadius: body.bottomLeftRadius
|
|
bottomRightRadius: body.bottomRightRadius
|
|
targetColor: "transparent"
|
|
shadowBlurPx: root.shadowBlurPx
|
|
shadowOffsetX: root.shadowOffsetX
|
|
shadowOffsetY: root.shadowOffsetY
|
|
shadowColor: root.shadowColor
|
|
}
|
|
|
|
MorphSurface {
|
|
id: body
|
|
motion: motion
|
|
x: Math.max(0, root.axis.isVertical ? motion.currentOffsetCross : (root.alongWings ? motion.currentOffsetAlong : 0))
|
|
y: Math.max(0, root.axis.isVertical ? (root.alongWings ? motion.currentOffsetAlong : 0) : motion.currentOffsetCross)
|
|
color: root.barWindow._bgColor
|
|
}
|
|
|
|
GothCorner {
|
|
id: leadingWing
|
|
radius: root.wingAlong
|
|
radiusX: root.axis.isVertical ? root.wingCross : radius
|
|
radiusY: root.axis.isVertical ? radius : root.wingCross
|
|
color: root.barWindow._bgColor
|
|
visible: root.gothEnabled && radius > 0 && root.startCover <= 0
|
|
x: root.alongWings ? (root.axis.isVertical && root.isRight ? root.width - width : 0) : (root.isLeft ? body.width : 0)
|
|
y: root.alongWings ? (!root.axis.isVertical && root.isBottom ? root.height - height : 0) : (root.isTop ? body.height : 0)
|
|
corner: root.alongWings ? root.alongWingCorner(true) : root.isTop ? "bottomRight" : root.isBottom ? "topRight" : root.isLeft ? "bottomRight" : "bottomLeft"
|
|
}
|
|
|
|
GothCorner {
|
|
id: trailingWing
|
|
radius: root.wingAlong
|
|
radiusX: root.axis.isVertical ? root.wingCross : radius
|
|
radiusY: root.axis.isVertical ? radius : root.wingCross
|
|
color: root.barWindow._bgColor
|
|
visible: root.gothEnabled && radius > 0 && root.endCover <= 0
|
|
x: root.axis.isVertical ? (root.alongWings ? (root.isRight ? root.width - width : 0) : (root.isLeft ? body.width : 0)) : root.width - width
|
|
y: root.axis.isVertical ? root.height - height : (root.alongWings ? (root.isBottom ? root.height - height : 0) : (root.isTop ? body.height : 0))
|
|
corner: root.alongWings ? root.alongWingCorner(false) : root.isTop ? "bottomLeft" : root.isBottom ? "topLeft" : root.isLeft ? "topRight" : "topLeft"
|
|
}
|
|
|
|
Loader {
|
|
id: border
|
|
readonly property real thickness: Theme.snap(Math.max(Theme.outlineWidth, barConfig?.borderThickness ?? Theme.outlineWidth), CompositorService.getScreenScale(root.barWindow.screen))
|
|
readonly property string colorKey: barConfig?.borderColor || "surfaceText"
|
|
readonly property color baseColor: colorKey === "surfaceText" ? Theme.surfaceText : colorKey === "primary" ? Theme.primary : Theme.secondary
|
|
readonly property bool showFullBorder: (barConfig?.spacing ?? 4) > 0
|
|
readonly property string path: BarOutline.borderPath({
|
|
position: root.axis.edge,
|
|
width: root.width,
|
|
height: root.height,
|
|
body: {
|
|
x: body.x,
|
|
y: body.y,
|
|
width: body.width,
|
|
height: body.height
|
|
},
|
|
corners: {
|
|
topLeft: body.topLeftRadius,
|
|
topRight: body.topRightRadius,
|
|
bottomLeft: body.bottomLeftRadius,
|
|
bottomRight: body.bottomRightRadius
|
|
},
|
|
wing: root.gothEnabled ? leadingWing.radius : 0,
|
|
wingCross: root.gothEnabled ? root.wingCross : 0,
|
|
alongWings: root.alongWings,
|
|
inset: thickness / 2,
|
|
open: !showFullBorder,
|
|
coverStart: root.startCover,
|
|
coverStartWing: root.startNeighbour?.wing ?? 0,
|
|
coverEnd: root.endCover,
|
|
coverEndWing: root.endNeighbour?.wing ?? 0,
|
|
seamStart: root.startNeighbour?.tucked ?? false,
|
|
seamEnd: root.endNeighbour?.tucked ?? false
|
|
})
|
|
z: 100
|
|
anchors.fill: parent
|
|
active: barConfig?.borderEnabled ?? false
|
|
sourceComponent: Shape {
|
|
preferredRendererType: Shape.CurveRenderer
|
|
|
|
ShapePath {
|
|
fillColor: "transparent"
|
|
strokeColor: Theme.withAlpha(border.baseColor, barConfig?.borderOpacity ?? 1.0)
|
|
strokeWidth: border.thickness
|
|
joinStyle: ShapePath.RoundJoin
|
|
capStyle: ShapePath.FlatCap
|
|
|
|
PathSvg {
|
|
path: border.path
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|