Files
noctalia-shell/Modules/MainScreen/Backgrounds/BarBackground.qml
T

282 lines
14 KiB
QML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import QtQuick
import QtQuick.Shapes
import qs.Commons
import qs.Modules.MainScreen.Backgrounds
import qs.Services.UI
/**
* BarBackground - ShapePath component for rendering the bar background
*
* Unified shadow system. This component is a ShapePath that will be
* a child of the unified AllBackgrounds Shape container.
*
* Uses 4-state per-corner system for flexible corner rendering:
* - State -1: No radius (flat/square corner)
* - State 0: Normal (inner curve)
* - State 1: Horizontal inversion (outer curve on X-axis)
* - State 2: Vertical inversion (outer curve on Y-axis)
*/
ShapePath {
id: root
// Required reference to the bar component
required property var bar
// Required reference to AllBackgrounds shapeContainer
required property var shapeContainer
// Required reference to windowRoot for screen access
required property var windowRoot
required property color backgroundColor
// Check if bar should be visible on this screen
readonly property bool shouldShow: {
// Check global bar visibility (includes overview state)
if (!BarService.effectivelyVisible)
return false;
// Check screen-specific configuration
var monitors = Settings.data.bar.monitors || [];
var screenName = windowRoot?.screen?.name || "";
// If no monitors specified, show on all screens
// If monitors specified, only show if this screen is in the list
return monitors.length === 0 || monitors.includes(screenName);
}
// Corner radius (from Style)
readonly property real radius: Style.radiusL
// Framed bar properties
readonly property bool isFramed: Settings.data.bar.barType === "framed"
readonly property real frameThickness: Settings.data.bar.frameThickness ?? 12
readonly property real frameRadius: Settings.data.bar.frameRadius ?? 20
// Bar position - since bar's parent fills the screen and Shape also fills the screen,
// we can use bar.x and bar.y directly (they're already in screen coordinates)
readonly property point barMappedPos: bar ? Qt.point(bar.x, bar.y) : Qt.point(0, 0)
// Effective dimensions - 0 when bar shouldn't show (similar to panel behavior)
readonly property real barWidth: (bar && shouldShow) ? bar.width : 0
readonly property real barHeight: (bar && shouldShow) ? bar.height : 0
// Screen dimensions for frame
readonly property real screenWidth: windowRoot?.screen?.width || 0
readonly property real screenHeight: windowRoot?.screen?.height || 0
// Inner hole dimensions for framed mode - always relative to screen
readonly property string barPosition: Settings.getBarPositionForScreen(windowRoot?.screen?.name)
readonly property bool barIsVertical: barPosition === "left" || barPosition === "right"
readonly property real holeX: (barPosition === "left") ? barWidth : frameThickness
readonly property real holeY: (barPosition === "top") ? barHeight : frameThickness
readonly property real holeWidth: screenWidth - (barPosition === "left" || barPosition === "right" ? (barWidth + frameThickness) : (frameThickness * 2))
readonly property real holeHeight: screenHeight - (barPosition === "top" || barPosition === "bottom" ? (barHeight + frameThickness) : (frameThickness * 2))
// Flatten corners if bar is too small (handle null bar)
readonly property bool shouldFlatten: bar ? ShapeCornerHelper.shouldFlatten(barWidth, barHeight, radius) : false
readonly property real effectiveRadius: shouldFlatten ? (bar ? ShapeCornerHelper.getFlattenedRadius(Math.min(barWidth, barHeight), radius) : 0) : radius
// Minimum safe arc radius — prevents zero-displacement zero-radius PathArcs
// that crash qTriangulate in CurveRenderer. 0.01px is sub-pixel and invisible.
readonly property real _minR: 0.01
// Helper function for getting corner radius based on state
function getCornerRadius(cornerState) {
// State -1 = flat corner — use minimum safe radius instead of 0
// to prevent degenerate PathArc (zero displacement + zero radius)
if (cornerState === -1)
return _minR;
// All other states use effectiveRadius (clamped to safe minimum)
return Math.max(_minR, effectiveRadius);
}
// Per-corner multipliers and radii based on bar's corner states (handle null bar)
readonly property real tlMultX: bar ? ShapeCornerHelper.getMultX(bar.topLeftCornerState) : 1
readonly property real tlMultY: bar ? ShapeCornerHelper.getMultY(bar.topLeftCornerState) : 1
readonly property real tlRadius: bar ? getCornerRadius(bar.topLeftCornerState) : 0
readonly property real trMultX: bar ? ShapeCornerHelper.getMultX(bar.topRightCornerState) : 1
readonly property real trMultY: bar ? ShapeCornerHelper.getMultY(bar.topRightCornerState) : 1
readonly property real trRadius: bar ? getCornerRadius(bar.topRightCornerState) : 0
readonly property real brMultX: bar ? ShapeCornerHelper.getMultX(bar.bottomRightCornerState) : 1
readonly property real brMultY: bar ? ShapeCornerHelper.getMultY(bar.bottomRightCornerState) : 1
readonly property real brRadius: bar ? getCornerRadius(bar.bottomRightCornerState) : 0
readonly property real blMultX: bar ? ShapeCornerHelper.getMultX(bar.bottomLeftCornerState) : 1
readonly property real blMultY: bar ? ShapeCornerHelper.getMultY(bar.bottomLeftCornerState) : 1
readonly property real blRadius: bar ? getCornerRadius(bar.bottomLeftCornerState) : 0
// True when the bar path has valid, non-degenerate geometry to render.
// Mirrors PanelBackground.isRenderable — prevents CurveRenderer crash on zero-area paths.
readonly property bool isRenderable: bar !== null && shouldShow && (isFramed ? (screenWidth > 0 && screenHeight > 0) : (barWidth > 0 && barHeight > 0))
// Edge overshoot: extend bar background beyond screen edges where both adjacent
// corners are flat (state -1) to prevent CurveRenderer antialiasing artifacts.
// Uses corner state checks instead of radius === 0 since flat corners now have _minR.
readonly property real screenEdgeOvershoot: 2
readonly property real topEdgeOvs: (!isFramed && shouldShow && bar && bar.topLeftCornerState === -1 && bar.topRightCornerState === -1 && barMappedPos.y <= 0) ? -screenEdgeOvershoot : 0
readonly property real bottomEdgeOvs: (!isFramed && shouldShow && bar && bar.bottomLeftCornerState === -1 && bar.bottomRightCornerState === -1 && (barMappedPos.y + barHeight) >= screenHeight) ? screenEdgeOvershoot : 0
readonly property real leftEdgeOvs: (!isFramed && shouldShow && bar && bar.topLeftCornerState === -1 && bar.bottomLeftCornerState === -1 && barMappedPos.x <= 0) ? -screenEdgeOvershoot : 0
readonly property real rightEdgeOvs: (!isFramed && shouldShow && bar && bar.topRightCornerState === -1 && bar.bottomRightCornerState === -1 && (barMappedPos.x + barWidth) >= screenWidth) ? screenEdgeOvershoot : 0
// Auto-hide opacity factor for background fade
property real opacityFactor: (bar && bar.isHidden) ? 0 : 1
Behavior on opacityFactor {
enabled: bar && bar.autoHide
NumberAnimation {
duration: Style.animationFast
easing.type: Easing.OutQuad
}
}
// ShapePath configuration
strokeWidth: -1 // No stroke, fill only
fillColor: isRenderable ? Qt.rgba(backgroundColor.r, backgroundColor.g, backgroundColor.b, backgroundColor.a * opacityFactor) : "transparent"
fillRule: isFramed ? ShapePath.OddEvenFill : ShapePath.WindingFill
// Starting position — falls back to off-screen when not renderable so that
// all subsequent path elements form a valid non-degenerate off-screen square.
// Each edge is split between PathLine and PathArc so no arc has zero displacement,
// preventing CurveRenderer triangulation crashes on degenerate arcs.
startX: isRenderable ? (isFramed ? 0 : (barMappedPos.x + leftEdgeOvs + tlRadius * tlMultX)) : -0.75
startY: isRenderable ? (isFramed ? 0 : (barMappedPos.y + topEdgeOvs)) : -1
// ========== PATH DEFINITION ==========
// 1. Main Bar / Outer Screen Rectangle
// When !isRenderable all elements use fallback coordinates forming a valid 1×1
// off-screen square with non-degenerate arcs so CurveRenderer never receives
// a zero-area, bare-moveto, or zero-displacement arc path.
PathLine {
x: root.isRenderable ? (root.isFramed ? root.screenWidth : (root.barMappedPos.x + root.barWidth + root.rightEdgeOvs - root.trRadius * root.trMultX)) : 0
y: root.isRenderable ? (root.isFramed ? 0 : (root.barMappedPos.y + root.topEdgeOvs)) : -1
}
// Bar top-right corner (only if not framed)
PathArc {
x: root.isRenderable ? (root.isFramed ? root.screenWidth : (root.barMappedPos.x + root.barWidth + root.rightEdgeOvs)) : 0
y: root.isRenderable ? (root.isFramed ? 0 : (root.barMappedPos.y + root.topEdgeOvs + root.trRadius * root.trMultY)) : -0.75
radiusX: root.isRenderable ? (root.isFramed ? 0 : root.trRadius) : 0
radiusY: root.isRenderable ? (root.isFramed ? 0 : root.trRadius) : 0
direction: ShapeCornerHelper.getArcDirection(root.trMultX, root.trMultY)
}
PathLine {
x: root.isRenderable ? (root.isFramed ? root.screenWidth : (root.barMappedPos.x + root.barWidth + root.rightEdgeOvs)) : 0
y: root.isRenderable ? (root.isFramed ? root.screenHeight : (root.barMappedPos.y + root.barHeight + root.bottomEdgeOvs - root.brRadius * root.brMultY)) : 0
}
// Bar bottom-right corner (only if not framed)
PathArc {
x: root.isRenderable ? (root.isFramed ? root.screenWidth : (root.barMappedPos.x + root.barWidth + root.rightEdgeOvs - root.brRadius * root.brMultX)) : -0.25
y: root.isRenderable ? (root.isFramed ? root.screenHeight : (root.barMappedPos.y + root.barHeight + root.bottomEdgeOvs)) : 0
radiusX: root.isRenderable ? (root.isFramed ? 0 : root.brRadius) : 0
radiusY: root.isRenderable ? (root.isFramed ? 0 : root.brRadius) : 0
direction: ShapeCornerHelper.getArcDirection(root.brMultX, root.brMultY)
}
PathLine {
x: root.isRenderable ? (root.isFramed ? 0 : (root.barMappedPos.x + root.leftEdgeOvs + root.blRadius * root.blMultX)) : -1
y: root.isRenderable ? (root.isFramed ? root.screenHeight : (root.barMappedPos.y + root.barHeight + root.bottomEdgeOvs)) : 0
}
// Bar bottom-left corner (only if not framed)
PathArc {
x: root.isRenderable ? (root.isFramed ? 0 : (root.barMappedPos.x + root.leftEdgeOvs)) : -1
y: root.isRenderable ? (root.isFramed ? root.screenHeight : (root.barMappedPos.y + root.barHeight + root.bottomEdgeOvs - root.blRadius * root.blMultY)) : -0.25
radiusX: root.isRenderable ? (root.isFramed ? 0 : root.blRadius) : 0
radiusY: root.isRenderable ? (root.isFramed ? 0 : root.blRadius) : 0
direction: ShapeCornerHelper.getArcDirection(root.blMultX, root.blMultY)
}
PathLine {
x: root.isRenderable ? (root.isFramed ? 0 : (root.barMappedPos.x + root.leftEdgeOvs)) : -1
y: root.isRenderable ? (root.isFramed ? 0 : (root.barMappedPos.y + root.topEdgeOvs + root.tlRadius * root.tlMultY)) : -1
}
// Bar top-left corner (only if not framed, back to start)
PathArc {
x: root.isRenderable ? (root.isFramed ? 0 : (root.barMappedPos.x + root.leftEdgeOvs + root.tlRadius * root.tlMultX)) : -0.75
y: root.isRenderable ? (root.isFramed ? 0 : (root.barMappedPos.y + root.topEdgeOvs)) : -1
radiusX: root.isRenderable ? (root.isFramed ? 0 : root.tlRadius) : 0
radiusY: root.isRenderable ? (root.isFramed ? 0 : root.tlRadius) : 0
direction: ShapeCornerHelper.getArcDirection(root.tlMultX, root.tlMultY)
}
// 2. Inner Hole for Framed Mode (Clockwise)
// When !isFramed, draws a tiny 1x1 rectangle inside the bar as a non-degenerate WindingFill
// no-op to prevent a zero-area degenerate subpath crashing qTriangulate.
// Note: an exact duplicate of the outer path cannot be used here because Qt's CurveRenderer
// has issues with exactly coincident subpaths, causing the fill to not render on some systems.
// When !isRenderable, falls back to a valid 1×1 off-screen square at (-3,-3)→(-2,-2).
readonly property real _nhX: barMappedPos.x + barWidth / 2
readonly property real _nhY: barMappedPos.y + barHeight / 2
PathMove {
x: root.isRenderable ? (root.isFramed ? (root.holeX + root.frameRadius) : (root._nhX + 0.25)) : -2.75
y: root.isRenderable ? (root.isFramed ? root.holeY : root._nhY) : -3
}
// Top edge
PathLine {
x: root.isRenderable ? (root.isFramed ? (root.holeX + root.holeWidth - root.frameRadius) : (root._nhX + 1)) : -2
y: root.isRenderable ? (root.isFramed ? root.holeY : root._nhY) : -3
}
// Top-right corner
PathArc {
x: root.isRenderable ? (root.isFramed ? (root.holeX + root.holeWidth) : (root._nhX + 1)) : -2
y: root.isRenderable ? (root.isFramed ? (root.holeY + root.frameRadius) : (root._nhY + 0.25)) : -2.75
radiusX: root.isRenderable ? (root.isFramed ? root.frameRadius : 0) : 0
radiusY: root.isRenderable ? (root.isFramed ? root.frameRadius : 0) : 0
direction: PathArc.Clockwise
}
// Right edge
PathLine {
x: root.isRenderable ? (root.isFramed ? (root.holeX + root.holeWidth) : (root._nhX + 1)) : -2
y: root.isRenderable ? (root.isFramed ? (root.holeY + root.holeHeight - root.frameRadius) : (root._nhY + 1)) : -2
}
// Bottom-right corner
PathArc {
x: root.isRenderable ? (root.isFramed ? (root.holeX + root.holeWidth - root.frameRadius) : (root._nhX + 0.75)) : -2.25
y: root.isRenderable ? (root.isFramed ? (root.holeY + root.holeHeight) : (root._nhY + 1)) : -2
radiusX: root.isRenderable ? (root.isFramed ? root.frameRadius : 0) : 0
radiusY: root.isRenderable ? (root.isFramed ? root.frameRadius : 0) : 0
direction: PathArc.Clockwise
}
// Bottom edge
PathLine {
x: root.isRenderable ? (root.isFramed ? (root.holeX + root.frameRadius) : root._nhX) : -3
y: root.isRenderable ? (root.isFramed ? (root.holeY + root.holeHeight) : (root._nhY + 1)) : -2
}
// Bottom-left corner
PathArc {
x: root.isRenderable ? (root.isFramed ? root.holeX : root._nhX) : -3
y: root.isRenderable ? (root.isFramed ? (root.holeY + root.holeHeight - root.frameRadius) : (root._nhY + 0.75)) : -2.25
radiusX: root.isRenderable ? (root.isFramed ? root.frameRadius : 0) : 0
radiusY: root.isRenderable ? (root.isFramed ? root.frameRadius : 0) : 0
direction: PathArc.Clockwise
}
// Left edge
PathLine {
x: root.isRenderable ? (root.isFramed ? root.holeX : root._nhX) : -3
y: root.isRenderable ? (root.isFramed ? (root.holeY + root.frameRadius) : root._nhY) : -3
}
// Top-left corner (back to start)
PathArc {
x: root.isRenderable ? (root.isFramed ? (root.holeX + root.frameRadius) : (root._nhX + 0.25)) : -2.75
y: root.isRenderable ? (root.isFramed ? root.holeY : root._nhY) : -3
radiusX: root.isRenderable ? (root.isFramed ? root.frameRadius : 0) : 0
radiusY: root.isRenderable ? (root.isFramed ? root.frameRadius : 0) : 0
direction: PathArc.Clockwise
}
}