mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-06-25 07:37:42 +00:00
Compare commits
15 commits
8f3a94b104
...
a09f349a34
| Author | SHA1 | Date | |
|---|---|---|---|
| a09f349a34 | |||
| a29019631e | |||
| 209570d3b2 | |||
| 667efec483 | |||
| 9b94c200ee | |||
| 2059f14faf | |||
| 76e027f630 | |||
| dd7f087fac | |||
| 2a0e4428c7 | |||
| d0b4cbe2b0 | |||
| 6e48055b40 | |||
| 0b9ae2eca7 | |||
| 3466a8c5d5 | |||
| 5c3c3b9720 | |||
| e4f4232cf2 |
29 changed files with 765 additions and 50 deletions
|
|
@ -1,2 +0,0 @@
|
|||
SPDX-FileCopyrightText: 2025 Marco A.
|
||||
SPDX-License-Identifier: EUPL-1.2
|
||||
2
OVERVIEW.md.license
Normal file
2
OVERVIEW.md.license
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
SPDX-FileCopyrightText: 2026 Marco Allegretti
|
||||
SPDX-License-Identifier: EUPL-1.2
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
Shift is a fork of [plasma-mobile](https://invent.kde.org/plasma/plasma-mobile).
|
||||
The upstream phone UI is untouched; convergence adds a layer on top.
|
||||
|
|
|
|||
|
|
@ -91,7 +91,18 @@ QQuickItem *AppletHost::fullRepresentationFor(const QString &pluginId)
|
|||
|
||||
item->setPreloadFullRepresentation(true);
|
||||
|
||||
return item->fullRepresentationItem();
|
||||
auto *fullRepItem = item->fullRepresentationItem();
|
||||
if (!fullRepItem) {
|
||||
connect(
|
||||
item,
|
||||
&PlasmaQuick::AppletQuickItem::fullRepresentationItemChanged,
|
||||
this,
|
||||
[this, pluginId]() {
|
||||
Q_EMIT appletReady(pluginId);
|
||||
},
|
||||
static_cast<Qt::ConnectionType>(Qt::AutoConnection | Qt::SingleShotConnection));
|
||||
}
|
||||
return fullRepItem;
|
||||
}
|
||||
|
||||
#include "applethost.moc"
|
||||
|
|
|
|||
|
|
@ -32,6 +32,9 @@ public:
|
|||
|
||||
Q_INVOKABLE QQuickItem *fullRepresentationFor(const QString &pluginId);
|
||||
|
||||
Q_SIGNALS:
|
||||
void appletReady(const QString &pluginId);
|
||||
|
||||
private:
|
||||
void ensureCorona();
|
||||
|
||||
|
|
|
|||
|
|
@ -186,6 +186,7 @@ Item {
|
|||
|
||||
// In convergence, cap the height so it doesn't stretch full-screen
|
||||
maximumHeight: isConvergence ? root.height * 0.6 : -1
|
||||
toolButtonsItem: toolButtons
|
||||
}
|
||||
|
||||
// Secondary swipe area for uses in portrait.
|
||||
|
|
|
|||
|
|
@ -25,6 +25,9 @@ Item {
|
|||
property alias notificationWidget: notificationWidget
|
||||
property real contentY: notificationWidget.listView.contentY
|
||||
|
||||
// The sibling toolbar whose height must be subtracted from the available space.
|
||||
property Item toolButtonsItem: null
|
||||
|
||||
property real topPadding: {
|
||||
if (actionDrawer.mode == MobileShell.ActionDrawer.Portrait)
|
||||
return Kirigami.Units.largeSpacing;
|
||||
|
|
@ -42,7 +45,8 @@ Item {
|
|||
property real maximumHeight: -1
|
||||
|
||||
height: {
|
||||
let h = Math.min(actionDrawer.height - toolButtons.height, notificationWidget.listView.contentHeight + 10 + topMargin);
|
||||
let toolH = toolButtonsItem ? toolButtonsItem.height : 0;
|
||||
let h = Math.min(actionDrawer.height - toolH, notificationWidget.listView.contentHeight + Kirigami.Units.largeSpacing + topMargin);
|
||||
return maximumHeight > 0 ? Math.min(h, maximumHeight) : h;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -241,10 +241,22 @@ Item {
|
|||
asynchronous: true
|
||||
|
||||
sourceComponent: PageIndicator {
|
||||
id: pageIndicatorItem
|
||||
count: swipeView.count
|
||||
currentIndex: swipeView.currentIndex
|
||||
interactive: true
|
||||
onCurrentIndexChanged: swipeView.currentIndex = currentIndex
|
||||
onCurrentIndexChanged: {
|
||||
if (swipeView.currentIndex !== currentIndex)
|
||||
swipeView.currentIndex = currentIndex;
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: swipeView
|
||||
function onCurrentIndexChanged() {
|
||||
if (pageIndicatorItem.currentIndex !== swipeView.currentIndex)
|
||||
pageIndicatorItem.currentIndex = swipeView.currentIndex;
|
||||
}
|
||||
}
|
||||
|
||||
delegate: Rectangle {
|
||||
implicitWidth: 8
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ Item {
|
|||
orientation: root.isVertical ? ListView.Vertical : ListView.Horizontal
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
clip: true
|
||||
interactive: contentWidth > width
|
||||
interactive: root.isVertical ? contentHeight > height : contentWidth > width
|
||||
model: root.taskModel
|
||||
|
||||
delegate: NavigationPanelButton {
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ Item {
|
|||
// Hover highlight in convergence mode to indicate the bar is clickable
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(255, 255, 255, 0.1)
|
||||
color: Qt.rgba(1.0, 1.0, 1.0, 0.1)
|
||||
visible: ShellSettings.Settings.convergenceModeEnabled && statusBarHover.hovered
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,6 +50,9 @@ Item {
|
|||
|
||||
var operationName = mouse.button === Qt.RightButton ? "ContextMenu" : "Activate";
|
||||
var operation = model.service.operationDescription(operationName);
|
||||
if (!operation) {
|
||||
return;
|
||||
}
|
||||
operation.x = taskIcon.mapToGlobal(0, 0).x;
|
||||
operation.y = taskIcon.mapToGlobal(0, taskIcon.height).y;
|
||||
model.service.startOperationCall(operation);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
# SPDX-FileCopyrightText: 2025 Florian RICHER <florian.richer@protonmail.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
if(NOT TARGET KF6::AuthCore)
|
||||
find_package(KF6Auth NO_MODULE REQUIRED)
|
||||
endif()
|
||||
|
||||
add_executable(waydroidhelper)
|
||||
target_sources(waydroidhelper PRIVATE waydroidhelper.cpp)
|
||||
|
||||
|
|
@ -12,10 +16,6 @@ target_link_libraries(waydroidhelper
|
|||
|
||||
install(TARGETS waydroidhelper DESTINATION ${KAUTH_HELPER_INSTALL_DIR})
|
||||
|
||||
if(NOT TARGET KF6::AuthCore)
|
||||
find_package(KF6Auth NO_MODULE REQUIRED)
|
||||
endif()
|
||||
|
||||
kauth_install_helper_files(waydroidhelper org.kde.plasma.mobileshell.waydroidhelper root)
|
||||
kauth_install_actions(org.kde.plasma.mobileshell.waydroidhelper waydroidhelper.actions)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ plasma_add_applet(org.kde.plasma.mobile.homescreen.folio
|
|||
qml/AppDrawer.qml
|
||||
qml/AppDrawerGrid.qml
|
||||
qml/AppDrawerHeader.qml
|
||||
qml/CategoryPanel.qml
|
||||
qml/DelegateDragItem.qml
|
||||
qml/DelegateDropArea.qml
|
||||
qml/FavouritesBar.qml
|
||||
|
|
|
|||
|
|
@ -46,7 +46,11 @@ ApplicationListModel::~ApplicationListModel() = default;
|
|||
|
||||
QHash<int, QByteArray> ApplicationListModel::roleNames() const
|
||||
{
|
||||
return {{DelegateRole, QByteArrayLiteral("delegate")}};
|
||||
return {
|
||||
{DelegateRole, QByteArrayLiteral("delegate")},
|
||||
{NameRole, QByteArrayLiteral("name")},
|
||||
{CategoriesRole, QByteArrayLiteral("categories")},
|
||||
};
|
||||
}
|
||||
|
||||
void ApplicationListModel::sycocaDbChanged()
|
||||
|
|
@ -157,6 +161,11 @@ QVariant ApplicationListModel::data(const QModelIndex &index, int role) const
|
|||
return QVariant();
|
||||
}
|
||||
return delegate->application()->name();
|
||||
case CategoriesRole:
|
||||
if (!delegate->application()) {
|
||||
return QVariant();
|
||||
}
|
||||
return QVariant::fromValue(delegate->application()->categories());
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
|
@ -171,8 +180,54 @@ int ApplicationListModel::rowCount(const QModelIndex &parent) const
|
|||
return m_delegates.count();
|
||||
}
|
||||
|
||||
// Sub-categories merged into their canonical parent, mirroring Kickoff's grouping.
|
||||
static QString normalizeCategory(const QString &cat)
|
||||
{
|
||||
if (cat == QLatin1String("Audio") || cat == QLatin1String("Video"))
|
||||
return QStringLiteral("AudioVideo");
|
||||
if (cat == QLatin1String("Settings"))
|
||||
return QStringLiteral("System");
|
||||
return cat;
|
||||
}
|
||||
|
||||
static const QSet<QString> &mainCategories()
|
||||
{
|
||||
static const QSet<QString> s = {
|
||||
QStringLiteral("AudioVideo"),
|
||||
QStringLiteral("Development"),
|
||||
QStringLiteral("Education"),
|
||||
QStringLiteral("Game"),
|
||||
QStringLiteral("Graphics"),
|
||||
QStringLiteral("Network"),
|
||||
QStringLiteral("Office"),
|
||||
QStringLiteral("Science"),
|
||||
QStringLiteral("System"),
|
||||
QStringLiteral("Utility"),
|
||||
};
|
||||
return s;
|
||||
}
|
||||
|
||||
QStringList ApplicationListModel::allCategories() const
|
||||
{
|
||||
QSet<QString> found;
|
||||
for (const auto &del : m_delegates) {
|
||||
if (!del->application())
|
||||
continue;
|
||||
for (const QString &raw : del->application()->categories()) {
|
||||
const QString cat = normalizeCategory(raw);
|
||||
if (mainCategories().contains(cat))
|
||||
found.insert(cat);
|
||||
}
|
||||
}
|
||||
|
||||
QStringList result = found.values();
|
||||
result.sort();
|
||||
return result;
|
||||
}
|
||||
|
||||
ApplicationListSearchModel::ApplicationListSearchModel(HomeScreen *parent, ApplicationListModel *model)
|
||||
: QSortFilterProxyModel(parent)
|
||||
, m_homeScreen{parent}
|
||||
{
|
||||
setSourceModel(model);
|
||||
|
||||
|
|
@ -185,3 +240,52 @@ ApplicationListSearchModel::ApplicationListSearchModel(HomeScreen *parent, Appli
|
|||
|
||||
sort(0, Qt::AscendingOrder);
|
||||
}
|
||||
|
||||
QString ApplicationListSearchModel::categoryFilter() const
|
||||
{
|
||||
return m_categoryFilter;
|
||||
}
|
||||
|
||||
void ApplicationListSearchModel::setCategoryFilter(const QString &filter)
|
||||
{
|
||||
if (m_categoryFilter == filter)
|
||||
return;
|
||||
beginFilterChange();
|
||||
m_categoryFilter = filter;
|
||||
Q_EMIT categoryFilterChanged();
|
||||
endFilterChange(QSortFilterProxyModel::Direction::Rows);
|
||||
}
|
||||
|
||||
bool ApplicationListSearchModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
||||
{
|
||||
if (!QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent))
|
||||
return false;
|
||||
|
||||
if (m_categoryFilter.isEmpty())
|
||||
return true;
|
||||
|
||||
auto *src = static_cast<ApplicationListModel *>(sourceModel());
|
||||
if (!src)
|
||||
return false;
|
||||
const QModelIndex idx = src->index(sourceRow, 0, sourceParent);
|
||||
auto *del = src->data(idx, ApplicationListModel::DelegateRole).value<FolioDelegate *>();
|
||||
if (!del || !del->application())
|
||||
return false;
|
||||
|
||||
if (m_categoryFilter == QLatin1String("__favorites__")) {
|
||||
if (!m_homeScreen)
|
||||
return false;
|
||||
auto *favModel = m_homeScreen->favouritesModel();
|
||||
if (!favModel)
|
||||
return false;
|
||||
return favModel->containsApplication(del->application()->storageId());
|
||||
}
|
||||
|
||||
// Match both the canonical name and any raw aliases it absorbs.
|
||||
const QStringList &cats = del->application()->categories();
|
||||
for (const QString &raw : cats) {
|
||||
if (normalizeCategory(raw) == m_categoryFilter)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@
|
|||
#include <QObject>
|
||||
#include <QQuickItem>
|
||||
#include <QSet>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QStringList>
|
||||
|
||||
#include <KService>
|
||||
|
||||
|
|
@ -31,6 +33,7 @@ public:
|
|||
enum Roles {
|
||||
DelegateRole = Qt::UserRole + 1,
|
||||
NameRole,
|
||||
CategoriesRole,
|
||||
};
|
||||
|
||||
ApplicationListModel(HomeScreen *parent = nullptr);
|
||||
|
|
@ -42,6 +45,8 @@ public:
|
|||
|
||||
void load();
|
||||
|
||||
Q_INVOKABLE QStringList allCategories() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
// Emitted when an application was detected to have been removed from the system
|
||||
void applicationRemoved(QString storageId);
|
||||
|
|
@ -64,6 +69,21 @@ class ApplicationListSearchModel : public QSortFilterProxyModel
|
|||
QML_ELEMENT
|
||||
QML_UNCREATABLE("")
|
||||
|
||||
Q_PROPERTY(QString categoryFilter READ categoryFilter WRITE setCategoryFilter NOTIFY categoryFilterChanged)
|
||||
|
||||
public:
|
||||
ApplicationListSearchModel(HomeScreen *parent = nullptr, ApplicationListModel *model = nullptr);
|
||||
|
||||
QString categoryFilter() const;
|
||||
void setCategoryFilter(const QString &filter);
|
||||
|
||||
Q_SIGNALS:
|
||||
void categoryFilterChanged();
|
||||
|
||||
protected:
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
|
||||
private:
|
||||
HomeScreen *m_homeScreen{nullptr};
|
||||
QString m_categoryFilter;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ FolioApplication::FolioApplication(KService::Ptr service, QObject *parent)
|
|||
, m_name{service->name()}
|
||||
, m_icon{service->icon()}
|
||||
, m_storageId{service->storageId()}
|
||||
, m_categories{service->categories()}
|
||||
{
|
||||
if (service->property<bool>(QStringLiteral("X-KDE-PlasmaMobile-UseGenericName"))) {
|
||||
m_name = service->genericName();
|
||||
|
|
@ -76,6 +77,11 @@ QString FolioApplication::storageId() const
|
|||
return m_storageId;
|
||||
}
|
||||
|
||||
QStringList FolioApplication::categories() const
|
||||
{
|
||||
return m_categories;
|
||||
}
|
||||
|
||||
KWayland::Client::PlasmaWindow *FolioApplication::window() const
|
||||
{
|
||||
return m_window;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include <QObject>
|
||||
#include <QQuickItem>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
#include <KIO/ApplicationLauncherJob>
|
||||
#include <KService>
|
||||
|
|
@ -33,6 +34,7 @@ class FolioApplication : public QObject, public std::enable_shared_from_this<Fol
|
|||
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
|
||||
Q_PROPERTY(QString icon READ icon NOTIFY iconChanged)
|
||||
Q_PROPERTY(QString storageId READ storageId NOTIFY storageIdChanged)
|
||||
Q_PROPERTY(QStringList categories READ categories CONSTANT)
|
||||
|
||||
public:
|
||||
typedef std::shared_ptr<FolioApplication> Ptr;
|
||||
|
|
@ -46,6 +48,7 @@ public:
|
|||
QString name() const;
|
||||
QString icon() const;
|
||||
QString storageId() const;
|
||||
QStringList categories() const;
|
||||
KWayland::Client::PlasmaWindow *window() const;
|
||||
|
||||
void setName(QString &name);
|
||||
|
|
@ -67,5 +70,6 @@ private:
|
|||
QString m_name;
|
||||
QString m_icon;
|
||||
QString m_storageId;
|
||||
QStringList m_categories;
|
||||
KWayland::Client::PlasmaWindow *m_window{nullptr};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import QtQuick.Layouts
|
|||
import org.kde.kirigami as Kirigami
|
||||
|
||||
import org.kde.plasma.components 3.0 as PlasmaComponents
|
||||
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
|
||||
import plasma.applet.org.kde.plasma.mobile.homescreen.folio as Folio
|
||||
import './delegate'
|
||||
|
||||
|
|
@ -90,21 +89,6 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
// Close button for convergence mode
|
||||
QQC2.ToolButton {
|
||||
visible: ShellSettings.Settings.convergenceModeEnabled
|
||||
icon.name: "window-close-symbolic"
|
||||
icon.color: "white"
|
||||
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
|
||||
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
|
||||
onClicked: folio.HomeScreenState.closeAppDrawer()
|
||||
|
||||
QQC2.ToolTip.text: i18n("Close")
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
|
||||
Kirigami.Theme.inherit: false
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
188
containments/homescreens/folio/qml/CategoryPanel.qml
Normal file
188
containments/homescreens/folio/qml/CategoryPanel.qml
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
// SPDX-FileCopyrightText: Marco Allegretti
|
||||
// SPDX-License-Identifier: EUPL 1.2
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.plasma.components 3.0 as PlasmaComponents
|
||||
|
||||
import plasma.applet.org.kde.plasma.mobile.homescreen.folio as Folio
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
required property Folio.HomeScreen folio
|
||||
|
||||
// Emitted when the user taps a tile.
|
||||
signal categorySelected(string categoryId)
|
||||
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
radius: Kirigami.Units.cornerRadius
|
||||
|
||||
// Swallow clicks so the dismiss area underneath is not triggered.
|
||||
MouseArea { anchors.fill: parent }
|
||||
|
||||
// ---------- helpers ----------
|
||||
|
||||
function catDisplayName(cat) {
|
||||
switch (cat) {
|
||||
case "AudioVideo": return i18n("Multimedia")
|
||||
case "Development": return i18n("Development")
|
||||
case "Education": return i18n("Education")
|
||||
case "Game": return i18n("Games")
|
||||
case "Graphics": return i18n("Graphics")
|
||||
case "Network": return i18n("Internet")
|
||||
case "Office": return i18n("Office")
|
||||
case "Science": return i18n("Science")
|
||||
case "System": return i18n("System")
|
||||
case "Utility": return i18n("Utilities")
|
||||
default: return cat
|
||||
}
|
||||
}
|
||||
|
||||
function catIconName(cat) {
|
||||
switch (cat) {
|
||||
case "AudioVideo": return "applications-multimedia"
|
||||
case "Development": return "applications-development"
|
||||
case "Education": return "applications-education"
|
||||
case "Game": return "applications-games"
|
||||
case "Graphics": return "applications-graphics"
|
||||
case "Network": return "applications-internet"
|
||||
case "Office": return "applications-office"
|
||||
case "Science": return "applications-science"
|
||||
case "System": return "applications-system"
|
||||
case "Utility": return "applications-utilities"
|
||||
default: return "applications-other"
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- model ----------
|
||||
|
||||
ListModel { id: categoryModel }
|
||||
|
||||
function populate() {
|
||||
categoryModel.clear()
|
||||
categoryModel.append({ catId: "", catName: i18n("All Apps"), catIcon: "applications-all" })
|
||||
const cats = folio.ApplicationListModel.allCategories()
|
||||
for (let i = 0; i < cats.length; i++) {
|
||||
categoryModel.append({
|
||||
catId: cats[i],
|
||||
catName: root.catDisplayName(cats[i]),
|
||||
catIcon: root.catIconName(cats[i]),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: populate()
|
||||
|
||||
Connections {
|
||||
target: folio.ApplicationListModel
|
||||
function onRowsInserted() { root.populate() }
|
||||
function onRowsRemoved() { root.populate() }
|
||||
function onModelReset() { root.populate() }
|
||||
}
|
||||
|
||||
// ---------- tile list ----------
|
||||
|
||||
QQC2.ScrollView {
|
||||
id: scrollView
|
||||
anchors.fill: parent
|
||||
anchors.margins: Kirigami.Units.smallSpacing
|
||||
contentWidth: availableWidth
|
||||
clip: true
|
||||
|
||||
QQC2.ScrollBar.vertical.policy: QQC2.ScrollBar.AsNeeded
|
||||
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
|
||||
|
||||
Column {
|
||||
width: scrollView.availableWidth
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
Repeater {
|
||||
model: categoryModel
|
||||
|
||||
delegate: Rectangle {
|
||||
id: tile
|
||||
|
||||
required property string catId
|
||||
required property string catName
|
||||
required property string catIcon
|
||||
|
||||
readonly property bool isActive:
|
||||
folio.ApplicationListSearchModel.categoryFilter === catId
|
||||
|
||||
width: parent.width
|
||||
height: Kirigami.Units.iconSizes.medium + 2 * Kirigami.Units.largeSpacing
|
||||
radius: Kirigami.Units.cornerRadius
|
||||
|
||||
color: isActive
|
||||
? Qt.rgba(Kirigami.Theme.highlightColor.r,
|
||||
Kirigami.Theme.highlightColor.g,
|
||||
Kirigami.Theme.highlightColor.b, 0.2)
|
||||
: tileArea.containsPress
|
||||
? Qt.rgba(Kirigami.Theme.textColor.r,
|
||||
Kirigami.Theme.textColor.g,
|
||||
Kirigami.Theme.textColor.b, 0.2)
|
||||
: tileArea.containsMouse
|
||||
? Qt.rgba(Kirigami.Theme.textColor.r,
|
||||
Kirigami.Theme.textColor.g,
|
||||
Kirigami.Theme.textColor.b, 0.1)
|
||||
: "transparent"
|
||||
|
||||
// Active accent bar on left edge
|
||||
Rectangle {
|
||||
visible: tile.isActive
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.topMargin: Kirigami.Units.smallSpacing
|
||||
anchors.bottomMargin: Kirigami.Units.smallSpacing
|
||||
width: 3
|
||||
radius: 2
|
||||
color: Kirigami.Theme.highlightColor
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors {
|
||||
fill: parent
|
||||
leftMargin: Kirigami.Units.largeSpacing
|
||||
rightMargin: Kirigami.Units.smallSpacing
|
||||
}
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
Kirigami.Icon {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: Kirigami.Units.iconSizes.medium
|
||||
height: width
|
||||
source: tile.catIcon
|
||||
active: tileArea.containsMouse || tile.isActive
|
||||
}
|
||||
|
||||
PlasmaComponents.Label {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width
|
||||
- Kirigami.Units.iconSizes.medium
|
||||
- Kirigami.Units.largeSpacing * 2
|
||||
- Kirigami.Units.smallSpacing
|
||||
text: tile.catName
|
||||
elide: Text.ElideRight
|
||||
font.weight: tile.isActive ? Font.Medium : Font.Normal
|
||||
color: tile.isActive
|
||||
? Kirigami.Theme.highlightColor
|
||||
: Kirigami.Theme.textColor
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: tileArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.categorySelected(tile.catId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -130,8 +130,8 @@ MouseArea {
|
|||
anchors.bottom: parent.bottom
|
||||
width: root.navButtonWidth
|
||||
color: homeMouseArea.containsPress
|
||||
? Qt.rgba(255, 255, 255, 0.2)
|
||||
: (homeMouseArea.containsMouse ? Qt.rgba(255, 255, 255, 0.1) : "transparent")
|
||||
? Qt.rgba(1, 1, 1, 0.2)
|
||||
: (homeMouseArea.containsMouse ? Qt.rgba(1, 1, 1, 0.1) : "transparent")
|
||||
radius: Kirigami.Units.cornerRadius
|
||||
|
||||
Kirigami.Icon {
|
||||
|
|
@ -160,8 +160,8 @@ MouseArea {
|
|||
anchors.bottom: parent.bottom
|
||||
width: root.navButtonWidth
|
||||
color: overviewMouseArea.containsPress
|
||||
? Qt.rgba(255, 255, 255, 0.2)
|
||||
: (overviewMouseArea.containsMouse ? Qt.rgba(255, 255, 255, 0.1) : "transparent")
|
||||
? Qt.rgba(1, 1, 1, 0.2)
|
||||
: (overviewMouseArea.containsMouse ? Qt.rgba(1, 1, 1, 0.1) : "transparent")
|
||||
radius: Kirigami.Units.cornerRadius
|
||||
|
||||
Kirigami.Icon {
|
||||
|
|
@ -854,8 +854,8 @@ MouseArea {
|
|||
anchors.fill: parent
|
||||
radius: Kirigami.Units.cornerRadius
|
||||
color: taskMouseArea.containsPress
|
||||
? Qt.rgba(255, 255, 255, 0.2)
|
||||
: (taskMouseArea.containsMouse ? Qt.rgba(255, 255, 255, 0.1) : "transparent")
|
||||
? Qt.rgba(1.0, 1.0, 1.0, 0.2)
|
||||
: (taskMouseArea.containsMouse ? Qt.rgba(1.0, 1.0, 1.0, 0.1) : "transparent")
|
||||
}
|
||||
|
||||
// Task icon
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ Folio.DelegateTouchArea {
|
|||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Kirigami.Units.cornerRadius
|
||||
color: Qt.rgba(255, 255, 255, 0.1)
|
||||
color: Qt.rgba(1.0, 1.0, 1.0, 0.1)
|
||||
visible: ShellSettings.Settings.convergenceModeEnabled && root.hovered
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@ import org.kde.plasma.private.mobileshell.windowplugin as WindowPlugin
|
|||
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
|
||||
|
||||
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
|
||||
|
||||
|
|
@ -194,12 +198,19 @@ ContainmentItem {
|
|||
LayerShell.Window.keyboardInteractivity: LayerShell.Window.KeyboardInteractivityOnDemand
|
||||
|
||||
// Auto-hide: slide dock content off-screen when a window is
|
||||
// maximized. A HoverHandler brings it back on mouse proximity.
|
||||
// maximized. The reveal strip at the screen edge brings it back.
|
||||
property real dockOffset: 0
|
||||
readonly property real dockHeight: Kirigami.Units.gridUnit * 3
|
||||
readonly property bool dockHovered: dockHoverHandler.hovered
|
||||
|
||||
// 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: Kirigami.Units.gridUnit
|
||||
|
||||
// True once the hover-reveal timer fires; cleared on hover-exit.
|
||||
property bool hoverRevealing: false
|
||||
|
||||
readonly property bool shouldHide: ShellSettings.Settings.autoHidePanelsEnabled
|
||||
&& windowMaximizedTracker.showingWindow && !dockHovered
|
||||
&& windowMaximizedTracker.showingWindow && !hoverRevealing
|
||||
|
||||
onShouldHideChanged: {
|
||||
if (shouldHide) {
|
||||
|
|
@ -209,8 +220,38 @@ ContainmentItem {
|
|||
}
|
||||
}
|
||||
|
||||
// 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: {
|
||||
if (dockOffset >= dockHeight) {
|
||||
MobileShell.ShellUtil.setInputRegion(dockOverlay,
|
||||
Qt.rect(0, dockOverlay.height - revealStripHeight,
|
||||
dockOverlay.width, revealStripHeight))
|
||||
} else if (dockOffset === 0) {
|
||||
MobileShell.ShellUtil.setInputRegion(dockOverlay, Qt.rect(0, 0, 0, 0))
|
||||
}
|
||||
}
|
||||
|
||||
// Delay reveal by 300 ms so a quick edge graze does not pop the
|
||||
// dock up mid-interaction with the underlying application.
|
||||
Timer {
|
||||
id: hoverRevealTimer
|
||||
interval: 300
|
||||
repeat: false
|
||||
onTriggered: dockOverlay.hoverRevealing = true
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
id: dockHoverHandler
|
||||
onHoveredChanged: {
|
||||
if (hovered) {
|
||||
hoverRevealTimer.start()
|
||||
} else {
|
||||
hoverRevealTimer.stop()
|
||||
dockOverlay.hoverRevealing = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on dockOffset {
|
||||
|
|
@ -222,9 +263,7 @@ ContainmentItem {
|
|||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
Kirigami.Theme.inherit: false
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
color: Qt.rgba(0, 0, 0, 0.5)
|
||||
transform: Translate { y: dockOverlay.dockOffset }
|
||||
}
|
||||
|
||||
|
|
@ -308,7 +347,297 @@ ContainmentItem {
|
|||
target: folio.HomeScreenState
|
||||
|
||||
function onAppDrawerOpened() {
|
||||
overlayDrawer.forceActiveFocus();
|
||||
folio.ApplicationListSearchModel.categoryFilter = ""
|
||||
overlayDrawer.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Drop shadow rendered separately so categoryPanel itself needs no
|
||||
// layer FBO (which would rasterize and blur the icons inside).
|
||||
Rectangle {
|
||||
id: categoryPanelShadow
|
||||
width: categoryPanel.width
|
||||
height: categoryPanel.height
|
||||
x: categoryPanel.x
|
||||
y: categoryPanel.y
|
||||
radius: categoryPanel.radius
|
||||
color: categoryPanel.color
|
||||
opacity: categoryPanel.opacity
|
||||
layer.enabled: true
|
||||
layer.effect: DropShadow {
|
||||
transparentBorder: true
|
||||
horizontalOffset: 0
|
||||
verticalOffset: 2
|
||||
radius: 12
|
||||
samples: 25
|
||||
color: Qt.rgba(0, 0, 0, 0.4)
|
||||
}
|
||||
}
|
||||
|
||||
CategoryPanel {
|
||||
id: categoryPanel
|
||||
folio: root.folio
|
||||
|
||||
width: Kirigami.Units.gridUnit * 9
|
||||
height: overlayDrawer.popupHeight
|
||||
x: overlayDrawer.x + overlayDrawer.width + Kirigami.Units.smallSpacing
|
||||
y: overlayDrawer.y
|
||||
opacity: overlayDrawer.opacity
|
||||
|
||||
onCategorySelected: (catId) => {
|
||||
folio.ApplicationListSearchModel.categoryFilter = catId
|
||||
overlayDrawerHeader.clearSearchText()
|
||||
}
|
||||
}
|
||||
|
||||
// Drop shadow rendered separately so powerPanel itself needs no layer FBO,
|
||||
// which would rasterize and blur the icons inside.
|
||||
Rectangle {
|
||||
id: powerPanelShadow
|
||||
width: powerPanel.width
|
||||
height: powerPanel.height
|
||||
x: powerPanel.x
|
||||
y: powerPanel.y
|
||||
radius: powerPanel.radius
|
||||
color: powerPanel.color
|
||||
opacity: powerPanel.opacity
|
||||
layer.enabled: true
|
||||
layer.effect: DropShadow {
|
||||
transparentBorder: true
|
||||
horizontalOffset: 0
|
||||
verticalOffset: 2
|
||||
radius: 12
|
||||
samples: 25
|
||||
color: Qt.rgba(0, 0, 0, 0.4)
|
||||
}
|
||||
}
|
||||
|
||||
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: categoryPanel.x + categoryPanel.width + Kirigami.Units.smallSpacing
|
||||
y: overlayDrawer.y
|
||||
opacity: overlayDrawer.opacity
|
||||
radius: Kirigami.Units.cornerRadius
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -158,13 +158,13 @@ ContainmentItem {
|
|||
color: "transparent"
|
||||
flags: Qt.FramelessWindowHint | Qt.WindowTransparentForInput
|
||||
// height is set by layer-shell anchoring; provide a fallback.
|
||||
height: Kirigami.Units.gridUnit * 3
|
||||
height: root.navigationPanelHeight
|
||||
width: 1 // layer-shell stretches it via AnchorLeft|AnchorRight
|
||||
|
||||
LayerShell.Window.scope: "dock-space"
|
||||
LayerShell.Window.layer: LayerShell.Window.LayerBottom
|
||||
LayerShell.Window.anchors: LayerShell.Window.AnchorBottom | LayerShell.Window.AnchorLeft | LayerShell.Window.AnchorRight
|
||||
LayerShell.Window.exclusionZone: Kirigami.Units.gridUnit * 3
|
||||
LayerShell.Window.exclusionZone: root.navigationPanelHeight
|
||||
LayerShell.Window.keyboardInteractivity: LayerShell.Window.KeyboardInteractivityNone
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,43 @@ Loader {
|
|||
|
||||
property var currentWindow
|
||||
|
||||
// Window that needs geometry clamping after un-maximize in convergence
|
||||
// mode. Set in onMaximizedChanged and consumed by the timer below.
|
||||
property var pendingConstrainWindow: null
|
||||
|
||||
// After a window is un-maximized in convergence mode, the dockSpaceReserver
|
||||
// LayerShell surface needs one Wayland roundtrip to (re)commit its exclusive
|
||||
// zone so that KWin updates MaximizeArea. We wait 200 ms — well within the
|
||||
// dock slide-in animation — then clamp the window bottom to MaximizeArea so
|
||||
// it cannot overlap the dock.
|
||||
Timer {
|
||||
id: constrainAfterRestoreTimer
|
||||
interval: 200
|
||||
onTriggered: {
|
||||
const window = root.pendingConstrainWindow
|
||||
root.pendingConstrainWindow = null
|
||||
if (!window || window.deleted || !window.normalWindow) return
|
||||
if (!ShellSettings.Settings.convergenceModeEnabled) return
|
||||
|
||||
const output = window.output
|
||||
const desktop = window.desktops[0]
|
||||
if (!desktop) return
|
||||
|
||||
const maxRect = KWinComponents.Workspace.clientArea(
|
||||
KWinComponents.Workspace.MaximizeArea, output, desktop)
|
||||
const geo = window.frameGeometry
|
||||
const maxBottom = maxRect.y + maxRect.height
|
||||
|
||||
if (geo.y + geo.height > maxBottom) {
|
||||
// Clip the bottom edge to MaximizeArea; preserve top position
|
||||
// and width. Ensure height is at least 100px to avoid
|
||||
// pathological cases where the window starts above maxRect.
|
||||
const newH = Math.max(100, maxBottom - geo.y)
|
||||
window.frameGeometry = Qt.rect(geo.x, geo.y, geo.width, newH)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function run(window) {
|
||||
// HACK: don't maximize xwaylandvideobridge
|
||||
// see: https://invent.kde.org/plasma/plasma-mobile/-/issues/324
|
||||
|
|
@ -67,6 +104,14 @@ Loader {
|
|||
root.run(currentWindow);
|
||||
});
|
||||
root.run(currentWindow);
|
||||
// Schedule a deferred geometry clamp so that the restored window
|
||||
// doesn't overlap the dock after the dockSpaceReserver exclusive
|
||||
// zone is re-committed over a Wayland roundtrip.
|
||||
if (ShellSettings.Settings.convergenceModeEnabled
|
||||
&& ShellSettings.Settings.autoHidePanelsEnabled) {
|
||||
root.pendingConstrainWindow = currentWindow
|
||||
constrainAfterRestoreTimer.restart()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@
|
|||
add_executable(flashlighthelper)
|
||||
target_sources(flashlighthelper PRIVATE flashlighthelper.cpp)
|
||||
|
||||
if(NOT TARGET KF6::AuthCore)
|
||||
find_package(KF6Auth NO_MODULE REQUIRED)
|
||||
endif()
|
||||
|
||||
target_link_libraries(flashlighthelper
|
||||
Qt6::Core
|
||||
KF6::AuthCore
|
||||
|
|
@ -13,10 +17,6 @@ target_link_libraries(flashlighthelper
|
|||
|
||||
install(TARGETS flashlighthelper DESTINATION ${KAUTH_HELPER_INSTALL_DIR})
|
||||
|
||||
if(NOT TARGET KF6::AuthCore)
|
||||
find_package(KF6Auth NO_MODULE REQUIRED)
|
||||
endif()
|
||||
|
||||
kauth_install_helper_files(flashlighthelper org.kde.plasma.mobileshell.flashlighthelper root)
|
||||
kauth_install_actions(org.kde.plasma.mobileshell.flashlighthelper flashlighthelper.actions)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
SPDX-FileCopyrightText: 2026 Marco Allegretti.
|
||||
SPDX-FileCopyrightText: 2026 Marco Allegretti
|
||||
SPDX-License-Identifier: EUPL-1.2
|
||||
|
|
|
|||
Loading…
Reference in a new issue