Compare commits

..

No commits in common. "5d83caaaa1eea8bde0c38f9dd89b4694da2b6322" and "847e516fcfd22b130bf816ea5ab1589e8fa50cf9" have entirely different histories.

25 changed files with 210 additions and 390 deletions

View file

@ -1,47 +1,22 @@
# Shift # Shift
SHIFT is a convergent environment for desktops, gaming consoles, tablets, and smartphones. SHIFT is an independent shell fork focused on convergence. It builds on KDE Plasma Mobile, keeps the upstream phone UI available, and layers desktop-oriented behaviour on top of the same session.
What does convergence mean? This project aims to offer the same environment across different form factors, with an interface that adapts to the device, input type, and required user experience. SHIFT tracks [plasma-mobile](https://invent.kde.org/plasma/plasma-mobile) as its upstream base.
The upstream phone UI remains intact; convergence-specific behaviour is added on top.
For example: Smartphones connected to an external display can benefit from a graphical experience change from undocked to docked, transforming the workspace from mobile to desktop. Another example: 2-in-1 laptops that convert to tablets or have 360° rotatable displays can switch the graphical interface while offering a tablet or desktop experience.
Desktop:
SHIFT aims to offer a complete desktop experience, featuring a dockbar, a top bar, an app drawer, and an actions drawer with a notification history, a System Tray, and a Quick Settings Menu. The desktop experience is available in dynamic tiling mode or with classic floating windows.
Smartphone / Tablet:
The environment is capable of adapting its interface to work with tablets and smartphones, adopting a touch-first interface and workflow optimized for touchscreens.
Gaming Shell:
The Gaming Shell aims to be optimized for gamepad navigation, offering integration with game launchers and game centers. The gaming experience must also adapt to different factors, adapting to handheld devices and gaming stations that also serve as workstations.
### Screenshot
![SHIFT main interface](screenshots/SHIFT-main.png)
![SHIFT dynamic tiling interface](screenshots/SHIFT-dynamic_tiling.png)
![SHIFT gaming shell interface](screenshots/SHIFT-Gaming_Shell.png)
### What convergence mode changes ### What convergence mode changes
* The old navigation panel is replaced by a unified dock that combines * Navigation panel replaced by a dock with running-app indicators,
pinned apps, running windows, desktop pager controls, and quick favourites, context menus, and hover tooltips
actions such as Home and Overview. * App drawer opens as a floating popup above the dock
* The dock supports desktop workflows: pin and unpin, drag reorder,
grouped window entries, middle-click close, and thumbnail previews. * Window management: edge tiling, edge maximize, close buttons, task
* The app drawer and action drawer run as convergence surfaces (bounded context menus, Overview integration
and shaped), with calendar, notifications, quick settings, and * Status bar gains a system tray, date display, and hover highlights
desktop-focused utility actions. * Screen space reserved for the dock via layer-shell exclusive zone
* The status bar integrates tray/date behavior for desktop usage and is * Desktop niceties: right-click wallpaper settings, minimize-all on
coordinated with the convergence drawers and workspace chrome. home press, clickable page indicators
* Screen space is explicitly reserved for the dock, with geometry and
input-region guards to keep overlays and windows stable.
* Window workflows are desktop-oriented: edge tiling/maximize,
dynamic-tiling actions from task menus, and optional snap-layout
behavior.
* Overview and virtual desktop operations are integrated into dock and
drawer actions, including desktop/window management entries.
* Theme behavior is convergence-aware, with Shift defaults and
wallpaper-driven dynamic theming applied to shell surfaces.
### Locations ### Locations
@ -64,9 +39,7 @@ cmake --install build
Compatibility-sensitive identifiers such as `org.kde.plasma.mobileshell` Compatibility-sensitive identifiers such as `org.kde.plasma.mobileshell`
are intentionally preserved in the runtime and build instructions. They are are intentionally preserved in the runtime and build instructions. They are
part of the current compatibility boundary. part of the current compatibility boundary, not the public product name.
SHIFT tracks [plasma-mobile](https://invent.kde.org/plasma/plasma-mobile) as its upstream base.
The upstream phone UI remains intact; convergence-specific behaviour is added on top.
### Disclaimer ### Disclaimer
@ -74,7 +47,7 @@ SHIFT is an independent project based on KDE Plasma Mobile.
It is **not affiliated with or endorsed by** KDE or the KDE community. It is **not affiliated with or endorsed by** KDE or the KDE community.
Some visual elements may originate from KDE Plasma Mobile and are used in accordance with their respective licenses. These elements may be replaced in future versions as the project evolves. Some visual elements (such as icons or graphical assets) may originate from KDE Plasma Mobile and are used in accordance with their respective licenses. These elements may be replaced in future versions as the project evolves.
All trademarks, including KDE, belong to their respective owners. All trademarks, including KDE, belong to their respective owners.

View file

@ -26,7 +26,6 @@ set_source_files_properties(
qml/components/AppLaunch.qml qml/components/AppLaunch.qml
qml/components/Constants.qml qml/components/Constants.qml
qml/components/Motion.qml qml/components/Motion.qml
qml/components/SurfaceColors.qml
qml/dataproviders/AudioInfo.qml qml/dataproviders/AudioInfo.qml
qml/dataproviders/BatteryInfo.qml qml/dataproviders/BatteryInfo.qml
qml/dataproviders/BluetoothInfo.qml qml/dataproviders/BluetoothInfo.qml
@ -61,7 +60,6 @@ ecm_target_qml_sources(mobileshellplugin SOURCES
qml/components/ScreenEdgeDragEffect.qml qml/components/ScreenEdgeDragEffect.qml
qml/components/StartupFeedbackPanelFill.qml qml/components/StartupFeedbackPanelFill.qml
qml/components/StartupFeedbackWindows.qml qml/components/StartupFeedbackWindows.qml
qml/components/SurfaceColors.qml
qml/components/TextDropShadow.qml qml/components/TextDropShadow.qml
qml/components/VelocityCalculator.qml qml/components/VelocityCalculator.qml

View file

@ -77,7 +77,10 @@ Item {
// Background color // Background color
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
color: MobileShell.SurfaceColors.withAlpha(MobileShell.SurfaceColors.accentSurface(Kirigami.Theme.backgroundColor, 0.18, 0.10), 0.9) color: Qt.rgba(Kirigami.Theme.backgroundColor.r,
Kirigami.Theme.backgroundColor.g,
Kirigami.Theme.backgroundColor.b,
0.9)
Behavior on color { MobileShell.MotionColorAnimation { type: MobileShell.Motion.StandardDecel } } Behavior on color { MobileShell.MotionColorAnimation { type: MobileShell.Motion.StandardDecel } }
opacity: { opacity: {
let base = Math.max(0, Math.min(brightnessPressedValue, actionDrawer.offset / root.minimizedQuickSettingsOffset)); let base = Math.max(0, Math.min(brightnessPressedValue, actionDrawer.offset / root.minimizedQuickSettingsOffset));

View file

@ -28,7 +28,6 @@ QQC2.Popup {
padding: Kirigami.Units.smallSpacing padding: Kirigami.Units.smallSpacing
readonly property int popupAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.EffectsFast) readonly property int popupAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.EffectsFast)
readonly property color surfaceColor: MobileShell.SurfaceColors.accentSurface(Kirigami.Theme.backgroundColor, 0.24, 0.12)
property string currentPluginId: "" property string currentPluginId: ""
property Item __currentItem: null property Item __currentItem: null
@ -82,11 +81,11 @@ QQC2.Popup {
} }
background: Kirigami.ShadowedRectangle { background: Kirigami.ShadowedRectangle {
color: popup.surfaceColor color: Kirigami.Theme.backgroundColor
radius: Kirigami.Units.cornerRadius radius: Kirigami.Units.cornerRadius
border.color: Kirigami.ColorUtils.linearInterpolation( border.color: Kirigami.ColorUtils.linearInterpolation(
popup.surfaceColor, Kirigami.Theme.textColor, 0.2) Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.2)
border.width: 1 border.width: 1
shadow.size: Kirigami.Units.gridUnit shadow.size: Kirigami.Units.gridUnit

View file

@ -74,7 +74,7 @@ Item {
id: actionDrawerSurfacePath id: actionDrawerSurfacePath
readonly property real cornerRadius: actionDrawerSurface.cornerRadius readonly property real cornerRadius: actionDrawerSurface.cornerRadius
fillColor: MobileShell.SurfaceColors.accentSurface(Kirigami.Theme.backgroundColor, 0.32, 0.18) fillColor: Kirigami.Theme.backgroundColor
strokeWidth: 0 strokeWidth: 0
startX: actionDrawerSurface.width startX: actionDrawerSurface.width
startY: 0 startY: 0

View file

@ -26,11 +26,6 @@ MobileShell.BaseItem {
required property string text required property string text
required property string status required property string status
required property string icon required property string icon
// Visual active-state of the tile. Declared here to shadow the built-in
// Item.enabled: binding the real Item.enabled would disable the contentItem
// MouseArea, making an inactive tile impossible to tap (and thus turn on).
// Tiles are removed from the list via `available`, never disabled.
required property bool enabled
required property string settingsCommand required property string settingsCommand
required property var toggleFunction required property var toggleFunction

View file

@ -18,10 +18,6 @@ Item {
required property string text required property string text
required property string status required property string status
required property string icon required property string icon
// Visual active-state only. Shadows the built-in Item.enabled so binding it
// does not disable the inner toggle/detail MouseAreas (which would make an
// inactive service impossible to switch on).
required property bool enabled
required property var toggleFunction required property var toggleFunction
property bool compact: false property bool compact: false

View file

@ -28,7 +28,6 @@ QQC2.Popup {
readonly property int popupAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.EffectsFast) readonly property int popupAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.EffectsFast)
readonly property int trayItemCount: trayList.count readonly property int trayItemCount: trayList.count
readonly property color surfaceColor: MobileShell.SurfaceColors.accentSurface(Kirigami.Theme.backgroundColor, 0.24, 0.12)
function show() { function show() {
popup.open(); popup.open();
@ -39,11 +38,11 @@ QQC2.Popup {
} }
background: Kirigami.ShadowedRectangle { background: Kirigami.ShadowedRectangle {
color: popup.surfaceColor color: Kirigami.Theme.backgroundColor
radius: Kirigami.Units.cornerRadius radius: Kirigami.Units.cornerRadius
border.color: Kirigami.ColorUtils.linearInterpolation( border.color: Kirigami.ColorUtils.linearInterpolation(
popup.surfaceColor, Kirigami.Theme.textColor, 0.2) Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.2)
border.width: 1 border.width: 1
shadow.size: Kirigami.Units.gridUnit shadow.size: Kirigami.Units.gridUnit

View file

@ -13,7 +13,7 @@ Item {
property bool active: false property bool active: false
property bool stateLayerEnabled: true property bool stateLayerEnabled: true
property color color: Kirigami.Theme.textColor property color color: Kirigami.Theme.textColor
property color activeColor: SurfaceColors.accent() property color activeColor: Kirigami.Theme.highlightColor
property real hoverOpacity: 0.08 property real hoverOpacity: 0.08
property real pressedOpacity: 0.14 property real pressedOpacity: 0.14
property real activeOpacity: 0.12 property real activeOpacity: 0.12

View file

@ -48,24 +48,15 @@ Item {
// adjust color depending on panel type // adjust color depending on panel type
property color panelColor: { property color panelColor: {
let tintPercent let tintPercent
let accentTintDark
let accentTintLight
if (panelType === PanelBackground.PanelType.Popup) { if (panelType === PanelBackground.PanelType.Popup) {
tintPercent = 0.035 tintPercent = 0.035
accentTintDark = 0.16
accentTintLight = 0.08
} else if (panelType === PanelBackground.PanelType.Base || panelType === PanelBackground.PanelType.Stacked || panelType === PanelBackground.PanelType.Flat) { } else if (panelType === PanelBackground.PanelType.Base || panelType === PanelBackground.PanelType.Stacked || panelType === PanelBackground.PanelType.Flat) {
tintPercent = 0 tintPercent = 0
accentTintDark = 0.18
accentTintLight = 0.10
} else { } else {
tintPercent = 0.06 tintPercent = 0.06
accentTintDark = 0.22
accentTintLight = 0.12
} }
const baseColor = Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, "white", tintPercent) return Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, "white", tintPercent)
return SurfaceColors.accentSurface(baseColor, accentTintDark, accentTintLight)
} }
// in some circumstances, panels can change there type // in some circumstances, panels can change there type
// for example, popup notifition when opening the popup notifition drawer // for example, popup notifition when opening the popup notifition drawer

View file

@ -1,47 +0,0 @@
// SPDX-FileCopyrightText: 2026 Marco Allegretti
// SPDX-License-Identifier: EUPL-1.2
pragma Singleton
import QtQuick
import org.kde.kirigami as Kirigami
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
QtObject {
function hasColor(color) {
return color && color.a > 0
}
function accent() {
if (hasColor(ShellSettings.Settings.accentColor)) {
return ShellSettings.Settings.accentColor
}
if ((ShellSettings.Settings.wallpaperThemeEnabled || ShellSettings.Settings.wallpaperAccentEnabled)
&& hasColor(ShellSettings.Settings.wallpaperThemeColor)) {
return ShellSettings.Settings.wallpaperThemeColor
}
return Kirigami.Theme.highlightColor
}
function mix(base, overlay, ratio) {
return Qt.rgba(
base.r + (overlay.r - base.r) * ratio,
base.g + (overlay.g - base.g) * ratio,
base.b + (overlay.b - base.b) * ratio,
base.a + (overlay.a - base.a) * ratio)
}
function withAlpha(color, alpha) {
return Qt.rgba(color.r, color.g, color.b, alpha)
}
function accentSurface(base, darkRatio, lightRatio) {
if (!ShellSettings.Settings.wallpaperThemeEnabled && !ShellSettings.Settings.wallpaperAccentEnabled) {
return base
}
const darkSurface = Kirigami.ColorUtils.brightnessForColor(base) === Kirigami.ColorUtils.Dark
return mix(base, accent(), darkSurface ? darkRatio : lightRatio)
}
}

View file

@ -57,7 +57,7 @@ Window {
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
Kirigami.Theme.inherit: false Kirigami.Theme.inherit: false
readonly property color backgroundColor: Qt.darker(MobileShell.SurfaceColors.withAlpha(MobileShell.SurfaceColors.accentSurface(Kirigami.Theme.backgroundColor, 0.18, 0.10), 0.95), 1.05) readonly property color backgroundColor: Qt.darker(Qt.rgba(Kirigami.Theme.backgroundColor.r, Kirigami.Theme.backgroundColor.g, Kirigami.Theme.backgroundColor.b, 0.95), 1.05)
color: popupDrawerOpened && visible ? backgroundColor : "transparent" color: popupDrawerOpened && visible ? backgroundColor : "transparent"
Behavior on color { Behavior on color {
MobileShell.MotionColorAnimation { MobileShell.MotionColorAnimation {

View file

@ -34,7 +34,7 @@ Window {
LayerShell.Window.layer: LayerShell.Window.LayerOverlay LayerShell.Window.layer: LayerShell.Window.LayerOverlay
LayerShell.Window.exclusionZone: -1 LayerShell.Window.exclusionZone: -1
readonly property color backgroundColor: Qt.darker(MobileShell.SurfaceColors.withAlpha(MobileShell.SurfaceColors.accentSurface(Kirigami.Theme.backgroundColor, 0.18, 0.10), 0.95), 1.05) readonly property color backgroundColor: Qt.darker(Qt.rgba(Kirigami.Theme.backgroundColor.r, Kirigami.Theme.backgroundColor.g, Kirigami.Theme.backgroundColor.b, 0.95), 1.05)
readonly property int overlayAnimationDuration: Math.round(MobileShell.Motion.duration(MobileShell.Motion.SpatialSlow) * 1.25) readonly property int overlayAnimationDuration: Math.round(MobileShell.Motion.duration(MobileShell.Motion.SpatialSlow) * 1.25)
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary Kirigami.Theme.colorSet: Kirigami.Theme.Complementary

View file

@ -57,8 +57,7 @@ MouseArea {
return Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.18) return Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.18)
} }
if (active) { if (active) {
const accent = MobileShell.SurfaceColors.accent() return Qt.rgba(Kirigami.Theme.highlightColor.r, Kirigami.Theme.highlightColor.g, Kirigami.Theme.highlightColor.b, hovered ? 0.18 : 0.12)
return Qt.rgba(accent.r, accent.g, accent.b, hovered ? 0.18 : 0.12)
} }
if (hovered) { if (hovered) {
return Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.08) return Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.08)
@ -757,7 +756,7 @@ MouseArea {
PC3.Label { PC3.Label {
anchors.centerIn: parent anchors.centerIn: parent
text: (leftDesktopBtn.index + 1).toString() text: (leftDesktopBtn.index + 1).toString()
color: leftDesktopBtn.isCurrent ? MobileShell.SurfaceColors.accent() : Kirigami.Theme.textColor color: leftDesktopBtn.isCurrent ? Kirigami.Theme.highlightColor : Kirigami.Theme.textColor
font.pixelSize: Math.round(parent.height * 0.3) font.pixelSize: Math.round(parent.height * 0.3)
font.bold: leftDesktopBtn.isCurrent font.bold: leftDesktopBtn.isCurrent
} }
@ -1488,7 +1487,7 @@ MouseArea {
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
color: MobileShell.SurfaceColors.accentSurface(Kirigami.Theme.backgroundColor, 0.24, 0.12) color: Kirigami.Theme.backgroundColor
border.color: Qt.rgba( border.color: Qt.rgba(
Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.r,
Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.g,
@ -1545,7 +1544,9 @@ MouseArea {
anchors.fill: parent anchors.fill: parent
radius: Kirigami.Units.cornerRadius radius: Kirigami.Units.cornerRadius
color: thumbEntry.containsMouse color: thumbEntry.containsMouse
? MobileShell.SurfaceColors.withAlpha(MobileShell.SurfaceColors.accent(), 0.15) ? Qt.rgba(Kirigami.Theme.highlightColor.r,
Kirigami.Theme.highlightColor.g,
Kirigami.Theme.highlightColor.b, 0.15)
: "transparent" : "transparent"
} }
@ -1789,7 +1790,7 @@ MouseArea {
width: Kirigami.Units.smallSpacing * 3 width: Kirigami.Units.smallSpacing * 3
height: Math.max(2, Math.round(Kirigami.Units.devicePixelRatio)) height: Math.max(2, Math.round(Kirigami.Units.devicePixelRatio))
radius: height / 2 radius: height / 2
color: MobileShell.SurfaceColors.accent() color: Kirigami.Theme.highlightColor
opacity: taskDelegate.dynamicTilingMaximized ? 0.95 : 0 opacity: taskDelegate.dynamicTilingMaximized ? 0.95 : 0
Behavior on opacity { Behavior on opacity {
@ -1860,7 +1861,7 @@ MouseArea {
width: taskDelegate.model.IsActive === true ? Kirigami.Units.smallSpacing * 3 : Kirigami.Units.smallSpacing * 1.5 width: taskDelegate.model.IsActive === true ? Kirigami.Units.smallSpacing * 3 : Kirigami.Units.smallSpacing * 1.5
height: Math.max(2, Math.round(Kirigami.Units.devicePixelRatio)) height: Math.max(2, Math.round(Kirigami.Units.devicePixelRatio))
radius: height / 2 radius: height / 2
color: MobileShell.SurfaceColors.accent() color: Kirigami.Theme.highlightColor
opacity: taskDelegate.model.IsActive === true ? 1.0 : 0.45 opacity: taskDelegate.model.IsActive === true ? 1.0 : 0.45
Behavior on width { Behavior on width {

View file

@ -33,7 +33,6 @@ Window {
readonly property string backButtonLabel: GamingShell.GamepadManager.buttonLabel(GamingShell.GamepadManager.ButtonB) readonly property string backButtonLabel: GamingShell.GamepadManager.buttonLabel(GamingShell.GamepadManager.ButtonB)
readonly property string closeButtonLabel: GamingShell.GamepadManager.buttonLabel(GamingShell.GamepadManager.ButtonX) readonly property string closeButtonLabel: GamingShell.GamepadManager.buttonLabel(GamingShell.GamepadManager.ButtonX)
readonly property string exitButtonLabel: GamingShell.GamepadManager.buttonLabel(GamingShell.GamepadManager.ButtonY) readonly property string exitButtonLabel: GamingShell.GamepadManager.buttonLabel(GamingShell.GamepadManager.ButtonY)
readonly property string leftShoulderLabel: GamingShell.GamepadManager.buttonLabel(GamingShell.GamepadManager.ButtonLeftShoulder) readonly property string leftShoulderLabel: GamingShell.GamepadManager.buttonLabel(GamingShell.GamepadManager.ButtonLeftShoulder)
readonly property string rightShoulderLabel: GamingShell.GamepadManager.buttonLabel(GamingShell.GamepadManager.ButtonRightShoulder) readonly property string rightShoulderLabel: GamingShell.GamepadManager.buttonLabel(GamingShell.GamepadManager.ButtonRightShoulder)
readonly property string quickSettingsButtonLabel: GamingShell.GamepadManager.buttonLabel(GamingShell.GamepadManager.ButtonBack) readonly property string quickSettingsButtonLabel: GamingShell.GamepadManager.buttonLabel(GamingShell.GamepadManager.ButtonBack)
@ -41,41 +40,6 @@ Window {
readonly property int shortAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.EffectsFast) readonly property int shortAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.EffectsFast)
readonly property int longAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.EffectsDefault) readonly property int longAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.EffectsDefault)
readonly property int launchFadeDuration: MobileShell.Motion.duration(MobileShell.Motion.StandardAccel) readonly property int launchFadeDuration: MobileShell.Motion.duration(MobileShell.Motion.StandardAccel)
readonly property int shortestSide: Math.min(width, height)
readonly property bool compactMode: !ShellSettings.Settings.convergenceModeEnabled && shortestSide <= Kirigami.Units.gridUnit * 50
readonly property bool bigScreenMode: !compactMode
readonly property int horizontalPadding: compactMode ? Kirigami.Units.largeSpacing : Kirigami.Units.largeSpacing * 2
readonly property int verticalPadding: compactMode ? Kirigami.Units.largeSpacing : Kirigami.Units.largeSpacing * 2
readonly property real gridMinCellSize: compactMode ? Kirigami.Units.gridUnit * 6.8 : Kirigami.Units.gridUnit * 8.8
// Steam library assets heavily favor wide capsules and 16:9 media surfaces.
// Keep game tiles landscape-first to avoid mobile-style portrait cards.
readonly property real capsuleArtAspect: 16 / 9
function controlLegendText() {
if (GamingShell.GamepadManager.hasGamepad) {
if (runningGames.activeFocus) {
return i18n("%1: Select %2: Close %3: Back %4: Exit %5: Settings %6: Search",
actionButtonLabel, closeButtonLabel, backButtonLabel, exitButtonLabel,
quickSettingsButtonLabel, searchButtonLabel)
}
if (recentList.activeFocus) {
return i18n("%1: Play %2: Details %3: Back %4: Exit %5: Settings %6: Search",
actionButtonLabel, closeButtonLabel, backButtonLabel, exitButtonLabel,
quickSettingsButtonLabel, searchButtonLabel)
}
return i18n("%1: Play %2: Details %3: Back %4: Exit %5/%6: Filter %7: Settings %8: Search",
actionButtonLabel, closeButtonLabel, backButtonLabel, exitButtonLabel,
leftShoulderLabel, rightShoulderLabel, quickSettingsButtonLabel, searchButtonLabel)
}
if (runningGames.activeFocus) {
return i18n("Mouse: click a running game to focus it. Keyboard: arrows move between cards, Enter selects, Esc closes.")
}
if (recentList.activeFocus) {
return i18n("Mouse: click a recent game to play it. Keyboard: arrows move between cards, Enter plays, Esc closes.")
}
return i18n("Mouse: click a game or details button. Keyboard: arrows navigate, Enter plays, I shows details, Esc closes.")
}
function pulsePrimaryGamepad(lowIntensity, highIntensity, durationMs) { function pulsePrimaryGamepad(lowIntensity, highIntensity, durationMs) {
var pad = GamingShell.GamepadManager.primaryGamepad var pad = GamingShell.GamepadManager.primaryGamepad
@ -517,7 +481,9 @@ Window {
anchors.fill: parent anchors.fill: parent
Kirigami.Theme.inherit: false Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Window Kirigami.Theme.colorSet: Kirigami.Theme.Window
color: MobileShell.SurfaceColors.withAlpha(MobileShell.SurfaceColors.accentSurface(Kirigami.Theme.backgroundColor, 0.24, 0.12), root.bigScreenMode ? 0.94 : 0.9) color: Qt.rgba(Kirigami.Theme.backgroundColor.r,
Kirigami.Theme.backgroundColor.g,
Kirigami.Theme.backgroundColor.b, 0.92)
} }
FocusScope { FocusScope {
@ -530,11 +496,8 @@ Window {
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: root.horizontalPadding anchors.margins: Kirigami.Units.largeSpacing * 2
anchors.rightMargin: root.horizontalPadding spacing: Kirigami.Units.largeSpacing
anchors.topMargin: root.verticalPadding
anchors.bottomMargin: root.verticalPadding
spacing: root.compactMode ? Kirigami.Units.smallSpacing : Kirigami.Units.largeSpacing
// ---- header ---- // ---- header ----
RowLayout { RowLayout {
@ -543,7 +506,7 @@ Window {
Kirigami.Heading { Kirigami.Heading {
text: i18n("Game Center") text: i18n("Game Center")
level: root.compactMode ? 2 : 1 level: 1
} }
Item { Layout.fillWidth: true } Item { Layout.fillWidth: true }
@ -598,7 +561,7 @@ Window {
QQC2.ToolButton { QQC2.ToolButton {
icon.name: "window-close" icon.name: "window-close"
text: root.compactMode ? i18n("Exit") : i18n("Exit Gaming Mode") text: i18n("Exit Gaming Mode")
display: QQC2.AbstractButton.TextBesideIcon display: QQC2.AbstractButton.TextBesideIcon
Keys.onReturnPressed: clicked() Keys.onReturnPressed: clicked()
Keys.onEnterPressed: clicked() Keys.onEnterPressed: clicked()
@ -610,7 +573,6 @@ Window {
RunningGamesView { RunningGamesView {
id: runningGames id: runningGames
Layout.fillWidth: true Layout.fillWidth: true
compactMode: root.compactMode
onTaskActivated: { onTaskActivated: {
GamingShell.GameLauncherProvider.clearPendingLaunch() GamingShell.GameLauncherProvider.clearPendingLaunch()
root.gameStarted() root.gameStarted()
@ -665,9 +627,7 @@ Window {
ListView { ListView {
id: recentList id: recentList
Layout.fillWidth: true Layout.fillWidth: true
readonly property int cardWidth: root.compactMode ? Kirigami.Units.gridUnit * 8 : Kirigami.Units.gridUnit * 10 Layout.preferredHeight: Kirigami.Units.gridUnit * 5
readonly property int artHeight: Math.round(cardWidth / root.capsuleArtAspect)
Layout.preferredHeight: artHeight + Kirigami.Units.gridUnit * 1.7
orientation: ListView.Horizontal orientation: ListView.Horizontal
spacing: Kirigami.Units.largeSpacing spacing: Kirigami.Units.largeSpacing
clip: true clip: true
@ -705,7 +665,7 @@ Window {
Keys.onDownPressed: grid.forceActiveFocus() Keys.onDownPressed: grid.forceActiveFocus()
delegate: QQC2.ItemDelegate { delegate: QQC2.ItemDelegate {
width: recentList.cardWidth width: Kirigami.Units.gridUnit * 7
height: recentList.height height: recentList.height
required property var modelData required property var modelData
@ -719,26 +679,16 @@ Window {
background: Rectangle { background: Rectangle {
radius: Kirigami.Units.cornerRadius radius: Kirigami.Units.cornerRadius
color: parent.isCurrent color: parent.isCurrent
? Qt.rgba(Kirigami.Theme.highlightColor.r, Kirigami.Theme.highlightColor.g, Kirigami.Theme.highlightColor.b, 0.22) ? Kirigami.Theme.highlightColor
: (parent.hovered : (parent.hovered ? Kirigami.Theme.hoverColor : "transparent")
? Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.08)
: "transparent")
border.color: parent.isCurrent ? Kirigami.Theme.highlightColor : "transparent"
border.width: parent.isCurrent ? 2 : 0
} }
contentItem: ColumnLayout { contentItem: ColumnLayout {
spacing: Kirigami.Units.smallSpacing spacing: Kirigami.Units.smallSpacing
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: recentList.artHeight
radius: Kirigami.Units.cornerRadius
clip: true
color: Qt.rgba(Kirigami.Theme.alternateBackgroundColor.r, Kirigami.Theme.alternateBackgroundColor.g, Kirigami.Theme.alternateBackgroundColor.b, 0.8)
Image { Image {
anchors.fill: parent Layout.fillWidth: true
Layout.fillHeight: true
source: hasArt ? "file://" + modelData.artwork : "" source: hasArt ? "file://" + modelData.artwork : ""
fillMode: Image.PreserveAspectCrop fillMode: Image.PreserveAspectCrop
visible: hasArt visible: hasArt
@ -746,20 +696,19 @@ Window {
} }
Kirigami.Icon { Kirigami.Icon {
anchors.centerIn: parent Layout.alignment: Qt.AlignHCenter
implicitWidth: Kirigami.Units.iconSizes.large implicitWidth: Kirigami.Units.iconSizes.large
implicitHeight: Kirigami.Units.iconSizes.large implicitHeight: Kirigami.Units.iconSizes.large
source: modelData.icon source: modelData.icon
visible: !hasArt visible: !hasArt
} }
}
PC3.Label { PC3.Label {
Layout.fillWidth: true Layout.fillWidth: true
text: modelData.name text: modelData.name
maximumLineCount: 1 maximumLineCount: 1
elide: Text.ElideRight elide: Text.ElideRight
horizontalAlignment: Text.AlignLeft horizontalAlignment: Text.AlignHCenter
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.85 font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.85
color: parent.parent.isCurrent color: parent.parent.isCurrent
? Kirigami.Theme.highlightedTextColor ? Kirigami.Theme.highlightedTextColor
@ -787,14 +736,9 @@ Window {
} }
// ---- search + filter ---- // ---- search + filter ----
Item { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: searchFilterStack.implicitHeight spacing: Kirigami.Units.largeSpacing
ColumnLayout {
id: searchFilterStack
anchors.fill: parent
spacing: Kirigami.Units.smallSpacing
Kirigami.SearchField { Kirigami.SearchField {
id: searchField id: searchField
@ -814,7 +758,6 @@ Window {
QQC2.TabBar { QQC2.TabBar {
id: sourceFilterBar id: sourceFilterBar
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Repeater { Repeater {
@ -840,7 +783,6 @@ Window {
} }
} }
} }
}
// ---- game grid ---- // ---- game grid ----
@ -852,12 +794,11 @@ Window {
model: GamingShell.GameLauncherProvider model: GamingShell.GameLauncherProvider
readonly property real minCellSize: root.gridMinCellSize readonly property real minCellSize: Kirigami.Units.gridUnit * 8
readonly property int columns: Math.max(2, Math.floor(width / minCellSize)) readonly property int columns: Math.max(2, Math.floor(width / minCellSize))
cellWidth: Math.floor(width / columns) cellWidth: Math.floor(width / columns)
readonly property int artHeight: Math.round(cellWidth / root.capsuleArtAspect) cellHeight: Math.floor(cellWidth * 1.5) + Kirigami.Units.gridUnit * 2
cellHeight: artHeight + (root.compactMode ? Kirigami.Units.gridUnit * 1.9 : Kirigami.Units.gridUnit * 2.2)
keyNavigationEnabled: true keyNavigationEnabled: true
highlightMoveDuration: 0 highlightMoveDuration: 0
@ -932,7 +873,7 @@ Window {
QQC2.ItemDelegate { QQC2.ItemDelegate {
anchors.fill: parent anchors.fill: parent
anchors.margins: root.compactMode ? 0 : Kirigami.Units.smallSpacing anchors.margins: Kirigami.Units.smallSpacing
padding: 0 padding: 0
readonly property bool isCurrent: GridView.isCurrentItem && grid.activeFocus readonly property bool isCurrent: GridView.isCurrentItem && grid.activeFocus
@ -940,30 +881,24 @@ Window {
background: Rectangle { background: Rectangle {
Kirigami.Theme.colorSet: Kirigami.Theme.Button Kirigami.Theme.colorSet: Kirigami.Theme.Button
color: parent.isCurrent color: parent.isCurrent
? Qt.rgba(Kirigami.Theme.highlightColor.r, Kirigami.Theme.highlightColor.g, ? Kirigami.Theme.highlightColor
Kirigami.Theme.highlightColor.b, 0.22) : (parent.hovered ? Kirigami.Theme.hoverColor : "transparent")
: (parent.hovered
? Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g,
Kirigami.Theme.textColor.b, 0.08)
: "transparent")
radius: Kirigami.Units.cornerRadius radius: Kirigami.Units.cornerRadius
border.color: parent.isCurrent ? Kirigami.Theme.highlightColor : "transparent"
border.width: parent.isCurrent ? 2 : 0
} }
contentItem: Item { contentItem: Item {
// ---- cover art tile ----
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
spacing: Kirigami.Units.smallSpacing spacing: 0
visible: hasArt
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: grid.artHeight Layout.fillHeight: true
radius: Kirigami.Units.cornerRadius radius: Kirigami.Units.cornerRadius
clip: true clip: true
color: Qt.rgba(Kirigami.Theme.alternateBackgroundColor.r, color: "transparent"
Kirigami.Theme.alternateBackgroundColor.g,
Kirigami.Theme.alternateBackgroundColor.b, 0.85)
Image { Image {
anchors.fill: parent anchors.fill: parent
@ -971,15 +906,6 @@ Window {
fillMode: Image.PreserveAspectCrop fillMode: Image.PreserveAspectCrop
smooth: true smooth: true
asynchronous: true asynchronous: true
visible: hasArt
}
Kirigami.Icon {
anchors.centerIn: parent
implicitWidth: root.compactMode ? Kirigami.Units.iconSizes.large : Kirigami.Units.iconSizes.huge
implicitHeight: implicitWidth
source: icon
visible: !hasArt
} }
Rectangle { Rectangle {
@ -1003,35 +929,76 @@ Window {
} }
} }
ColumnLayout { // Title beneath artwork
Layout.fillWidth: true
spacing: 0
PC3.Label { PC3.Label {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
text: name text: name
maximumLineCount: 1 maximumLineCount: 1
elide: Text.ElideRight elide: Text.ElideRight
horizontalAlignment: Text.AlignLeft horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
leftPadding: Kirigami.Units.smallSpacing leftPadding: Kirigami.Units.smallSpacing
rightPadding: Kirigami.Units.smallSpacing rightPadding: Kirigami.Units.smallSpacing
color: parent.parent.parent.isCurrent color: parent.parent.parent.isCurrent
? Kirigami.Theme.highlightedTextColor ? Kirigami.Theme.highlightedTextColor
: Kirigami.Theme.textColor : Kirigami.Theme.textColor
} }
}
// ---- fallback icon tile ----
ColumnLayout {
anchors.fill: parent
anchors.margins: Kirigami.Units.smallSpacing
visible: !hasArt
spacing: Kirigami.Units.smallSpacing
Item { Layout.fillHeight: true }
Kirigami.Icon {
Layout.alignment: Qt.AlignHCenter
implicitWidth: Kirigami.Units.iconSizes.huge
implicitHeight: Kirigami.Units.iconSizes.huge
source: icon
scale: parent.parent.parent.isCurrent ? 1.08 : 1.0
Behavior on scale {
MobileShell.MotionNumberAnimation { type: MobileShell.Motion.EffectsFast; duration: root.shortAnimationDuration }
}
}
PC3.Label { PC3.Label {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true Layout.fillWidth: true
visible: lastPlayedText.length > 0 text: name
text: lastPlayedText maximumLineCount: 2
maximumLineCount: 1 wrapMode: Text.Wrap
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight elide: Text.ElideRight
leftPadding: Kirigami.Units.smallSpacing color: parent.parent.parent.isCurrent
rightPadding: Kirigami.Units.smallSpacing ? Kirigami.Theme.highlightedTextColor
opacity: 0.65 : Kirigami.Theme.textColor
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.78 }
Rectangle {
Layout.alignment: Qt.AlignHCenter
visible: source !== "desktop"
radius: height / 2
color: root.sourceChipColor(source)
implicitHeight: sourceChipLabel.implicitHeight + Kirigami.Units.smallSpacing
implicitWidth: sourceChipLabel.implicitWidth + Kirigami.Units.largeSpacing
PC3.Label {
id: sourceChipLabel
anchors.centerIn: parent
text: root.sourceLabel(source)
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.72
font.weight: Font.DemiBold
color: "white"
} }
} }
Item { Layout.fillHeight: true }
} }
} }
@ -1082,7 +1049,6 @@ Window {
Repeater { Repeater {
model: GamingShell.GamepadManager model: GamingShell.GamepadManager
visible: root.bigScreenMode
RowLayout { RowLayout {
spacing: Kirigami.Units.smallSpacing spacing: Kirigami.Units.smallSpacing
@ -1120,12 +1086,19 @@ Window {
// Gamepad legend // Gamepad legend
PC3.Label { PC3.Label {
Layout.fillWidth: true text: runningGames.activeFocus
text: root.controlLegendText() ? i18n("%1: Select %2: Close %3: Back %4: Exit %5: Settings %6: Search",
font.pointSize: Kirigami.Theme.defaultFont.pointSize * (root.compactMode ? 0.7 : 0.75) actionButtonLabel, closeButtonLabel, backButtonLabel, exitButtonLabel,
quickSettingsButtonLabel, searchButtonLabel)
: recentList.activeFocus
? i18n("%1: Play %2: Details %3: Back %4: Exit %5: Settings %6: Search",
actionButtonLabel, closeButtonLabel, backButtonLabel, exitButtonLabel,
quickSettingsButtonLabel, searchButtonLabel)
: i18n("%1: Play %2: Details %3: Back %4: Exit %5/%6: Filter %7: Settings %8: Search",
actionButtonLabel, closeButtonLabel, backButtonLabel, exitButtonLabel,
leftShoulderLabel, rightShoulderLabel, quickSettingsButtonLabel, searchButtonLabel)
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.75
opacity: 0.5 opacity: 0.5
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignRight
} }
} }
} }
@ -1135,7 +1108,6 @@ Window {
GamingQuickSettings { GamingQuickSettings {
id: quickSettings id: quickSettings
z: 50 z: 50
compactMode: root.compactMode
} }
// Launch transition: brief fade to black, then dismiss // Launch transition: brief fade to black, then dismiss

View file

@ -21,19 +21,10 @@ Item {
id: root id: root
anchors.fill: parent anchors.fill: parent
property bool compactMode: false
property bool opened: false property bool opened: false
readonly property string acceptButtonLabel: GamingShell.GamepadManager.buttonLabel(GamingShell.GamepadManager.ButtonA) readonly property string acceptButtonLabel: GamingShell.GamepadManager.buttonLabel(GamingShell.GamepadManager.ButtonA)
readonly property string closeButtonLabel: GamingShell.GamepadManager.buttonLabel(GamingShell.GamepadManager.ButtonB) readonly property string closeButtonLabel: GamingShell.GamepadManager.buttonLabel(GamingShell.GamepadManager.ButtonB)
function controlLegendText() {
if (GamingShell.GamepadManager.hasGamepad) {
return i18n("↕: Navigate ↔: Adjust %1: Toggle %2: Close", acceptButtonLabel, closeButtonLabel)
}
return i18n("↑↓: Navigate ←→: Adjust Enter: Toggle Esc: Close")
}
function pulsePrimaryGamepad(lowIntensity, highIntensity, durationMs) { function pulsePrimaryGamepad(lowIntensity, highIntensity, durationMs) {
var pad = GamingShell.GamepadManager.primaryGamepad var pad = GamingShell.GamepadManager.primaryGamepad
if (!pad || !pad.hasRumble) { if (!pad || !pad.hasRumble) {
@ -151,9 +142,7 @@ Item {
// Panel sliding in from the right // Panel sliding in from the right
Rectangle { Rectangle {
id: panel id: panel
width: root.compactMode width: Math.min(root.width * 0.35, Kirigami.Units.gridUnit * 22)
? Math.min(root.width * 0.92, Kirigami.Units.gridUnit * 26)
: Math.min(root.width * 0.35, Kirigami.Units.gridUnit * 24)
height: root.height height: root.height
anchors.top: root.top anchors.top: root.top
anchors.bottom: root.bottom anchors.bottom: root.bottom
@ -166,7 +155,9 @@ Item {
Kirigami.Theme.inherit: false Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Window Kirigami.Theme.colorSet: Kirigami.Theme.Window
color: MobileShell.SurfaceColors.withAlpha(MobileShell.SurfaceColors.accentSurface(Kirigami.Theme.backgroundColor, 0.24, 0.12), 0.96) color: Qt.rgba(Kirigami.Theme.backgroundColor.r,
Kirigami.Theme.backgroundColor.g,
Kirigami.Theme.backgroundColor.b, 0.96)
// Subtle left border // Subtle left border
Rectangle { Rectangle {
@ -192,7 +183,7 @@ Item {
Flickable { Flickable {
anchors.fill: parent anchors.fill: parent
anchors.margins: root.compactMode ? Kirigami.Units.largeSpacing : Kirigami.Units.largeSpacing * 2 anchors.margins: Kirigami.Units.largeSpacing * 2
contentHeight: settingsColumn.implicitHeight contentHeight: settingsColumn.implicitHeight
clip: true clip: true
@ -699,11 +690,11 @@ Item {
// ---- Gamepad legend ---- // ---- Gamepad legend ----
PC3.Label { PC3.Label {
Layout.fillWidth: true Layout.fillWidth: true
text: root.controlLegendText() text: i18n("↕: Navigate ↔: Adjust %1: Toggle %2: Close",
acceptButtonLabel, closeButtonLabel)
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.8 font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.8
opacity: 0.5 opacity: 0.5
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
} }
Item { Layout.fillHeight: true } Item { Layout.fillHeight: true }

View file

@ -15,8 +15,6 @@ import plasma.applet.org.kde.plasma.mobile.homescreen.folio as Folio
Item { Item {
id: root id: root
property bool compactMode: false
implicitHeight: taskList.count > 0 ? column.implicitHeight : 0 implicitHeight: taskList.count > 0 ? column.implicitHeight : 0
readonly property bool hasTasks: taskList.count > 0 readonly property bool hasTasks: taskList.count > 0
readonly property int taskCount: taskList.count readonly property int taskCount: taskList.count
@ -75,7 +73,7 @@ Item {
visible: taskList.count > 0 visible: taskList.count > 0
Kirigami.Heading { Kirigami.Heading {
level: root.compactMode ? 3 : 2 level: 2
text: i18n("Running") text: i18n("Running")
} }
@ -122,8 +120,8 @@ Item {
required property var decoration required property var decoration
required property var winIdList required property var winIdList
width: root.compactMode ? Kirigami.Units.gridUnit * 6.4 : Kirigami.Units.gridUnit * 8 width: Kirigami.Units.gridUnit * 8
height: root.compactMode ? Kirigami.Units.gridUnit * 4.2 : Kirigami.Units.gridUnit * 5 height: Kirigami.Units.gridUnit * 5
readonly property var modelIndex: tasks.makeModelIndex(index) readonly property var modelIndex: tasks.makeModelIndex(index)
readonly property bool isCurrent: ListView.isCurrentItem && taskList.activeFocus readonly property bool isCurrent: ListView.isCurrentItem && taskList.activeFocus

View file

@ -44,7 +44,6 @@ ContainmentItem {
// State saved when gaming mode activates, restored when it deactivates // State saved when gaming mode activates, restored when it deactivates
property string _savedPowerProfile: "" property string _savedPowerProfile: ""
property bool _savedDnd: false property bool _savedDnd: false
property bool _savedDynamicTiling: false
property bool _gamingSessionActive: false property bool _gamingSessionActive: false
function _applyGamingModeState(enabled) { function _applyGamingModeState(enabled) {
@ -58,20 +57,7 @@ ContainmentItem {
if (enabled) { if (enabled) {
// Save current state and apply gaming optimizations // Save current state and apply gaming optimizations
root._savedDnd = MobileShellState.ShellDBusClient.doNotDisturb root._savedDnd = MobileShellState.ShellDBusClient.doNotDisturb
root._savedDynamicTiling = ShellSettings.Settings.dynamicTilingEnabled
root._gamingSessionActive = true
MobileShellState.ShellDBusClient.doNotDisturb = true MobileShellState.ShellDBusClient.doNotDisturb = true
ShellSettings.Settings.dynamicTilingEnabled = false
if (MobileShellState.ShellDBusClient.isActionDrawerOpen) {
MobileShellState.ShellDBusClient.closeActionDrawer()
}
folio.HomeScreenState.closeFolder()
folio.HomeScreenState.closeSearchWidget()
folio.HomeScreenState.closeAppDrawer()
if (folio.HomeScreenState.viewState === Folio.HomeScreenState.SettingsView) {
folio.HomeScreenState.closeSettingsView()
}
if (GamingShell.PowerProfileControl.available) { if (GamingShell.PowerProfileControl.available) {
root._savedPowerProfile = GamingShell.PowerProfileControl.activeProfile root._savedPowerProfile = GamingShell.PowerProfileControl.activeProfile
@ -79,17 +65,17 @@ ContainmentItem {
} }
GamingShell.GameModeControl.requestStart() GamingShell.GameModeControl.requestStart()
root._gamingSessionActive = true
} else { } else {
// Restore previous state // Restore previous state
root._gamingSessionActive = false
MobileShellState.ShellDBusClient.doNotDisturb = root._savedDnd MobileShellState.ShellDBusClient.doNotDisturb = root._savedDnd
ShellSettings.Settings.dynamicTilingEnabled = root._savedDynamicTiling
if (GamingShell.PowerProfileControl.available && root._savedPowerProfile.length > 0) { if (GamingShell.PowerProfileControl.available && root._savedPowerProfile.length > 0) {
GamingShell.PowerProfileControl.activeProfile = root._savedPowerProfile GamingShell.PowerProfileControl.activeProfile = root._savedPowerProfile
} }
GamingShell.GameModeControl.requestEnd() GamingShell.GameModeControl.requestEnd()
root._gamingSessionActive = false
} }
} }
@ -265,7 +251,7 @@ ContainmentItem {
// Convergence: no scrim (popup has own background); mobile: dark scrim // Convergence: no scrim (popup has own background); mobile: dark scrim
color: ShellSettings.Settings.convergenceModeEnabled color: ShellSettings.Settings.convergenceModeEnabled
? "transparent" ? "transparent"
: MobileShell.SurfaceColors.withAlpha(MobileShell.SurfaceColors.accentSurface(Kirigami.Theme.backgroundColor, 0.18, 0.10), 0.46) : Qt.rgba(Kirigami.Theme.backgroundColor.r, Kirigami.Theme.backgroundColor.g, Kirigami.Theme.backgroundColor.b, 0.46)
opacity: folio.HomeScreenState.appDrawerOpenProgress opacity: folio.HomeScreenState.appDrawerOpenProgress
} }
@ -275,7 +261,7 @@ ContainmentItem {
anchors.fill: parent anchors.fill: parent
Kirigami.Theme.inherit: false Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Window Kirigami.Theme.colorSet: Kirigami.Theme.Window
color: MobileShell.SurfaceColors.withAlpha(MobileShell.SurfaceColors.accentSurface(Kirigami.Theme.backgroundColor, 0.18, 0.10), 0.30) color: Qt.rgba(Kirigami.Theme.backgroundColor.r, Kirigami.Theme.backgroundColor.g, Kirigami.Theme.backgroundColor.b, 0.30)
opacity: folio.HomeScreenState.searchWidgetOpenProgress opacity: folio.HomeScreenState.searchWidgetOpenProgress
} }
@ -285,7 +271,7 @@ ContainmentItem {
anchors.fill: parent anchors.fill: parent
Kirigami.Theme.inherit: false Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Window Kirigami.Theme.colorSet: Kirigami.Theme.Window
color: MobileShell.SurfaceColors.withAlpha(MobileShell.SurfaceColors.accentSurface(Kirigami.Theme.backgroundColor, 0.18, 0.10), 0.30) color: Qt.rgba(Kirigami.Theme.backgroundColor.r, Kirigami.Theme.backgroundColor.g, Kirigami.Theme.backgroundColor.b, 0.30)
opacity: folio.HomeScreenState.settingsOpenProgress opacity: folio.HomeScreenState.settingsOpenProgress
} }
@ -362,7 +348,7 @@ ContainmentItem {
readonly property real rightFrameBulgeEdgeTopY: rightFrameBulgeApexY - rightFrameBulgeHalfLength readonly property real rightFrameBulgeEdgeTopY: rightFrameBulgeApexY - rightFrameBulgeHalfLength
readonly property real rightFrameBulgeEdgeBottomY: rightFrameBulgeApexY + rightFrameBulgeHalfLength readonly property real rightFrameBulgeEdgeBottomY: rightFrameBulgeApexY + rightFrameBulgeHalfLength
readonly property real rightFrameBulgeTangent: rightFrameBulgeHalfLength * 0.55 readonly property real rightFrameBulgeTangent: rightFrameBulgeHalfLength * 0.55
readonly property color chromeColor: MobileShell.SurfaceColors.accentSurface(Kirigami.Theme.backgroundColor, 0.32, 0.18) 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 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)
@ -1444,7 +1430,7 @@ ContainmentItem {
id: drawerSurfacePath id: drawerSurfacePath
readonly property real cornerRadius: drawerSurface.cornerRadius readonly property real cornerRadius: drawerSurface.cornerRadius
fillColor: MobileShell.SurfaceColors.accentSurface(Kirigami.Theme.backgroundColor, 0.32, 0.18) fillColor: Kirigami.Theme.backgroundColor
strokeWidth: 0 strokeWidth: 0
startX: 0 startX: 0
startY: 0 startY: 0

View file

@ -4,6 +4,7 @@
import QtQuick import QtQuick
import QtQuick.Shapes import QtQuick.Shapes
import org.kde.kwin.decoration import org.kde.kwin.decoration
import org.kde.plasma.private.mobileshell as MobileShell
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
Decoration { Decoration {
@ -23,7 +24,7 @@ Decoration {
readonly property int normalCornerRadius: 8 readonly property int normalCornerRadius: 8
readonly property int cornerRadius: decoration.client.maximized ? 0 : normalCornerRadius readonly property int cornerRadius: decoration.client.maximized ? 0 : normalCornerRadius
readonly property int frameThickness: decoration.client.maximized ? 0 : normalCornerRadius readonly property int frameThickness: decoration.client.maximized ? 0 : normalCornerRadius
readonly property int shortAnimationDuration: 120 readonly property int shortAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.EffectsFast)
readonly property bool windowMenuAllowed: !ShellSettings.Settings.convergenceModeEnabled readonly property bool windowMenuAllowed: !ShellSettings.Settings.convergenceModeEnabled
|| ShellSettings.Settings.gamingModeEnabled || ShellSettings.Settings.gamingModeEnabled
|| !ShellSettings.Settings.dynamicTilingEnabled || !ShellSettings.Settings.dynamicTilingEnabled
@ -93,7 +94,7 @@ Decoration {
height: root.barHeight height: root.barHeight
radius: root.cornerRadius radius: root.cornerRadius
color: decoration.client.active ? root.activeBar : root.inactiveBar color: decoration.client.active ? root.activeBar : root.inactiveBar
Behavior on color { ColorAnimation { duration: root.shortAnimationDuration; easing.type: Easing.OutCubic } } Behavior on color { MobileShell.MotionColorAnimation { type: MobileShell.Motion.EffectsFast; duration: root.shortAnimationDuration } }
// Square off bottom half only top corners are rounded // Square off bottom half only top corners are rounded
Rectangle { Rectangle {
@ -134,7 +135,7 @@ Decoration {
elide: Text.ElideMiddle elide: Text.ElideMiddle
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
renderType: Text.NativeRendering renderType: Text.NativeRendering
Behavior on color { ColorAnimation { duration: root.shortAnimationDuration; easing.type: Easing.OutCubic } } Behavior on color { MobileShell.MotionColorAnimation { type: MobileShell.Motion.EffectsFast; duration: root.shortAnimationDuration } }
} }
Row { Row {
@ -242,7 +243,7 @@ Decoration {
color: parent.pressed ? Qt.darker(parent.hoverColor, 1.3) color: parent.pressed ? Qt.darker(parent.hoverColor, 1.3)
: parent.hovered ? parent.hoverColor : parent.hovered ? parent.hoverColor
: parent.normalColor : parent.normalColor
Behavior on color { ColorAnimation { duration: root.shortAnimationDuration; easing.type: Easing.OutCubic } } Behavior on color { MobileShell.MotionColorAnimation { type: MobileShell.Motion.EffectsFast; duration: root.shortAnimationDuration } }
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
@ -251,7 +252,7 @@ Decoration {
font.pixelSize: Math.round(parent.width * 0.66) font.pixelSize: Math.round(parent.width * 0.66)
font.weight: Font.Bold font.weight: Font.Bold
opacity: 1.0 opacity: 1.0
Behavior on opacity { NumberAnimation { duration: root.shortAnimationDuration; easing.type: Easing.OutCubic } } Behavior on opacity { MobileShell.MotionNumberAnimation { type: MobileShell.Motion.EffectsFast; duration: root.shortAnimationDuration } }
} }
} }
} }

View file

@ -14,6 +14,7 @@
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import org.kde.kwin as KWinComponents import org.kde.kwin as KWinComponents
import org.kde.plasma.private.mobileshell as MobileShell
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
KWinComponents.SceneEffect { KWinComponents.SceneEffect {
@ -38,7 +39,7 @@ KWinComponents.SceneEffect {
readonly property int panelScreenMargin: 8 readonly property int panelScreenMargin: 8
readonly property int panelCursorGap: 12 readonly property int panelCursorGap: 12
readonly property int panelCursorRightBias: 34 readonly property int panelCursorRightBias: 34
readonly property int hoverAnimationDuration: 100 readonly property int hoverAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.Press)
property var hoverWindowId: null property var hoverWindowId: null
property int hoverTicks: 0 property int hoverTicks: 0
property string hoverWindowStateKey: "" property string hoverWindowStateKey: ""
@ -650,7 +651,7 @@ KWinComponents.SceneEffect {
property bool hovered: false property bool hovered: false
Behavior on color { ColorAnimation { duration: effect.hoverAnimationDuration; easing.type: Easing.OutCubic } } Behavior on color { MobileShell.MotionColorAnimation { type: MobileShell.Motion.Press; duration: effect.hoverAnimationDuration } }
Rectangle { Rectangle {
id: previewFrame id: previewFrame

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

View file

@ -1,2 +0,0 @@
SPDX-FileCopyrightText: 2026 Marco Allegretti
SPDX-License-Identifier: EUPL-1.2

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

View file

@ -1,2 +0,0 @@
SPDX-FileCopyrightText: 2026 Marco Allegretti
SPDX-License-Identifier: EUPL-1.2

View file

@ -27,12 +27,6 @@ kcm_main=kcms/mobileshell/ui/main.qml
kcm_appearance=kcms/mobileshell/ui/AppearanceForm.qml kcm_appearance=kcms/mobileshell/ui/AppearanceForm.qml
desktop_view=shell/contents/views/Desktop.qml desktop_view=shell/contents/views/Desktop.qml
kcm_metadata=kcms/mobileshell/kcm_mobileshell.json kcm_metadata=kcms/mobileshell/kcm_mobileshell.json
panel_background=components/mobileshell/qml/components/PanelBackground.qml
motion_state_layer=components/mobileshell/qml/components/MotionStateLayer.qml
surface_colors=components/mobileshell/qml/components/SurfaceColors.qml
folio_main=containments/homescreens/folio/qml/main.qml
favourites_bar=containments/homescreens/folio/qml/FavouritesBar.qml
landscape_drawer=components/mobileshell/qml/actiondrawer/private/LandscapeContentContainer.qml
require_line "$settings_header" 'Q_PROPERTY\(QString colorScheme READ colorScheme NOTIFY colorSchemeChanged\)' \ require_line "$settings_header" 'Q_PROPERTY\(QString colorScheme READ colorScheme NOTIFY colorSchemeChanged\)' \
"mobile shell settings must expose the active KDE color scheme" "mobile shell settings must expose the active KDE color scheme"
@ -154,33 +148,6 @@ require_line "$desktop_view" 'ShellSettings\.Settings\.applyWallpaperThemeColor\
"desktop wallpaper color extraction must feed Shift's wallpaper theme mode" "desktop wallpaper color extraction must feed Shift's wallpaper theme mode"
require_line "$desktop_view" 'onWallpaperThemeEnabledChanged' \ require_line "$desktop_view" 'onWallpaperThemeEnabledChanged' \
"desktop wallpaper color extraction must react when wallpaper theme mode is toggled" "desktop wallpaper color extraction must react when wallpaper theme mode is toggled"
require_line "$surface_colors" '!ShellSettings\.Settings\.wallpaperThemeEnabled && !ShellSettings\.Settings\.wallpaperAccentEnabled' \
"shared shell surface color helper must react to wallpaper/accent theming"
require_line "$surface_colors" 'ShellSettings\.Settings\.accentColor' \
"shared shell surface color helper must prefer the applied KDE accent color"
require_line "$surface_colors" 'ShellSettings\.Settings\.wallpaperThemeColor' \
"shared shell surface color helper must fall back to the extracted wallpaper color"
require_line "$panel_background" 'SurfaceColors\.accentSurface\(baseColor, accentTintDark, accentTintLight\)' \
"shared shell panel backgrounds must react to wallpaper/accent theming"
require_line "$surface_colors" 'mix\(base, accent\(\), darkSurface \? darkRatio : lightRatio\)' \
"shared shell panel backgrounds must tint surfaces from the active accent color"
require_line "$motion_state_layer" 'property color activeColor: SurfaceColors\.accent\(\)' \
"shared active state layers must use the same accent source as shell surfaces"
require_line "$folio_main" 'readonly property color chromeColor: MobileShell\.SurfaceColors\.accentSurface\(Kirigami\.Theme\.backgroundColor, 0\.32, 0\.18\)' \
"convergence top bar, frame, and dock chrome must tint from the active accent color"
require_line "$surface_colors" '!ShellSettings\.Settings\.wallpaperThemeEnabled && !ShellSettings\.Settings\.wallpaperAccentEnabled' \
"convergence chrome must react to wallpaper/accent theming mode"
require_line "$folio_main" 'fillColor: MobileShell\.SurfaceColors\.accentSurface\(Kirigami\.Theme\.backgroundColor, 0\.32, 0\.18\)' \
"convergence app drawer surface must tint from the active accent color"
require_line "$landscape_drawer" 'fillColor: MobileShell\.SurfaceColors\.accentSurface\(Kirigami\.Theme\.backgroundColor, 0\.32, 0\.18\)' \
"convergence action drawer surface must tint from the active accent color"
require_line "$favourites_bar" 'color: MobileShell\.SurfaceColors\.accentSurface\(Kirigami\.Theme\.backgroundColor, 0\.24, 0\.12\)' \
"dock thumbnail popup surfaces must tint from the active accent color"
require_line "$favourites_bar" 'color: leftDesktopBtn\.isCurrent \? MobileShell\.SurfaceColors\.accent\(\) : Kirigami\.Theme\.textColor' \
"dock workspace pager labels must use the same accent source as shell surfaces"
if grep -Eq -- 'Kirigami\.Theme\.highlightColor' "$favourites_bar"; then
fail "convergence dock must not use Kirigami.Theme.highlightColor directly; use MobileShell.SurfaceColors.accent()"
fi
require_line tests/CMakeLists.txt 'NAME shift-dynamic-theming' \ require_line tests/CMakeLists.txt 'NAME shift-dynamic-theming' \
"dynamic theming regression test must be registered with CTest" "dynamic theming regression test must be registered with CTest"