// SPDX-FileCopyrightText: 2023 Devin Lin // SPDX-License-Identifier: LGPL-2.0-or-later import QtQuick import QtQuick.Window import QtQuick.Layouts import QtQuick.Effects import QtQuick.Shapes 1.8 import org.kde.kirigami as Kirigami import org.kde.plasma.plasmoid 2.0 import org.kde.plasma.components 3.0 as PlasmaComponents import org.kde.plasma.private.mobileshell as MobileShell import org.kde.plasma.private.mobileshell.state as MobileShellState import org.kde.plasma.private.mobileshell.windowplugin as WindowPlugin import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings import org.kde.plasma.private.mobileshell.gamingshellplugin as GamingShell import org.kde.layershell 1.0 as LayerShell import org.kde.plasma.private.sessions 2.0 import org.kde.coreaddons as KCoreAddons import org.kde.kcmutils as KCM import org.kde.kirigamiaddons.components as KirigamiAddonsComponents import plasma.applet.org.kde.plasma.mobile.homescreen.folio as Folio import "./gaming" import "./private" ContainmentItem { id: root property var folio: root.plasmoid readonly property int shortAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.EffectsFast) // Tracks whether the Game Center grid is visible within gaming mode. // If gaming mode is already enabled at startup, open it immediately so // the user is never left without controls. property bool gameCenterOpen: ShellSettings.Settings.gamingModeEnabled property bool showGameCenterHint: false // State saved when gaming mode activates, restored when it deactivates property string _savedPowerProfile: "" property bool _savedDnd: false property bool _gamingSessionActive: false function _applyGamingModeState(enabled) { root.gameCenterOpen = enabled GamingShell.GamepadManager.active = enabled if (enabled === root._gamingSessionActive) { return } if (enabled) { // Save current state and apply gaming optimizations root._savedDnd = MobileShellState.ShellDBusClient.doNotDisturb MobileShellState.ShellDBusClient.doNotDisturb = true if (GamingShell.PowerProfileControl.available) { root._savedPowerProfile = GamingShell.PowerProfileControl.activeProfile GamingShell.PowerProfileControl.activeProfile = "performance" } GamingShell.GameModeControl.requestStart() root._gamingSessionActive = true } else { // Restore previous state MobileShellState.ShellDBusClient.doNotDisturb = root._savedDnd if (GamingShell.PowerProfileControl.available && root._savedPowerProfile.length > 0) { GamingShell.PowerProfileControl.activeProfile = root._savedPowerProfile } GamingShell.GameModeControl.requestEnd() root._gamingSessionActive = false } } Timer { id: gameCenterHintTimer interval: 2600 onTriggered: root.showGameCenterHint = false } Connections { target: ShellSettings.Settings function onGamingModeEnabledChanged() { root._applyGamingModeState(ShellSettings.Settings.gamingModeEnabled) } } // Gamepad Guide button toggles Game Center overlay Connections { target: GamingShell.GamepadManager enabled: ShellSettings.Settings.gamingModeEnabled function onButtonPressed(button, gamepadIndex) { if (button === GamingShell.GamepadManager.ButtonGuide) { root.gameCenterOpen = !root.gameCenterOpen } } } Component.onCompleted: { root._applyGamingModeState(ShellSettings.Settings.gamingModeEnabled) folio.FolioSettings.load(); folio.FavouritesModel.load(); folio.PageListModel.load(); } property MobileShell.MaskManager maskManager: MobileShell.MaskManager { height: root.height width: root.width } property MobileShell.MaskManager frontMaskManager: MobileShell.MaskManager { height: root.height width: root.width } // wallpaper blur layer MobileShell.BlurEffect { id: wallpaperBlur active: folio.FolioSettings.wallpaperBlurEffect > 0 anchors.fill: parent sourceLayer: Plasmoid.wallpaperGraphicsObject maskSourceLayer: folio.FolioSettings.wallpaperBlurEffect > 1 ? maskManager.maskLayer : null fullBlur: Math.min(1, Math.max( 1 - homeScreen.contentOpacity, // Convergence: no blur for popup drawer ShellSettings.Settings.convergenceModeEnabled ? 0 : folio.HomeScreenState.appDrawerOpenProgress * 2, folio.HomeScreenState.searchWidgetOpenProgress * 1.5, // blur faster during swipe folio.HomeScreenState.folderOpenProgress ) ) } WindowPlugin.WindowMaximizedTracker { id: windowMaximizedTracker screenGeometry: Plasmoid.containment.screenGeometry } // In gaming mode, reopen Game Center when the last window goes away // so the user is never stranded on a bare wallpaper. Connections { target: windowMaximizedTracker enabled: ShellSettings.Settings.gamingModeEnabled function onShowingWindowChanged() { if (!windowMaximizedTracker.showingWindow && !root.gameCenterOpen) { root.gameCenterOpen = true } } } // Close app drawer when a new window appears Connections { target: WindowPlugin.WindowUtil function onWindowCreated() { if (folio.HomeScreenState.viewState === Folio.HomeScreenState.AppDrawerView) { folio.HomeScreenState.closeAppDrawer(); } } } function homeAction() { const isInWindow = (!WindowPlugin.WindowUtil.isShowingDesktop && windowMaximizedTracker.showingWindow); // Always close action drawer if (MobileShellState.ShellDBusClient.isActionDrawerOpen) { MobileShellState.ShellDBusClient.closeActionDrawer(); } if (ShellSettings.Settings.gamingModeEnabled) { // In gaming mode Home/Menu should reopen the Game Center overlay. root.gameCenterOpen = true; return; } if (ShellSettings.Settings.convergenceModeEnabled) { // Convergence: toggle the app drawer as a layer-shell overlay // without disturbing open windows. switch (folio.HomeScreenState.viewState) { case Folio.HomeScreenState.AppDrawerView: folio.HomeScreenState.closeAppDrawer(); break; case Folio.HomeScreenState.FolderView: folio.HomeScreenState.closeFolder(); break; case Folio.HomeScreenState.SearchWidgetView: folio.HomeScreenState.closeSearchWidget(); break; case Folio.HomeScreenState.SettingsView: folio.HomeScreenState.closeSettingsView(); break; default: folio.HomeScreenState.openAppDrawer(); break; } return; } if (isInWindow) { folio.HomeScreenState.closeFolder(); folio.HomeScreenState.closeSearchWidget(); folio.HomeScreenState.closeAppDrawer(); folio.HomeScreenState.goToPage(0, false); WindowPlugin.WindowUtil.minimizeAll(); // Always ensure settings view is closed if (folio.HomeScreenState.viewState == Folio.HomeScreenState.SettingsView) { folio.HomeScreenState.closeSettingsView(); } } else { // If we are already on the homescreen switch (folio.HomeScreenState.viewState) { case Folio.HomeScreenState.PageView: if (folio.HomeScreenState.currentPage === 0) { folio.HomeScreenState.openAppDrawer(); } else { folio.HomeScreenState.goToPage(0, false); } break; case Folio.HomeScreenState.AppDrawerView: folio.HomeScreenState.closeAppDrawer(); break; case Folio.HomeScreenState.SearchWidgetView: folio.HomeScreenState.closeSearchWidget(); break; case Folio.HomeScreenState.FolderView: folio.HomeScreenState.closeFolder(); break; case Folio.HomeScreenState.SettingsView: folio.HomeScreenState.closeSettingsView(); break; } } } Plasmoid.onActivated: homeAction() Rectangle { id: appDrawerBackground anchors.fill: parent // Convergence: no scrim (popup has own background); mobile: dark scrim color: ShellSettings.Settings.convergenceModeEnabled ? "transparent" : Qt.rgba(0, 0, 0, 0.6) opacity: folio.HomeScreenState.appDrawerOpenProgress } Rectangle { id: searchWidgetBackground anchors.fill: parent color: Qt.rgba(0, 0, 0, 0.3) opacity: folio.HomeScreenState.searchWidgetOpenProgress } Rectangle { id: settingsViewBackground anchors.fill: parent color: Qt.rgba(0, 0, 0, 0.3) 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. Window { id: dockOverlay 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 LayerShell.Window.scope: "dock-overlay" 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.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 readonly property real dockHeight: MobileShell.Constants.convergenceDockHeight 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. readonly property real revealStripHeight: MobileShell.Constants.convergenceDockRevealHeight // True once the hover-reveal timer fires; cleared on hover-exit. property bool hoverRevealing: false readonly property bool shouldHide: ShellSettings.Settings.autoHidePanelsEnabled && windowMaximizedTracker.showingWindow && !hoverRevealing readonly property bool shouldReserveSpace: ShellSettings.Settings.autoHidePanelsEnabled && windowMaximizedTracker.showingWindow && hoverRevealing function updateInputRegion() { if (shouldHide && dockOffset >= dockHeight) { MobileShell.ShellUtil.setInputRegion(dockOverlay, Qt.rect(0, dockOverlay.height - revealStripHeight, dockOverlay.width, revealStripHeight)) } else { MobileShell.ShellUtil.setInputRegion(dockOverlay, Qt.rect(0, 0, 0, 0)) } } onActiveChanged: { hoverRevealTimer.stop() hoverRevealing = false dockOffset = shouldHide ? dockHeight : 0 updateInputRegion() } onShouldHideChanged: { if (shouldHide) { dockOffset = dockHeight } else { dockOffset = 0 } updateInputRegion() } // 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() } onWidthChanged: updateInputRegion() onHeightChanged: updateInputRegion() // Delay reveal briefly so a quick edge graze does not pop the // dock up mid-interaction with the underlying application. Timer { 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 } } } Behavior on dockOffset { MobileShell.MotionNumberAnimation { type: MobileShell.Motion.SpatialDefault duration: dockOverlay.dockAnimationDuration } } Behavior on opacity { MobileShell.MotionNumberAnimation { type: MobileShell.Motion.EffectsFast; duration: dockOverlay.dockFadeDuration } } 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 } 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 } } // App-drawer overlay — renders the popup drawer above application // windows in convergence mode. Same pattern as the dock overlay: // a fullscreen layer-shell surface at LayerTop so that it appears // over normal windows without minimizing them. Window { id: drawerOverlay visible: ShellSettings.Settings.convergenceModeEnabled && !ShellSettings.Settings.gamingModeEnabled && folio.HomeScreenState.appDrawerOpenProgress > 0 color: "transparent" width: Screen.width height: Screen.height LayerShell.Window.scope: "drawer-overlay" LayerShell.Window.layer: LayerShell.Window.LayerTop 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 // Click outside the popup to dismiss MouseArea { anchors.fill: parent onClicked: folio.HomeScreenState.closeAppDrawer() } AppDrawer { id: overlayDrawer folio: root.folio homeScreen: folioHomeScreen readonly property real popupWidth: Math.min(Kirigami.Units.gridUnit * 28, parent.width * 0.5) readonly property real dockHeight: MobileShell.Constants.convergenceDockHeight readonly property real sideInset: MobileShell.Constants.convergenceWorkspaceFrameThickness readonly property real connectedPanelGap: 0 readonly property real popupTopY: MobileShell.Constants.topPanelHeight + MobileShell.Constants.convergenceWorkspaceFrameThickness readonly property real popupBottomY: parent.height - dockHeight - MobileShell.Constants.convergenceWorkspaceFrameThickness readonly property real popupHeight: Math.max(0, popupBottomY - popupTopY) width: popupWidth height: popupHeight opacity: folio.HomeScreenState.appDrawerOpenProgress < 0.5 ? 0 : (folio.HomeScreenState.appDrawerOpenProgress - 0.5) * 2 property real animationY: (1 - folio.HomeScreenState.appDrawerOpenProgress) * (Kirigami.Units.gridUnit * 2) x: sideInset y: (opacity > 0) ? popupTopY + animationY : parent.height headerHeight: Math.round(Kirigami.Units.gridUnit * 3) headerItem: AppDrawerHeader { id: overlayDrawerHeader folio: root.folio onReleaseFocusRequested: overlayDrawer.forceActiveFocus() } Keys.onPressed: (event) => { if (event.text.trim().length > 0) { overlayDrawerHeader.addSearchText(event.text); overlayDrawerHeader.forceActiveFocus(); event.accepted = true; } else if (event.key === Qt.Key_Left || event.key === Qt.Key_Right || event.key === Qt.Key_Up || event.key === Qt.Key_Down) { overlayDrawerHeader.forceActiveFocus(); event.accepted = true; } } Connections { target: folio.HomeScreenState function onAppDrawerOpened() { folio.ApplicationListSearchModel.categoryFilter = "" overlayDrawer.forceActiveFocus() } } } CategoryPanel { id: categoryPanel folio: root.folio width: Kirigami.Units.gridUnit * 9 height: overlayDrawer.popupHeight x: overlayDrawer.x + overlayDrawer.width + overlayDrawer.connectedPanelGap y: overlayDrawer.y opacity: overlayDrawer.opacity onCategorySelected: (catId) => { folio.ApplicationListSearchModel.categoryFilter = catId overlayDrawerHeader.clearSearchText() } } Rectangle { id: powerPanel // Width: just enough for one icon button + side margins readonly property real tileSize: Kirigami.Units.iconSizes.medium + 2 * Kirigami.Units.largeSpacing width: tileSize height: overlayDrawer.popupHeight x: runningAppsPanel.visible ? runningAppsPanel.x + runningAppsPanel.width + overlayDrawer.connectedPanelGap : categoryPanel.x + categoryPanel.width + overlayDrawer.connectedPanelGap y: overlayDrawer.y opacity: overlayDrawer.opacity radius: 0 color: Kirigami.Theme.backgroundColor MouseArea { anchors.fill: parent } KCoreAddons.KUser { id: kuser } SessionManagement { id: powerSession } // Close button anchored to top — smaller than power icons Rectangle { id: closeButton anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right anchors.margins: Kirigami.Units.smallSpacing height: Kirigami.Units.iconSizes.smallMedium + 2 * Kirigami.Units.smallSpacing radius: Kirigami.Units.cornerRadius color: closeArea.containsPress ? Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.2) : closeArea.containsMouse ? Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.1) : "transparent" Kirigami.Icon { anchors.centerIn: parent width: Kirigami.Units.iconSizes.smallMedium height: width source: "window-close-symbolic" active: closeArea.containsMouse isMask: true color: Kirigami.Theme.textColor } PlasmaComponents.ToolTip { text: i18n("Close") visible: closeArea.containsMouse } MouseArea { id: closeArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: folio.HomeScreenState.closeAppDrawer() } } // Separator below close button Rectangle { anchors.top: closeButton.bottom anchors.topMargin: Kirigami.Units.smallSpacing anchors.left: parent.left anchors.right: parent.right anchors.leftMargin: Kirigami.Units.smallSpacing anchors.rightMargin: Kirigami.Units.smallSpacing height: 1 color: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.15) } // Power buttons centred vertically in the panel Column { id: powerColumn anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.right: parent.right anchors.margins: Kirigami.Units.smallSpacing spacing: Kirigami.Units.smallSpacing Rectangle { width: parent.width height: width radius: Kirigami.Units.cornerRadius color: lockArea.containsPress ? Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.2) : lockArea.containsMouse ? Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.1) : "transparent" Kirigami.Icon { anchors.centerIn: parent width: Kirigami.Units.iconSizes.medium height: width source: "system-lock-screen" active: lockArea.containsMouse isMask: true color: Kirigami.Theme.textColor } PlasmaComponents.ToolTip { text: i18n("Lock Screen") visible: lockArea.containsMouse } MouseArea { id: lockArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { powerSession.lock() folio.HomeScreenState.closeAppDrawer() } } } Rectangle { width: parent.width height: width radius: Kirigami.Units.cornerRadius color: rebootArea.containsPress ? Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.2) : rebootArea.containsMouse ? Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.1) : "transparent" Kirigami.Icon { anchors.centerIn: parent width: Kirigami.Units.iconSizes.medium height: width source: "system-reboot" active: rebootArea.containsMouse isMask: true color: Kirigami.Theme.textColor } PlasmaComponents.ToolTip { text: i18n("Restart") visible: rebootArea.containsMouse } MouseArea { id: rebootArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { folio.HomeScreenState.closeAppDrawer() powerSession.requestReboot() } } } Rectangle { width: parent.width height: width radius: Kirigami.Units.cornerRadius color: shutdownArea.containsPress ? Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.2) : shutdownArea.containsMouse ? Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.1) : "transparent" Kirigami.Icon { anchors.centerIn: parent width: Kirigami.Units.iconSizes.medium height: width source: "system-shutdown" active: shutdownArea.containsMouse isMask: true color: Kirigami.Theme.textColor } PlasmaComponents.ToolTip { text: i18n("Shut Down") visible: shutdownArea.containsMouse } MouseArea { id: shutdownArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { folio.HomeScreenState.closeAppDrawer() powerSession.requestShutdown() } } } } // Separator above user avatar Rectangle { anchors.bottom: userSection.top anchors.bottomMargin: Kirigami.Units.smallSpacing anchors.left: parent.left anchors.right: parent.right anchors.leftMargin: Kirigami.Units.smallSpacing anchors.rightMargin: Kirigami.Units.smallSpacing height: 1 color: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.15) } // User avatar anchored to bottom — tooltip shows name, click opens user settings Rectangle { id: userSection anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right anchors.margins: Kirigami.Units.smallSpacing height: width radius: Kirigami.Units.cornerRadius color: userArea.containsPress ? Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.2) : userArea.containsMouse ? Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.1) : "transparent" KirigamiAddonsComponents.Avatar { anchors.centerIn: parent width: Kirigami.Units.iconSizes.medium height: width source: kuser.faceIconUrl name: kuser.fullName || kuser.loginName } PlasmaComponents.ToolTip { text: kuser.fullName || kuser.loginName visible: userArea.containsMouse } MouseArea { id: userArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { KCM.KCMLauncher.openSystemSettings("kcm_users") folio.HomeScreenState.closeAppDrawer() } } } } RunningAppsPanel { id: runningAppsPanel folio: root.folio x: categoryPanel.x + categoryPanel.width + overlayDrawer.connectedPanelGap y: overlayDrawer.y width: Math.max(0, parent.width - x - powerPanel.width - overlayDrawer.sideInset - overlayDrawer.connectedPanelGap) height: overlayDrawer.popupHeight opacity: overlayDrawer.opacity visible: hasTasks && opacity > 0 onTaskActivated: folio.HomeScreenState.closeAppDrawer() } } // Game Center overlay — full-screen grid of games shown when gaming mode // is active. Sits at LayerTop so it covers running application windows // without going above system notifications. GameCenterOverlay { id: gameCenterOverlay folio: root.folio visible: ShellSettings.Settings.gamingModeEnabled && root.gameCenterOpen onGameStarted: root.gameCenterOpen = false onDismissRequested: { root.gameCenterOpen = false if (ShellSettings.Settings.gamingDismissHintEnabled) { root.showGameCenterHint = true gameCenterHintTimer.restart() } } } // Small persistent button at the top-right corner of the screen that lets // the user return to the Game Center after launching a game. // Keep the Loader active for the full duration of gaming mode so the // opacity Behavior in GamingHUD can animate both fade-in and fade-out. // // Hide the HUD while a game window covers the screen. A mapped LayerShell // surface prevents KWin from using DRM direct scanout for the fullscreen // game window. Setting showing=false triggers the opacity fade-out and then // sets visible=false, which unmaps the Wayland surface and lets KWin bypass // the compositor render loop entirely for the game frame. Loader { active: ShellSettings.Settings.gamingModeEnabled sourceComponent: GamingHUD { visible: showing showing: !root.gameCenterOpen && !windowMaximizedTracker.showingWindow onOpenRequested: root.gameCenterOpen = true } } Rectangle { id: gameCenterHint anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top anchors.topMargin: Kirigami.Units.gridUnit * 2 visible: root.showGameCenterHint && ShellSettings.Settings.gamingDismissHintEnabled opacity: visible ? 1 : 0 z: 2000 radius: Kirigami.Units.cornerRadius color: Qt.rgba(0, 0, 0, 0.65) border.width: 1 border.color: Qt.rgba(1, 1, 1, 0.2) Behavior on opacity { MobileShell.MotionNumberAnimation { type: MobileShell.Motion.EffectsFast; duration: root.shortAnimationDuration } } implicitWidth: hintText.implicitWidth + Kirigami.Units.gridUnit * 2 implicitHeight: hintText.implicitHeight + Kirigami.Units.largeSpacing PlasmaComponents.Label { id: hintText anchors.centerIn: parent text: i18n("Gaming mode is still on. Use Home or the gamepad icon to reopen Game Center.") color: "white" wrapMode: Text.WordWrap width: Math.min(root.width * 0.8, Kirigami.Units.gridUnit * 30) horizontalAlignment: Text.AlignHCenter } } MobileShell.HomeScreen { id: homeScreen anchors.fill: parent plasmoidItem: root onResetHomeScreenPosition: { // NOTE: empty, because this is handled by homeAction() } 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 folio: root.folio maskManager: root.maskManager anchors.fill: parent topMargin: homeScreen.topMargin bottomMargin: homeScreen.bottomMargin leftMargin: homeScreen.leftMargin rightMargin: homeScreen.rightMargin // Ensure is the focused item at start Component.onCompleted: forceActiveFocus() onWallpaperSelectorTriggered: wallpaperSelectorLoader.active = true } } } // top blur layer for items on top of the base homescreen MobileShell.BlurEffect { id: homescreenBlur anchors.fill: parent active: folio.FolioSettings.wallpaperBlurEffect > 1 && ((delegateDragItem.visible && folio.HomeScreenState.dragState.dropDelegate.type === Folio.FolioDelegate.Folder) || wallpaperSelectorLoader.active) visible: active fullBlur: 0 sourceLayer: homeScreenLayer maskSourceLayer: frontMaskManager.maskLayer // stacking both wallpaper and homescreen layers so we can blur them in one pass Item { id: homeScreenLayer anchors.fill: parent opacity: 0 // wallpaper blur ShaderEffectSource { anchors.fill: parent textureSize: homescreenBlur.textureSize sourceItem: Plasmoid.wallpaperGraphicsObject hideSource: false } // homescreen blur ShaderEffectSource { anchors.fill: parent textureSize: homescreenBlur.textureSize sourceItem: homeScreen hideSource: false } } } // drag and drop component DelegateDragItem { id: delegateDragItem folio: root.folio maskManager: root.frontMaskManager } // drag and drop for widgets WidgetDragItem { id: widgetDragItem folio: root.folio } // loader for wallpaper selector Loader { id: wallpaperSelectorLoader anchors.fill: parent asynchronous: true active: false onLoaded: { wallpaperSelectorLoader.item.open(); } sourceComponent: MobileShell.WallpaperSelector { maskManager: root.frontMaskManager horizontal: root.width > root.height edge: horizontal ? Qt.LeftEdge : Qt.BottomEdge topMargin: horizontal ? folioHomeScreen.topMargin : 0 bottomMargin: horizontal ? 0 : folioHomeScreen.bottomMargin leftMargin: horizontal ? folioHomeScreen.leftMargin : 0 rightMargin: horizontal ? folioHomeScreen.rightMargin : 0 onClosed: { wallpaperSelectorLoader.active = false; } onWallpaperSettingsRequested: { close(); folioHomeScreen.openConfigure(); } } } }