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 opacity: folio.HomeScreenState.settingsOpenProgress
} }
// Dock overlay window renders the favourites bar above application // Unified convergence chrome renders the visible top bar, workspace
// windows in convergence mode. LayerTop sits above normal windows but // frame, and dock in one mapped surface so they appear together.
// below LayerOverlay (notifications, volume OSD). The exclusive zone // Invisible reserver surfaces in the panel/taskpanel containments still
// that reserves screen space is handled by the dockSpaceReserver in the // provide the exclusive zones that shrink KWin's MaximizeArea.
// task panel containment; this window only provides the visible dock.
Window { Window {
id: dockOverlay id: convergenceChrome
readonly property bool active: ShellSettings.Settings.convergenceModeEnabled readonly property bool active: ShellSettings.Settings.convergenceModeEnabled
&& !ShellSettings.Settings.gamingModeEnabled && !ShellSettings.Settings.gamingModeEnabled
&& !folio.overviewActive && !folio.overviewActive
visible: active visible: active
opacity: active ? 1 : 0
color: "transparent" color: "transparent"
width: Screen.width 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.layer: LayerShell.Window.LayerTop
LayerShell.Window.anchors: LayerShell.Window.AnchorBottom | LayerShell.Window.AnchorLeft | LayerShell.Window.AnchorRight LayerShell.Window.anchors: LayerShell.Window.AnchorTop | LayerShell.Window.AnchorBottom
LayerShell.Window.exclusionZone: shouldReserveSpace ? dockHeight : -1 | LayerShell.Window.AnchorLeft | LayerShell.Window.AnchorRight
LayerShell.Window.exclusionZone: -1
LayerShell.Window.keyboardInteractivity: LayerShell.Window.KeyboardInteractivityOnDemand LayerShell.Window.keyboardInteractivity: LayerShell.Window.KeyboardInteractivityOnDemand
// Auto-hide: slide dock content off-screen when a window is // Auto-hide: slide dock content off-screen when a window is
// maximized. The reveal strip at the screen edge brings it back. // maximized. The reveal strip at the screen edge brings it back.
property real dockOffset: 0 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 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 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 // Height of the input-receive strip kept at the screen edge when
// the dock is hidden. Matches the navigation panel convention. // the dock is hidden. Matches the navigation panel convention.
@ -309,24 +317,31 @@ ContainmentItem {
readonly property bool shouldHide: ShellSettings.Settings.autoHidePanelsEnabled readonly property bool shouldHide: ShellSettings.Settings.autoHidePanelsEnabled
&& windowMaximizedTracker.showingWindow && !hoverRevealing && 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() { function updateInputRegion() {
const topBarRegion = Qt.rect(0, 0, width, topBarHeight)
if (shouldHide && dockOffset >= dockHeight) { if (shouldHide && dockOffset >= dockHeight) {
MobileShell.ShellUtil.setInputRegion(dockOverlay, MobileShell.ShellUtil.setInputRegions(convergenceChrome, [
Qt.rect(0, dockOverlay.height - revealStripHeight, topBarRegion,
dockOverlay.width, revealStripHeight)) Qt.rect(0, height - revealStripHeight, width, revealStripHeight)
])
} else { } else {
MobileShell.ShellUtil.setInputRegion(dockOverlay, Qt.rect(0, 0, 0, 0)) MobileShell.ShellUtil.setInputRegions(convergenceChrome, [
topBarRegion,
Qt.rect(0, height - dockHeight, width, dockHeight)
])
} }
} }
onActiveChanged: { onActiveChanged: {
hoverRevealTimer.stop() hoverRevealTimer.stop()
hoverRevealing = false hoverRevealing = false
inputRegionInitialized = false
dockOffset = shouldHide ? dockHeight : 0 dockOffset = shouldHide ? dockHeight : 0
updateInputRegion() inputRegionTimer.restart()
} }
onShouldHideChanged: { onShouldHideChanged: {
@ -335,17 +350,30 @@ ContainmentItem {
} else { } else {
dockOffset = 0 dockOffset = 0
} }
updateInputRegion() inputRegionTimer.restart()
} }
// Narrow the input region to a strip at the screen edge when hidden // Narrow the input region to a strip at the screen edge when hidden
// so that app controls near the bottom edge are not accidentally // so that app controls near the bottom edge are not accidentally
// intercepted. Mirrors the same pattern used by NavigationPanel. // intercepted. Mirrors the same pattern used by NavigationPanel.
onDockOffsetChanged: { 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 // Delay reveal briefly so a quick edge graze does not pop the
// dock up mid-interaction with the underlying application. // dock up mid-interaction with the underlying application.
@ -353,53 +381,116 @@ ContainmentItem {
id: hoverRevealTimer id: hoverRevealTimer
interval: Kirigami.Units.shortDuration interval: Kirigami.Units.shortDuration
repeat: false repeat: false
onTriggered: dockOverlay.hoverRevealing = true onTriggered: convergenceChrome.hoverRevealing = true
}
HoverHandler {
id: dockHoverHandler
onHoveredChanged: {
if (hovered) {
hoverRevealTimer.start()
} else {
hoverRevealTimer.stop()
dockOverlay.hoverRevealing = false
}
}
} }
Behavior on dockOffset { Behavior on dockOffset {
MobileShell.MotionNumberAnimation { MobileShell.MotionNumberAnimation {
type: MobileShell.Motion.SpatialDefault type: MobileShell.Motion.SpatialDefault
duration: dockOverlay.dockAnimationDuration duration: convergenceChrome.dockAnimationDuration
} }
} }
Behavior on opacity { Rectangle {
MobileShell.MotionNumberAnimation { type: MobileShell.Motion.EffectsFast; duration: dockOverlay.dockFadeDuration } 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 { Rectangle {
anchors.fill: parent x: convergenceChrome.workAreaX
visible: !dockOverlay.shouldHide || dockOverlay.dockOffset < dockOverlay.dockHeight y: convergenceChrome.workAreaY
Kirigami.Theme.inherit: false width: convergenceChrome.workAreaWidth
Kirigami.Theme.colorSet: Kirigami.Theme.Window height: convergenceChrome.workAreaHeight
color: Kirigami.Theme.backgroundColor radius: convergenceChrome.frameRadius
color: "transparent"
border.width: 1
border.color: convergenceChrome.edgeColor
} }
FavouritesBar { Rectangle {
id: dockOverlayBar id: dockSurface
anchors.fill: parent anchors.left: parent.left
folio: root.folio anchors.right: parent.right
maskManager: root.maskManager anchors.bottom: parent.bottom
homeScreen: folioHomeScreen height: convergenceChrome.dockHeight
suppressRunningTasks: runningAppsPanel.visible color: "transparent"
transform: Translate { y: dockOverlay.dockOffset }
// Dock is an opaque panel use Window colorset so all content HoverHandler {
// (labels, hover highlights, icon tints) follows the system theme id: dockHoverHandler
// instead of the containment's Complementary wallpaper context. onHoveredChanged: {
Kirigami.Theme.inherit: false if (hovered) {
Kirigami.Theme.colorSet: Kirigami.Theme.Window 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() onHomeTriggered: root.homeAction()
contentItem: Item { 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 // homescreen component
FolioHomeScreen { FolioHomeScreen {
id: 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 readonly property real convergenceWorkspaceFrameThickness: ShellSettings.Settings.convergenceModeEnabled && !gamingMode
? MobileShell.Constants.convergenceWorkspaceFrameThickness ? MobileShell.Constants.convergenceWorkspaceFrameThickness
: 0 : 0
@ -85,12 +86,14 @@ ContainmentItem {
if (root.panel) { if (root.panel) {
root.panel.floating = false; root.panel.floating = false;
root.panel.maximize(); // maximize first, then we can apply offsets (otherwise they are overridden) 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?? // HACK: set thickness twice, sometimes it doesn't set the first time??
root.panel.thickness = root.panelHeight; root.panel.thickness = root.panelHeight;
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) MobileShell.ShellUtil.setWindowLayer(root.panel, LayerShell.Window.LayerOverlay)
root.updateTouchArea(); root.updateTouchArea();
} }
@ -144,29 +147,28 @@ ContainmentItem {
} }
// Invisible layer-shell surface that reserves screen space for the // Invisible layer-shell surface that reserves screen space for the
// status bar in convergence mode. The panel itself uses WindowsGoBelow // status bar in convergence mode. The visible convergence top bar is
// (exclusiveZone -1) so it stays above windows; this separate surface // rendered by Folio's unified chrome surface; this window only shrinks
// at LayerBottom provides the actual exclusive zone so KWin shrinks // KWin's MaximizeArea.
// MaximizeArea by the panel height.
Window { Window {
id: topBarSpaceReserver id: topBarSpaceReserver
visible: ShellSettings.Settings.convergenceModeEnabled && !ShellSettings.Settings.gamingModeEnabled visible: ShellSettings.Settings.convergenceModeEnabled && !ShellSettings.Settings.gamingModeEnabled
color: "transparent" color: "transparent"
flags: Qt.FramelessWindowHint | Qt.WindowTransparentForInput flags: Qt.FramelessWindowHint | Qt.WindowTransparentForInput
height: Math.max(1, root.panelHeight + root.convergenceWorkspaceFrameThickness) height: Math.max(1, root.topBarHeight + root.convergenceWorkspaceFrameThickness)
width: 1 width: 1
LayerShell.Window.scope: "topbar-space" LayerShell.Window.scope: "topbar-space"
LayerShell.Window.layer: LayerShell.Window.LayerBottom LayerShell.Window.layer: LayerShell.Window.LayerBottom
LayerShell.Window.anchors: LayerShell.Window.AnchorTop | LayerShell.Window.AnchorLeft | LayerShell.Window.AnchorRight 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 LayerShell.Window.keyboardInteractivity: LayerShell.Window.KeyboardInteractivityNone
} }
// Visual panel component // Visual panel component
StatusPanel { StatusPanel {
id: statusPanel id: statusPanel
visible: !ShellSettings.Settings.gamingModeEnabled visible: !ShellSettings.Settings.gamingModeEnabled && !ShellSettings.Settings.convergenceModeEnabled
anchors.fill: parent anchors.fill: parent
containmentItem: root containmentItem: root
} }

View file

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

View file

@ -8,8 +8,11 @@ repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
constants="$repo_root/components/mobileshell/qml/components/Constants.qml" constants="$repo_root/components/mobileshell/qml/components/Constants.qml"
panel="$repo_root/containments/panel/qml/main.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" taskpanel="$repo_root/containments/taskpanel/qml/main.qml"
folio_main="$repo_root/containments/homescreens/folio/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_home="$repo_root/containments/homescreens/folio/qml/FolioHomeScreen.qml"
folio_backend="$repo_root/containments/homescreens/folio/homescreen.h" folio_backend="$repo_root/containments/homescreens/folio/homescreen.h"
folio_backend_cpp="$repo_root/containments/homescreens/folio/homescreen.cpp" 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 "$constants" "readonly property real convergenceWorkspaceFrameRadius:"
require_line "$panel" "readonly property real convergenceWorkspaceFrameThickness:" require_line "$panel" "readonly property real convergenceWorkspaceFrameThickness:"
require_line "$panel" "height: Math.max(1, root.panelHeight + root.convergenceWorkspaceFrameThickness)" require_line "$panel" "root.panel.location = PlasmaCore.Types.TopEdge"
require_line "$panel" "LayerShell.Window.exclusionZone: Math.max(1, root.panelHeight + root.convergenceWorkspaceFrameThickness)" 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" "readonly property real convergenceWorkspaceFrameThickness:"
require_line "$taskpanel" "height: MobileShell.Constants.convergenceDockHeight + root.convergenceWorkspaceFrameThickness" require_line "$taskpanel" "height: MobileShell.Constants.convergenceDockHeight + root.convergenceWorkspaceFrameThickness"
require_line "$taskpanel" "LayerShell.Window.exclusionZone: 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-left\""
require_line "$taskpanel" "LayerShell.Window.scope: \"workspace-frame-right\"" 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 dockHeight: MobileShell.Constants.convergenceDockHeight"
require_line "$folio_main" "readonly property real revealStripHeight: MobileShell.Constants.convergenceDockRevealHeight" require_line "$folio_main" "readonly property real revealStripHeight: MobileShell.Constants.convergenceDockRevealHeight"
require_line "$folio_main" "import QtQuick.Shapes 1.8" 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 frameThickness: MobileShell.Constants.convergenceWorkspaceFrameThickness"
require_line "$folio_main" "readonly property real frameRadius:" require_line "$folio_main" "readonly property real frameRadius:"
require_line "$folio_main" "readonly property real workAreaX: frameThickness" 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 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" "fillRule: ShapePath.OddEvenFill"
require_line "$folio_main" "PathLine { x: workspaceFrame.width; y: workspaceFrame.height - workspaceFrame.bottomReservedHeight }" require_line "$folio_main" "PathLine { x: convergenceChrome.width; y: convergenceChrome.height - convergenceChrome.dockHeight }"
require_line "$folio_main" "PathLine { x: 0; y: workspaceFrame.height - workspaceFrame.bottomReservedHeight }" require_line "$folio_main" "PathLine { x: 0; y: convergenceChrome.height - convergenceChrome.dockHeight }"
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: convergenceChrome.workAreaX + convergenceChrome.workAreaWidth - convergenceChrome.frameRadius; y: convergenceChrome.workAreaY + convergenceChrome.workAreaHeight; radiusX: convergenceChrome.frameRadius; radiusY: convergenceChrome.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" "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" "readonly property real popupTopY: MobileShell.Constants.topPanelHeight"
require_line "$folio_main" "+ MobileShell.Constants.convergenceWorkspaceFrameThickness" require_line "$folio_main" "+ MobileShell.Constants.convergenceWorkspaceFrameThickness"
require_line "$folio_main" "readonly property real popupBottomY: parent.height" 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)" require_line "$folio_backend" "Q_PROPERTY(bool overviewActive READ overviewActive NOTIFY overviewActiveChanged)"
overview_hide_guards="$(grep -F "&& !folio.overviewActive" "$folio_main" | wc -l)" overview_hide_guards="$(grep -F "&& !folio.overviewActive" "$folio_main" | wc -l)"
if [[ "$overview_hide_guards" -ne 2 ]]; then if [[ "$overview_hide_guards" -ne 1 ]]; then
echo "Expected the Folio convergence workspace frame and dock overlay to hide during KWin Overview; found $overview_hide_guards overview guards" >&2 echo "Expected Folio convergence chrome to hide during KWin Overview; found $overview_hide_guards overview guards" >&2
exit 1 exit 1
fi fi
@ -195,7 +216,7 @@ if grep -Fq "contentParent.children.push(contentItem);" "$notification_card"; th
exit 1 exit 1
fi 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 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 echo "Expected the workspace frame cutout to have four rounded inner corners; found $frame_arc_count arcs" >&2
exit 1 exit 1
@ -206,8 +227,8 @@ if grep -Fq "convergenceWallpaperLayer" "$folio_main"; then
exit 1 exit 1
fi 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 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 exit 1
fi fi