mobileshellstate: Heavily refactor to remove global shell margins to fix window binding loops

Having a global set margins and orientation (that were calculated from the panel containment) caused a lot of issues with the way bindings were evaluated across panels, and with high coupling.

Now use properties from within containments to determine shell margins instead, which removes the dependency on other containments for measurements. This allows us to get rid of TaskPanelControls as well!

Fixes: https://invent.kde.org/teams/plasma-mobile/issues/-/issues/198
This commit is contained in:
Devin Lin 2022-12-10 21:05:13 -05:00
parent 917a972e83
commit bdcbe4d6f7
19 changed files with 177 additions and 158 deletions

View file

@ -26,6 +26,12 @@ Item {
clip: true
required property var actionDrawer
required property int mode
enum Mode {
Pages,
ScrollView
}
readonly property real columns: Math.round(Util.applyMinMaxRange(3, 6, width / intendedColumnWidth))
readonly property real columnWidth: Math.floor(width / columns)
@ -47,15 +53,14 @@ Item {
readonly property int columnCount: Math.floor(width/columnWidth)
readonly property int rowCount: {
let totalRows = Math.ceil(quickSettingsCount / columnCount);
let isPortrait = MobileShellState.Shell.orientation === MobileShellState.Shell.Portrait;
if (isPortrait) {
if (root.mode === QuickSettings.Pages) {
// portrait orientation
let maxRows = 5; // more than 5 is just disorienting
let targetRows = Math.floor(Window.height * 0.65 / rowHeight);
return Math.min(maxRows, Math.min(totalRows, targetRows));
} else {
} else if (root.mode === QuickSettings.ScrollView) {
// horizontal orientation
let targetRows = Math.floor(Window.height * 0.8 / rowHeight);
return Math.min(totalRows, targetRows);
@ -66,7 +71,7 @@ Item {
readonly property int quickSettingsCount: quickSettingsModel.count
function resetSwipeView() {
if (MobileShellState.Shell.orientation === MobileShellState.Shell.Portrait) {
if (root.mode === QuickSettings.Pages) {
pageLoader.item.view.currentIndex = 0;
}
}
@ -101,7 +106,7 @@ Item {
Layout.minimumHeight: rowCount * rowHeight
asynchronous: true
sourceComponent: MobileShellState.Shell.orientation === MobileShellState.Shell.Portrait ? swipeViewComponent : scrollViewComponent
sourceComponent: root.mode === QuickSettings.Pages ? swipeViewComponent : scrollViewComponent
}
BrightnessItem {

View file

@ -97,6 +97,7 @@ Components.BaseItem {
Layout.topMargin: PlasmaCore.Units.smallSpacing
Layout.fillWidth: true
mode: QuickSettings.Pages
actionDrawer: root.actionDrawer
minimizedViewProgress: 1 - root.minimizedToFullProgress
fullViewProgress: root.minimizedToFullProgress

View file

@ -78,6 +78,7 @@ Components.BaseItem {
QuickSettings {
id: quickSettings
mode: QuickSettings.ScrollView
width: column.width
implicitHeight: quickSettings.fullHeight

View file

@ -44,9 +44,18 @@ Item {
* Whether a component is being shown on top of the homescreen within the same
* window.
*/
property bool overlayShown: taskSwitcher.visible || startupFeedback.visible
readonly property bool overlayShown: taskSwitcher.visible || startupFeedback.visible
/**
* Margins for the homescreen, taking panels into account.
*/
readonly property real topMargin: plasmoid.availableScreenRect.y
readonly property real bottomMargin: root.height - (plasmoid.availableScreenRect.y + plasmoid.availableScreenRect.height)
readonly property real leftMargin: plasmoid.availableScreenRect.x
readonly property real rightMargin: root.width - (plasmoid.availableScreenRect.x + plasmoid.availableScreenRect.width)
//BEGIN API implementation
Connections {
target: MobileShellState.HomeScreenControls
@ -210,6 +219,11 @@ Item {
id: taskSwitcher
z: 999999
topMargin: root.topMargin
bottomMargin: root.bottomMargin
leftMargin: root.leftMargin
rightMargin: root.rightMargin
tasksModel: TaskManager.TasksModel {
groupMode: TaskManager.TasksModel.GroupDisabled

View file

@ -14,14 +14,16 @@ import org.kde.plasma.private.mobileshell.state 1.0 as MobileShellState
Item {
id: root
required property real shellTopMargin
required property real shellBottomMargin
required property var taskSwitcher
readonly property var taskSwitcherState: taskSwitcher.taskSwitcherState
// account for system header and footer offset (center the preview image)
readonly property real taskY: {
let headerHeight = MobileShellState.Shell.topMargin;
let footerHeight = MobileShellState.Shell.bottomMargin;
let headerHeight = shellTopMargin;
let footerHeight = shellBottomMargin;
let diff = headerHeight - footerHeight;
let baseY = (taskSwitcher.height / 2) - (taskSwitcherState.taskHeight / 2) - (taskSwitcherState.taskHeaderHeight / 2)

View file

@ -27,6 +27,14 @@ Item {
visible: false
opacity: 0
/**
* Margins for the content (taking shell panels into account).
*/
required property real topMargin
required property real bottomMargin
required property real leftMargin
required property real rightMargin
// state object
property var taskSwitcherState: TaskSwitcherState {
taskSwitcher: root
@ -201,10 +209,10 @@ Item {
// provide shell margins
anchors.fill: parent
anchors.leftMargin: MobileShellState.Shell.leftMargin
anchors.rightMargin: MobileShellState.Shell.rightMargin
anchors.bottomMargin: MobileShellState.Shell.bottomMargin
anchors.topMargin: MobileShellState.Shell.topMargin
anchors.leftMargin: root.leftMargin
anchors.rightMargin: root.rightMargin
anchors.bottomMargin: root.bottomMargin
anchors.topMargin: root.topMargin
FlickContainer {
id: flickable
@ -216,6 +224,8 @@ Item {
// the item is effectively anchored to the flickable bounds
TaskList {
id: taskList
shellTopMargin: root.topMargin
shellBottomMargin: root.bottomMargin
taskSwitcher: root

View file

@ -77,8 +77,8 @@ QtObject {
// ~~ measurement constants ~~
// dimensions of a real window on the screen
readonly property real windowHeight: taskSwitcher.height - MobileShellState.Shell.topMargin - MobileShellState.Shell.bottomMargin
readonly property real windowWidth: taskSwitcher.width - MobileShellState.Shell.leftMargin - MobileShellState.Shell.rightMargin
readonly property real windowHeight: taskSwitcher.height - taskSwitcher.topMargin - taskSwitcher.bottomMargin
readonly property real windowWidth: taskSwitcher.width - taskSwitcher.leftMargin - taskSwitcher.rightMargin
// dimensions of the task previews
readonly property real previewHeight: windowHeight * scalingFactor

View file

@ -29,6 +29,12 @@ import "../../components" as Components
Item {
id: root
// content margins (background ignores this)
property real topMargin: 0
property real bottomMargin: 0
property real leftMargin: 0
property real rightMargin: 0
function startGesture() {
queryField.text = "";
flickable.contentY = closedContentY;
@ -76,10 +82,10 @@ Item {
id: flickable
anchors.fill: parent
anchors.topMargin: MobileShellState.Shell.topMargin
anchors.bottomMargin: MobileShellState.Shell.bottomMargin
anchors.leftMargin: MobileShellState.Shell.leftMargin
anchors.rightMargin: MobileShellState.Shell.rightMargin
anchors.topMargin: root.topMargin
anchors.bottomMargin: root.bottomMargin
anchors.leftMargin: root.leftMargin
anchors.rightMargin: root.rightMargin
contentHeight: flickable.height + root.closedContentY + 999999
contentY: root.closedContentY

View file

@ -18,6 +18,5 @@ void MobileShellStatePlugin::registerTypes(const char *uri)
// /
qmlRegisterSingletonType(resolvePath("HomeScreenControls.qml"), uri, 1, 0, "HomeScreenControls");
qmlRegisterSingletonType(resolvePath("Shell.qml"), uri, 1, 0, "Shell");
qmlRegisterSingletonType(resolvePath("TaskPanelControls.qml"), uri, 1, 0, "TaskPanelControls");
qmlRegisterSingletonType(resolvePath("TopPanelControls.qml"), uri, 1, 0, "TopPanelControls");
}

View file

@ -5,13 +5,15 @@
import QtQuick 2.12
import QtQuick.Window 2.2
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
pragma Singleton
/**
* Provides access to the homescreen plasmoid containment within the shell.
*/
QtObject {
id: delegate
id: root
signal openHomeScreen()
signal resetHomeScreenPosition()
@ -22,6 +24,16 @@ QtObject {
property var taskSwitcher
property QtObject homeScreenWindow
property bool homeScreenVisible: true
property bool taskSwitcherVisible: false
// this state is updated from WindowUtil
property bool homeScreenVisible: true
property var windowListener: Connections {
target: MobileShell.WindowUtil
function onAllWindowsMinimizedChanged() {
root.homeScreenVisible = MobileShell.WindowUtil.allWindowsMinimized
}
}
}

View file

@ -12,36 +12,6 @@ pragma Singleton
QtObject {
id: delegate
/**
* Top margin from the screen edge where application windows would display.
*/
readonly property real topMargin: TopPanelControls.panelHeight
/**
* Bottom margin from the screen edge where application windows would display.
*/
readonly property real bottomMargin: TaskPanelControls.isPortrait ? TaskPanelControls.panelHeight : 0
/**
* Left margin from the screen edge where application windows would display.
*/
readonly property real leftMargin: 0
/**
* Right margin from the screen edge where application windows would display.
*/
readonly property real rightMargin: !TaskPanelControls.isPortrait ? TaskPanelControls.panelWidth : 0
/**
* Orientation of the mobile device.
*/
readonly property int orientation: TaskPanelControls.isPortrait ? Shell.Portrait : Shell.Landscape
enum Orientation {
Landscape,
Portrait
}
/**
* Whether the task switcher is open.
*/

View file

@ -1,22 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.12
import org.kde.plasma.core 2.0 as PlasmaCore
pragma Singleton
/**
* Provides access to the taskpanel plasmoid containment within the shell.
* Properties are updated by the taskpanel containment.
*/
QtObject {
id: root
property bool isPortrait
property real panelHeight
property real panelWidth
}

View file

@ -6,9 +6,7 @@
<qresource prefix="/org/kde/plasma/private/mobileshell/state/">
<file>qml/HomeScreenControls.qml</file>
<file>qml/Shell.qml</file>
<file>qml/TaskPanelControls.qml</file>
<file>qml/TopPanelControls.qml</file>
</qresource>
</RCC>

View file

@ -26,6 +26,11 @@ import org.kde.phone.homescreen.default 1.0 as HomeScreenLib
Item {
id: root
required property real topMargin
required property real bottomMargin
required property real leftMargin
required property real rightMargin
property bool interactive: true
property var homeScreenState: HomeScreenState {
@ -35,8 +40,8 @@ Item {
appDrawerFlickable: appDrawer.flickable
availableScreenHeight: height - MobileShellState.Shell.bottomMargin
availableScreenWidth: width - MobileShellState.Shell.leftMargin - MobileShellState.Shell.rightMargin
availableScreenHeight: height - root.topMargin - root.bottomMargin
availableScreenWidth: width - root.leftMargin - root.rightMargin
appDrawerBottomOffset: favoriteStrip.height
}
@ -75,10 +80,10 @@ Item {
// account for panels
anchors.fill: parent
anchors.topMargin: MobileShellState.Shell.topMargin
anchors.bottomMargin: MobileShellState.Shell.bottomMargin
anchors.leftMargin: MobileShellState.Shell.leftMargin
anchors.rightMargin: MobileShellState.Shell.rightMargin
anchors.topMargin: root.topMargin
anchors.bottomMargin: root.bottomMargin
anchors.leftMargin: root.leftMargin
anchors.rightMargin: root.rightMargin
// animation when app drawer is being shown
opacity: root.appDrawer ? 1 - root.appDrawer.openFactor : 1
@ -133,10 +138,10 @@ Item {
homeScreenState: root.homeScreenState
// account for panels
topPadding: MobileShellState.Shell.topMargin
bottomPadding: MobileShellState.Shell.bottomMargin
leftPadding: MobileShellState.Shell.leftMargin
rightPadding: MobileShellState.Shell.rightMargin
topPadding: root.topMargin
bottomPadding: root.bottomMargin
leftPadding: root.leftMargin
rightPadding: root.rightMargin
}
}
}

View file

@ -106,7 +106,9 @@ Item {
OpenDrawerButton {
id: openDrawerButton
anchors {
leftMargin: root.leftPadding
left: parent.left
rightMargin: root.rightPadding
right: parent.right
bottom: parent.bottom
}

View file

@ -74,6 +74,12 @@ MobileShell.HomeScreen {
HomeScreen {
id: homescreen
anchors.fill: parent
topMargin: root.topMargin
bottomMargin: root.bottomMargin
leftMargin: root.leftMargin
rightMargin: root.rightMargin
opacity: (1 - searchWidget.openFactor)
// make the homescreen not interactable when task switcher or startup feedback is on
@ -87,6 +93,11 @@ MobileShell.HomeScreen {
visible: openFactor > 0
topMargin: root.topMargin
bottomMargin: root.bottomMargin
leftMargin: root.leftMargin
rightMargin: root.rightMargin
// close search component when task switcher is shown or hidden
Connections {
target: MobileShellState.HomeScreenControls.taskSwitcher

View file

@ -19,7 +19,12 @@ import org.kde.phone.homescreen.halcyon 1.0 as Halcyon
Item {
id: root
property bool interactive: true
required property real topMargin
required property real bottomMargin
required property real leftMargin
required property real rightMargin
required property bool interactive
required property var searchWidget
property alias page: swipeView.currentIndex
@ -42,10 +47,10 @@ Item {
interactive: root.interactive
anchors.fill: parent
anchors.topMargin: MobileShellState.Shell.topMargin
anchors.bottomMargin: MobileShellState.Shell.bottomMargin
anchors.leftMargin: MobileShellState.Shell.leftMargin
anchors.rightMargin: MobileShellState.Shell.rightMargin
anchors.topMargin: root.topMargin
anchors.bottomMargin: root.bottomMargin
anchors.leftMargin: root.leftMargin
anchors.rightMargin: root.rightMargin
Item {
height: swipeView.height

View file

@ -74,6 +74,11 @@ MobileShell.HomeScreen {
id: homescreen
anchors.fill: parent
topMargin: root.topMargin
bottomMargin: root.bottomMargin
leftMargin: root.leftMargin
rightMargin: root.rightMargin
// make the homescreen not interactable when task switcher or startup feedback is on
interactive: !root.overlayShown
searchWidget: search
@ -85,6 +90,11 @@ MobileShell.HomeScreen {
anchors.fill: parent
visible: openFactor > 0
topMargin: root.topMargin
bottomMargin: root.bottomMargin
leftMargin: root.leftMargin
rightMargin: root.rightMargin
// close search component when task switcher is shown or hidden
Connections {
target: MobileShellState.HomeScreenControls.taskSwitcher

View file

@ -23,6 +23,9 @@ PlasmaCore.ColorScope {
id: root
Plasmoid.backgroundHints: PlasmaCore.Types.NoBackground
width: 480
height: PlasmaCore.Units.gridUnit * 2
// toggle visibility of navigation bar (show, or use gestures only)
Binding {
target: plasmoid.Window.window // assumed to be plasma-workspace "PanelView" component
@ -32,78 +35,65 @@ PlasmaCore.ColorScope {
value: MobileShell.MobileShellSettings.navigationPanelEnabled ? 0 : 3
}
// HACK: There seems to be some component that overrides our initial bindings for the panel,
// which is particularly problematic on first start (since the panel is misplaced)
// - We set an event to override any attempts to override our bindings.
function setBindings() {
// we have the following scenarios:
// - system is in landscape orientation & nav panel is enabled (panel on right)
// - system is in landscape orientation & gesture mode is enabled (panel on bottom)
// - system is in portrait orientation (panel on bottom)
readonly property bool inLandscape: Screen.width > Screen.height;
readonly property bool isInLandscapeNavPanelMode: inLandscape && MobileShell.MobileShellSettings.navigationPanelEnabled
readonly property real navigationPanelHeight: PlasmaCore.Units.gridUnit * 2
readonly property real gesturePanelHeight: 8
readonly property real intendedWindowThickness: MobileShell.MobileShellSettings.navigationPanelEnabled ? navigationPanelHeight : gesturePanelHeight
readonly property real intendedWindowLength: isInLandscapeNavPanelMode ? Screen.height : Screen.width
readonly property real intendedWindowOffset: isInLandscapeNavPanelMode ? MobileShellState.TopPanelControls.panelHeight : 0; // offset for top panel
readonly property int intendedWindowLocation: isInLandscapeNavPanelMode ? PlasmaCore.Types.RightEdge : PlasmaCore.Types.BottomEdge
onIntendedWindowThicknessChanged: plasmoid.Window.window.thickness = intendedWindowThickness
onIntendedWindowLengthChanged: maximizeTimer.restart() // ensure it always takes up the full length of the screen
onIntendedWindowOffsetChanged: plasmoid.Window.window.offset = intendedWindowOffset
onIntendedWindowLocationChanged: locationChangeTimer.restart()
// use a timer so we don't have to maximize for every single pixel
// - improves performance if the shell is run in a window, and can be resized
Timer {
id: maximizeTimer
running: false
interval: 100
onTriggered: plasmoid.Window.window.maximize()
}
// use a timer so that rotation events are faster (offload the panel movement to later, after everything is figured out)
Timer {
id: locationChangeTimer
running: false
interval: 100
onTriggered: plasmoid.Window.window.location = intendedWindowLocation
}
function setWindowProperties() {
// plasmoid.Window.window is assumed to be plasma-workspace "PanelView" component
plasmoid.Window.window.offset = Qt.binding(() => {
return (MobileShellState.Shell.orientation === MobileShellState.Shell.Landscape) ? MobileShellState.TopPanelControls.panelHeight : 0;
});
plasmoid.Window.window.thickness = Qt.binding(() => {
// height of panel:
// - if navigation panel is enabled: PlasmaCore.Units.gridUnit * 2
// - if gestures only is enabled: 8 (just large enough for touch swipe to register, without blocking app content)
return MobileShell.MobileShellSettings.navigationPanelEnabled ? PlasmaCore.Units.gridUnit * 2 : 8
});
plasmoid.Window.window.length = Qt.binding(() => {
return MobileShellState.Shell.orientation === MobileShellState.Shell.Portrait ? Screen.width : Screen.height;
});
plasmoid.Window.window.maximumLength = Qt.binding(() => {
return MobileShellState.Shell.orientation === MobileShellState.Shell.Portrait ? Screen.width : Screen.height;
});
plasmoid.Window.window.minimumLength = Qt.binding(() => {
return MobileShellState.Shell.orientation === MobileShellState.Shell.Portrait ? Screen.width : Screen.height;
});
plasmoid.Window.window.location = Qt.binding(() => {
if (MobileShellState.Shell.orientation === MobileShellState.Shell.Portrait) {
return PlasmaCore.Types.BottomEdge;
} else if (MobileShellState.Shell.orientation === MobileShellState.Shell.Landscape) {
return MobileShell.MobileShellSettings.navigationPanelEnabled ? PlasmaCore.Types.RightEdge : PlasmaCore.Types.BottomEdge
}
});
plasmoid.Window.window.offset = intendedWindowOffset;
plasmoid.Window.window.thickness = intendedWindowThickness;
plasmoid.Window.window.location = intendedWindowLocation;
plasmoid.Window.window.maximize();
}
Connections {
target: plasmoid.Window.window
function onThicknessChanged() {
root.setBindings();
}
// HACK: There seems to be some component that overrides our initial bindings for the panel,
// which is particularly problematic on first start (since the panel is misplaced)
// - We set an event to override any attempts to override our bindings.
function onLocationChanged() {
root.setBindings();
if (plasmoid.Window.window.location !== root.intendedWindowLocation) {
root.setWindowProperties();
}
}
}
Component.onCompleted: setBindings();
//BEGIN API implementation
Binding {
target: MobileShellState.TaskPanelControls
property: "isPortrait"
value: Screen.width <= Screen.height
}
Binding {
target: MobileShellState.TaskPanelControls
property: "panelHeight"
value: MobileShell.MobileShellSettings.navigationPanelEnabled ? root.height : 0
}
Binding {
target: MobileShellState.TaskPanelControls
property: "panelWidth"
value: MobileShell.MobileShellSettings.navigationPanelEnabled ? root.width : 0
}
Connections {
target: MobileShell.WindowUtil
function onAllWindowsMinimizedChanged() {
MobileShellState.HomeScreenControls.homeScreenVisible = MobileShell.WindowUtil.allWindowsMinimized
}
}
//END API implementation
Component.onCompleted: setWindowProperties();
TaskManager.VirtualDesktopInfo {
id: virtualDesktopInfo