Files
Aldair Meneses c8d9a8617e bar: add configurable middle and right click actions on empty space (#3582)
Revives PR 2710 using in-process PopoutService toggles per review, extended
with a right click action and a follow-mouse option for anchored popouts.
2026-10-06 02:11:21 -04:00

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
}
}
}
}
}