Render convergence chrome from Folio

Move the visible convergence top bar, workspace frame, and dock
into one Folio-owned layer-shell surface so startup maps them as
a single piece of chrome. Keep the legacy panel/taskpanel surfaces
for reservations and plumbing, and update the convergence invariant
for the new ownership.
This commit is contained in:
Marco Allegretti 2026-05-25 08:26:58 +02:00
parent 2066a48995
commit d3ab18d200
4 changed files with 195 additions and 142 deletions

View file

@ -270,35 +270,43 @@ ContainmentItem {
opacity: folio.HomeScreenState.settingsOpenProgress
}
// Dock overlay window renders the favourites bar above application
// windows in convergence mode. LayerTop sits above normal windows but
// below LayerOverlay (notifications, volume OSD). The exclusive zone
// that reserves screen space is handled by the dockSpaceReserver in the
// task panel containment; this window only provides the visible dock.
// Unified convergence chrome renders the visible top bar, workspace
// frame, and dock in one mapped surface so they appear together.
// Invisible reserver surfaces in the panel/taskpanel containments still
// provide the exclusive zones that shrink KWin's MaximizeArea.
Window {
id: dockOverlay
id: convergenceChrome
readonly property bool active: ShellSettings.Settings.convergenceModeEnabled
&& !ShellSettings.Settings.gamingModeEnabled
&& !folio.overviewActive
visible: active
opacity: active ? 1 : 0
color: "transparent"
width: Screen.width
height: MobileShell.Constants.convergenceDockHeight
height: Screen.height
LayerShell.Window.scope: "dock-overlay"
LayerShell.Window.scope: "convergence-chrome"
LayerShell.Window.layer: LayerShell.Window.LayerTop
LayerShell.Window.anchors: LayerShell.Window.AnchorBottom | LayerShell.Window.AnchorLeft | LayerShell.Window.AnchorRight
LayerShell.Window.exclusionZone: shouldReserveSpace ? dockHeight : -1
LayerShell.Window.anchors: LayerShell.Window.AnchorTop | LayerShell.Window.AnchorBottom
| LayerShell.Window.AnchorLeft | LayerShell.Window.AnchorRight
LayerShell.Window.exclusionZone: -1
LayerShell.Window.keyboardInteractivity: LayerShell.Window.KeyboardInteractivityOnDemand
// Auto-hide: slide dock content off-screen when a window is
// maximized. The reveal strip at the screen edge brings it back.
property real dockOffset: 0
property bool inputRegionInitialized: false
readonly property real topBarHeight: MobileShell.Constants.topPanelHeight
readonly property real dockHeight: MobileShell.Constants.convergenceDockHeight
readonly property real frameThickness: MobileShell.Constants.convergenceWorkspaceFrameThickness
readonly property real frameRadius: Math.min(MobileShell.Constants.convergenceWorkspaceFrameRadius, Math.max(0, Math.min(workAreaWidth, workAreaHeight) / 2))
readonly property real workAreaX: frameThickness
readonly property real workAreaY: topBarHeight + frameThickness
readonly property real workAreaWidth: Math.max(0, width - frameThickness * 2)
readonly property real workAreaHeight: Math.max(0, height - topBarHeight - dockHeight - frameThickness * 2)
readonly property color chromeColor: Kirigami.Theme.backgroundColor
readonly property color edgeColor: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.1)
readonly property int dockAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.SpatialDefault)
readonly property int dockFadeDuration: MobileShell.Motion.duration(MobileShell.Motion.EffectsFast)
// Height of the input-receive strip kept at the screen edge when
// the dock is hidden. Matches the navigation panel convention.
@ -309,24 +317,31 @@ ContainmentItem {
readonly property bool shouldHide: ShellSettings.Settings.autoHidePanelsEnabled
&& windowMaximizedTracker.showingWindow && !hoverRevealing
readonly property bool shouldReserveSpace: ShellSettings.Settings.autoHidePanelsEnabled
&& windowMaximizedTracker.showingWindow && hoverRevealing
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Window
function updateInputRegion() {
const topBarRegion = Qt.rect(0, 0, width, topBarHeight)
if (shouldHide && dockOffset >= dockHeight) {
MobileShell.ShellUtil.setInputRegion(dockOverlay,
Qt.rect(0, dockOverlay.height - revealStripHeight,
dockOverlay.width, revealStripHeight))
MobileShell.ShellUtil.setInputRegions(convergenceChrome, [
topBarRegion,
Qt.rect(0, height - revealStripHeight, width, revealStripHeight)
])
} else {
MobileShell.ShellUtil.setInputRegion(dockOverlay, Qt.rect(0, 0, 0, 0))
MobileShell.ShellUtil.setInputRegions(convergenceChrome, [
topBarRegion,
Qt.rect(0, height - dockHeight, width, dockHeight)
])
}
}
onActiveChanged: {
hoverRevealTimer.stop()
hoverRevealing = false
inputRegionInitialized = false
dockOffset = shouldHide ? dockHeight : 0
updateInputRegion()
inputRegionTimer.restart()
}
onShouldHideChanged: {
@ -335,17 +350,30 @@ ContainmentItem {
} else {
dockOffset = 0
}
updateInputRegion()
inputRegionTimer.restart()
}
// Narrow the input region to a strip at the screen edge when hidden
// so that app controls near the bottom edge are not accidentally
// intercepted. Mirrors the same pattern used by NavigationPanel.
onDockOffsetChanged: {
updateInputRegion()
inputRegionTimer.restart()
}
onWidthChanged: inputRegionTimer.restart()
onHeightChanged: inputRegionTimer.restart()
onFrameSwapped: {
if (!inputRegionInitialized) {
inputRegionInitialized = true
inputRegionTimer.restart()
}
}
Timer {
id: inputRegionTimer
interval: 0
repeat: false
onTriggered: convergenceChrome.updateInputRegion()
}
onWidthChanged: updateInputRegion()
onHeightChanged: updateInputRegion()
// Delay reveal briefly so a quick edge graze does not pop the
// dock up mid-interaction with the underlying application.
@ -353,53 +381,116 @@ ContainmentItem {
id: hoverRevealTimer
interval: Kirigami.Units.shortDuration
repeat: false
onTriggered: dockOverlay.hoverRevealing = true
}
HoverHandler {
id: dockHoverHandler
onHoveredChanged: {
if (hovered) {
hoverRevealTimer.start()
} else {
hoverRevealTimer.stop()
dockOverlay.hoverRevealing = false
}
}
onTriggered: convergenceChrome.hoverRevealing = true
}
Behavior on dockOffset {
MobileShell.MotionNumberAnimation {
type: MobileShell.Motion.SpatialDefault
duration: dockOverlay.dockAnimationDuration
duration: convergenceChrome.dockAnimationDuration
}
}
Behavior on opacity {
MobileShell.MotionNumberAnimation { type: MobileShell.Motion.EffectsFast; duration: dockOverlay.dockFadeDuration }
Rectangle {
id: topBarSurface
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
height: convergenceChrome.topBarHeight
color: convergenceChrome.chromeColor
MobileShell.StatusBar {
anchors.fill: parent
showSecondRow: false
showTime: true
backgroundColor: "transparent"
}
TapHandler {
acceptedButtons: Qt.LeftButton
onTapped: MobileShellState.ShellDBusClient.openActionDrawer()
}
}
Shape {
id: workspaceFrame
anchors.fill: parent
ShapePath {
fillColor: convergenceChrome.chromeColor
fillRule: ShapePath.OddEvenFill
strokeWidth: 0
startX: 0
startY: convergenceChrome.topBarHeight
PathLine { x: convergenceChrome.width; y: convergenceChrome.topBarHeight }
PathLine { x: convergenceChrome.width; y: convergenceChrome.height - convergenceChrome.dockHeight }
PathLine { x: 0; y: convergenceChrome.height - convergenceChrome.dockHeight }
PathLine { x: 0; y: convergenceChrome.topBarHeight }
PathMove { x: convergenceChrome.workAreaX + convergenceChrome.frameRadius; y: convergenceChrome.workAreaY }
PathLine { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth - convergenceChrome.frameRadius; y: convergenceChrome.workAreaY }
PathArc { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth; y: convergenceChrome.workAreaY + convergenceChrome.frameRadius; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.frameRadius }
PathLine { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight - convergenceChrome.frameRadius }
PathArc { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth - convergenceChrome.frameRadius; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.frameRadius }
PathLine { x: convergenceChrome.workAreaX + convergenceChrome.frameRadius; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight }
PathArc { x: convergenceChrome.workAreaX; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight - convergenceChrome.frameRadius; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.frameRadius }
PathLine { x: convergenceChrome.workAreaX; y: convergenceChrome.workAreaY + convergenceChrome.frameRadius }
PathArc { x: convergenceChrome.workAreaX + convergenceChrome.frameRadius; y: convergenceChrome.workAreaY; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.frameRadius }
}
}
Rectangle {
anchors.fill: parent
visible: !dockOverlay.shouldHide || dockOverlay.dockOffset < dockOverlay.dockHeight
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Window
color: Kirigami.Theme.backgroundColor
x: convergenceChrome.workAreaX
y: convergenceChrome.workAreaY
width: convergenceChrome.workAreaWidth
height: convergenceChrome.workAreaHeight
radius: convergenceChrome.frameRadius
color: "transparent"
border.width: 1
border.color: convergenceChrome.edgeColor
}
FavouritesBar {
id: dockOverlayBar
anchors.fill: parent
folio: root.folio
maskManager: root.maskManager
homeScreen: folioHomeScreen
suppressRunningTasks: runningAppsPanel.visible
transform: Translate { y: dockOverlay.dockOffset }
// Dock is an opaque panel use Window colorset so all content
// (labels, hover highlights, icon tints) follows the system theme
// instead of the containment's Complementary wallpaper context.
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Window
Rectangle {
id: dockSurface
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: convergenceChrome.dockHeight
color: "transparent"
HoverHandler {
id: dockHoverHandler
onHoveredChanged: {
if (hovered) {
hoverRevealTimer.start()
} else {
hoverRevealTimer.stop()
convergenceChrome.hoverRevealing = false
}
}
}
Rectangle {
anchors.fill: parent
visible: !convergenceChrome.shouldHide || convergenceChrome.dockOffset < convergenceChrome.dockHeight
color: convergenceChrome.chromeColor
}
FavouritesBar {
id: dockOverlayBar
anchors.fill: parent
folio: root.folio
maskManager: root.maskManager
homeScreen: folioHomeScreen
suppressRunningTasks: runningAppsPanel.visible
transform: Translate { y: convergenceChrome.dockOffset }
// Dock is an opaque panel use Window colorset so all content
// (labels, hover highlights, icon tints) follows the system theme
// instead of the containment's Complementary wallpaper context.
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Window
}
}
}
@ -872,67 +963,6 @@ ContainmentItem {
onHomeTriggered: root.homeAction()
contentItem: Item {
Item {
id: workspaceFrame
anchors.fill: parent
visible: ShellSettings.Settings.convergenceModeEnabled
&& !ShellSettings.Settings.gamingModeEnabled
&& !folio.overviewActive
z: -1
readonly property real frameThickness: MobileShell.Constants.convergenceWorkspaceFrameThickness
readonly property real frameRadius: Math.min(MobileShell.Constants.convergenceWorkspaceFrameRadius, Math.max(0, Math.min(workAreaWidth, workAreaHeight) / 2))
readonly property real topReservedHeight: MobileShell.Constants.topPanelHeight
readonly property real bottomReservedHeight: MobileShell.Constants.convergenceDockHeight
readonly property real workAreaX: frameThickness
readonly property real workAreaY: topReservedHeight + frameThickness
readonly property real workAreaWidth: Math.max(0, width - frameThickness * 2)
readonly property real workAreaHeight: Math.max(0, height - topReservedHeight - bottomReservedHeight - frameThickness * 2)
readonly property color frameColor: Kirigami.Theme.backgroundColor
readonly property color edgeColor: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.1)
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Window
Shape {
anchors.fill: parent
ShapePath {
fillColor: workspaceFrame.frameColor
fillRule: ShapePath.OddEvenFill
strokeWidth: 0
startX: 0
startY: workspaceFrame.topReservedHeight
PathLine { x: workspaceFrame.width; y: workspaceFrame.topReservedHeight }
PathLine { x: workspaceFrame.width; y: workspaceFrame.height - workspaceFrame.bottomReservedHeight }
PathLine { x: 0; y: workspaceFrame.height - workspaceFrame.bottomReservedHeight }
PathLine { x: 0; y: workspaceFrame.topReservedHeight }
PathMove { x: workspaceFrame.workAreaX + workspaceFrame.frameRadius; y: workspaceFrame.workAreaY }
PathLine { x: workspaceFrame.workAreaX + workspaceFrame.workAreaWidth - workspaceFrame.frameRadius; y: workspaceFrame.workAreaY }
PathArc { x: workspaceFrame.workAreaX + workspaceFrame.workAreaWidth; y: workspaceFrame.workAreaY + workspaceFrame.frameRadius; radiusX: workspaceFrame.frameRadius; radiusY: workspaceFrame.frameRadius }
PathLine { x: workspaceFrame.workAreaX + workspaceFrame.workAreaWidth; y: workspaceFrame.workAreaY + workspaceFrame.workAreaHeight - workspaceFrame.frameRadius }
PathArc { x: workspaceFrame.workAreaX + workspaceFrame.workAreaWidth - workspaceFrame.frameRadius; y: workspaceFrame.workAreaY + workspaceFrame.workAreaHeight; radiusX: workspaceFrame.frameRadius; radiusY: workspaceFrame.frameRadius }
PathLine { x: workspaceFrame.workAreaX + workspaceFrame.frameRadius; y: workspaceFrame.workAreaY + workspaceFrame.workAreaHeight }
PathArc { x: workspaceFrame.workAreaX; y: workspaceFrame.workAreaY + workspaceFrame.workAreaHeight - workspaceFrame.frameRadius; radiusX: workspaceFrame.frameRadius; radiusY: workspaceFrame.frameRadius }
PathLine { x: workspaceFrame.workAreaX; y: workspaceFrame.workAreaY + workspaceFrame.frameRadius }
PathArc { x: workspaceFrame.workAreaX + workspaceFrame.frameRadius; y: workspaceFrame.workAreaY; radiusX: workspaceFrame.frameRadius; radiusY: workspaceFrame.frameRadius }
}
}
Rectangle {
x: workspaceFrame.workAreaX
y: workspaceFrame.workAreaY
width: workspaceFrame.workAreaWidth
height: workspaceFrame.workAreaHeight
radius: workspaceFrame.frameRadius
color: "transparent"
border.width: 1
border.color: workspaceFrame.edgeColor
}
}
// homescreen component
FolioHomeScreen {
id: folioHomeScreen

View file

@ -75,7 +75,8 @@ ContainmentItem {
}
}
readonly property real panelHeight: gamingMode ? 0 : MobileShell.Constants.topPanelHeight
readonly property real topBarHeight: gamingMode ? 0 : MobileShell.Constants.topPanelHeight
readonly property real panelHeight: (ShellSettings.Settings.convergenceModeEnabled || gamingMode) ? 0 : MobileShell.Constants.topPanelHeight
readonly property real convergenceWorkspaceFrameThickness: ShellSettings.Settings.convergenceModeEnabled && !gamingMode
? MobileShell.Constants.convergenceWorkspaceFrameThickness
: 0
@ -85,12 +86,14 @@ ContainmentItem {
if (root.panel) {
root.panel.floating = false;
root.panel.maximize(); // maximize first, then we can apply offsets (otherwise they are overridden)
root.panel.location = PlasmaCore.Types.TopEdge;
root.panel.offset = 0;
// HACK: set thickness twice, sometimes it doesn't set the first time??
root.panel.thickness = root.panelHeight;
root.panel.thickness = root.panelHeight;
root.panel.visibilityMode = (ShellSettings.Settings.autoHidePanelsEnabled || ShellSettings.Settings.convergenceModeEnabled) ? 3 : 0;
root.panel.visibilityMode = (!ShellSettings.Settings.convergenceModeEnabled && ShellSettings.Settings.autoHidePanelsEnabled) ? 3 : 0;
MobileShell.ShellUtil.setWindowLayer(root.panel, LayerShell.Window.LayerOverlay)
root.updateTouchArea();
}
@ -144,29 +147,28 @@ ContainmentItem {
}
// Invisible layer-shell surface that reserves screen space for the
// status bar in convergence mode. The panel itself uses WindowsGoBelow
// (exclusiveZone -1) so it stays above windows; this separate surface
// at LayerBottom provides the actual exclusive zone so KWin shrinks
// MaximizeArea by the panel height.
// status bar in convergence mode. The visible convergence top bar is
// rendered by Folio's unified chrome surface; this window only shrinks
// KWin's MaximizeArea.
Window {
id: topBarSpaceReserver
visible: ShellSettings.Settings.convergenceModeEnabled && !ShellSettings.Settings.gamingModeEnabled
color: "transparent"
flags: Qt.FramelessWindowHint | Qt.WindowTransparentForInput
height: Math.max(1, root.panelHeight + root.convergenceWorkspaceFrameThickness)
height: Math.max(1, root.topBarHeight + root.convergenceWorkspaceFrameThickness)
width: 1
LayerShell.Window.scope: "topbar-space"
LayerShell.Window.layer: LayerShell.Window.LayerBottom
LayerShell.Window.anchors: LayerShell.Window.AnchorTop | LayerShell.Window.AnchorLeft | LayerShell.Window.AnchorRight
LayerShell.Window.exclusionZone: Math.max(1, root.panelHeight + root.convergenceWorkspaceFrameThickness)
LayerShell.Window.exclusionZone: Math.max(1, root.topBarHeight + root.convergenceWorkspaceFrameThickness)
LayerShell.Window.keyboardInteractivity: LayerShell.Window.KeyboardInteractivityNone
}
// Visual panel component
StatusPanel {
id: statusPanel
visible: !ShellSettings.Settings.gamingModeEnabled
visible: !ShellSettings.Settings.gamingModeEnabled && !ShellSettings.Settings.convergenceModeEnabled
anchors.fill: parent
containmentItem: root
}

View file

@ -245,7 +245,7 @@ ContainmentItem {
screen: Plasmoid.screen
maximizedTracker: windowMaximizedTracker
visible: !root.fullscreen
visible: !root.fullscreen && !ShellSettings.Settings.convergenceModeEnabled
}
Item {

View file

@ -8,8 +8,11 @@ repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
constants="$repo_root/components/mobileshell/qml/components/Constants.qml"
panel="$repo_root/containments/panel/qml/main.qml"
status_bar_template="$repo_root/layout-templates/org.kde.plasma.mobile.defaultStatusBar/contents/layout.js"
taskpanel="$repo_root/containments/taskpanel/qml/main.qml"
folio_main="$repo_root/containments/homescreens/folio/qml/main.qml"
shellutil_header="$repo_root/components/mobileshell/shellutil.h"
shellutil_cpp="$repo_root/components/mobileshell/shellutil.cpp"
folio_home="$repo_root/containments/homescreens/folio/qml/FolioHomeScreen.qml"
folio_backend="$repo_root/containments/homescreens/folio/homescreen.h"
folio_backend_cpp="$repo_root/containments/homescreens/folio/homescreen.cpp"
@ -41,16 +44,31 @@ require_line "$constants" "readonly property real convergenceWorkspaceFrameThick
require_line "$constants" "readonly property real convergenceWorkspaceFrameRadius:"
require_line "$panel" "readonly property real convergenceWorkspaceFrameThickness:"
require_line "$panel" "height: Math.max(1, root.panelHeight + root.convergenceWorkspaceFrameThickness)"
require_line "$panel" "LayerShell.Window.exclusionZone: Math.max(1, root.panelHeight + root.convergenceWorkspaceFrameThickness)"
require_line "$panel" "root.panel.location = PlasmaCore.Types.TopEdge"
require_line "$panel" "root.panel.offset = 0"
require_line "$panel" "root.panel.visibilityMode = (!ShellSettings.Settings.convergenceModeEnabled && ShellSettings.Settings.autoHidePanelsEnabled) ? 3 : 0"
require_line "$panel" "readonly property real topBarHeight: gamingMode ? 0 : MobileShell.Constants.topPanelHeight"
require_line "$panel" "readonly property real panelHeight: (ShellSettings.Settings.convergenceModeEnabled || gamingMode) ? 0 : MobileShell.Constants.topPanelHeight"
require_line "$panel" "height: Math.max(1, root.topBarHeight + root.convergenceWorkspaceFrameThickness)"
require_line "$panel" "LayerShell.Window.exclusionZone: Math.max(1, root.topBarHeight + root.convergenceWorkspaceFrameThickness)"
require_line "$panel" "visible: !ShellSettings.Settings.gamingModeEnabled && !ShellSettings.Settings.convergenceModeEnabled"
require_line "$status_bar_template" "panel.location = \"top\";"
require_line "$taskpanel" "readonly property real convergenceWorkspaceFrameThickness:"
require_line "$taskpanel" "height: MobileShell.Constants.convergenceDockHeight + root.convergenceWorkspaceFrameThickness"
require_line "$taskpanel" "LayerShell.Window.exclusionZone: MobileShell.Constants.convergenceDockHeight + root.convergenceWorkspaceFrameThickness"
require_line "$taskpanel" "visible: !root.fullscreen && !ShellSettings.Settings.convergenceModeEnabled"
require_line "$taskpanel" "LayerShell.Window.scope: \"workspace-frame-left\""
require_line "$taskpanel" "LayerShell.Window.scope: \"workspace-frame-right\""
require_line "$folio_main" "height: MobileShell.Constants.convergenceDockHeight"
require_line "$folio_main" "id: convergenceChrome"
require_line "$folio_main" "LayerShell.Window.scope: \"convergence-chrome\""
require_line "$folio_main" "height: Screen.height"
require_line "$folio_main" "MobileShell.StatusBar {"
require_line "$folio_main" "onTapped: MobileShellState.ShellDBusClient.openActionDrawer()"
require_line "$folio_main" "MobileShell.ShellUtil.setInputRegions(convergenceChrome, ["
require_line "$folio_main" "const topBarRegion = Qt.rect(0, 0, width, topBarHeight)"
require_line "$folio_main" "readonly property real dockHeight: MobileShell.Constants.convergenceDockHeight"
require_line "$folio_main" "readonly property real revealStripHeight: MobileShell.Constants.convergenceDockRevealHeight"
require_line "$folio_main" "import QtQuick.Shapes 1.8"
@ -58,14 +76,17 @@ require_line "$folio_main" "id: workspaceFrame"
require_line "$folio_main" "readonly property real frameThickness: MobileShell.Constants.convergenceWorkspaceFrameThickness"
require_line "$folio_main" "readonly property real frameRadius:"
require_line "$folio_main" "readonly property real workAreaX: frameThickness"
require_line "$folio_main" "readonly property real workAreaY: topReservedHeight + frameThickness"
require_line "$folio_main" "readonly property real workAreaY: topBarHeight + frameThickness"
require_line "$folio_main" "readonly property real workAreaWidth: Math.max(0, width - frameThickness * 2)"
require_line "$folio_main" "readonly property real workAreaHeight: Math.max(0, height - topReservedHeight - bottomReservedHeight - frameThickness * 2)"
require_line "$folio_main" "readonly property real workAreaHeight: Math.max(0, height - topBarHeight - dockHeight - frameThickness * 2)"
require_line "$folio_main" "fillRule: ShapePath.OddEvenFill"
require_line "$folio_main" "PathLine { x: workspaceFrame.width; y: workspaceFrame.height - workspaceFrame.bottomReservedHeight }"
require_line "$folio_main" "PathLine { x: 0; y: workspaceFrame.height - workspaceFrame.bottomReservedHeight }"
require_line "$folio_main" "PathArc { x: workspaceFrame.workAreaX + workspaceFrame.workAreaWidth - workspaceFrame.frameRadius; y: workspaceFrame.workAreaY + workspaceFrame.workAreaHeight; radiusX: workspaceFrame.frameRadius; radiusY: workspaceFrame.frameRadius }"
require_line "$folio_main" "PathArc { x: workspaceFrame.workAreaX; y: workspaceFrame.workAreaY + workspaceFrame.workAreaHeight - workspaceFrame.frameRadius; radiusX: workspaceFrame.frameRadius; radiusY: workspaceFrame.frameRadius }"
require_line "$folio_main" "PathLine { x: convergenceChrome.width; y: convergenceChrome.height - convergenceChrome.dockHeight }"
require_line "$folio_main" "PathLine { x: 0; y: convergenceChrome.height - convergenceChrome.dockHeight }"
require_line "$folio_main" "PathArc { x: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth - convergenceChrome.frameRadius; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.frameRadius }"
require_line "$folio_main" "PathArc { x: convergenceChrome.workAreaX; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight - convergenceChrome.frameRadius; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.frameRadius }"
require_line "$shellutil_header" "Q_INVOKABLE void setInputRegions(QWindow *window, const QVariantList &regions);"
require_line "$shellutil_cpp" "void ShellUtil::setInputRegions(QWindow *window, const QVariantList &regions)"
require_line "$shellutil_cpp" "for (const QVariant &value : regions)"
require_line "$folio_main" "readonly property real popupTopY: MobileShell.Constants.topPanelHeight"
require_line "$folio_main" "+ MobileShell.Constants.convergenceWorkspaceFrameThickness"
require_line "$folio_main" "readonly property real popupBottomY: parent.height"
@ -82,8 +103,8 @@ require_line "$folio_home" "&& !ShellSettings.Settings.convergenceModeEnabled"
require_line "$folio_backend" "Q_PROPERTY(bool overviewActive READ overviewActive NOTIFY overviewActiveChanged)"
overview_hide_guards="$(grep -F "&& !folio.overviewActive" "$folio_main" | wc -l)"
if [[ "$overview_hide_guards" -ne 2 ]]; then
echo "Expected the Folio convergence workspace frame and dock overlay to hide during KWin Overview; found $overview_hide_guards overview guards" >&2
if [[ "$overview_hide_guards" -ne 1 ]]; then
echo "Expected Folio convergence chrome to hide during KWin Overview; found $overview_hide_guards overview guards" >&2
exit 1
fi
@ -195,7 +216,7 @@ if grep -Fq "contentParent.children.push(contentItem);" "$notification_card"; th
exit 1
fi
frame_arc_count="$(grep -F "PathArc { x: workspaceFrame." "$folio_main" | wc -l)"
frame_arc_count="$(grep -F "PathArc { x: convergenceChrome." "$folio_main" | wc -l || true)"
if [[ "$frame_arc_count" -ne 4 ]]; then
echo "Expected the workspace frame cutout to have four rounded inner corners; found $frame_arc_count arcs" >&2
exit 1
@ -206,8 +227,8 @@ if grep -Fq "convergenceWallpaperLayer" "$folio_main"; then
exit 1
fi
dock_offset_transforms="$(grep -F "transform: Translate { y: dockOverlay.dockOffset }" "$folio_main" | wc -l)"
dock_offset_transforms="$(grep -F "transform: Translate { y: convergenceChrome.dockOffset }" "$folio_main" | wc -l || true)"
if [[ "$dock_offset_transforms" -ne 1 ]]; then
echo "Expected only dock contents to slide with dockOverlay.dockOffset; found $dock_offset_transforms transforms" >&2
echo "Expected only dock contents to slide with convergenceChrome.dockOffset; found $dock_offset_transforms transforms" >&2
exit 1
fi