From 98f9b3a8fc4c429c6350362353c65354828f7c6c Mon Sep 17 00:00:00 2001 From: Toast Date: Fri, 4 Jul 2025 22:31:07 +0200 Subject: [PATCH] Add kde patches The nixpkgs patch is to clear out a conflict with a patch that's applied to plasma-workspace by nixpkgs --- flake.nix | 1 + .../plasma-workspace-patch-trimming.patch | 65 + roles/kde/patches/bluedevil/patches.txt | 2 + roles/kde/patches/bluedevil/pr218.patch | 53 + roles/kde/patches/breeze/patches.txt | 2 + roles/kde/patches/breeze/pr545.patch | 333 ++ roles/kde/patches/dolphin/patches.txt | 3 + roles/kde/patches/dolphin/pr946.patch | 3978 +++++++++++++++++ roles/kde/patches/kwin/patches.txt | 7 + roles/kde/patches/kwin/pr3612.patch | 2101 +++++++++ roles/kde/patches/kwin/pr7822.patch | 40 + roles/kde/patches/kwin/pr7823.patch | 26 + roles/kde/patches/kwin/pr7829.patch | 85 + roles/kde/patches/plasma-nm/patches.txt | 4 + roles/kde/patches/plasma-nm/pr439.patch | 140 + roles/kde/patches/plasma-nm/pr442.patch | 168 + .../kde/patches/plasma-workspace/patches.txt | 6 + .../kde/patches/plasma-workspace/pr5589.patch | 37 + .../kde/patches/plasma-workspace/pr5626.patch | 102 + .../kde/patches/plasma-workspace/pr5627.patch | 157 + .../kde/patches/plasma-workspace/pr5628.patch | 277 ++ roles/kde/patches/spectacle/patches.txt | 3 + roles/kde/patches/spectacle/pr460.patch | 228 + 23 files changed, 7818 insertions(+) create mode 100644 nixpkgs-patches/plasma-workspace-patch-trimming.patch create mode 100644 roles/kde/patches/bluedevil/patches.txt create mode 100644 roles/kde/patches/bluedevil/pr218.patch create mode 100644 roles/kde/patches/breeze/patches.txt create mode 100644 roles/kde/patches/breeze/pr545.patch create mode 100644 roles/kde/patches/dolphin/patches.txt create mode 100644 roles/kde/patches/dolphin/pr946.patch create mode 100644 roles/kde/patches/kwin/patches.txt create mode 100644 roles/kde/patches/kwin/pr3612.patch create mode 100644 roles/kde/patches/kwin/pr7822.patch create mode 100644 roles/kde/patches/kwin/pr7823.patch create mode 100644 roles/kde/patches/kwin/pr7829.patch create mode 100644 roles/kde/patches/plasma-nm/patches.txt create mode 100644 roles/kde/patches/plasma-nm/pr439.patch create mode 100644 roles/kde/patches/plasma-nm/pr442.patch create mode 100644 roles/kde/patches/plasma-workspace/patches.txt create mode 100644 roles/kde/patches/plasma-workspace/pr5589.patch create mode 100644 roles/kde/patches/plasma-workspace/pr5626.patch create mode 100644 roles/kde/patches/plasma-workspace/pr5627.patch create mode 100644 roles/kde/patches/plasma-workspace/pr5628.patch create mode 100644 roles/kde/patches/spectacle/patches.txt create mode 100644 roles/kde/patches/spectacle/pr460.patch diff --git a/flake.nix b/flake.nix index 688fc21..275efa9 100644 --- a/flake.nix +++ b/flake.nix @@ -97,6 +97,7 @@ name = "patched-nixpkgs-unstable"; src = nixpkgs-unstable-raw; patches = [ + ./nixpkgs-patches/plasma-workspace-patch-trimming.patch ]; }; nixpkgs-patched = nixpkgs-raw.legacyPackages.x86_64-linux.applyPatches { diff --git a/nixpkgs-patches/plasma-workspace-patch-trimming.patch b/nixpkgs-patches/plasma-workspace-patch-trimming.patch new file mode 100644 index 0000000..bfd45ce --- /dev/null +++ b/nixpkgs-patches/plasma-workspace-patch-trimming.patch @@ -0,0 +1,65 @@ +diff --git a/pkgs/kde/plasma/plasma-workspace/dependency-paths.patch b/pkgs/kde/plasma/plasma-workspace/dependency-paths.patch +index b4d46cd869bb..ca5bfd2c4889 100644 +--- a/pkgs/kde/plasma/plasma-workspace/dependency-paths.patch ++++ b/pkgs/kde/plasma/plasma-workspace/dependency-paths.patch +@@ -1,13 +1,14 @@ + diff --git a/applets/devicenotifier/plugin/deviceerrormonitor_p.cpp b/applets/devicenotifier/plugin/deviceerrormonitor_p.cpp +-index ba214a555d..421d940738 100644 ++index 183f207946..0f64b068ab 100644 + --- a/applets/devicenotifier/plugin/deviceerrormonitor_p.cpp + +++ b/applets/devicenotifier/plugin/deviceerrormonitor_p.cpp +-@@ -155,7 +155,7 @@ void DeviceErrorMonitor::queryBlockingApps(const QString &devicePath) ++@@ -168,7 +168,8 @@ void DeviceErrorMonitor::queryBlockingApps(const QString &devicePath) + Q_EMIT blockingAppsReady(blockApps); + p->deleteLater(); + }); + - p->start(QStringLiteral("lsof"), {QStringLiteral("-t"), devicePath}); + + p->start(QStringLiteral("@lsof@"), {QStringLiteral("-t"), devicePath}); +++ // @dbusSend@ + // p.start(QStringLiteral("fuser"), {QStringLiteral("-m"), devicePath}); + } + +@@ -51,7 +52,7 @@ index e4d1ad4311..d45bdfad98 100644 + + void CFcQuery::procExited() + diff --git a/kcms/krdb/krdb.cpp b/kcms/krdb/krdb.cpp +-index f3c9956921..09c818739d 100644 ++index 53f77d0a18..680e81b6e4 100644 + --- a/kcms/krdb/krdb.cpp + +++ b/kcms/krdb/krdb.cpp + @@ -425,7 +425,7 @@ void runRdb(unsigned int flags) +@@ -107,7 +108,7 @@ index 7218628ce9..9126475ea4 100644 + +ExecStart=@qdbus@ org.kde.kcminit /kcminit org.kde.KCMInit.runPhase1 + Slice=session.slice + diff --git a/startkde/startplasma.cpp b/startkde/startplasma.cpp +-index 4d31c6f408..17418b1ff7 100644 ++index 02c3f260fb..795244064d 100644 + --- a/startkde/startplasma.cpp + +++ b/startkde/startplasma.cpp + @@ -57,7 +57,7 @@ void sigtermHandler(int signalNumber) +@@ -119,7 +120,7 @@ index 4d31c6f408..17418b1ff7 100644 + } + + QStringList allServices(const QLatin1String &prefix) +-@@ -512,7 +512,7 @@ QProcess *setupKSplash() ++@@ -508,7 +508,7 @@ QProcess *setupKSplash() + if (ksplashCfg.readEntry("Engine", QStringLiteral("KSplashQML")) == QLatin1String("KSplashQML")) { + p = new QProcess; + p->setProcessChannelMode(QProcess::ForwardedChannels); +@@ -128,14 +129,4 @@ index 4d31c6f408..17418b1ff7 100644 + } + } + return p; +-diff --git a/startkde/systemd/plasma-ksplash-ready.service.in b/startkde/systemd/plasma-ksplash-ready.service.in +-index 1e903130a9..0861c3d136 100644 +---- a/startkde/systemd/plasma-ksplash-ready.service.in +-+++ b/startkde/systemd/plasma-ksplash-ready.service.in +-@@ -6,5 +6,5 @@ PartOf=graphical-session.target +- +- [Service] +- Type=oneshot +--ExecStart=dbus-send --session --reply-timeout=1 --type=method_call --dest=org.kde.KSplash /KSplash org.kde.KSplash.setStage string:ready +-+ExecStart=@dbusSend@ --session --reply-timeout=1 --type=method_call --dest=org.kde.KSplash /KSplash org.kde.KSplash.setStage string:ready +- Slice=session.slice ++ + diff --git a/roles/kde/patches/bluedevil/patches.txt b/roles/kde/patches/bluedevil/patches.txt new file mode 100644 index 0000000..f1e689d --- /dev/null +++ b/roles/kde/patches/bluedevil/patches.txt @@ -0,0 +1,2 @@ +Plasma 6.5.0: +Pr 218 https://invent.kde.org/plasma/bluedevil/-/merge_requests/218 diff --git a/roles/kde/patches/bluedevil/pr218.patch b/roles/kde/patches/bluedevil/pr218.patch new file mode 100644 index 0000000..c9eca02 --- /dev/null +++ b/roles/kde/patches/bluedevil/pr218.patch @@ -0,0 +1,53 @@ +From ddbf7452c6f2fe8849de723175710dbf39242422 Mon Sep 17 00:00:00 2001 +From: Nate Graham +Date: Fri, 13 Jun 2025 11:55:47 -0600 +Subject: [PATCH] applet: use standard section headers + +We have them; might as well use them. This lets us get rid of a bunch of +complex and fragile code that's responsible for the existing separator. +--- + .../contents/ui/FullRepresentation.qml | 27 +++---------------- + 1 file changed, 3 insertions(+), 24 deletions(-) + +diff --git a/src/applet/package/contents/ui/FullRepresentation.qml b/src/applet/package/contents/ui/FullRepresentation.qml +index df989267d..27295de21 100644 +--- a/src/applet/package/contents/ui/FullRepresentation.qml ++++ b/src/applet/package/contents/ui/FullRepresentation.qml +@@ -111,31 +111,10 @@ PlasmaExtras.Representation { + bottomMargin: Kirigami.Units.largeSpacing + + section.property: "Section" +- // We want to hide the section delegate for the "Connected" +- // group because it's unnecessary; all we want to do here is +- // separate the connected devices from the available ones +- section.delegate: Loader { ++ section.delegate: PlasmaExtras.ListSectionHeader { + required property string section +- +- active: section !== "Connected" && BluezQt.Manager.connectedDevices.length > 0 +- +- width: ListView.view.width - ListView.view.leftMargin - ListView.view.rightMargin +- // Need to manually set the height or else the loader takes up +- // space after the first time it unloads a previously-loaded item +- height: active ? Kirigami.Units.gridUnit : 0 +- +- // give us 2 frames to try and figure out a layout, this reduces jumpyness quite a bit but doesn't +- // entirely eliminate it https://bugs.kde.org/show_bug.cgi?id=438610 +- Behavior on height { PropertyAnimation { duration: 32 } } +- +- sourceComponent: Item { +- KSvg.SvgItem { +- width: parent.width - Kirigami.Units.gridUnit * 2 +- anchors.centerIn: parent +- imagePath: "widgets/line" +- elementId: "horizontal-line" +- } +- } ++ width: listView.width - listView.leftMargin - listView.rightMargin ++ text: section + } + highlight: PlasmaExtras.Highlight {} + highlightMoveDuration: Kirigami.Units.shortDuration +-- +GitLab + diff --git a/roles/kde/patches/breeze/patches.txt b/roles/kde/patches/breeze/patches.txt new file mode 100644 index 0000000..bf22bd1 --- /dev/null +++ b/roles/kde/patches/breeze/patches.txt @@ -0,0 +1,2 @@ +Plasma 6.5.0: +Pr 545 https://invent.kde.org/plasma/breeze/-/merge_requests/545 diff --git a/roles/kde/patches/breeze/pr545.patch b/roles/kde/patches/breeze/pr545.patch new file mode 100644 index 0000000..926a40d --- /dev/null +++ b/roles/kde/patches/breeze/pr545.patch @@ -0,0 +1,333 @@ +From 38567c2f2d2d0f8524e42224dff53a929cbf086a Mon Sep 17 00:00:00 2001 +From: Kai Uwe Broulik +Date: Sat, 24 May 2025 09:33:44 +0200 +Subject: [PATCH 1/3] kstyle/animations: Use QObject as base type rather than + QWidget + +This prepares for the animation engine being used with Qt Quick Controls. + +In most cases the type is cast to the relevant widget type for checking +anyway, so might as well use the cast object further down but not require +a QWidget at the beginning. +--- + kstyle/animations/breezeanimations.cpp | 48 +++++++++++++------------- + kstyle/animations/breezeanimations.h | 4 +-- + 2 files changed, 26 insertions(+), 26 deletions(-) + +diff --git a/kstyle/animations/breezeanimations.cpp b/kstyle/animations/breezeanimations.cpp +index 12e5107c5..ceab53802 100644 +--- a/kstyle/animations/breezeanimations.cpp ++++ b/kstyle/animations/breezeanimations.cpp +@@ -82,7 +82,7 @@ void Animations::setupEngines() + } + + //____________________________________________________________ +-void Animations::registerWidget(QWidget *widget) const ++void Animations::registerWidget(QObject *widget) const + { + if (!widget) { + return; +@@ -101,37 +101,37 @@ void Animations::registerWidget(QWidget *widget) const + // for optimization, one should put with most used widgets here first + + // buttons +- if (qobject_cast(widget)) { +- _toolButtonEngine->registerWidget(widget, AnimationHover | AnimationFocus); +- _widgetStateEngine->registerWidget(widget, AnimationHover | AnimationFocus); ++ if (auto toolButton = qobject_cast(widget)) { ++ _toolButtonEngine->registerWidget(toolButton, AnimationHover | AnimationFocus); ++ _widgetStateEngine->registerWidget(toolButton, AnimationHover | AnimationFocus); + + } else if (qobject_cast(widget) || qobject_cast(widget)) { + _widgetStateEngine->registerWidget(widget, AnimationHover | AnimationFocus | AnimationPressed); + +- } else if (qobject_cast(widget)) { ++ } else if (auto button = qobject_cast(widget)) { + // register to toolbox engine if needed +- if (qobject_cast(widget->parent())) { +- _toolBoxEngine->registerWidget(widget); ++ if (auto toolBox = qobject_cast(widget->parent())) { ++ _toolBoxEngine->registerWidget(toolBox); + } + +- _widgetStateEngine->registerWidget(widget, AnimationHover | AnimationFocus); ++ _widgetStateEngine->registerWidget(button, AnimationHover | AnimationFocus); + + } + + // groupboxes + else if (QGroupBox *groupBox = qobject_cast(widget)) { + if (groupBox->isCheckable()) { +- _widgetStateEngine->registerWidget(widget, AnimationHover | AnimationFocus); ++ _widgetStateEngine->registerWidget(groupBox, AnimationHover | AnimationFocus); + } + } + + // sliders +- else if (qobject_cast(widget)) { +- _scrollBarEngine->registerWidget(widget, AnimationHover | AnimationFocus); +- } else if (qobject_cast(widget)) { +- _widgetStateEngine->registerWidget(widget, AnimationHover | AnimationFocus); +- } else if (qobject_cast(widget)) { +- _dialEngine->registerWidget(widget, AnimationHover | AnimationFocus); ++ else if (auto scrollBar = qobject_cast(widget)) { ++ _scrollBarEngine->registerWidget(scrollBar, AnimationHover | AnimationFocus); ++ } else if (auto slider = qobject_cast(widget)) { ++ _widgetStateEngine->registerWidget(slider, AnimationHover | AnimationFocus); ++ } else if (auto dial = qobject_cast(widget)) { ++ _dialEngine->registerWidget(dial, AnimationHover | AnimationFocus); + } + + // progress bar +@@ -162,24 +162,24 @@ void Animations::registerWidget(QWidget *widget) const + + // header views + // need to come before abstract item view, otherwise is skipped +- else if (qobject_cast(widget)) { +- _headerViewEngine->registerWidget(widget); ++ else if (auto headerView = qobject_cast(widget)) { ++ _headerViewEngine->registerWidget(headerView); + } + + // lists +- else if (qobject_cast(widget)) { +- _inputWidgetEngine->registerWidget(widget, AnimationHover | AnimationFocus); ++ else if (auto itemView = qobject_cast(widget)) { ++ _inputWidgetEngine->registerWidget(itemView, AnimationHover | AnimationFocus); + } + + // tabbar +- else if (qobject_cast(widget)) { +- _tabBarEngine->registerWidget(widget); ++ else if (auto tabBar = qobject_cast(widget)) { ++ _tabBarEngine->registerWidget(tabBar); + } + + // scrollarea + else if (QAbstractScrollArea *scrollArea = qobject_cast(widget)) { +- if (scrollArea->frameShadow() == QFrame::Sunken && (widget->focusPolicy() & Qt::StrongFocus)) { +- _inputWidgetEngine->registerWidget(widget, AnimationHover | AnimationFocus); ++ if (scrollArea->frameShadow() == QFrame::Sunken && (scrollArea->focusPolicy() & Qt::StrongFocus)) { ++ _inputWidgetEngine->registerWidget(scrollArea, AnimationHover | AnimationFocus); + } + } + +@@ -190,7 +190,7 @@ void Animations::registerWidget(QWidget *widget) const + } + + //____________________________________________________________ +-void Animations::unregisterWidget(QWidget *widget) const ++void Animations::unregisterWidget(QObject *widget) const + { + if (!widget) { + return; +diff --git a/kstyle/animations/breezeanimations.h b/kstyle/animations/breezeanimations.h +index 6551900a5..3215e8fb9 100644 +--- a/kstyle/animations/breezeanimations.h ++++ b/kstyle/animations/breezeanimations.h +@@ -31,10 +31,10 @@ public: + explicit Animations(); + + //* register animations corresponding to given widget, depending on its type. +- void registerWidget(QWidget *widget) const; ++ void registerWidget(QObject *widget) const; + + /** unregister all animations associated to a widget */ +- void unregisterWidget(QWidget *widget) const; ++ void unregisterWidget(QObject *widget) const; + + //* enability engine + [[nodiscard]] WidgetStateEngine &widgetEnabilityEngine() const +-- +GitLab + + +From 75201d1e63d82f7282c8bedcc7cc3d2417b1e123 Mon Sep 17 00:00:00 2001 +From: Kai Uwe Broulik +Date: Sat, 24 May 2025 09:40:01 +0200 +Subject: [PATCH 2/3] kstyle: Use styleObject for widgetStateEngine in + CheckBox/RadioButton + +Prepares it for doing the animation also with Qt Quick Controls +without an actual QWidget. +--- + kstyle/breezestyle.cpp | 44 ++++++++++++++++++++++++++++-------------- + 1 file changed, 29 insertions(+), 15 deletions(-) + +diff --git a/kstyle/breezestyle.cpp b/kstyle/breezestyle.cpp +index 09cee6003..19149c382 100644 +--- a/kstyle/breezestyle.cpp ++++ b/kstyle/breezestyle.cpp +@@ -4738,6 +4738,8 @@ bool Style::drawPanelItemViewItemPrimitive(const QStyleOption *option, QPainter + //___________________________________________________________________________________ + bool Style::drawIndicatorCheckBoxPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const + { ++ const QObject *styleObject = widget ? widget : option->styleObject; ++ + // copy rect and palette + const auto &rect(option->rect); + const auto &palette(option->palette); +@@ -4757,15 +4759,15 @@ bool Style::drawIndicatorCheckBoxPrimitive(const QStyleOption *option, QPainter + } + + // animation state +- _animations->widgetStateEngine().updateState(widget, AnimationHover, mouseOver); +- _animations->widgetStateEngine().updateState(widget, AnimationPressed, checkBoxState != CheckOff); ++ _animations->widgetStateEngine().updateState(styleObject, AnimationHover, mouseOver); ++ _animations->widgetStateEngine().updateState(styleObject, AnimationPressed, checkBoxState != CheckOff); + auto target = checkBoxState; +- if (_animations->widgetStateEngine().isAnimated(widget, AnimationPressed)) { ++ if (_animations->widgetStateEngine().isAnimated(styleObject, AnimationPressed)) { + checkBoxState = CheckAnimated; + } +- const qreal animation(_animations->widgetStateEngine().opacity(widget, AnimationPressed)); ++ const qreal animation(_animations->widgetStateEngine().opacity(styleObject, AnimationPressed)); + +- const qreal opacity(_animations->widgetStateEngine().opacity(widget, AnimationHover)); ++ const qreal opacity(_animations->widgetStateEngine().opacity(styleObject, AnimationHover)); + + // render + _helper->renderCheckBoxBackground(painter, rect, palette, checkBoxState, hasHighlightNeutral(widget, option, mouseOver), sunken, animation); +@@ -4777,6 +4779,8 @@ bool Style::drawIndicatorCheckBoxPrimitive(const QStyleOption *option, QPainter + //___________________________________________________________________________________ + bool Style::drawIndicatorRadioButtonPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const + { ++ const QObject *styleObject = widget ? widget : option->styleObject; ++ + // copy rect and palette + const auto &rect(option->rect); + const auto &palette(option->palette); +@@ -4791,19 +4795,27 @@ bool Style::drawIndicatorRadioButtonPrimitive(const QStyleOption *option, QPaint + RadioButtonState radioButtonState((state & State_On) ? RadioOn : RadioOff); + + // animation state +- _animations->widgetStateEngine().updateState(widget, AnimationHover, mouseOver); +- _animations->widgetStateEngine().updateState(widget, AnimationPressed, radioButtonState != RadioOff); +- if (_animations->widgetStateEngine().isAnimated(widget, AnimationPressed)) { ++ _animations->widgetStateEngine().updateState(styleObject, AnimationHover, mouseOver); ++ _animations->widgetStateEngine().updateState(styleObject, AnimationPressed, radioButtonState != RadioOff); ++ if (_animations->widgetStateEngine().isAnimated(styleObject, AnimationPressed)) { + radioButtonState = RadioAnimated; + } +- const qreal animation(_animations->widgetStateEngine().opacity(widget, AnimationPressed)); ++ const qreal animation(_animations->widgetStateEngine().opacity(styleObject, AnimationPressed)); + + // colors +- const qreal opacity(_animations->widgetStateEngine().opacity(widget, AnimationHover)); ++ const qreal opacity(_animations->widgetStateEngine().opacity(styleObject, AnimationHover)); + + // render +- _helper->renderRadioButtonBackground(painter, rect, palette, radioButtonState, hasHighlightNeutral(widget, option, mouseOver), sunken, animation); +- _helper->renderRadioButton(painter, rect, palette, mouseOver, radioButtonState, hasHighlightNeutral(widget, option, mouseOver), sunken, animation, opacity); ++ _helper->renderRadioButtonBackground(painter, rect, palette, radioButtonState, hasHighlightNeutral(styleObject, option, mouseOver), sunken, animation); ++ _helper->renderRadioButton(painter, ++ rect, ++ palette, ++ mouseOver, ++ radioButtonState, ++ hasHighlightNeutral(styleObject, option, mouseOver), ++ sunken, ++ animation, ++ opacity); + + return true; + } +@@ -5450,6 +5462,8 @@ bool Style::drawCheckBoxLabelControl(const QStyleOption *option, QPainter *paint + return true; + } + ++ const QObject *styleObject = widget ? widget : option->styleObject; ++ + // copy palette and rect + const auto &palette(option->palette); + const auto &rect(option->rect); +@@ -5501,10 +5515,10 @@ bool Style::drawCheckBoxLabelControl(const QStyleOption *option, QPainter *paint + const bool hasFocus(enabled && (state & State_HasFocus)); + + // update animation state +- _animations->widgetStateEngine().updateState(widget, AnimationFocus, hasFocus); ++ _animations->widgetStateEngine().updateState(styleObject, AnimationFocus, hasFocus); + +- const bool isFocusAnimated(_animations->widgetStateEngine().isAnimated(widget, AnimationFocus)); +- const qreal opacity(_animations->widgetStateEngine().opacity(widget, AnimationFocus)); ++ const bool isFocusAnimated(_animations->widgetStateEngine().isAnimated(styleObject, AnimationFocus)); ++ const qreal opacity(_animations->widgetStateEngine().opacity(styleObject, AnimationFocus)); + // focus color + QColor focusColor; + if (isFocusAnimated) { +-- +GitLab + + +From a572df8f099969919544dbc2478de6aa2082a705 Mon Sep 17 00:00:00 2001 +From: Kai Uwe Broulik +Date: Sat, 24 May 2025 09:54:44 +0200 +Subject: [PATCH 3/3] kstyle: Register Qt Quick Controls with animations + +Since it's all software-rendered and indirect, only the +CheckBox and RadioButton controls are actually wired up +since the rest of the animations is quite subtle. +--- + kstyle/animations/breezeanimations.cpp | 9 +++++++++ + kstyle/breezestyle.cpp | 4 ++++ + 2 files changed, 13 insertions(+) + +diff --git a/kstyle/animations/breezeanimations.cpp b/kstyle/animations/breezeanimations.cpp +index ceab53802..5285f5daf 100644 +--- a/kstyle/animations/breezeanimations.cpp ++++ b/kstyle/animations/breezeanimations.cpp +@@ -100,6 +100,15 @@ void Animations::registerWidget(QObject *widget) const + // install animation timers + // for optimization, one should put with most used widgets here first + ++ // KQuickStyleItem. ++ const QString elementType = widget->property("elementType").toString(); ++ if (!elementType.isEmpty()) { ++ if (elementType == QLatin1String("checkbox") || elementType == QLatin1String("radiobutton")) { ++ _widgetStateEngine->registerWidget(widget, AnimationHover | AnimationFocus | AnimationPressed); ++ } ++ return; ++ } ++ + // buttons + if (auto toolButton = qobject_cast(widget)) { + _toolButtonEngine->registerWidget(toolButton, AnimationHover | AnimationFocus); +diff --git a/kstyle/breezestyle.cpp b/kstyle/breezestyle.cpp +index 19149c382..e549803f1 100644 +--- a/kstyle/breezestyle.cpp ++++ b/kstyle/breezestyle.cpp +@@ -4739,6 +4739,7 @@ bool Style::drawPanelItemViewItemPrimitive(const QStyleOption *option, QPainter + bool Style::drawIndicatorCheckBoxPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const + { + const QObject *styleObject = widget ? widget : option->styleObject; ++ isQtQuickControl(option, widget); // registers it with widgetStateEngine. + + // copy rect and palette + const auto &rect(option->rect); +@@ -4780,6 +4781,7 @@ bool Style::drawIndicatorCheckBoxPrimitive(const QStyleOption *option, QPainter + bool Style::drawIndicatorRadioButtonPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const + { + const QObject *styleObject = widget ? widget : option->styleObject; ++ isQtQuickControl(option, widget); // registers it with widgetStateEngine. + + // copy rect and palette + const auto &rect(option->rect); +@@ -5463,6 +5465,7 @@ bool Style::drawCheckBoxLabelControl(const QStyleOption *option, QPainter *paint + } + + const QObject *styleObject = widget ? widget : option->styleObject; ++ isQtQuickControl(option, widget); // registers it with widgetStateEngine. + + // copy palette and rect + const auto &palette(option->palette); +@@ -8460,6 +8463,7 @@ bool Style::isQtQuickControl(const QStyleOption *option, const QWidget *widget) + if (!widget && option) { + if (const auto item = qobject_cast(option->styleObject)) { + _windowManager->registerQuickItem(item); ++ _animations->registerWidget(item); + return true; + } + } +-- +GitLab + diff --git a/roles/kde/patches/dolphin/patches.txt b/roles/kde/patches/dolphin/patches.txt new file mode 100644 index 0000000..a7f0a2a --- /dev/null +++ b/roles/kde/patches/dolphin/patches.txt @@ -0,0 +1,3 @@ +Gear 25.08.0: + +Pr 946 https://invent.kde.org/system/dolphin/-/merge_requests/946 diff --git a/roles/kde/patches/dolphin/pr946.patch b/roles/kde/patches/dolphin/pr946.patch new file mode 100644 index 0000000..f01f4bd --- /dev/null +++ b/roles/kde/patches/dolphin/pr946.patch @@ -0,0 +1,3978 @@ +From 21990d59c176084af615b97a6e586d32aa47fb66 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Fri, 11 Apr 2025 17:31:12 +0300 +Subject: [PATCH 01/69] WIP: new selection effect stuff + +based on felix branch +https://invent.kde.org/felixernst/dolphin/-/commit/b3fa4479c1c23da08c120e8462bae42c712381d3 +--- + src/kitemviews/kitemlistwidget.cpp | 49 +++++++++++------ + src/kitemviews/kstandarditemlistwidget.cpp | 63 +++++++--------------- + 2 files changed, 52 insertions(+), 60 deletions(-) + +diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp +index baf2445726..a6fd75a347 100644 +--- a/src/kitemviews/kitemlistwidget.cpp ++++ b/src/kitemviews/kitemlistwidget.cpp +@@ -124,23 +124,11 @@ void KItemListWidget::paint(QPainter *painter, const QStyleOptionGraphicsItem *o + painter->fillRect(backgroundRect, backgroundColor); + } + +- if (m_selected && m_editedRole.isEmpty()) { ++ if ((m_selected || m_current) && m_editedRole.isEmpty()) { + const QStyle::State activeState(isActiveWindow() && widget->hasFocus() ? QStyle::State_Active : 0); + drawItemStyleOption(painter, widget, activeState | QStyle::State_Enabled | QStyle::State_Selected | QStyle::State_Item); + } + +- if (m_current && m_editedRole.isEmpty()) { +- QStyleOptionFocusRect focusRectOption; +- initStyleOption(&focusRectOption); +- focusRectOption.rect = textFocusRect().toRect(); +- focusRectOption.state = QStyle::State_Enabled | QStyle::State_Item | QStyle::State_KeyboardFocusChange; +- if (m_selected && widget->hasFocus()) { +- focusRectOption.state |= QStyle::State_Selected; +- } +- +- style()->drawPrimitive(QStyle::PE_FrameFocusRect, &focusRectOption, painter, widget); +- } +- + if (m_hoverOpacity > 0.0) { + if (!m_hoverCache) { + // Initialize the m_hoverCache pixmap to improve the drawing performance +@@ -623,8 +611,39 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + viewItemOption.state = styleState; + viewItemOption.viewItemPosition = QStyleOptionViewItem::OnlyOne; + viewItemOption.showDecorationSelected = true; +- viewItemOption.rect = selectionRect().toRect(); +- style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &viewItemOption, painter, widget); ++ viewItemOption.rect = selectionRect().toRect().adjusted(2, 2, -2, -2); ++ QPainterPath path; ++ path.addRoundedRect(viewItemOption.rect, 5, 5); ++ QColor accentColor{widget->palette().color(QPalette::Accent)}; ++ painter->setRenderHint(QPainter::Antialiasing); ++ bool current = m_current && styleState & QStyle::State_Active; ++ ++ // Background item ++ accentColor.setAlphaF(0.0); ++ if (m_selected && m_hovered) { ++ accentColor.setAlphaF(0.5); ++ } else if (m_selected) { ++ accentColor.setAlphaF(0.4); ++ } else if (m_hovered && current) { ++ accentColor.setAlphaF(0.3); ++ } else if (m_hovered) { ++ accentColor.setAlphaF(0.1); ++ } ++ painter->fillPath(path, accentColor); ++ ++ // Focus decoration ++ if (current && m_hovered) { ++ accentColor.setAlphaF(1.0); ++ } else if (current || m_hovered) { ++ accentColor.setAlphaF(0.9); ++ } else if (m_current) { ++ accentColor.setAlphaF(0.3); ++ } ++ if (m_current || m_hovered) { ++ const QPen pen{accentColor, 2}; ++ painter->setPen(pen); ++ painter->drawPath(path); ++ } + } + + #include "moc_kitemlistwidget.cpp" +diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp +index 49d2f26bfd..c320e375b4 100644 +--- a/src/kitemviews/kstandarditemlistwidget.cpp ++++ b/src/kitemviews/kstandarditemlistwidget.cpp +@@ -541,31 +541,23 @@ QRectF KStandardItemListWidget::selectionRect() const + { + const_cast(this)->triggerCacheRefreshing(); + +- switch (m_layout) { +- case IconsLayout: +- return m_textRect; +- +- case CompactLayout: +- case DetailsLayout: { +- const int padding = styleOption().padding; +- QRectF adjustedIconRect = iconRect().adjusted(-padding, -padding, padding, padding); +- QRectF result = adjustedIconRect | m_textRect; +- if (m_highlightEntireRow) { +- if (layoutDirection() == Qt::LeftToRight) { +- result.setRight(leftPadding() + m_columnWidthSum); +- } else { +- result.setLeft(size().width() - m_columnWidthSum - rightPadding()); +- } ++ const int padding = styleOption().padding; ++ QRectF adjustedIconRect = iconRect().adjusted(-padding, -padding, padding, padding); ++ QRectF result = adjustedIconRect | m_textRect; ++ if (m_highlightEntireRow) { ++ if (layoutDirection() == Qt::LeftToRight) { ++ result.setRight(leftPadding() + m_columnWidthSum); ++ } else { ++ result.setLeft(size().width() - m_columnWidthSum - rightPadding()); + } +- return result; + } + +- default: +- Q_ASSERT(false); +- break; ++ if (m_layout == IconsLayout) { ++ const int availableWidth = size().width() - 2 * padding - result.width(); ++ result = result.adjusted(-0.5 * availableWidth, 0, 0.5 * availableWidth, 0); + } + +- return m_textRect; ++ return result; + } + + QRectF KStandardItemListWidget::expansionToggleRect() const +@@ -578,7 +570,6 @@ QRectF KStandardItemListWidget::selectionToggleRect() const + { + const_cast(this)->triggerCacheRefreshing(); + +- const QRectF widgetIconRect = iconRect(); + const int widgetIconSize = iconSize(); + int toggleSize = KIconLoader::SizeSmall; + if (widgetIconSize >= KIconLoader::SizeEnormous) { +@@ -587,29 +578,11 @@ QRectF KStandardItemListWidget::selectionToggleRect() const + toggleSize = KIconLoader::SizeSmallMedium; + } + +- QPointF pos = widgetIconRect.topLeft(); +- +- // If the selection toggle has a very small distance to the +- // widget borders, the size of the selection toggle will get +- // increased to prevent an accidental clicking of the item +- // when trying to hit the toggle. +- const int widgetHeight = size().height(); +- const int widgetWidth = size().width(); +- const int minMargin = 2; +- +- if (toggleSize + minMargin * 2 >= widgetHeight) { +- pos.rx() -= (widgetHeight - toggleSize) / 2; +- toggleSize = widgetHeight; +- pos.setY(0); +- } +- if (toggleSize + minMargin * 2 >= widgetWidth) { +- pos.ry() -= (widgetWidth - toggleSize) / 2; +- toggleSize = widgetWidth; +- pos.setX(0); +- } +- ++ const int padding = styleOption().padding; ++ const QRectF selectionRectMinusPadding = selectionRect().adjusted(padding, padding, -padding, -padding); ++ QPointF pos = selectionRectMinusPadding.topLeft(); + if (QApplication::isRightToLeft()) { +- pos.setX(widgetIconRect.right() - (pos.x() + toggleSize - widgetIconRect.left())); ++ pos.setX(selectionRectMinusPadding.right() - (pos.x() + toggleSize - selectionRectMinusPadding.left())); + } + + return QRectF(pos, QSizeF(toggleSize, toggleSize)); +@@ -748,7 +721,7 @@ QColor KStandardItemListWidget::textColor(const QWidget &widget) const + } + + const QPalette::ColorGroup group = isActiveWindow() && widget.hasFocus() ? QPalette::Active : QPalette::Inactive; +- const QPalette::ColorRole role = isSelected() ? QPalette::HighlightedText : normalTextColorRole(); ++ const QPalette::ColorRole role = /*isSelected() ? QPalette::HighlightedText :*/ normalTextColorRole(); + return styleOption().palette.color(group, role); + } + +@@ -1568,7 +1541,7 @@ void KStandardItemListWidget::updateAdditionalInfoTextColor() + const bool hasFocus = scene()->views()[0]->parentWidget()->hasFocus(); + if (m_customTextColor.isValid()) { + c1 = m_customTextColor; +- } else if (isSelected() && hasFocus && (m_layout != DetailsLayout || m_highlightEntireRow)) { ++ } else if (false && isSelected() && hasFocus && (m_layout != DetailsLayout || m_highlightEntireRow)) { + // The detail text color needs to match the main text (HighlightedText) for the same level + // of readability. We short circuit early here to avoid interpolating with another color. + m_additionalInfoTextColor = styleOption().palette.color(QPalette::HighlightedText); +-- +GitLab + + +From 64650add73385948fd76b487a90da2c7635a2b7a Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Mon, 14 Apr 2025 14:53:01 +0300 +Subject: [PATCH 02/69] Show a gap between selection and focus + +--- + src/kitemviews/kitemlistwidget.cpp | 35 ++++++++++++++++-------------- + 1 file changed, 19 insertions(+), 16 deletions(-) + +diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp +index a6fd75a347..1e82c27b1b 100644 +--- a/src/kitemviews/kitemlistwidget.cpp ++++ b/src/kitemviews/kitemlistwidget.cpp +@@ -607,13 +607,16 @@ void KItemListWidget::clearHoverCache() + void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QStyle::State styleState) + { + QStyleOptionViewItem viewItemOption; ++ const int focusPenWidth = 2; ++ const int roundness = 5; + initStyleOption(&viewItemOption); + viewItemOption.state = styleState; + viewItemOption.viewItemPosition = QStyleOptionViewItem::OnlyOne; + viewItemOption.showDecorationSelected = true; +- viewItemOption.rect = selectionRect().toRect().adjusted(2, 2, -2, -2); ++ viewItemOption.rect = selectionRect().toRect(); ++ viewItemOption.rect = viewItemOption.rect.adjusted(focusPenWidth, focusPenWidth, -focusPenWidth, -focusPenWidth); + QPainterPath path; +- path.addRoundedRect(viewItemOption.rect, 5, 5); ++ path.addRoundedRect(viewItemOption.rect, roundness, roundness); + QColor accentColor{widget->palette().color(QPalette::Accent)}; + painter->setRenderHint(QPainter::Antialiasing); + bool current = m_current && styleState & QStyle::State_Active; +@@ -621,26 +624,26 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + // Background item + accentColor.setAlphaF(0.0); + if (m_selected && m_hovered) { +- accentColor.setAlphaF(0.5); ++ accentColor.setAlphaF(1.0); + } else if (m_selected) { +- accentColor.setAlphaF(0.4); +- } else if (m_hovered && current) { +- accentColor.setAlphaF(0.3); ++ accentColor.setAlphaF(0.8); + } else if (m_hovered) { +- accentColor.setAlphaF(0.1); ++ accentColor.setAlphaF(0.3); ++ } ++ if (current) { ++ auto currentGap = focusPenWidth; ++ auto currentPathRect = viewItemOption.rect.adjusted(currentGap, currentGap, -currentGap, -currentGap); ++ QPainterPath currentPath; ++ currentPath.addRoundedRect(currentPathRect, roundness, roundness); ++ painter->fillPath(currentPath, accentColor); ++ } else { ++ painter->fillPath(path, accentColor); + } +- painter->fillPath(path, accentColor); + + // Focus decoration +- if (current && m_hovered) { +- accentColor.setAlphaF(1.0); +- } else if (current || m_hovered) { +- accentColor.setAlphaF(0.9); +- } else if (m_current) { +- accentColor.setAlphaF(0.3); +- } + if (m_current || m_hovered) { +- const QPen pen{accentColor, 2}; ++ accentColor.setAlphaF(1.0); ++ const QPen pen{accentColor, focusPenWidth}; + painter->setPen(pen); + painter->drawPath(path); + } +-- +GitLab + + +From da8cbfdd2d72237e2d2a13c88b5a0fafd558ea24 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Mon, 14 Apr 2025 15:20:00 +0300 +Subject: [PATCH 03/69] Do not colorize icons + +--- + src/kitemviews/kstandarditemlistwidget.cpp | 11 ----------- + 1 file changed, 11 deletions(-) + +diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp +index c320e375b4..c56c859a71 100644 +--- a/src/kitemviews/kstandarditemlistwidget.cpp ++++ b/src/kitemviews/kstandarditemlistwidget.cpp +@@ -1120,17 +1120,6 @@ void KStandardItemListWidget::updatePixmapCache() + if (m_isHidden) { + KIconEffect::semiTransparent(m_pixmap); + } +- +- if (m_layout == IconsLayout && isSelected()) { +- const QColor color = palette().brush(QPalette::Normal, QPalette::Highlight).color(); +- QImage image = m_pixmap.toImage(); +- if (image.isNull()) { +- m_hoverPixmap = QPixmap(); +- return; +- } +- KIconEffect::colorize(image, color, 0.8f); +- m_pixmap = QPixmap::fromImage(image); +- } + } + + int scaledIconSize = 0; +-- +GitLab + + +From 434aee5f8375f66c41eefc75206a2b2a81849ea9 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Mon, 14 Apr 2025 15:58:30 +0300 +Subject: [PATCH 04/69] add highlightedTextColor back + +--- + src/kitemviews/kstandarditemlistwidget.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp +index c56c859a71..486c909de1 100644 +--- a/src/kitemviews/kstandarditemlistwidget.cpp ++++ b/src/kitemviews/kstandarditemlistwidget.cpp +@@ -721,7 +721,7 @@ QColor KStandardItemListWidget::textColor(const QWidget &widget) const + } + + const QPalette::ColorGroup group = isActiveWindow() && widget.hasFocus() ? QPalette::Active : QPalette::Inactive; +- const QPalette::ColorRole role = /*isSelected() ? QPalette::HighlightedText :*/ normalTextColorRole(); ++ const QPalette::ColorRole role = isSelected() ? QPalette::HighlightedText : normalTextColorRole(); + return styleOption().palette.color(group, role); + } + +-- +GitLab + + +From fa6ed958b64cf5fd39d3624a661b5984249fce30 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Mon, 14 Apr 2025 16:51:26 +0300 +Subject: [PATCH 05/69] Modify padding for iconslayout + +--- + src/kitemviews/kstandarditemlistwidget.cpp | 10 +++++----- + 1 file changed, 5 insertions(+), 5 deletions(-) + +diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp +index 486c909de1..2d209ba5bb 100644 +--- a/src/kitemviews/kstandarditemlistwidget.cpp ++++ b/src/kitemviews/kstandarditemlistwidget.cpp +@@ -550,11 +550,11 @@ QRectF KStandardItemListWidget::selectionRect() const + } else { + result.setLeft(size().width() - m_columnWidthSum - rightPadding()); + } +- } +- +- if (m_layout == IconsLayout) { +- const int availableWidth = size().width() - 2 * padding - result.width(); +- result = result.adjusted(-0.5 * availableWidth, 0, 0.5 * availableWidth, 0); ++ } else { ++ // Make sure values are always positive ++ const int availableWidth = qAbs((size().width() - 2 * padding - result.width()) * 0.5); ++ const int availableHeight = m_layout == CompactLayout ? 0 : qAbs((size().height() - 2 * padding - result.height()) * 0.5); ++ result = result.adjusted(-availableWidth, -availableHeight, availableWidth, availableHeight); + } + + return result; +-- +GitLab + + +From 66c8dbf844c81197ec30a90ead4b392b803573ae Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Mon, 14 Apr 2025 16:55:17 +0300 +Subject: [PATCH 06/69] Use highlight color + +--- + src/kitemviews/kitemlistwidget.cpp | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp +index 1e82c27b1b..8c5b1e6259 100644 +--- a/src/kitemviews/kitemlistwidget.cpp ++++ b/src/kitemviews/kitemlistwidget.cpp +@@ -617,7 +617,7 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + viewItemOption.rect = viewItemOption.rect.adjusted(focusPenWidth, focusPenWidth, -focusPenWidth, -focusPenWidth); + QPainterPath path; + path.addRoundedRect(viewItemOption.rect, roundness, roundness); +- QColor accentColor{widget->palette().color(QPalette::Accent)}; ++ QColor accentColor{widget->palette().color(QPalette::Highlight)}; + painter->setRenderHint(QPainter::Antialiasing); + bool current = m_current && styleState & QStyle::State_Active; + +@@ -643,7 +643,7 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + // Focus decoration + if (m_current || m_hovered) { + accentColor.setAlphaF(1.0); +- const QPen pen{accentColor, focusPenWidth}; ++ const QPen pen{accentColor.lighter(120), focusPenWidth}; + painter->setPen(pen); + painter->drawPath(path); + } +-- +GitLab + + +From 49441c8362b56b04ce0ccf25f6f45070a4ca8477 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Mon, 14 Apr 2025 17:00:54 +0300 +Subject: [PATCH 07/69] adjust the viewitemoption.rect + +--- + src/kitemviews/kitemlistwidget.cpp | 9 ++++++--- + 1 file changed, 6 insertions(+), 3 deletions(-) + +diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp +index 8c5b1e6259..9444616f58 100644 +--- a/src/kitemviews/kitemlistwidget.cpp ++++ b/src/kitemviews/kitemlistwidget.cpp +@@ -608,20 +608,23 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + { + QStyleOptionViewItem viewItemOption; + const int focusPenWidth = 2; ++ // Small adjustment due to how QRect coordinates work ++ const int viewItemRectAdjustment = focusPenWidth + 1; + const int roundness = 5; + initStyleOption(&viewItemOption); + viewItemOption.state = styleState; + viewItemOption.viewItemPosition = QStyleOptionViewItem::OnlyOne; + viewItemOption.showDecorationSelected = true; + viewItemOption.rect = selectionRect().toRect(); +- viewItemOption.rect = viewItemOption.rect.adjusted(focusPenWidth, focusPenWidth, -focusPenWidth, -focusPenWidth); ++ viewItemOption.rect = viewItemOption.rect.adjusted(viewItemRectAdjustment, viewItemRectAdjustment, -viewItemRectAdjustment, -viewItemRectAdjustment); + QPainterPath path; + path.addRoundedRect(viewItemOption.rect, roundness, roundness); + QColor accentColor{widget->palette().color(QPalette::Highlight)}; + painter->setRenderHint(QPainter::Antialiasing); + bool current = m_current && styleState & QStyle::State_Active; + +- // Background item ++ // Background item, alpha values are from ++ // https://invent.kde.org/plasma/libplasma/-/blob/master/src/desktoptheme/breeze/widgets/viewitem.svg + accentColor.setAlphaF(0.0); + if (m_selected && m_hovered) { + accentColor.setAlphaF(1.0); +@@ -641,7 +644,7 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + } + + // Focus decoration +- if (m_current || m_hovered) { ++ if (current || m_hovered) { + accentColor.setAlphaF(1.0); + const QPen pen{accentColor.lighter(120), focusPenWidth}; + painter->setPen(pen); +-- +GitLab + + +From 35ffad2d67c6dca2bab0d979b8017a5da377ae05 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Tue, 15 Apr 2025 15:03:01 +0300 +Subject: [PATCH 08/69] Make hovered outline transparent + +--- + src/kitemviews/kitemlistwidget.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp +index 9444616f58..ddf9a4ea1a 100644 +--- a/src/kitemviews/kitemlistwidget.cpp ++++ b/src/kitemviews/kitemlistwidget.cpp +@@ -645,7 +645,7 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + + // Focus decoration + if (current || m_hovered) { +- accentColor.setAlphaF(1.0); ++ accentColor.setAlphaF(m_hovered ? 0.4 : 1.0); + const QPen pen{accentColor.lighter(120), focusPenWidth}; + painter->setPen(pen); + painter->drawPath(path); +-- +GitLab + + +From 528f25f0c412073a3ae643fb5f9f8d163cc83d21 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Tue, 15 Apr 2025 15:10:27 +0300 +Subject: [PATCH 09/69] Only apply hover effect to icon when its selected + +Better contrast this way +--- + src/kitemviews/kstandarditemlistwidget.cpp | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp +index 2d209ba5bb..b9bbb68b8a 100644 +--- a/src/kitemviews/kstandarditemlistwidget.cpp ++++ b/src/kitemviews/kstandarditemlistwidget.cpp +@@ -1171,7 +1171,9 @@ void KStandardItemListWidget::updatePixmapCache() + // Prepare the pixmap that is used when the item gets hovered + if (isHovered()) { + m_hoverPixmap = m_pixmap; +- KIconEffect::toActive(m_hoverPixmap); ++ if (isSelected()) { ++ KIconEffect::toActive(m_hoverPixmap); ++ } + } else if (hoverOpacity() <= 0.0) { + // No hover animation is ongoing. Clear m_hoverPixmap to save memory. + m_hoverPixmap = QPixmap(); +-- +GitLab + + +From edf7272c9c8245f7297d2bd9738996433fd8bb82 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Tue, 15 Apr 2025 16:53:09 +0300 +Subject: [PATCH 10/69] Use style 2 + +--- + src/kitemviews/kitemlistwidget.cpp | 17 +++++------------ + 1 file changed, 5 insertions(+), 12 deletions(-) + +diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp +index ddf9a4ea1a..14795af9fa 100644 +--- a/src/kitemviews/kitemlistwidget.cpp ++++ b/src/kitemviews/kitemlistwidget.cpp +@@ -629,23 +629,16 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + if (m_selected && m_hovered) { + accentColor.setAlphaF(1.0); + } else if (m_selected) { +- accentColor.setAlphaF(0.8); ++ accentColor.setAlphaF(0.5); + } else if (m_hovered) { + accentColor.setAlphaF(0.3); + } +- if (current) { +- auto currentGap = focusPenWidth; +- auto currentPathRect = viewItemOption.rect.adjusted(currentGap, currentGap, -currentGap, -currentGap); +- QPainterPath currentPath; +- currentPath.addRoundedRect(currentPathRect, roundness, roundness); +- painter->fillPath(currentPath, accentColor); +- } else { +- painter->fillPath(path, accentColor); +- } ++ ++ painter->fillPath(path, accentColor); + + // Focus decoration +- if (current || m_hovered) { +- accentColor.setAlphaF(m_hovered ? 0.4 : 1.0); ++ if (current) { ++ accentColor.setAlphaF(1.0); + const QPen pen{accentColor.lighter(120), focusPenWidth}; + painter->setPen(pen); + painter->drawPath(path); +-- +GitLab + + +From 9933d7c49a1d0bcf956afe69b347df18fb2f1c70 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Tue, 15 Apr 2025 17:04:08 +0300 +Subject: [PATCH 11/69] Set pen color according to base lightness + +--- + src/kitemviews/kitemlistwidget.cpp | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp +index 14795af9fa..64584abce2 100644 +--- a/src/kitemviews/kitemlistwidget.cpp ++++ b/src/kitemviews/kitemlistwidget.cpp +@@ -629,7 +629,7 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + if (m_selected && m_hovered) { + accentColor.setAlphaF(1.0); + } else if (m_selected) { +- accentColor.setAlphaF(0.5); ++ accentColor.setAlphaF(0.8); + } else if (m_hovered) { + accentColor.setAlphaF(0.3); + } +@@ -639,7 +639,8 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + // Focus decoration + if (current) { + accentColor.setAlphaF(1.0); +- const QPen pen{accentColor.lighter(120), focusPenWidth}; ++ // Set the pen color lighter or darker depending on background color ++ const QPen pen{m_styleOption.palette.color(QPalette::Base).lightnessF() > 0.5 ? accentColor.darker() : accentColor.lighter(), focusPenWidth}; + painter->setPen(pen); + painter->drawPath(path); + } +-- +GitLab + + +From 0ddc8b6f93bda0afc525e57576ef00ab0ad94697 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Tue, 15 Apr 2025 17:21:11 +0300 +Subject: [PATCH 12/69] Dont show focus before interaction + +--- + src/kitemviews/kitemlistcontroller.cpp | 4 ++++ + src/kitemviews/kitemlistselectionmanager.cpp | 4 +--- + 2 files changed, 5 insertions(+), 3 deletions(-) + +diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp +index cd60c3a41b..f0c6a26878 100644 +--- a/src/kitemviews/kitemlistcontroller.cpp ++++ b/src/kitemviews/kitemlistcontroller.cpp +@@ -478,6 +478,10 @@ bool KItemListController::keyPressEvent(QKeyEvent *event) + return false; + } + ++ if (index < 0) { ++ index = 0; ++ } ++ + if (m_selectionManager->currentItem() != index) { + switch (m_selectionBehavior) { + case NoSelection: +diff --git a/src/kitemviews/kitemlistselectionmanager.cpp b/src/kitemviews/kitemlistselectionmanager.cpp +index 6f7f0e0776..abf58df240 100644 +--- a/src/kitemviews/kitemlistselectionmanager.cpp ++++ b/src/kitemviews/kitemlistselectionmanager.cpp +@@ -217,9 +217,7 @@ void KItemListSelectionManager::itemsInserted(const KItemRangeList &itemRanges) + const KItemSet previousSelection = selectedItems(); + + // Update the current item +- if (m_currentItem < 0) { +- setCurrentItem(0); +- } else { ++ if (m_currentItem >= 0) { + const int previousCurrent = m_currentItem; + int inc = 0; + for (const KItemRange &itemRange : itemRanges) { +-- +GitLab + + +From 8588d3aa3bb73e7c6f35df8844de0b9ccc335078 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Wed, 16 Apr 2025 15:23:08 +0300 +Subject: [PATCH 13/69] Less intense selection lightening/darkening + +--- + src/kitemviews/kitemlistwidget.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp +index 64584abce2..ebbfb98cc6 100644 +--- a/src/kitemviews/kitemlistwidget.cpp ++++ b/src/kitemviews/kitemlistwidget.cpp +@@ -640,7 +640,7 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + if (current) { + accentColor.setAlphaF(1.0); + // Set the pen color lighter or darker depending on background color +- const QPen pen{m_styleOption.palette.color(QPalette::Base).lightnessF() > 0.5 ? accentColor.darker() : accentColor.lighter(), focusPenWidth}; ++ const QPen pen{m_styleOption.palette.color(QPalette::Base).lightnessF() > 0.5 ? accentColor.darker(110) : accentColor.lighter(110), focusPenWidth}; + painter->setPen(pen); + painter->drawPath(path); + } +-- +GitLab + + +From cbec8b9acd0aa13ddb984097e9c9ea37baba6a33 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Wed, 16 Apr 2025 15:23:43 +0300 +Subject: [PATCH 14/69] Clean up currentItem when clicking on empty area + +--- + src/kitemviews/kitemlistcontroller.cpp | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp +index f0c6a26878..4f5c8fbb39 100644 +--- a/src/kitemviews/kitemlistcontroller.cpp ++++ b/src/kitemviews/kitemlistcontroller.cpp +@@ -1720,6 +1720,7 @@ bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifier + + if (!m_pressedIndex.has_value()) { + // We have a right-click in an empty region, don't create rubber band. ++ m_selectionManager->setCurrentItem(-1); + return true; + } + } +@@ -1783,6 +1784,8 @@ bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifier + } + + return !createRubberBand; ++ } else { ++ m_selectionManager->setCurrentItem(-1); + } + + return false; +-- +GitLab + + +From cfe048eabb2e29c8e01f9a0428548f8366078b73 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Wed, 16 Apr 2025 15:52:33 +0300 +Subject: [PATCH 15/69] Clean up the current item after mouse drag if it + selects nothing + +--- + src/kitemviews/kitemlistcontroller.cpp | 16 ++++++++++++++-- + src/kitemviews/kitemlistcontroller.h | 5 +++++ + 2 files changed, 19 insertions(+), 2 deletions(-) + +diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp +index 4f5c8fbb39..e8edc33c03 100644 +--- a/src/kitemviews/kitemlistcontroller.cpp ++++ b/src/kitemviews/kitemlistcontroller.cpp +@@ -1720,7 +1720,7 @@ bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifier + + if (!m_pressedIndex.has_value()) { + // We have a right-click in an empty region, don't create rubber band. +- m_selectionManager->setCurrentItem(-1); ++ cleanUpCurrentItem(); + return true; + } + } +@@ -1785,7 +1785,7 @@ bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifier + + return !createRubberBand; + } else { +- m_selectionManager->setCurrentItem(-1); ++ cleanUpCurrentItem(); + } + + return false; +@@ -1881,6 +1881,8 @@ bool KItemListController::onRelease(const QPointF &pos, const Qt::KeyboardModifi + m_pressedMouseGlobalPos = QPointF(); + m_pressedIndex = std::nullopt; + m_clearSelectionIfItemsAreNotDragged = false; ++ // Clean up current item if nothing is selected after release from drag operation ++ cleanUpCurrentItem(); + return false; + } + +@@ -1913,4 +1915,14 @@ void KItemListController::slotStateChanged(QScroller::State newState) + } + } + ++void KItemListController::cleanUpCurrentItem() ++{ ++ if (m_selectionManager->currentItem() != -1 && m_selectionManager->selectedItems().count() == 0) { ++ if (m_selectionManager->isAnchoredSelectionActive()) { ++ m_selectionManager->endAnchoredSelection(); ++ } ++ m_selectionManager->setCurrentItem(-1); ++ } ++} ++ + #include "moc_kitemlistcontroller.cpp" +diff --git a/src/kitemviews/kitemlistcontroller.h b/src/kitemviews/kitemlistcontroller.h +index 48da07206d..7fcda72795 100644 +--- a/src/kitemviews/kitemlistcontroller.h ++++ b/src/kitemviews/kitemlistcontroller.h +@@ -325,6 +325,11 @@ private: + bool onPress(const QPointF &pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons); + bool onRelease(const QPointF &pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons, bool touch); + void startRubberBand(); ++ /** ++ * Ends the anchoredSelection and sets the currentItem in selectionManager to -1. ++ * This is used to not draw the focus item when using mouse to operate the view. ++ */ ++ void cleanUpCurrentItem(); + + private: + bool m_singleClickActivationEnforced; +-- +GitLab + + +From d833acb8a5af26fef199e9dd6c1e26416947a0b9 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Wed, 16 Apr 2025 16:10:42 +0300 +Subject: [PATCH 16/69] Fix text colors + +--- + src/kitemviews/kitemlistwidget.cpp | 6 +++--- + src/kitemviews/kstandarditemlistwidget.cpp | 10 ++++------ + 2 files changed, 7 insertions(+), 9 deletions(-) + +diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp +index ebbfb98cc6..0f85b726a8 100644 +--- a/src/kitemviews/kitemlistwidget.cpp ++++ b/src/kitemviews/kitemlistwidget.cpp +@@ -619,7 +619,7 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + viewItemOption.rect = viewItemOption.rect.adjusted(viewItemRectAdjustment, viewItemRectAdjustment, -viewItemRectAdjustment, -viewItemRectAdjustment); + QPainterPath path; + path.addRoundedRect(viewItemOption.rect, roundness, roundness); +- QColor accentColor{widget->palette().color(QPalette::Highlight)}; ++ QColor accentColor{widget->palette().color(QPalette::Accent)}; + painter->setRenderHint(QPainter::Antialiasing); + bool current = m_current && styleState & QStyle::State_Active; + +@@ -627,9 +627,9 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + // https://invent.kde.org/plasma/libplasma/-/blob/master/src/desktoptheme/breeze/widgets/viewitem.svg + accentColor.setAlphaF(0.0); + if (m_selected && m_hovered) { +- accentColor.setAlphaF(1.0); ++ accentColor.setAlphaF(0.5); + } else if (m_selected) { +- accentColor.setAlphaF(0.8); ++ accentColor.setAlphaF(0.4); + } else if (m_hovered) { + accentColor.setAlphaF(0.3); + } +diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp +index b9bbb68b8a..b60aeb19ba 100644 +--- a/src/kitemviews/kstandarditemlistwidget.cpp ++++ b/src/kitemviews/kstandarditemlistwidget.cpp +@@ -721,7 +721,7 @@ QColor KStandardItemListWidget::textColor(const QWidget &widget) const + } + + const QPalette::ColorGroup group = isActiveWindow() && widget.hasFocus() ? QPalette::Active : QPalette::Inactive; +- const QPalette::ColorRole role = isSelected() ? QPalette::HighlightedText : normalTextColorRole(); ++ const QPalette::ColorRole role = normalTextColorRole(); + return styleOption().palette.color(group, role); + } + +@@ -1171,9 +1171,7 @@ void KStandardItemListWidget::updatePixmapCache() + // Prepare the pixmap that is used when the item gets hovered + if (isHovered()) { + m_hoverPixmap = m_pixmap; +- if (isSelected()) { +- KIconEffect::toActive(m_hoverPixmap); +- } ++ KIconEffect::toActive(m_hoverPixmap); + } else if (hoverOpacity() <= 0.0) { + // No hover animation is ongoing. Clear m_hoverPixmap to save memory. + m_hoverPixmap = QPixmap(); +@@ -1532,10 +1530,10 @@ void KStandardItemListWidget::updateAdditionalInfoTextColor() + const bool hasFocus = scene()->views()[0]->parentWidget()->hasFocus(); + if (m_customTextColor.isValid()) { + c1 = m_customTextColor; +- } else if (false && isSelected() && hasFocus && (m_layout != DetailsLayout || m_highlightEntireRow)) { ++ } else if (isSelected() && hasFocus && (m_layout != DetailsLayout || m_highlightEntireRow)) { + // The detail text color needs to match the main text (HighlightedText) for the same level + // of readability. We short circuit early here to avoid interpolating with another color. +- m_additionalInfoTextColor = styleOption().palette.color(QPalette::HighlightedText); ++ m_additionalInfoTextColor = styleOption().palette.color(normalTextColorRole()); + return; + } else { + c1 = styleOption().palette.text().color(); +-- +GitLab + + +From ec725291eac833b8982cda4b1592d54239626b4f Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Wed, 16 Apr 2025 16:32:35 +0300 +Subject: [PATCH 17/69] Add bit more padding around the text to take outline in + account + +--- + src/kitemviews/kstandarditemlistwidget.cpp | 7 +++++-- + 1 file changed, 5 insertions(+), 2 deletions(-) + +diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp +index b60aeb19ba..8e2515c686 100644 +--- a/src/kitemviews/kstandarditemlistwidget.cpp ++++ b/src/kitemviews/kstandarditemlistwidget.cpp +@@ -1171,7 +1171,9 @@ void KStandardItemListWidget::updatePixmapCache() + // Prepare the pixmap that is used when the item gets hovered + if (isHovered()) { + m_hoverPixmap = m_pixmap; +- KIconEffect::toActive(m_hoverPixmap); ++ if (isSelected()) { ++ KIconEffect::toActive(m_hoverPixmap); ++ } + } else if (hoverOpacity() <= 0.0) { + // No hover animation is ongoing. Clear m_hoverPixmap to save memory. + m_hoverPixmap = QPixmap(); +@@ -1300,7 +1302,8 @@ void KStandardItemListWidget::updateIconsLayoutTextCache() + + const KItemListStyleOption &option = styleOption(); + const qreal padding = option.padding; +- const qreal maxWidth = size().width() - 2 * padding; ++ // adjust the max width according to new outline style ++ const qreal maxWidth = size().width() - 2 * padding - 10; + const qreal lineSpacing = m_customizedFontMetrics.lineSpacing(); + + // Initialize properties for the "text" role. It will be used as anchor +-- +GitLab + + +From 230a6221183594954086187553016d577d738a38 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Wed, 16 Apr 2025 16:36:26 +0300 +Subject: [PATCH 18/69] revert padding + +--- + src/kitemviews/kstandarditemlistwidget.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp +index 8e2515c686..0303547b23 100644 +--- a/src/kitemviews/kstandarditemlistwidget.cpp ++++ b/src/kitemviews/kstandarditemlistwidget.cpp +@@ -1303,7 +1303,7 @@ void KStandardItemListWidget::updateIconsLayoutTextCache() + const KItemListStyleOption &option = styleOption(); + const qreal padding = option.padding; + // adjust the max width according to new outline style +- const qreal maxWidth = size().width() - 2 * padding - 10; ++ const qreal maxWidth = size().width() - 2 * padding; + const qreal lineSpacing = m_customizedFontMetrics.lineSpacing(); + + // Initialize properties for the "text" role. It will be used as anchor +-- +GitLab + + +From 462e3846d8f077f739d7551a0930df53d9c7069e Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Thu, 17 Apr 2025 11:41:33 +0300 +Subject: [PATCH 19/69] Use rect instead of selectionRect for the selection + painting + +Selectionrect is for mouse selection things +--- + src/kitemviews/kitemlistwidget.cpp | 5 +---- + 1 file changed, 1 insertion(+), 4 deletions(-) + +diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp +index 0f85b726a8..92041fc1e7 100644 +--- a/src/kitemviews/kitemlistwidget.cpp ++++ b/src/kitemviews/kitemlistwidget.cpp +@@ -608,15 +608,12 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + { + QStyleOptionViewItem viewItemOption; + const int focusPenWidth = 2; +- // Small adjustment due to how QRect coordinates work +- const int viewItemRectAdjustment = focusPenWidth + 1; + const int roundness = 5; + initStyleOption(&viewItemOption); + viewItemOption.state = styleState; + viewItemOption.viewItemPosition = QStyleOptionViewItem::OnlyOne; + viewItemOption.showDecorationSelected = true; +- viewItemOption.rect = selectionRect().toRect(); +- viewItemOption.rect = viewItemOption.rect.adjusted(viewItemRectAdjustment, viewItemRectAdjustment, -viewItemRectAdjustment, -viewItemRectAdjustment); ++ viewItemOption.rect = rect().toRect(); + QPainterPath path; + path.addRoundedRect(viewItemOption.rect, roundness, roundness); + QColor accentColor{widget->palette().color(QPalette::Accent)}; +-- +GitLab + + +From 06dbf47020e8a3bc33f8ae87a451a8418efc3306 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Thu, 17 Apr 2025 11:53:15 +0300 +Subject: [PATCH 20/69] Revert currentItem changes + +--- + src/kitemviews/kitemlistcontroller.cpp | 19 ------------------- + src/kitemviews/kitemlistcontroller.h | 5 ----- + src/kitemviews/kitemlistselectionmanager.cpp | 4 +++- + 3 files changed, 3 insertions(+), 25 deletions(-) + +diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp +index e8edc33c03..cd60c3a41b 100644 +--- a/src/kitemviews/kitemlistcontroller.cpp ++++ b/src/kitemviews/kitemlistcontroller.cpp +@@ -478,10 +478,6 @@ bool KItemListController::keyPressEvent(QKeyEvent *event) + return false; + } + +- if (index < 0) { +- index = 0; +- } +- + if (m_selectionManager->currentItem() != index) { + switch (m_selectionBehavior) { + case NoSelection: +@@ -1720,7 +1716,6 @@ bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifier + + if (!m_pressedIndex.has_value()) { + // We have a right-click in an empty region, don't create rubber band. +- cleanUpCurrentItem(); + return true; + } + } +@@ -1784,8 +1779,6 @@ bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifier + } + + return !createRubberBand; +- } else { +- cleanUpCurrentItem(); + } + + return false; +@@ -1881,8 +1874,6 @@ bool KItemListController::onRelease(const QPointF &pos, const Qt::KeyboardModifi + m_pressedMouseGlobalPos = QPointF(); + m_pressedIndex = std::nullopt; + m_clearSelectionIfItemsAreNotDragged = false; +- // Clean up current item if nothing is selected after release from drag operation +- cleanUpCurrentItem(); + return false; + } + +@@ -1915,14 +1906,4 @@ void KItemListController::slotStateChanged(QScroller::State newState) + } + } + +-void KItemListController::cleanUpCurrentItem() +-{ +- if (m_selectionManager->currentItem() != -1 && m_selectionManager->selectedItems().count() == 0) { +- if (m_selectionManager->isAnchoredSelectionActive()) { +- m_selectionManager->endAnchoredSelection(); +- } +- m_selectionManager->setCurrentItem(-1); +- } +-} +- + #include "moc_kitemlistcontroller.cpp" +diff --git a/src/kitemviews/kitemlistcontroller.h b/src/kitemviews/kitemlistcontroller.h +index 7fcda72795..48da07206d 100644 +--- a/src/kitemviews/kitemlistcontroller.h ++++ b/src/kitemviews/kitemlistcontroller.h +@@ -325,11 +325,6 @@ private: + bool onPress(const QPointF &pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons); + bool onRelease(const QPointF &pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons, bool touch); + void startRubberBand(); +- /** +- * Ends the anchoredSelection and sets the currentItem in selectionManager to -1. +- * This is used to not draw the focus item when using mouse to operate the view. +- */ +- void cleanUpCurrentItem(); + + private: + bool m_singleClickActivationEnforced; +diff --git a/src/kitemviews/kitemlistselectionmanager.cpp b/src/kitemviews/kitemlistselectionmanager.cpp +index abf58df240..6f7f0e0776 100644 +--- a/src/kitemviews/kitemlistselectionmanager.cpp ++++ b/src/kitemviews/kitemlistselectionmanager.cpp +@@ -217,7 +217,9 @@ void KItemListSelectionManager::itemsInserted(const KItemRangeList &itemRanges) + const KItemSet previousSelection = selectedItems(); + + // Update the current item +- if (m_currentItem >= 0) { ++ if (m_currentItem < 0) { ++ setCurrentItem(0); ++ } else { + const int previousCurrent = m_currentItem; + int inc = 0; + for (const KItemRange &itemRange : itemRanges) { +-- +GitLab + + +From 71ed6a4a0745770410056ad234ee8d7104c10767 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Thu, 17 Apr 2025 12:20:14 +0300 +Subject: [PATCH 21/69] Make the focus effect hiding/showing visual only + +--- + src/kitemviews/kitemlistcontroller.cpp | 14 ++++++++++++++ + src/kitemviews/kitemlistcontroller.h | 1 + + src/kitemviews/kitemlistview.cpp | 3 +++ + src/kitemviews/kitemlistwidget.cpp | 8 +++++++- + src/kitemviews/kitemlistwidget.h | 3 +++ + 5 files changed, 28 insertions(+), 1 deletion(-) + +diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp +index cd60c3a41b..de8bd26254 100644 +--- a/src/kitemviews/kitemlistcontroller.cpp ++++ b/src/kitemviews/kitemlistcontroller.cpp +@@ -1731,6 +1731,7 @@ bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifier + if (rightClick && hitTargetIsRowEmptyRegion) { + // We have a right click outside the icon and text rect but within the hover highlight area. + // We don't want items to get selected through this, so we return now. ++ showFocusWidget(false); + return true; + } + +@@ -1779,6 +1780,8 @@ bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifier + } + + return !createRubberBand; ++ } else { ++ showFocusWidget(false); + } + + return false; +@@ -1906,4 +1909,15 @@ void KItemListController::slotStateChanged(QScroller::State newState) + } + } + ++void KItemListController::showFocusWidget(bool show) ++{ ++ const auto widgets = m_view->visibleItemListWidgets(); ++ for (auto widget : widgets) { ++ if (widget->isCurrent()) { ++ widget->showFocusEffect(show); ++ return; ++ } ++ } ++} ++ + #include "moc_kitemlistcontroller.cpp" +diff --git a/src/kitemviews/kitemlistcontroller.h b/src/kitemviews/kitemlistcontroller.h +index 48da07206d..d071f20ab0 100644 +--- a/src/kitemviews/kitemlistcontroller.h ++++ b/src/kitemviews/kitemlistcontroller.h +@@ -325,6 +325,7 @@ private: + bool onPress(const QPointF &pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons); + bool onRelease(const QPointF &pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons, bool touch); + void startRubberBand(); ++ void showFocusWidget(bool show); + + private: + bool m_singleClickActivationEnforced; +diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp +index 265e41e6ca..4db8d4ed0a 100644 +--- a/src/kitemviews/kitemlistview.cpp ++++ b/src/kitemviews/kitemlistview.cpp +@@ -1529,6 +1529,8 @@ void KItemListView::slotCurrentChanged(int current, int previous) + + KItemListWidget *currentWidget = m_visibleItems.value(current, nullptr); + if (currentWidget) { ++ // If previous item was -1, we're setting the first item current, so we can hide focus ++ currentWidget->showFocusEffect(previous >= 0 ? true : false); + currentWidget->setCurrent(true); + } + } +@@ -1548,6 +1550,7 @@ void KItemListView::slotSelectionChanged(const KItemSet ¤t, const KItemSet + KItemListWidget *widget = it.value(); + const bool isSelected(current.contains(index)); + widget->setSelected(isSelected); ++ widget->showFocusEffect(true); + + #ifndef QT_NO_ACCESSIBILITY + if (!QAccessible::isActive()) { +diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp +index 92041fc1e7..e41f8a1557 100644 +--- a/src/kitemviews/kitemlistwidget.cpp ++++ b/src/kitemviews/kitemlistwidget.cpp +@@ -37,6 +37,7 @@ KItemListWidget::KItemListWidget(KItemListWidgetInformant *informant, QGraphicsI + , m_expansionAreaHovered(false) + , m_alternateBackground(false) + , m_enabledSelectionToggle(false) ++ , m_showFocusEffect(true) + , m_data() + , m_visibleRoles() + , m_columnWidths() +@@ -634,7 +635,7 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + painter->fillPath(path, accentColor); + + // Focus decoration +- if (current) { ++ if (current && m_showFocusEffect) { + accentColor.setAlphaF(1.0); + // Set the pen color lighter or darker depending on background color + const QPen pen{m_styleOption.palette.color(QPalette::Base).lightnessF() > 0.5 ? accentColor.darker(110) : accentColor.lighter(110), focusPenWidth}; +@@ -643,4 +644,9 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + } + } + ++void KItemListWidget::showFocusEffect(bool show) ++{ ++ m_showFocusEffect = show; ++} ++ + #include "moc_kitemlistwidget.cpp" +diff --git a/src/kitemviews/kitemlistwidget.h b/src/kitemviews/kitemlistwidget.h +index 0e07d7ab55..06ed511155 100644 +--- a/src/kitemviews/kitemlistwidget.h ++++ b/src/kitemviews/kitemlistwidget.h +@@ -194,6 +194,8 @@ public: + */ + virtual void startActivateSoonAnimation(int timeUntilActivation); + ++ void showFocusEffect(bool showFocusEffect); ++ + Q_SIGNALS: + void roleEditingCanceled(int index, const QByteArray &role, const QVariant &value); + void roleEditingFinished(int index, const QByteArray &role, const QVariant &value); +@@ -258,6 +260,7 @@ private: + bool m_expansionAreaHovered; + bool m_alternateBackground; + bool m_enabledSelectionToggle; ++ bool m_showFocusEffect; + QHash m_data; + QList m_visibleRoles; + QHash m_columnWidths; +-- +GitLab + + +From e89ce5966ac02f7349922b4a0794d433f705d1dd Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Thu, 17 Apr 2025 14:46:51 +0300 +Subject: [PATCH 22/69] Start drag from background area + +Now that we have full size selection item, starting +drag when mouse is in there makes sense. + +Signed-off-by: Akseli Lahtinen +--- + src/kitemviews/kitemlistcontroller.cpp | 14 +------------- + 1 file changed, 1 insertion(+), 13 deletions(-) + +diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp +index de8bd26254..3757fae1f2 100644 +--- a/src/kitemviews/kitemlistcontroller.cpp ++++ b/src/kitemviews/kitemlistcontroller.cpp +@@ -1721,19 +1721,7 @@ bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifier + } + + if (m_pressedIndex.has_value()) { +- // The hover highlight area of an item is being pressed. +- const auto row = m_view->m_visibleItems.value(m_pressedIndex.value()); // anything outside of row.contains() will be the empty region of the row rect +- const bool hitTargetIsRowEmptyRegion = !row->contains(row->mapFromItem(m_view, pos)); +- // again, when this method returns false, a rubberBand selection is created as the event is not consumed; +- // createRubberBand here tells us whether to return true or false. +- bool createRubberBand = (hitTargetIsRowEmptyRegion && m_selectionManager->selectedItems().isEmpty()); +- +- if (rightClick && hitTargetIsRowEmptyRegion) { +- // We have a right click outside the icon and text rect but within the hover highlight area. +- // We don't want items to get selected through this, so we return now. +- showFocusWidget(false); +- return true; +- } ++ bool createRubberBand = false; + + m_selectionManager->setCurrentItem(m_pressedIndex.value()); + +-- +GitLab + + +From d92820622459bf1c15f24cc76747680e7e15fbf0 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Thu, 17 Apr 2025 15:39:58 +0300 +Subject: [PATCH 23/69] Show focus effects always on keypress + +--- + src/kitemviews/kitemlistcontroller.cpp | 6 ++---- + src/kitemviews/kitemlistwidget.cpp | 5 ++++- + 2 files changed, 6 insertions(+), 5 deletions(-) + +diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp +index 3757fae1f2..190402c705 100644 +--- a/src/kitemviews/kitemlistcontroller.cpp ++++ b/src/kitemviews/kitemlistcontroller.cpp +@@ -238,6 +238,7 @@ bool KItemListController::isSearchAsYouTypeActive() const + bool KItemListController::keyPressEvent(QKeyEvent *event) + { + int index = m_selectionManager->currentItem(); ++ showFocusWidget(true); + int key = event->key(); + const bool shiftPressed = event->modifiers() & Qt::ShiftModifier; + +@@ -1901,10 +1902,7 @@ void KItemListController::showFocusWidget(bool show) + { + const auto widgets = m_view->visibleItemListWidgets(); + for (auto widget : widgets) { +- if (widget->isCurrent()) { +- widget->showFocusEffect(show); +- return; +- } ++ widget->showFocusEffect(show); + } + } + +diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp +index e41f8a1557..174e2feb6a 100644 +--- a/src/kitemviews/kitemlistwidget.cpp ++++ b/src/kitemviews/kitemlistwidget.cpp +@@ -646,7 +646,10 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + + void KItemListWidget::showFocusEffect(bool show) + { +- m_showFocusEffect = show; ++ if (m_showFocusEffect != show) { ++ m_showFocusEffect = show; ++ update(); ++ } + } + + #include "moc_kitemlistwidget.cpp" +-- +GitLab + + +From 39063dd2c47d79a1adadbeefd9c612c03fe8b24c Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Thu, 17 Apr 2025 16:20:12 +0300 +Subject: [PATCH 24/69] Rename methods, add method for checking if focus is + shown for widget + +--- + src/kitemviews/kitemlistcontroller.cpp | 8 ++++---- + src/kitemviews/kitemlistcontroller.h | 2 +- + src/kitemviews/kitemlistview.cpp | 3 --- + src/kitemviews/kitemlistwidget.cpp | 15 ++++++++++----- + src/kitemviews/kitemlistwidget.h | 5 +++-- + 5 files changed, 18 insertions(+), 15 deletions(-) + +diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp +index 190402c705..f30453e9cb 100644 +--- a/src/kitemviews/kitemlistcontroller.cpp ++++ b/src/kitemviews/kitemlistcontroller.cpp +@@ -238,7 +238,7 @@ bool KItemListController::isSearchAsYouTypeActive() const + bool KItemListController::keyPressEvent(QKeyEvent *event) + { + int index = m_selectionManager->currentItem(); +- showFocusWidget(true); ++ showKeyboardFocusEffect(true); + int key = event->key(); + const bool shiftPressed = event->modifiers() & Qt::ShiftModifier; + +@@ -1770,7 +1770,7 @@ bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifier + + return !createRubberBand; + } else { +- showFocusWidget(false); ++ showKeyboardFocusEffect(false); + } + + return false; +@@ -1898,11 +1898,11 @@ void KItemListController::slotStateChanged(QScroller::State newState) + } + } + +-void KItemListController::showFocusWidget(bool show) ++void KItemListController::showKeyboardFocusEffect(bool show) + { + const auto widgets = m_view->visibleItemListWidgets(); + for (auto widget : widgets) { +- widget->showFocusEffect(show); ++ widget->showKeyboardFocusEffect(show); + } + } + +diff --git a/src/kitemviews/kitemlistcontroller.h b/src/kitemviews/kitemlistcontroller.h +index d071f20ab0..d424f92dd0 100644 +--- a/src/kitemviews/kitemlistcontroller.h ++++ b/src/kitemviews/kitemlistcontroller.h +@@ -325,7 +325,7 @@ private: + bool onPress(const QPointF &pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons); + bool onRelease(const QPointF &pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons, bool touch); + void startRubberBand(); +- void showFocusWidget(bool show); ++ void showKeyboardFocusEffect(bool show); + + private: + bool m_singleClickActivationEnforced; +diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp +index 4db8d4ed0a..265e41e6ca 100644 +--- a/src/kitemviews/kitemlistview.cpp ++++ b/src/kitemviews/kitemlistview.cpp +@@ -1529,8 +1529,6 @@ void KItemListView::slotCurrentChanged(int current, int previous) + + KItemListWidget *currentWidget = m_visibleItems.value(current, nullptr); + if (currentWidget) { +- // If previous item was -1, we're setting the first item current, so we can hide focus +- currentWidget->showFocusEffect(previous >= 0 ? true : false); + currentWidget->setCurrent(true); + } + } +@@ -1550,7 +1548,6 @@ void KItemListView::slotSelectionChanged(const KItemSet ¤t, const KItemSet + KItemListWidget *widget = it.value(); + const bool isSelected(current.contains(index)); + widget->setSelected(isSelected); +- widget->showFocusEffect(true); + + #ifndef QT_NO_ACCESSIBILITY + if (!QAccessible::isActive()) { +diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp +index 174e2feb6a..6d1f40a444 100644 +--- a/src/kitemviews/kitemlistwidget.cpp ++++ b/src/kitemviews/kitemlistwidget.cpp +@@ -37,7 +37,7 @@ KItemListWidget::KItemListWidget(KItemListWidgetInformant *informant, QGraphicsI + , m_expansionAreaHovered(false) + , m_alternateBackground(false) + , m_enabledSelectionToggle(false) +- , m_showFocusEffect(true) ++ , m_showKeyboardFocusEffect(false) + , m_data() + , m_visibleRoles() + , m_columnWidths() +@@ -635,7 +635,7 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + painter->fillPath(path, accentColor); + + // Focus decoration +- if (current && m_showFocusEffect) { ++ if (current && m_showKeyboardFocusEffect) { + accentColor.setAlphaF(1.0); + // Set the pen color lighter or darker depending on background color + const QPen pen{m_styleOption.palette.color(QPalette::Base).lightnessF() > 0.5 ? accentColor.darker(110) : accentColor.lighter(110), focusPenWidth}; +@@ -644,12 +644,17 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + } + } + +-void KItemListWidget::showFocusEffect(bool show) ++void KItemListWidget::showKeyboardFocusEffect(bool show) + { +- if (m_showFocusEffect != show) { +- m_showFocusEffect = show; ++ if (m_showKeyboardFocusEffect != show) { ++ m_showKeyboardFocusEffect = show; + update(); + } + } + ++bool KItemListWidget::keyboardFocusEffectShown() ++{ ++ return m_showKeyboardFocusEffect; ++} ++ + #include "moc_kitemlistwidget.cpp" +diff --git a/src/kitemviews/kitemlistwidget.h b/src/kitemviews/kitemlistwidget.h +index 06ed511155..21e2af4ba1 100644 +--- a/src/kitemviews/kitemlistwidget.h ++++ b/src/kitemviews/kitemlistwidget.h +@@ -194,7 +194,8 @@ public: + */ + virtual void startActivateSoonAnimation(int timeUntilActivation); + +- void showFocusEffect(bool showFocusEffect); ++ void showKeyboardFocusEffect(bool showFocusEffect); ++ bool keyboardFocusEffectShown(); + + Q_SIGNALS: + void roleEditingCanceled(int index, const QByteArray &role, const QVariant &value); +@@ -260,7 +261,7 @@ private: + bool m_expansionAreaHovered; + bool m_alternateBackground; + bool m_enabledSelectionToggle; +- bool m_showFocusEffect; ++ bool m_showKeyboardFocusEffect; + QHash m_data; + QList m_visibleRoles; + QHash m_columnWidths; +-- +GitLab + + +From 80eaae9049df5806380de6a68f957dc503d8b06a Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Thu, 17 Apr 2025 16:50:45 +0300 +Subject: [PATCH 25/69] Show focus when selection mode is toggled or navigation + keys pressed + +--- + src/kitemviews/kitemlistcontroller.cpp | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp +index f30453e9cb..466bf401ed 100644 +--- a/src/kitemviews/kitemlistcontroller.cpp ++++ b/src/kitemviews/kitemlistcontroller.cpp +@@ -222,6 +222,9 @@ bool KItemListController::singleClickActivationEnforced() const + + void KItemListController::setSelectionModeEnabled(bool enabled) + { ++ if (enabled) { ++ showKeyboardFocusEffect(true); ++ } + m_selectionMode = enabled; + } + +@@ -238,7 +241,6 @@ bool KItemListController::isSearchAsYouTypeActive() const + bool KItemListController::keyPressEvent(QKeyEvent *event) + { + int index = m_selectionManager->currentItem(); +- showKeyboardFocusEffect(true); + int key = event->key(); + const bool shiftPressed = event->modifiers() & Qt::ShiftModifier; + +@@ -291,6 +293,10 @@ bool KItemListController::keyPressEvent(QKeyEvent *event) + const bool navigationPressed = key == Qt::Key_Home || key == Qt::Key_End || key == Qt::Key_PageUp || key == Qt::Key_PageDown || key == Qt::Key_Up + || key == Qt::Key_Down || key == Qt::Key_Left || key == Qt::Key_Right; + ++ if (navigationPressed) { ++ showKeyboardFocusEffect(true); ++ } ++ + const int itemCount = m_model->count(); + + // For horizontal scroll orientation, transform +-- +GitLab + + +From 0faf1eeabd4f6be73b622b13058c0b56a473be61 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Tue, 22 Apr 2025 16:23:18 +0300 +Subject: [PATCH 26/69] Always show focus, make focus when unselected more + transparent, remove showKeyboardFocusEffect + +--- + src/kitemviews/kitemlistcontroller.cpp | 17 ----------------- + src/kitemviews/kitemlistcontroller.h | 1 - + src/kitemviews/kitemlistwidget.cpp | 23 +++++------------------ + src/kitemviews/kitemlistwidget.h | 4 ---- + 4 files changed, 5 insertions(+), 40 deletions(-) + +diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp +index 466bf401ed..cd19cd7f69 100644 +--- a/src/kitemviews/kitemlistcontroller.cpp ++++ b/src/kitemviews/kitemlistcontroller.cpp +@@ -222,9 +222,6 @@ bool KItemListController::singleClickActivationEnforced() const + + void KItemListController::setSelectionModeEnabled(bool enabled) + { +- if (enabled) { +- showKeyboardFocusEffect(true); +- } + m_selectionMode = enabled; + } + +@@ -293,10 +290,6 @@ bool KItemListController::keyPressEvent(QKeyEvent *event) + const bool navigationPressed = key == Qt::Key_Home || key == Qt::Key_End || key == Qt::Key_PageUp || key == Qt::Key_PageDown || key == Qt::Key_Up + || key == Qt::Key_Down || key == Qt::Key_Left || key == Qt::Key_Right; + +- if (navigationPressed) { +- showKeyboardFocusEffect(true); +- } +- + const int itemCount = m_model->count(); + + // For horizontal scroll orientation, transform +@@ -1775,8 +1768,6 @@ bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifier + } + + return !createRubberBand; +- } else { +- showKeyboardFocusEffect(false); + } + + return false; +@@ -1904,12 +1895,4 @@ void KItemListController::slotStateChanged(QScroller::State newState) + } + } + +-void KItemListController::showKeyboardFocusEffect(bool show) +-{ +- const auto widgets = m_view->visibleItemListWidgets(); +- for (auto widget : widgets) { +- widget->showKeyboardFocusEffect(show); +- } +-} +- + #include "moc_kitemlistcontroller.cpp" +diff --git a/src/kitemviews/kitemlistcontroller.h b/src/kitemviews/kitemlistcontroller.h +index d424f92dd0..48da07206d 100644 +--- a/src/kitemviews/kitemlistcontroller.h ++++ b/src/kitemviews/kitemlistcontroller.h +@@ -325,7 +325,6 @@ private: + bool onPress(const QPointF &pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons); + bool onRelease(const QPointF &pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons, bool touch); + void startRubberBand(); +- void showKeyboardFocusEffect(bool show); + + private: + bool m_singleClickActivationEnforced; +diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp +index 6d1f40a444..a67759d087 100644 +--- a/src/kitemviews/kitemlistwidget.cpp ++++ b/src/kitemviews/kitemlistwidget.cpp +@@ -37,7 +37,6 @@ KItemListWidget::KItemListWidget(KItemListWidgetInformant *informant, QGraphicsI + , m_expansionAreaHovered(false) + , m_alternateBackground(false) + , m_enabledSelectionToggle(false) +- , m_showKeyboardFocusEffect(false) + , m_data() + , m_visibleRoles() + , m_columnWidths() +@@ -608,7 +607,6 @@ void KItemListWidget::clearHoverCache() + void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QStyle::State styleState) + { + QStyleOptionViewItem viewItemOption; +- const int focusPenWidth = 2; + const int roundness = 5; + initStyleOption(&viewItemOption); + viewItemOption.state = styleState; +@@ -635,26 +633,15 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + painter->fillPath(path, accentColor); + + // Focus decoration +- if (current && m_showKeyboardFocusEffect) { +- accentColor.setAlphaF(1.0); ++ if (current) { ++ accentColor = m_styleOption.palette.color(QPalette::Base).lightnessF() > 0.5 ? accentColor.darker(110) : accentColor.lighter(110); ++ accentColor.setAlphaF(m_selected || m_hovered ? 1.0 : 0.5); + // Set the pen color lighter or darker depending on background color +- const QPen pen{m_styleOption.palette.color(QPalette::Base).lightnessF() > 0.5 ? accentColor.darker(110) : accentColor.lighter(110), focusPenWidth}; ++ QPen pen{accentColor, 1.5}; ++ pen.setCosmetic(true); + painter->setPen(pen); + painter->drawPath(path); + } + } + +-void KItemListWidget::showKeyboardFocusEffect(bool show) +-{ +- if (m_showKeyboardFocusEffect != show) { +- m_showKeyboardFocusEffect = show; +- update(); +- } +-} +- +-bool KItemListWidget::keyboardFocusEffectShown() +-{ +- return m_showKeyboardFocusEffect; +-} +- + #include "moc_kitemlistwidget.cpp" +diff --git a/src/kitemviews/kitemlistwidget.h b/src/kitemviews/kitemlistwidget.h +index 21e2af4ba1..0e07d7ab55 100644 +--- a/src/kitemviews/kitemlistwidget.h ++++ b/src/kitemviews/kitemlistwidget.h +@@ -194,9 +194,6 @@ public: + */ + virtual void startActivateSoonAnimation(int timeUntilActivation); + +- void showKeyboardFocusEffect(bool showFocusEffect); +- bool keyboardFocusEffectShown(); +- + Q_SIGNALS: + void roleEditingCanceled(int index, const QByteArray &role, const QVariant &value); + void roleEditingFinished(int index, const QByteArray &role, const QVariant &value); +@@ -261,7 +258,6 @@ private: + bool m_expansionAreaHovered; + bool m_alternateBackground; + bool m_enabledSelectionToggle; +- bool m_showKeyboardFocusEffect; + QHash m_data; + QList m_visibleRoles; + QHash m_columnWidths; +-- +GitLab + + +From c372ea1e5c1fa65c86f971a20a0cb4ccaf1e3d41 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Tue, 22 Apr 2025 16:59:40 +0300 +Subject: [PATCH 27/69] Fix inconsistent hover area sizing, refactor + selectionRect + +--- + src/kitemviews/kitemlistcontroller.cpp | 9 ++++----- + src/kitemviews/kitemlistview.cpp | 2 +- + src/kitemviews/kitemlistwidget.cpp | 2 +- + src/kitemviews/kitemlistwidget.h | 10 +++++----- + src/kitemviews/kstandarditemlistwidget.cpp | 14 +++++--------- + 5 files changed, 16 insertions(+), 21 deletions(-) + +diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp +index cd19cd7f69..6ef03dc609 100644 +--- a/src/kitemviews/kitemlistcontroller.cpp ++++ b/src/kitemviews/kitemlistcontroller.cpp +@@ -990,7 +990,7 @@ bool KItemListController::hoverMoveEvent(QGraphicsSceneHoverEvent *event, const + // we also unhover any old expansion toggle hovers, in case the mouse movement from expansion toggle to icon+text is too fast (i.e. newHoveredWidget is never null between the transition) + unhoverOldExpansionWidget(); + +- const bool isOverIconAndText = newHoveredWidget->iconRect().contains(mappedPos) || newHoveredWidget->textRect().contains(mappedPos); ++ const bool isOverIconAndText = newHoveredWidget->selectionRect().contains(mappedPos); + const bool hasMultipleSelection = m_selectionManager->selectedItems().count() > 1; + + if (hasMultipleSelection && !isOverIconAndText) { +@@ -1366,8 +1366,7 @@ void KItemListController::slotRubberBandChanged() + if (widgetRect.intersects(rubberBandRect)) { + // Select the full row intersecting with the rubberband rectangle + const QRectF selectionRect = widget->selectionRect().translated(widgetRect.topLeft()); +- const QRectF iconRect = widget->iconRect().translated(widgetRect.topLeft()); +- if (selectionRect.intersects(rubberBandRect) || iconRect.intersects(rubberBandRect)) { ++ if (selectionRect.intersects(rubberBandRect)) { + selectedItems.insert(index); + } + } +@@ -1661,7 +1660,7 @@ bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifier + if (selectedItemsCount > 1 && m_pressedIndex.has_value()) { + const auto row = m_view->m_visibleItems.value(m_pressedIndex.value()); + const auto mappedPos = row->mapFromItem(m_view, pos); +- if (pressedItemAlreadySelected || row->iconRect().contains(mappedPos) || row->textRect().contains(mappedPos)) { ++ if (pressedItemAlreadySelected || row->selectionRect().contains(mappedPos)) { + // we are indeed inside the text/icon rect, keep m_pressedIndex what it is + // and short-circuit for single-click activation (it will then propagate to onRelease and activate the item) + // or we just keep going for double-click activation +@@ -1744,7 +1743,7 @@ bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifier + // We rule out the latter, if the item is not clicked directly and was unselected previously. + const auto row = m_view->m_visibleItems.value(m_pressedIndex.value()); + const auto mappedPos = row->mapFromItem(m_view, pos); +- if (!row->iconRect().contains(mappedPos) && !row->textRect().contains(mappedPos) && !pressedItemAlreadySelected) { ++ if (!row->selectionRect().contains(mappedPos) && !pressedItemAlreadySelected) { + createRubberBand = true; + } else { + m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Toggle); +diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp +index 265e41e6ca..9e0a9e0e5c 100644 +--- a/src/kitemviews/kitemlistview.cpp ++++ b/src/kitemviews/kitemlistview.cpp +@@ -542,7 +542,7 @@ QRectF KItemListView::itemContextRect(int index) const + + const KItemListWidget *widget = m_visibleItems.value(index); + if (widget) { +- contextRect = widget->iconRect() | widget->textRect(); ++ contextRect = widget->selectionRect(); + contextRect.translate(itemRect(index).topLeft()); + } + +diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp +index a67759d087..8c2432967a 100644 +--- a/src/kitemviews/kitemlistwidget.cpp ++++ b/src/kitemviews/kitemlistwidget.cpp +@@ -612,7 +612,7 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + viewItemOption.state = styleState; + viewItemOption.viewItemPosition = QStyleOptionViewItem::OnlyOne; + viewItemOption.showDecorationSelected = true; +- viewItemOption.rect = rect().toRect(); ++ viewItemOption.rect = selectionRect().toRect(); + QPainterPath path; + path.addRoundedRect(viewItemOption.rect, roundness, roundness); + QColor accentColor{widget->palette().color(QPalette::Accent)}; +diff --git a/src/kitemviews/kitemlistwidget.h b/src/kitemviews/kitemlistwidget.h +index 0e07d7ab55..4d857c00d7 100644 +--- a/src/kitemviews/kitemlistwidget.h ++++ b/src/kitemviews/kitemlistwidget.h +@@ -144,11 +144,6 @@ public: + */ + bool contains(const QPointF &point) const override; + +- /** +- * @return Rectangle for the area that shows the icon. +- */ +- virtual QRectF iconRect() const = 0; +- + /** + * @return Rectangle for the area that contains the text-properties. + */ +@@ -245,6 +240,11 @@ private Q_SLOTS: + void slotHoverSequenceTimerTimeout(); + + private: ++ /** ++ * @return Rectangle for the area that shows the icon. ++ */ ++ virtual QRectF iconRect() const = 0; ++ + void initializeSelectionToggle(); + void setHoverOpacity(qreal opacity); + void drawItemStyleOption(QPainter *painter, QWidget *widget, QStyle::State styleState); +diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp +index 0303547b23..4b061c9bb1 100644 +--- a/src/kitemviews/kstandarditemlistwidget.cpp ++++ b/src/kitemviews/kstandarditemlistwidget.cpp +@@ -541,23 +541,19 @@ QRectF KStandardItemListWidget::selectionRect() const + { + const_cast(this)->triggerCacheRefreshing(); + +- const int padding = styleOption().padding; +- QRectF adjustedIconRect = iconRect().adjusted(-padding, -padding, padding, padding); +- QRectF result = adjustedIconRect | m_textRect; + if (m_highlightEntireRow) { ++ const int padding = styleOption().padding; ++ QRectF adjustedIconRect = iconRect().adjusted(-padding, -padding, padding, padding); ++ QRectF result = adjustedIconRect | m_textRect; + if (layoutDirection() == Qt::LeftToRight) { + result.setRight(leftPadding() + m_columnWidthSum); + } else { + result.setLeft(size().width() - m_columnWidthSum - rightPadding()); + } ++ return result; + } else { +- // Make sure values are always positive +- const int availableWidth = qAbs((size().width() - 2 * padding - result.width()) * 0.5); +- const int availableHeight = m_layout == CompactLayout ? 0 : qAbs((size().height() - 2 * padding - result.height()) * 0.5); +- result = result.adjusted(-availableWidth, -availableHeight, availableWidth, availableHeight); ++ return rect(); + } +- +- return result; + } + + QRectF KStandardItemListWidget::expansionToggleRect() const +-- +GitLab + + +From b09399593c25ab44013cf98afa61813fa628af2a Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Wed, 23 Apr 2025 11:58:20 +0300 +Subject: [PATCH 28/69] Add constexpr for roundness + +--- + src/kitemviews/kitemlistwidget.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp +index 8c2432967a..269c9b9955 100644 +--- a/src/kitemviews/kitemlistwidget.cpp ++++ b/src/kitemviews/kitemlistwidget.cpp +@@ -607,7 +607,7 @@ void KItemListWidget::clearHoverCache() + void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QStyle::State styleState) + { + QStyleOptionViewItem viewItemOption; +- const int roundness = 5; ++ constexpr int roundness = 5; // From Breeze style. + initStyleOption(&viewItemOption); + viewItemOption.state = styleState; + viewItemOption.viewItemPosition = QStyleOptionViewItem::OnlyOne; +-- +GitLab + + +From 688624a1521d8a1d70c37af79920283fda3f8e5f Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Wed, 23 Apr 2025 12:03:58 +0300 +Subject: [PATCH 29/69] Add slight padding to compactlayout selectionRect + +--- + src/kitemviews/kstandarditemlistwidget.cpp | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp +index 4b061c9bb1..8024d8cb54 100644 +--- a/src/kitemviews/kstandarditemlistwidget.cpp ++++ b/src/kitemviews/kstandarditemlistwidget.cpp +@@ -541,8 +541,8 @@ QRectF KStandardItemListWidget::selectionRect() const + { + const_cast(this)->triggerCacheRefreshing(); + ++ const int padding = styleOption().padding; + if (m_highlightEntireRow) { +- const int padding = styleOption().padding; + QRectF adjustedIconRect = iconRect().adjusted(-padding, -padding, padding, padding); + QRectF result = adjustedIconRect | m_textRect; + if (layoutDirection() == Qt::LeftToRight) { +@@ -552,6 +552,9 @@ QRectF KStandardItemListWidget::selectionRect() const + } + return result; + } else { ++ if (m_layout == CompactLayout) { ++ return rect().adjusted(0, padding, 0, -padding); ++ } + return rect(); + } + } +-- +GitLab + + +From 320e14cc150e90bbd2a9981ac5e48973755e6d02 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Wed, 23 Apr 2025 12:11:04 +0300 +Subject: [PATCH 30/69] Add rubberband start behavior back + +--- + src/kitemviews/kitemlistcontroller.cpp | 13 ++++++++++++- + 1 file changed, 12 insertions(+), 1 deletion(-) + +diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp +index 6ef03dc609..fd3feb1a1b 100644 +--- a/src/kitemviews/kitemlistcontroller.cpp ++++ b/src/kitemviews/kitemlistcontroller.cpp +@@ -1720,7 +1720,18 @@ bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifier + } + + if (m_pressedIndex.has_value()) { +- bool createRubberBand = false; ++ // The hover highlight area of an item is being pressed. ++ const auto row = m_view->m_visibleItems.value(m_pressedIndex.value()); // anything outside of row.contains() will be the empty region of the row rect ++ const bool hitTargetIsRowEmptyRegion = !row->contains(row->mapFromItem(m_view, pos)); ++ // again, when this method returns false, a rubberBand selection is created as the event is not consumed; ++ // createRubberBand here tells us whether to return true or false. ++ bool createRubberBand = (hitTargetIsRowEmptyRegion && m_selectionManager->selectedItems().isEmpty()); ++ ++ if (rightClick && hitTargetIsRowEmptyRegion) { ++ // We have a right click outside the icon and text rect but within the hover highlight area. ++ // We don't want items to get selected through this, so we return now. ++ return true; ++ } + + m_selectionManager->setCurrentItem(m_pressedIndex.value()); + +-- +GitLab + + +From bf66254daea497b588cdaf2578f3990597188f8a Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Fri, 25 Apr 2025 11:06:02 +0300 +Subject: [PATCH 31/69] Remove hover pixmap + +--- + src/kitemviews/kstandarditemlistwidget.cpp | 18 +++--------------- + src/kitemviews/kstandarditemlistwidget.h | 1 - + 2 files changed, 3 insertions(+), 16 deletions(-) + +diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp +index 8024d8cb54..da7fd5f4c7 100644 +--- a/src/kitemviews/kstandarditemlistwidget.cpp ++++ b/src/kitemviews/kstandarditemlistwidget.cpp +@@ -271,7 +271,6 @@ KStandardItemListWidget::KStandardItemListWidget(KItemListWidgetInformant *infor + , m_scaledPixmapSize() + , m_columnWidthSum() + , m_iconRect() +- , m_hoverPixmap() + , m_textRect() + , m_sortedVisibleRoles() + , m_expansionArea() +@@ -346,7 +345,7 @@ void KStandardItemListWidget::paint(QPainter *painter, const QStyleOptionGraphic + drawSiblingsInformation(painter); + } + +- auto pixmap = isHovered() ? m_hoverPixmap : m_pixmap; ++ auto pixmap = m_pixmap; + if (!m_overlays.isEmpty()) { + const qreal dpr = KItemViewsUtils::devicePixelRatio(this); + +@@ -388,7 +387,7 @@ void KStandardItemListWidget::paint(QPainter *painter, const QStyleOptionGraphic + { + QPainter p(&pixmap2); + p.setOpacity(hoverOpacity()); +- p.drawPixmap(0, 0, m_hoverPixmap); ++ p.drawPixmap(0, 0, m_pixmap); + } + + // Paint pixmap2 on pixmap1 using CompositionMode_Plus +@@ -1108,7 +1107,7 @@ void KStandardItemListWidget::updatePixmapCache() + } + + if (m_pixmap.isNull()) { +- m_hoverPixmap = QPixmap(); ++ m_pixmap = QPixmap(); + return; + } + +@@ -1166,17 +1165,6 @@ void KStandardItemListWidget::updatePixmapCache() + const QSizeF squareIconSize(widgetIconSize, widgetIconSize); + m_iconRect = QRectF(squareIconPos, squareIconSize); + } +- +- // Prepare the pixmap that is used when the item gets hovered +- if (isHovered()) { +- m_hoverPixmap = m_pixmap; +- if (isSelected()) { +- KIconEffect::toActive(m_hoverPixmap); +- } +- } else if (hoverOpacity() <= 0.0) { +- // No hover animation is ongoing. Clear m_hoverPixmap to save memory. +- m_hoverPixmap = QPixmap(); +- } + } + + void KStandardItemListWidget::updateTextsCache() +diff --git a/src/kitemviews/kstandarditemlistwidget.h b/src/kitemviews/kstandarditemlistwidget.h +index 594d3e4928..3dbf442913 100644 +--- a/src/kitemviews/kstandarditemlistwidget.h ++++ b/src/kitemviews/kstandarditemlistwidget.h +@@ -280,7 +280,6 @@ private: + + qreal m_columnWidthSum; + QRectF m_iconRect; // Cache for KItemListWidget::iconRect() +- QPixmap m_hoverPixmap; // Cache for modified m_pixmap when hovering the item + + QRectF m_textRect; + +-- +GitLab + + +From f41db3725ad36175721afd430de8ea1b63867803 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Fri, 25 Apr 2025 11:12:33 +0300 +Subject: [PATCH 32/69] Change the alpha values + +--- + src/kitemviews/kitemlistwidget.cpp | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp +index 269c9b9955..b52661e6ee 100644 +--- a/src/kitemviews/kitemlistwidget.cpp ++++ b/src/kitemviews/kitemlistwidget.cpp +@@ -623,11 +623,11 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + // https://invent.kde.org/plasma/libplasma/-/blob/master/src/desktoptheme/breeze/widgets/viewitem.svg + accentColor.setAlphaF(0.0); + if (m_selected && m_hovered) { +- accentColor.setAlphaF(0.5); ++ accentColor.setAlphaF(0.25); + } else if (m_selected) { +- accentColor.setAlphaF(0.4); ++ accentColor.setAlphaF(0.2); + } else if (m_hovered) { +- accentColor.setAlphaF(0.3); ++ accentColor.setAlphaF(0.15); + } + + painter->fillPath(path, accentColor); +@@ -635,7 +635,7 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + // Focus decoration + if (current) { + accentColor = m_styleOption.palette.color(QPalette::Base).lightnessF() > 0.5 ? accentColor.darker(110) : accentColor.lighter(110); +- accentColor.setAlphaF(m_selected || m_hovered ? 1.0 : 0.5); ++ accentColor.setAlphaF(m_selected || m_hovered ? 0.8 : 0.5); + // Set the pen color lighter or darker depending on background color + QPen pen{accentColor, 1.5}; + pen.setCosmetic(true); +-- +GitLab + + +From e299bf436628e58677f06513266811ac4f215f59 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Fri, 25 Apr 2025 12:53:42 +0300 +Subject: [PATCH 33/69] Set hovered effect to widgets when dragging + +--- + src/kitemviews/kitemlistcontroller.cpp | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp +index fd3feb1a1b..66e9ce4884 100644 +--- a/src/kitemviews/kitemlistcontroller.cpp ++++ b/src/kitemviews/kitemlistcontroller.cpp +@@ -652,6 +652,10 @@ bool KItemListController::mouseMoveEvent(QGraphicsSceneMouseEvent *event, const + m_selectionManager->endAnchoredSelection(); + m_selectionManager->setCurrentItem(newCurrent.value()); + m_selectionManager->beginAnchoredSelection(newCurrent.value()); ++ // Set hovered effect when dragging. ++ for (const auto widget : m_view->visibleItemListWidgets()) { ++ widget->setHovered(widget->isCurrent()); ++ } + } + + if (m_view->scrollOrientation() == Qt::Vertical) { +-- +GitLab + + +From 7a96007a21cc3a844a04992bd4edb764df561039 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Fri, 25 Apr 2025 13:08:52 +0300 +Subject: [PATCH 34/69] Move virtual iconrect back to public + +--- + src/kitemviews/kitemlistwidget.h | 10 +++++----- + 1 file changed, 5 insertions(+), 5 deletions(-) + +diff --git a/src/kitemviews/kitemlistwidget.h b/src/kitemviews/kitemlistwidget.h +index 4d857c00d7..0e07d7ab55 100644 +--- a/src/kitemviews/kitemlistwidget.h ++++ b/src/kitemviews/kitemlistwidget.h +@@ -144,6 +144,11 @@ public: + */ + bool contains(const QPointF &point) const override; + ++ /** ++ * @return Rectangle for the area that shows the icon. ++ */ ++ virtual QRectF iconRect() const = 0; ++ + /** + * @return Rectangle for the area that contains the text-properties. + */ +@@ -240,11 +245,6 @@ private Q_SLOTS: + void slotHoverSequenceTimerTimeout(); + + private: +- /** +- * @return Rectangle for the area that shows the icon. +- */ +- virtual QRectF iconRect() const = 0; +- + void initializeSelectionToggle(); + void setHoverOpacity(qreal opacity); + void drawItemStyleOption(QPainter *painter, QWidget *widget, QStyle::State styleState); +-- +GitLab + + +From 829d721b6ed44202ad91b4ee0ead1b578746d21f Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Fri, 25 Apr 2025 13:11:20 +0300 +Subject: [PATCH 35/69] Remove unecessary assignment + +--- + src/kitemviews/kstandarditemlistwidget.cpp | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp +index da7fd5f4c7..25dece0f62 100644 +--- a/src/kitemviews/kstandarditemlistwidget.cpp ++++ b/src/kitemviews/kstandarditemlistwidget.cpp +@@ -1107,7 +1107,6 @@ void KStandardItemListWidget::updatePixmapCache() + } + + if (m_pixmap.isNull()) { +- m_pixmap = QPixmap(); + return; + } + +-- +GitLab + + +From d8d4a220cb55e0de7364abd1ebfa4666dbbfa2a8 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Mon, 28 Apr 2025 15:56:09 +0300 +Subject: [PATCH 36/69] Better alpha values + +--- + src/kitemviews/kitemlistwidget.cpp | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp +index b52661e6ee..1b6969130f 100644 +--- a/src/kitemviews/kitemlistwidget.cpp ++++ b/src/kitemviews/kitemlistwidget.cpp +@@ -623,11 +623,11 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + // https://invent.kde.org/plasma/libplasma/-/blob/master/src/desktoptheme/breeze/widgets/viewitem.svg + accentColor.setAlphaF(0.0); + if (m_selected && m_hovered) { +- accentColor.setAlphaF(0.25); ++ accentColor.setAlphaF(0.32); + } else if (m_selected) { +- accentColor.setAlphaF(0.2); ++ accentColor.setAlphaF(0.30); + } else if (m_hovered) { +- accentColor.setAlphaF(0.15); ++ accentColor.setAlphaF(0.20); + } + + painter->fillPath(path, accentColor); +@@ -635,7 +635,7 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + // Focus decoration + if (current) { + accentColor = m_styleOption.palette.color(QPalette::Base).lightnessF() > 0.5 ? accentColor.darker(110) : accentColor.lighter(110); +- accentColor.setAlphaF(m_selected || m_hovered ? 0.8 : 0.5); ++ accentColor.setAlphaF(m_selected || m_hovered ? 0.8 : 0.6); + // Set the pen color lighter or darker depending on background color + QPen pen{accentColor, 1.5}; + pen.setCosmetic(true); +-- +GitLab + + +From c116d53065c1cd71a1f5d924643824145e2e834e Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Tue, 29 Apr 2025 11:35:57 +0300 +Subject: [PATCH 37/69] Use text color as background color, modify alpha values + +--- + src/kitemviews/kitemlistwidget.cpp | 20 +++++++++++--------- + 1 file changed, 11 insertions(+), 9 deletions(-) + +diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp +index 1b6969130f..0af131c586 100644 +--- a/src/kitemviews/kitemlistwidget.cpp ++++ b/src/kitemviews/kitemlistwidget.cpp +@@ -615,29 +615,31 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + viewItemOption.rect = selectionRect().toRect(); + QPainterPath path; + path.addRoundedRect(viewItemOption.rect, roundness, roundness); +- QColor accentColor{widget->palette().color(QPalette::Accent)}; ++ QColor backgroundColor{widget->palette().color(QPalette::Accent)}; + painter->setRenderHint(QPainter::Antialiasing); + bool current = m_current && styleState & QStyle::State_Active; + + // Background item, alpha values are from + // https://invent.kde.org/plasma/libplasma/-/blob/master/src/desktoptheme/breeze/widgets/viewitem.svg +- accentColor.setAlphaF(0.0); ++ backgroundColor.setAlphaF(0.0); + if (m_selected && m_hovered) { +- accentColor.setAlphaF(0.32); ++ backgroundColor.setAlphaF(0.40); + } else if (m_selected) { +- accentColor.setAlphaF(0.30); ++ backgroundColor.setAlphaF(0.32); + } else if (m_hovered) { +- accentColor.setAlphaF(0.20); ++ backgroundColor = widget->palette().color(QPalette::Text); ++ backgroundColor.setAlphaF(0.06); + } + +- painter->fillPath(path, accentColor); ++ painter->fillPath(path, backgroundColor); + + // Focus decoration + if (current) { +- accentColor = m_styleOption.palette.color(QPalette::Base).lightnessF() > 0.5 ? accentColor.darker(110) : accentColor.lighter(110); +- accentColor.setAlphaF(m_selected || m_hovered ? 0.8 : 0.6); ++ QColor focusColor{widget->palette().color(QPalette::Accent)}; ++ focusColor = m_styleOption.palette.color(QPalette::Base).lightnessF() > 0.5 ? focusColor.darker(110) : focusColor.lighter(110); ++ focusColor.setAlphaF(m_selected || m_hovered ? 0.8 : 0.6); + // Set the pen color lighter or darker depending on background color +- QPen pen{accentColor, 1.5}; ++ QPen pen{focusColor, 1.5}; + pen.setCosmetic(true); + painter->setPen(pen); + painter->drawPath(path); +-- +GitLab + + +From 66ccf1e63224471fa80ea4d2d868c8dc31697498 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Mon, 5 May 2025 14:31:47 +0300 +Subject: [PATCH 38/69] Make sure backgrounds dont overlap at the outline + +--- + src/kitemviews/kitemlistwidget.cpp | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp +index 0af131c586..c65c0b2d78 100644 +--- a/src/kitemviews/kitemlistwidget.cpp ++++ b/src/kitemviews/kitemlistwidget.cpp +@@ -608,13 +608,14 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + { + QStyleOptionViewItem viewItemOption; + constexpr int roundness = 5; // From Breeze style. ++ constexpr qreal penWidth = 1.5; + initStyleOption(&viewItemOption); + viewItemOption.state = styleState; + viewItemOption.viewItemPosition = QStyleOptionViewItem::OnlyOne; + viewItemOption.showDecorationSelected = true; + viewItemOption.rect = selectionRect().toRect(); + QPainterPath path; +- path.addRoundedRect(viewItemOption.rect, roundness, roundness); ++ path.addRoundedRect(selectionRect().adjusted(penWidth, penWidth, -penWidth, -penWidth), roundness, roundness); + QColor backgroundColor{widget->palette().color(QPalette::Accent)}; + painter->setRenderHint(QPainter::Antialiasing); + bool current = m_current && styleState & QStyle::State_Active; +@@ -639,7 +640,7 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + focusColor = m_styleOption.palette.color(QPalette::Base).lightnessF() > 0.5 ? focusColor.darker(110) : focusColor.lighter(110); + focusColor.setAlphaF(m_selected || m_hovered ? 0.8 : 0.6); + // Set the pen color lighter or darker depending on background color +- QPen pen{focusColor, 1.5}; ++ QPen pen{focusColor, penWidth}; + pen.setCosmetic(true); + painter->setPen(pen); + painter->drawPath(path); +-- +GitLab + + +From 91f8b3123f62db459c970c46190cbf967e1050f9 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Fri, 23 May 2025 14:22:14 +0300 +Subject: [PATCH 39/69] Add horizontal margin according to icon size + +--- + src/views/dolphinitemlistview.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/views/dolphinitemlistview.cpp b/src/views/dolphinitemlistview.cpp +index d31dc11bae..7910080f82 100644 +--- a/src/views/dolphinitemlistview.cpp ++++ b/src/views/dolphinitemlistview.cpp +@@ -194,7 +194,7 @@ void DolphinItemListView::updateGridSize() + + itemHeight = padding * 3 + iconSize + option.fontMetrics.lineSpacing(); + +- horizontalMargin = 4; ++ horizontalMargin = qMax(8.0, ((iconSize + padding) / 4.0)) * qApp->devicePixelRatio(); + verticalMargin = 8; + maxTextLines = IconsModeSettings::maximumTextLines(); + break; +-- +GitLab + + +From 4884701d6f4f4e86bf490c5f5df80289f8b40873 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Mon, 26 May 2025 16:21:21 +0300 +Subject: [PATCH 40/69] Use PM_SizeGripSize for horizontal and vertical margins + +--- + src/views/dolphinitemlistview.cpp | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/src/views/dolphinitemlistview.cpp b/src/views/dolphinitemlistview.cpp +index 7910080f82..8529f23a76 100644 +--- a/src/views/dolphinitemlistview.cpp ++++ b/src/views/dolphinitemlistview.cpp +@@ -194,8 +194,9 @@ void DolphinItemListView::updateGridSize() + + itemHeight = padding * 3 + iconSize + option.fontMetrics.lineSpacing(); + +- horizontalMargin = qMax(8.0, ((iconSize + padding) / 4.0)) * qApp->devicePixelRatio(); +- verticalMargin = 8; ++ const auto margin = style()->pixelMetric(QStyle::PM_SizeGripSize); ++ horizontalMargin = margin; ++ verticalMargin = margin; + maxTextLines = IconsModeSettings::maximumTextLines(); + break; + } +-- +GitLab + + +From 452a00a4fe2e1552d9b1e48b4d9ab98b67007072 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Mon, 2 Jun 2025 12:34:02 +0300 +Subject: [PATCH 41/69] Refactor selectionRect to + selectionRectCore/extended/visual + +--- + src/kitemviews/kitemlistcontroller.cpp | 10 +++--- + src/kitemviews/kitemlistview.cpp | 4 +-- + src/kitemviews/kitemlistwidget.cpp | 7 ++-- + src/kitemviews/kitemlistwidget.h | 22 ++++++++---- + src/kitemviews/kstandarditemlistwidget.cpp | 41 ++++++++++++---------- + src/kitemviews/kstandarditemlistwidget.h | 5 +-- + 6 files changed, 51 insertions(+), 38 deletions(-) + +diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp +index 66e9ce4884..566f1d6a24 100644 +--- a/src/kitemviews/kitemlistcontroller.cpp ++++ b/src/kitemviews/kitemlistcontroller.cpp +@@ -994,7 +994,7 @@ bool KItemListController::hoverMoveEvent(QGraphicsSceneHoverEvent *event, const + // we also unhover any old expansion toggle hovers, in case the mouse movement from expansion toggle to icon+text is too fast (i.e. newHoveredWidget is never null between the transition) + unhoverOldExpansionWidget(); + +- const bool isOverIconAndText = newHoveredWidget->selectionRect().contains(mappedPos); ++ const bool isOverIconAndText = newHoveredWidget->selectionRectCore().contains(mappedPos); + const bool hasMultipleSelection = m_selectionManager->selectedItems().count() > 1; + + if (hasMultipleSelection && !isOverIconAndText) { +@@ -1369,7 +1369,7 @@ void KItemListController::slotRubberBandChanged() + const QRectF widgetRect = m_view->itemRect(index); + if (widgetRect.intersects(rubberBandRect)) { + // Select the full row intersecting with the rubberband rectangle +- const QRectF selectionRect = widget->selectionRect().translated(widgetRect.topLeft()); ++ const QRectF selectionRect = widget->visualSelectionRect().translated(widgetRect.topLeft()); + if (selectionRect.intersects(rubberBandRect)) { + selectedItems.insert(index); + } +@@ -1470,7 +1470,7 @@ KItemListWidget *KItemListController::widgetForPos(const QPointF &pos) const + const auto widgets = m_view->visibleItemListWidgets(); + for (KItemListWidget *widget : widgets) { + const QPointF mappedPos = widget->mapFromItem(m_view, pos); +- if (widget->contains(mappedPos) || widget->selectionRect().contains(mappedPos)) { ++ if (widget->contains(mappedPos) || widget->visualSelectionRect().contains(mappedPos)) { + return widget; + } + } +@@ -1664,7 +1664,7 @@ bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifier + if (selectedItemsCount > 1 && m_pressedIndex.has_value()) { + const auto row = m_view->m_visibleItems.value(m_pressedIndex.value()); + const auto mappedPos = row->mapFromItem(m_view, pos); +- if (pressedItemAlreadySelected || row->selectionRect().contains(mappedPos)) { ++ if (pressedItemAlreadySelected || row->selectionRectCore().contains(mappedPos)) { + // we are indeed inside the text/icon rect, keep m_pressedIndex what it is + // and short-circuit for single-click activation (it will then propagate to onRelease and activate the item) + // or we just keep going for double-click activation +@@ -1758,7 +1758,7 @@ bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifier + // We rule out the latter, if the item is not clicked directly and was unselected previously. + const auto row = m_view->m_visibleItems.value(m_pressedIndex.value()); + const auto mappedPos = row->mapFromItem(m_view, pos); +- if (!row->selectionRect().contains(mappedPos) && !pressedItemAlreadySelected) { ++ if (!row->selectionRectCore().contains(mappedPos) && !pressedItemAlreadySelected) { + createRubberBand = true; + } else { + m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Toggle); +diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp +index 9e0a9e0e5c..4146dd8438 100644 +--- a/src/kitemviews/kitemlistview.cpp ++++ b/src/kitemviews/kitemlistview.cpp +@@ -425,7 +425,7 @@ std::optional KItemListView::itemAt(const QPointF &pos) const + + const KItemListWidget *widget = it.value(); + const QPointF mappedPos = widget->mapFromItem(this, pos); +- if (widget->contains(mappedPos) || widget->selectionRect().contains(mappedPos)) { ++ if (widget->contains(mappedPos) || widget->visualSelectionRect().contains(mappedPos)) { + return it.key(); + } + } +@@ -542,7 +542,7 @@ QRectF KItemListView::itemContextRect(int index) const + + const KItemListWidget *widget = m_visibleItems.value(index); + if (widget) { +- contextRect = widget->selectionRect(); ++ contextRect = widget->visualSelectionRect(); + contextRect.translate(itemRect(index).topLeft()); + } + +diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp +index c65c0b2d78..18bd4c48b3 100644 +--- a/src/kitemviews/kitemlistwidget.cpp ++++ b/src/kitemviews/kitemlistwidget.cpp +@@ -407,7 +407,7 @@ bool KItemListWidget::contains(const QPointF &point) const + return false; + } + +- return iconRect().contains(point) || textRect().contains(point) || expansionToggleRect().contains(point) || selectionToggleRect().contains(point); ++ return selectionRectCore().contains(point) || expansionToggleRect().contains(point) || selectionToggleRect().contains(point); + } + + QRectF KItemListWidget::textFocusRect() const +@@ -613,9 +613,10 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + viewItemOption.state = styleState; + viewItemOption.viewItemPosition = QStyleOptionViewItem::OnlyOne; + viewItemOption.showDecorationSelected = true; +- viewItemOption.rect = selectionRect().toRect(); ++ // TODO: check if we're extended or not ++ viewItemOption.rect = visualSelectionRect().toRect(); + QPainterPath path; +- path.addRoundedRect(selectionRect().adjusted(penWidth, penWidth, -penWidth, -penWidth), roundness, roundness); ++ path.addRoundedRect(visualSelectionRect().adjusted(penWidth, penWidth, -penWidth, -penWidth), roundness, roundness); + QColor backgroundColor{widget->palette().color(QPalette::Accent)}; + painter->setRenderHint(QPainter::Antialiasing); + bool current = m_current && styleState & QStyle::State_Active; +diff --git a/src/kitemviews/kitemlistwidget.h b/src/kitemviews/kitemlistwidget.h +index 0e07d7ab55..489081b604 100644 +--- a/src/kitemviews/kitemlistwidget.h ++++ b/src/kitemviews/kitemlistwidget.h +@@ -144,11 +144,6 @@ public: + */ + bool contains(const QPointF &point) const override; + +- /** +- * @return Rectangle for the area that shows the icon. +- */ +- virtual QRectF iconRect() const = 0; +- + /** + * @return Rectangle for the area that contains the text-properties. + */ +@@ -164,9 +159,22 @@ public: + virtual QRectF textFocusRect() const; + + /** +- * @return Rectangle around which a selection box should be drawn if the item is selected. ++ * Used for drawing the visuals, and situations where we want the behavior of the ++ * selection to match the visuals. ++ * ++ * @return The rectangle around selection, depending on if it's full width or not. ++ */ ++ virtual QRectF visualSelectionRect() const = 0; ++ ++ /** ++ * @return Rectangle around icon and it's text: The core area of the item. ++ */ ++ virtual QRectF selectionRectCore() const = 0; ++ ++ /** ++ * @return Same as core, but extended to the full width of the details view. + */ +- virtual QRectF selectionRect() const = 0; ++ virtual QRectF selectionRectExtended() const = 0; + + /** + * @return Rectangle for the selection-toggle that is used to select or deselect an item. +diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp +index 25dece0f62..0661a19944 100644 +--- a/src/kitemviews/kstandarditemlistwidget.cpp ++++ b/src/kitemviews/kstandarditemlistwidget.cpp +@@ -483,12 +483,6 @@ void KStandardItemListWidget::paint(QPainter *painter, const QStyleOptionGraphic + #endif + } + +-QRectF KStandardItemListWidget::iconRect() const +-{ +- const_cast(this)->triggerCacheRefreshing(); +- return m_iconRect; +-} +- + QRectF KStandardItemListWidget::textRect() const + { + const_cast(this)->triggerCacheRefreshing(); +@@ -536,28 +530,37 @@ QRectF KStandardItemListWidget::textFocusRect() const + return m_textRect; + } + +-QRectF KStandardItemListWidget::selectionRect() const ++QRectF KStandardItemListWidget::visualSelectionRect() const + { + const_cast(this)->triggerCacheRefreshing(); +- +- const int padding = styleOption().padding; + if (m_highlightEntireRow) { +- QRectF adjustedIconRect = iconRect().adjusted(-padding, -padding, padding, padding); +- QRectF result = adjustedIconRect | m_textRect; +- if (layoutDirection() == Qt::LeftToRight) { +- result.setRight(leftPadding() + m_columnWidthSum); +- } else { +- result.setLeft(size().width() - m_columnWidthSum - rightPadding()); +- } +- return result; ++ return selectionRectExtended(); + } else { + if (m_layout == CompactLayout) { +- return rect().adjusted(0, padding, 0, -padding); ++ const int padding = styleOption().padding; ++ return rect().adjusted(-padding, 0, padding, 0); + } + return rect(); + } + } + ++QRectF KStandardItemListWidget::selectionRectCore() const ++{ ++ QRectF result = m_iconRect | m_textRect; ++ return result; ++} ++ ++QRectF KStandardItemListWidget::selectionRectExtended() const ++{ ++ QRectF result = selectionRectCore(); ++ if (layoutDirection() == Qt::LeftToRight) { ++ result.setRight(leftPadding() + m_columnWidthSum); ++ } else { ++ result.setLeft(size().width() - m_columnWidthSum - rightPadding()); ++ } ++ return result; ++} ++ + QRectF KStandardItemListWidget::expansionToggleRect() const + { + const_cast(this)->triggerCacheRefreshing(); +@@ -577,7 +580,7 @@ QRectF KStandardItemListWidget::selectionToggleRect() const + } + + const int padding = styleOption().padding; +- const QRectF selectionRectMinusPadding = selectionRect().adjusted(padding, padding, -padding, -padding); ++ const QRectF selectionRectMinusPadding = selectionRectCore().adjusted(padding, padding, -padding, -padding); + QPointF pos = selectionRectMinusPadding.topLeft(); + if (QApplication::isRightToLeft()) { + pos.setX(selectionRectMinusPadding.right() - (pos.x() + toggleSize - selectionRectMinusPadding.left())); +diff --git a/src/kitemviews/kstandarditemlistwidget.h b/src/kitemviews/kstandarditemlistwidget.h +index 3dbf442913..c8feed1c9f 100644 +--- a/src/kitemviews/kstandarditemlistwidget.h ++++ b/src/kitemviews/kstandarditemlistwidget.h +@@ -105,10 +105,11 @@ public: + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; + +- QRectF iconRect() const override; + QRectF textRect() const override; + QRectF textFocusRect() const override; +- QRectF selectionRect() const override; ++ QRectF visualSelectionRect() const override; ++ QRectF selectionRectCore() const override; ++ QRectF selectionRectExtended() const override; + QRectF expansionToggleRect() const override; + QRectF selectionToggleRect() const override; + QPixmap createDragPixmap(const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; +-- +GitLab + + +From 6a9f1e168b201d6f6d2edf88d1eb040a208d48f5 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Mon, 2 Jun 2025 19:04:47 +0300 +Subject: [PATCH 42/69] fix emblem position + +--- + src/kitemviews/kstandarditemlistwidget.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp +index 0661a19944..58fc9b0415 100644 +--- a/src/kitemviews/kstandarditemlistwidget.cpp ++++ b/src/kitemviews/kstandarditemlistwidget.cpp +@@ -580,7 +580,7 @@ QRectF KStandardItemListWidget::selectionToggleRect() const + } + + const int padding = styleOption().padding; +- const QRectF selectionRectMinusPadding = selectionRectCore().adjusted(padding, padding, -padding, -padding); ++ const QRectF selectionRectMinusPadding = visualSelectionRect().adjusted(padding, padding, -padding, -padding); + QPointF pos = selectionRectMinusPadding.topLeft(); + if (QApplication::isRightToLeft()) { + pos.setX(selectionRectMinusPadding.right() - (pos.x() + toggleSize - selectionRectMinusPadding.left())); +-- +GitLab + + +From f1098aa09599e59fceacc3c90e3daf5b4c177860 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Mon, 2 Jun 2025 19:05:01 +0300 +Subject: [PATCH 43/69] fix widget contains + +--- + src/kitemviews/kitemlistwidget.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp +index 18bd4c48b3..ea6b31861e 100644 +--- a/src/kitemviews/kitemlistwidget.cpp ++++ b/src/kitemviews/kitemlistwidget.cpp +@@ -407,7 +407,7 @@ bool KItemListWidget::contains(const QPointF &point) const + return false; + } + +- return selectionRectCore().contains(point) || expansionToggleRect().contains(point) || selectionToggleRect().contains(point); ++ return visualSelectionRect().contains(point) || expansionToggleRect().contains(point) || selectionToggleRect().contains(point); + } + + QRectF KItemListWidget::textFocusRect() const +-- +GitLab + + +From 5e9a5e1a0801a4e9e8b3b72f741cb989e8637e18 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Mon, 2 Jun 2025 19:05:11 +0300 +Subject: [PATCH 44/69] remove unnecessary comparisons + +--- + src/kitemviews/kitemlistcontroller.cpp | 2 +- + src/kitemviews/kitemlistview.cpp | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp +index 566f1d6a24..e398985e8a 100644 +--- a/src/kitemviews/kitemlistcontroller.cpp ++++ b/src/kitemviews/kitemlistcontroller.cpp +@@ -1470,7 +1470,7 @@ KItemListWidget *KItemListController::widgetForPos(const QPointF &pos) const + const auto widgets = m_view->visibleItemListWidgets(); + for (KItemListWidget *widget : widgets) { + const QPointF mappedPos = widget->mapFromItem(m_view, pos); +- if (widget->contains(mappedPos) || widget->visualSelectionRect().contains(mappedPos)) { ++ if (widget->contains(mappedPos)) { + return widget; + } + } +diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp +index 4146dd8438..90f2beebb9 100644 +--- a/src/kitemviews/kitemlistview.cpp ++++ b/src/kitemviews/kitemlistview.cpp +@@ -425,7 +425,7 @@ std::optional KItemListView::itemAt(const QPointF &pos) const + + const KItemListWidget *widget = it.value(); + const QPointF mappedPos = widget->mapFromItem(this, pos); +- if (widget->contains(mappedPos) || widget->visualSelectionRect().contains(mappedPos)) { ++ if (widget->contains(mappedPos)) { + return it.key(); + } + } +-- +GitLab + + +From 57d43263cbee51f0f3cb9b0675ffd9fd347791dc Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Mon, 2 Jun 2025 19:42:30 +0300 +Subject: [PATCH 45/69] Draw different selection highlights depending on + settings + +--- + src/kitemviews/kstandarditemlistwidget.cpp | 8 ++++++-- + 1 file changed, 6 insertions(+), 2 deletions(-) + +diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp +index 58fc9b0415..d507d7c0f1 100644 +--- a/src/kitemviews/kstandarditemlistwidget.cpp ++++ b/src/kitemviews/kstandarditemlistwidget.cpp +@@ -533,8 +533,12 @@ QRectF KStandardItemListWidget::textFocusRect() const + QRectF KStandardItemListWidget::visualSelectionRect() const + { + const_cast(this)->triggerCacheRefreshing(); +- if (m_highlightEntireRow) { +- return selectionRectExtended(); ++ if (m_layout == DetailsLayout) { ++ if (m_highlightEntireRow) { ++ return rect(); ++ } else { ++ return selectionRectExtended(); ++ } + } else { + if (m_layout == CompactLayout) { + const int padding = styleOption().padding; +-- +GitLab + + +From 337040cbe81adb6e443fc11045e9e2fd5f800a2b Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Mon, 2 Jun 2025 19:44:24 +0300 +Subject: [PATCH 46/69] Make sure we follow details mode selection settings + +--- + src/kitemviews/kitemlistcontroller.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp +index e398985e8a..45cba1a0db 100644 +--- a/src/kitemviews/kitemlistcontroller.cpp ++++ b/src/kitemviews/kitemlistcontroller.cpp +@@ -1726,7 +1726,7 @@ bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifier + if (m_pressedIndex.has_value()) { + // The hover highlight area of an item is being pressed. + const auto row = m_view->m_visibleItems.value(m_pressedIndex.value()); // anything outside of row.contains() will be the empty region of the row rect +- const bool hitTargetIsRowEmptyRegion = !row->contains(row->mapFromItem(m_view, pos)); ++ const bool hitTargetIsRowEmptyRegion = !row->selectionRectCore().contains(row->mapFromItem(m_view, pos)); + // again, when this method returns false, a rubberBand selection is created as the event is not consumed; + // createRubberBand here tells us whether to return true or false. + bool createRubberBand = (hitTargetIsRowEmptyRegion && m_selectionManager->selectedItems().isEmpty()); +-- +GitLab + + +From e212678b7c1972cd28c72d6515ad7e52242e3082 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Tue, 3 Jun 2025 11:46:56 +0300 +Subject: [PATCH 47/69] Fix drawing for details view + +--- + src/kitemviews/kitemlistcontroller.cpp | 2 +- + src/kitemviews/kstandarditemlistwidget.cpp | 8 ++++---- + 2 files changed, 5 insertions(+), 5 deletions(-) + +diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp +index 45cba1a0db..dfe3fb0eaf 100644 +--- a/src/kitemviews/kitemlistcontroller.cpp ++++ b/src/kitemviews/kitemlistcontroller.cpp +@@ -1726,7 +1726,7 @@ bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifier + if (m_pressedIndex.has_value()) { + // The hover highlight area of an item is being pressed. + const auto row = m_view->m_visibleItems.value(m_pressedIndex.value()); // anything outside of row.contains() will be the empty region of the row rect +- const bool hitTargetIsRowEmptyRegion = !row->selectionRectCore().contains(row->mapFromItem(m_view, pos)); ++ const bool hitTargetIsRowEmptyRegion = !row->visualSelectionRect().contains(row->mapFromItem(m_view, pos)); + // again, when this method returns false, a rubberBand selection is created as the event is not consumed; + // createRubberBand here tells us whether to return true or false. + bool createRubberBand = (hitTargetIsRowEmptyRegion && m_selectionManager->selectedItems().isEmpty()); +diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp +index d507d7c0f1..a233472fc6 100644 +--- a/src/kitemviews/kstandarditemlistwidget.cpp ++++ b/src/kitemviews/kstandarditemlistwidget.cpp +@@ -533,15 +533,15 @@ QRectF KStandardItemListWidget::textFocusRect() const + QRectF KStandardItemListWidget::visualSelectionRect() const + { + const_cast(this)->triggerCacheRefreshing(); ++ const int padding = styleOption().padding; + if (m_layout == DetailsLayout) { ++ auto rect = selectionRectCore(); + if (m_highlightEntireRow) { +- return rect(); +- } else { +- return selectionRectExtended(); ++ rect = selectionRectExtended(); + } ++ return rect.adjusted(-padding, 0, padding, 0); + } else { + if (m_layout == CompactLayout) { +- const int padding = styleOption().padding; + return rect().adjusted(-padding, 0, padding, 0); + } + return rect(); +-- +GitLab + + +From 85e7e8eb933448b89138e0ce0b1d23f838711aa3 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Tue, 3 Jun 2025 12:00:17 +0300 +Subject: [PATCH 48/69] cleanup + +--- + src/kitemviews/kstandarditemlistwidget.cpp | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp +index a233472fc6..ed267bb25d 100644 +--- a/src/kitemviews/kstandarditemlistwidget.cpp ++++ b/src/kitemviews/kstandarditemlistwidget.cpp +@@ -1295,7 +1295,6 @@ void KStandardItemListWidget::updateIconsLayoutTextCache() + + const KItemListStyleOption &option = styleOption(); + const qreal padding = option.padding; +- // adjust the max width according to new outline style + const qreal maxWidth = size().width() - 2 * padding; + const qreal lineSpacing = m_customizedFontMetrics.lineSpacing(); + +-- +GitLab + + +From a22c63ce2472213ca27fe57c4feafde3985a99d7 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Tue, 3 Jun 2025 17:51:51 +0300 +Subject: [PATCH 49/69] remove hover effect when dragging + +--- + src/kitemviews/kitemlistcontroller.cpp | 6 +----- + 1 file changed, 1 insertion(+), 5 deletions(-) + +diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp +index dfe3fb0eaf..561917e80e 100644 +--- a/src/kitemviews/kitemlistcontroller.cpp ++++ b/src/kitemviews/kitemlistcontroller.cpp +@@ -652,10 +652,6 @@ bool KItemListController::mouseMoveEvent(QGraphicsSceneMouseEvent *event, const + m_selectionManager->endAnchoredSelection(); + m_selectionManager->setCurrentItem(newCurrent.value()); + m_selectionManager->beginAnchoredSelection(newCurrent.value()); +- // Set hovered effect when dragging. +- for (const auto widget : m_view->visibleItemListWidgets()) { +- widget->setHovered(widget->isCurrent()); +- } + } + + if (m_view->scrollOrientation() == Qt::Vertical) { +@@ -1363,7 +1359,7 @@ void KItemListController::slotRubberBandChanged() + + // Select all visible items that intersect with the rubberband + const auto widgets = m_view->visibleItemListWidgets(); +- for (const KItemListWidget *widget : widgets) { ++ for (KItemListWidget *widget : widgets) { + const int index = widget->index(); + + const QRectF widgetRect = m_view->itemRect(index); +-- +GitLab + + +From b4937aa0676d84beb41ee4d6750b0cee711a5682 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Wed, 4 Jun 2025 12:31:26 +0300 +Subject: [PATCH 50/69] set a click highlight color + +--- + src/kitemviews/kitemlistcontroller.cpp | 11 ++++++----- + src/kitemviews/kitemlistwidget.cpp | 14 ++++++++++++-- + src/kitemviews/kitemlistwidget.h | 3 +++ + 3 files changed, 21 insertions(+), 7 deletions(-) + +diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp +index 561917e80e..525e8107a4 100644 +--- a/src/kitemviews/kitemlistcontroller.cpp ++++ b/src/kitemviews/kitemlistcontroller.cpp +@@ -1640,6 +1640,7 @@ bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifier + // simplify the overall logic and possibilities both for users and devs. + const bool leftClick = buttons & Qt::LeftButton; + const bool rightClick = buttons & Qt::RightButton; ++ const bool singleClickActivation = m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick); + + // The previous selection is cleared if either + // 1. The selection mode is SingleSelection, or +@@ -1664,7 +1665,7 @@ bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifier + // we are indeed inside the text/icon rect, keep m_pressedIndex what it is + // and short-circuit for single-click activation (it will then propagate to onRelease and activate the item) + // or we just keep going for double-click activation +- if (m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced) { ++ if (singleClickActivation || m_singleClickActivationEnforced) { + if (!pressedItemAlreadySelected) { + // An unselected item was clicked directly while deselecting multiple other items so we mark it "current". + m_selectionManager->setCurrentItem(m_pressedIndex.value()); +@@ -1740,8 +1741,7 @@ bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifier + break; + + case SingleSelection: +- if (!leftClick || shiftOrControlPressed +- || (!m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) && !m_singleClickActivationEnforced)) { ++ if (!leftClick || shiftOrControlPressed || (!singleClickActivation && !m_singleClickActivationEnforced)) { + m_selectionManager->setSelected(m_pressedIndex.value()); + } + break; +@@ -1764,9 +1764,10 @@ bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifier + } + } else if (!shiftPressed || !m_selectionManager->isAnchoredSelectionActive()) { + // Select the pressed item and start a new anchored selection +- if (!leftClick || shiftOrControlPressed +- || (!m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) && !m_singleClickActivationEnforced)) { ++ if (!leftClick || shiftOrControlPressed || (!singleClickActivation && !m_singleClickActivationEnforced)) { + m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Select); ++ } else if (leftClick && (m_singleClickActivationEnforced || singleClickActivation)) { ++ row->setClickHighlight(true); + } + m_selectionManager->beginAnchoredSelection(m_pressedIndex.value()); + } +diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp +index ea6b31861e..22d10d1481 100644 +--- a/src/kitemviews/kitemlistwidget.cpp ++++ b/src/kitemviews/kitemlistwidget.cpp +@@ -37,6 +37,7 @@ KItemListWidget::KItemListWidget(KItemListWidgetInformant *informant, QGraphicsI + , m_expansionAreaHovered(false) + , m_alternateBackground(false) + , m_enabledSelectionToggle(false) ++ , m_clickHighlighted(false) + , m_data() + , m_visibleRoles() + , m_columnWidths() +@@ -281,6 +282,7 @@ void KItemListWidget::setHovered(bool hovered) + + m_hoverSequenceTimer.start(interval); + } else { ++ m_clickHighlighted = false; + setHoverOpacity(0.0); + + if (m_selectionToggle) { +@@ -604,6 +606,14 @@ void KItemListWidget::clearHoverCache() + m_hoverCache = nullptr; + } + ++void KItemListWidget::setClickHighlight(bool enabled) ++{ ++ if (m_clickHighlighted != enabled) { ++ m_clickHighlighted = enabled; ++ update(); ++ } ++} ++ + void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QStyle::State styleState) + { + QStyleOptionViewItem viewItemOption; +@@ -624,11 +634,11 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + // Background item, alpha values are from + // https://invent.kde.org/plasma/libplasma/-/blob/master/src/desktoptheme/breeze/widgets/viewitem.svg + backgroundColor.setAlphaF(0.0); +- if (m_selected && m_hovered) { ++ if ((m_selected && m_hovered) || m_clickHighlighted) { + backgroundColor.setAlphaF(0.40); + } else if (m_selected) { + backgroundColor.setAlphaF(0.32); +- } else if (m_hovered) { ++ } else if (m_hovered && !m_clickHighlighted) { + backgroundColor = widget->palette().color(QPalette::Text); + backgroundColor.setAlphaF(0.06); + } +diff --git a/src/kitemviews/kitemlistwidget.h b/src/kitemviews/kitemlistwidget.h +index 489081b604..415bc4b5c7 100644 +--- a/src/kitemviews/kitemlistwidget.h ++++ b/src/kitemviews/kitemlistwidget.h +@@ -109,6 +109,8 @@ public: + void setEnabledSelectionToggle(bool enabled); + bool enabledSelectionToggle() const; + ++ void setClickHighlight(bool enabled); ++ + /** + * Sets the sibling information for the item and all of its parents. + * The sibling information of the upper most parent is represented by +@@ -266,6 +268,7 @@ private: + bool m_expansionAreaHovered; + bool m_alternateBackground; + bool m_enabledSelectionToggle; ++ bool m_clickHighlighted; + QHash m_data; + QList m_visibleRoles; + QHash m_columnWidths; +-- +GitLab + + +From 9f5e8f169ac98a6d2b23bf850cb6a4470a51b30d Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Thu, 5 Jun 2025 12:17:14 +0300 +Subject: [PATCH 51/69] Add hitTargetRect + +--- + src/kitemviews/kitemlistcontroller.cpp | 2 +- + src/kitemviews/kitemlistwidget.h | 11 +++++++++-- + src/kitemviews/kstandarditemlistwidget.cpp | 9 +++++++++ + src/kitemviews/kstandarditemlistwidget.h | 1 + + 4 files changed, 20 insertions(+), 3 deletions(-) + +diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp +index 525e8107a4..d5c72a875b 100644 +--- a/src/kitemviews/kitemlistcontroller.cpp ++++ b/src/kitemviews/kitemlistcontroller.cpp +@@ -1723,7 +1723,7 @@ bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifier + if (m_pressedIndex.has_value()) { + // The hover highlight area of an item is being pressed. + const auto row = m_view->m_visibleItems.value(m_pressedIndex.value()); // anything outside of row.contains() will be the empty region of the row rect +- const bool hitTargetIsRowEmptyRegion = !row->visualSelectionRect().contains(row->mapFromItem(m_view, pos)); ++ const bool hitTargetIsRowEmptyRegion = !row->hitTargetRect().contains(row->mapFromItem(m_view, pos)); + // again, when this method returns false, a rubberBand selection is created as the event is not consumed; + // createRubberBand here tells us whether to return true or false. + bool createRubberBand = (hitTargetIsRowEmptyRegion && m_selectionManager->selectedItems().isEmpty()); +diff --git a/src/kitemviews/kitemlistwidget.h b/src/kitemviews/kitemlistwidget.h +index 415bc4b5c7..ca0dc704b8 100644 +--- a/src/kitemviews/kitemlistwidget.h ++++ b/src/kitemviews/kitemlistwidget.h +@@ -139,8 +139,8 @@ public: + int iconSize() const; + + /** +- * @return True if \a point is inside KItemListWidget::hoverRect(), +- * KItemListWidget::textRect(), KItemListWidget::selectionToggleRect() ++ * @return True if \a point is inside KItemListWidget::visualSelectionRect(), ++ * KItemListWidget::selectionToggleRect() + * or KItemListWidget::expansionToggleRect(). + * @reimp + */ +@@ -178,6 +178,13 @@ public: + */ + virtual QRectF selectionRectExtended() const = 0; + ++ /** ++ * @return Rectangle where dragging the item is allowed to start. ++ * In compact and icon views, it returns KItemListWidget::visualSelectionRect(). ++ * In details view, it returns KItemListWidget::selectionRectCore(). ++ */ ++ virtual QRectF hitTargetRect() const = 0; ++ + /** + * @return Rectangle for the selection-toggle that is used to select or deselect an item. + * Per default an empty rectangle is returned which means that no selection-toggle +diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp +index ed267bb25d..46374d577b 100644 +--- a/src/kitemviews/kstandarditemlistwidget.cpp ++++ b/src/kitemviews/kstandarditemlistwidget.cpp +@@ -565,6 +565,15 @@ QRectF KStandardItemListWidget::selectionRectExtended() const + return result; + } + ++QRectF KStandardItemListWidget::hitTargetRect() const ++{ ++ if (m_layout == DetailsLayout) { ++ return selectionRectCore(); ++ } else { ++ return visualSelectionRect(); ++ } ++} ++ + QRectF KStandardItemListWidget::expansionToggleRect() const + { + const_cast(this)->triggerCacheRefreshing(); +diff --git a/src/kitemviews/kstandarditemlistwidget.h b/src/kitemviews/kstandarditemlistwidget.h +index c8feed1c9f..b9deed7454 100644 +--- a/src/kitemviews/kstandarditemlistwidget.h ++++ b/src/kitemviews/kstandarditemlistwidget.h +@@ -110,6 +110,7 @@ public: + QRectF visualSelectionRect() const override; + QRectF selectionRectCore() const override; + QRectF selectionRectExtended() const override; ++ QRectF hitTargetRect() const override; + QRectF expansionToggleRect() const override; + QRectF selectionToggleRect() const override; + QPixmap createDragPixmap(const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; +-- +GitLab + + +From a58b2bcb52433019b1682113ae0feeebf7113b14 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Mon, 9 Jun 2025 11:41:46 +0300 +Subject: [PATCH 52/69] Set click highlight work like toolbar click highlight + +--- + src/kitemviews/kitemlistcontroller.cpp | 8 +++++++- + src/kitemviews/kitemlistwidget.cpp | 20 ++++++++++++-------- + 2 files changed, 19 insertions(+), 9 deletions(-) + +diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp +index d5c72a875b..5201134b9f 100644 +--- a/src/kitemviews/kitemlistcontroller.cpp ++++ b/src/kitemviews/kitemlistcontroller.cpp +@@ -675,6 +675,10 @@ bool KItemListController::mouseReleaseEvent(QGraphicsSceneMouseEvent *event, con + return false; + } + ++ for (KItemListWidget *widget : m_view->visibleItemListWidgets()) { ++ widget->setClickHighlight(false); ++ } ++ + if (m_view->m_tapAndHoldIndicator->isActive()) { + m_view->m_tapAndHoldIndicator->setActive(false); + } +@@ -1029,6 +1033,7 @@ bool KItemListController::hoverLeaveEvent(QGraphicsSceneHoverEvent *event, const + + const auto widgets = m_view->visibleItemListWidgets(); + for (KItemListWidget *widget : widgets) { ++ widget->setClickHighlight(false); + if (widget->isHovered()) { + widget->setHovered(false); + Q_EMIT itemUnhovered(widget->index()); +@@ -1766,7 +1771,8 @@ bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifier + // Select the pressed item and start a new anchored selection + if (!leftClick || shiftOrControlPressed || (!singleClickActivation && !m_singleClickActivationEnforced)) { + m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Select); +- } else if (leftClick && (m_singleClickActivationEnforced || singleClickActivation)) { ++ } ++ if (leftClick) { + row->setClickHighlight(true); + } + m_selectionManager->beginAnchoredSelection(m_pressedIndex.value()); +diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp +index 22d10d1481..a14d28162a 100644 +--- a/src/kitemviews/kitemlistwidget.cpp ++++ b/src/kitemviews/kitemlistwidget.cpp +@@ -282,7 +282,6 @@ void KItemListWidget::setHovered(bool hovered) + + m_hoverSequenceTimer.start(interval); + } else { +- m_clickHighlighted = false; + setHoverOpacity(0.0); + + if (m_selectionToggle) { +@@ -634,13 +633,18 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + // Background item, alpha values are from + // https://invent.kde.org/plasma/libplasma/-/blob/master/src/desktoptheme/breeze/widgets/viewitem.svg + backgroundColor.setAlphaF(0.0); +- if ((m_selected && m_hovered) || m_clickHighlighted) { +- backgroundColor.setAlphaF(0.40); +- } else if (m_selected) { +- backgroundColor.setAlphaF(0.32); +- } else if (m_hovered && !m_clickHighlighted) { +- backgroundColor = widget->palette().color(QPalette::Text); +- backgroundColor.setAlphaF(0.06); ++ ++ if (m_clickHighlighted) { ++ backgroundColor.setAlphaF(0.5); ++ } else { ++ if (m_selected && m_hovered) { ++ backgroundColor.setAlphaF(0.40); ++ } else if (m_selected) { ++ backgroundColor.setAlphaF(0.32); ++ } else if (m_hovered) { ++ backgroundColor = widget->palette().color(QPalette::Text); ++ backgroundColor.setAlphaF(0.06); ++ } + } + + painter->fillPath(path, backgroundColor); +-- +GitLab + + +From cf5f5e8d2735bef7df60926ccdfc1fae1473de97 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Wed, 11 Jun 2025 13:16:01 +0300 +Subject: [PATCH 53/69] make button click highlight stronger, fix highlight not + clearing + +--- + src/kitemviews/kitemlistcontroller.cpp | 7 ++++--- + src/kitemviews/kitemlistwidget.cpp | 3 ++- + 2 files changed, 6 insertions(+), 4 deletions(-) + +diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp +index 5201134b9f..5222b9c957 100644 +--- a/src/kitemviews/kitemlistcontroller.cpp ++++ b/src/kitemviews/kitemlistcontroller.cpp +@@ -1733,6 +1733,10 @@ bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifier + // createRubberBand here tells us whether to return true or false. + bool createRubberBand = (hitTargetIsRowEmptyRegion && m_selectionManager->selectedItems().isEmpty()); + ++ if (leftClick) { ++ row->setClickHighlight(true); ++ } ++ + if (rightClick && hitTargetIsRowEmptyRegion) { + // We have a right click outside the icon and text rect but within the hover highlight area. + // We don't want items to get selected through this, so we return now. +@@ -1772,9 +1776,6 @@ bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifier + if (!leftClick || shiftOrControlPressed || (!singleClickActivation && !m_singleClickActivationEnforced)) { + m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Select); + } +- if (leftClick) { +- row->setClickHighlight(true); +- } + m_selectionManager->beginAnchoredSelection(m_pressedIndex.value()); + } + break; +diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp +index a14d28162a..eb24a19dc8 100644 +--- a/src/kitemviews/kitemlistwidget.cpp ++++ b/src/kitemviews/kitemlistwidget.cpp +@@ -609,6 +609,7 @@ void KItemListWidget::setClickHighlight(bool enabled) + { + if (m_clickHighlighted != enabled) { + m_clickHighlighted = enabled; ++ clearHoverCache(); + update(); + } + } +@@ -635,7 +636,7 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + backgroundColor.setAlphaF(0.0); + + if (m_clickHighlighted) { +- backgroundColor.setAlphaF(0.5); ++ backgroundColor.setAlphaF(1.0); + } else { + if (m_selected && m_hovered) { + backgroundColor.setAlphaF(0.40); +-- +GitLab + + +From 4398a90bfcab35834d0fd84c4e449456509089fb Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Wed, 11 Jun 2025 13:44:04 +0300 +Subject: [PATCH 54/69] Make sure text is readable when clickHighlighted + +--- + src/kitemviews/kitemlistwidget.cpp | 5 +++++ + src/kitemviews/kitemlistwidget.h | 1 + + src/kitemviews/kstandarditemlistwidget.cpp | 6 +++++- + 3 files changed, 11 insertions(+), 1 deletion(-) + +diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp +index eb24a19dc8..0f15aa883b 100644 +--- a/src/kitemviews/kitemlistwidget.cpp ++++ b/src/kitemviews/kitemlistwidget.cpp +@@ -605,6 +605,11 @@ void KItemListWidget::clearHoverCache() + m_hoverCache = nullptr; + } + ++bool KItemListWidget::isClickHighlighted() const ++{ ++ return m_clickHighlighted; ++} ++ + void KItemListWidget::setClickHighlight(bool enabled) + { + if (m_clickHighlighted != enabled) { +diff --git a/src/kitemviews/kitemlistwidget.h b/src/kitemviews/kitemlistwidget.h +index ca0dc704b8..40b630cc4f 100644 +--- a/src/kitemviews/kitemlistwidget.h ++++ b/src/kitemviews/kitemlistwidget.h +@@ -109,6 +109,7 @@ public: + void setEnabledSelectionToggle(bool enabled); + bool enabledSelectionToggle() const; + ++ bool isClickHighlighted() const; + void setClickHighlight(bool enabled); + + /** +diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp +index 46374d577b..1a2976bfeb 100644 +--- a/src/kitemviews/kstandarditemlistwidget.cpp ++++ b/src/kitemviews/kstandarditemlistwidget.cpp +@@ -712,7 +712,11 @@ QFont KStandardItemListWidget::customizedFont(const QFont &baseFont) const + + QPalette::ColorRole KStandardItemListWidget::normalTextColorRole() const + { +- return QPalette::Text; ++ if (isClickHighlighted()) { ++ return QPalette::HighlightedText; ++ } else { ++ return QPalette::Text; ++ } + } + + void KStandardItemListWidget::setTextColor(const QColor &color) +-- +GitLab + + +From 71bcec57388d613955148b9d590c85ab977f978e Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Thu, 12 Jun 2025 18:03:53 +0300 +Subject: [PATCH 55/69] widgetForDropPos uses hitTargetRect instead of contains + +--- + src/kitemviews/kitemlistcontroller.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp +index 5222b9c957..cb97f484d3 100644 +--- a/src/kitemviews/kitemlistcontroller.cpp ++++ b/src/kitemviews/kitemlistcontroller.cpp +@@ -1490,7 +1490,7 @@ KItemListWidget *KItemListController::widgetForDropPos(const QPointF &pos) const + const auto widgets = m_view->visibleItemListWidgets(); + for (KItemListWidget *widget : widgets) { + const QPointF mappedPos = widget->mapFromItem(m_view, pos); +- if (widget->contains(mappedPos)) { ++ if (widget->hitTargetRect().contains(mappedPos)) { + return widget; + } + } +-- +GitLab + + +From 23d522fa8a4726f8b668419fa31fbac04e6e6a84 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Thu, 12 Jun 2025 18:03:53 +0300 +Subject: [PATCH 56/69] change hitTargetRect comment + +--- + src/kitemviews/kitemlistwidget.h | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/kitemviews/kitemlistwidget.h b/src/kitemviews/kitemlistwidget.h +index 40b630cc4f..6e73bb72d5 100644 +--- a/src/kitemviews/kitemlistwidget.h ++++ b/src/kitemviews/kitemlistwidget.h +@@ -180,7 +180,7 @@ public: + virtual QRectF selectionRectExtended() const = 0; + + /** +- * @return Rectangle where dragging the item is allowed to start. ++ * @return Returns rectangle where item is considered "hit" during actions, such as drag and drop. + * In compact and icon views, it returns KItemListWidget::visualSelectionRect(). + * In details view, it returns KItemListWidget::selectionRectCore(). + */ +-- +GitLab + + +From 105d70b4d6d5ae02305619cfd4fdb37b6048ddbd Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Thu, 12 Jun 2025 18:18:13 +0300 +Subject: [PATCH 57/69] Remove selectionRectExtended + +--- + src/kitemviews/kitemlistwidget.h | 5 ----- + src/kitemviews/kstandarditemlistwidget.cpp | 17 +++++------------ + src/kitemviews/kstandarditemlistwidget.h | 1 - + 3 files changed, 5 insertions(+), 18 deletions(-) + +diff --git a/src/kitemviews/kitemlistwidget.h b/src/kitemviews/kitemlistwidget.h +index 6e73bb72d5..2287683570 100644 +--- a/src/kitemviews/kitemlistwidget.h ++++ b/src/kitemviews/kitemlistwidget.h +@@ -174,11 +174,6 @@ public: + */ + virtual QRectF selectionRectCore() const = 0; + +- /** +- * @return Same as core, but extended to the full width of the details view. +- */ +- virtual QRectF selectionRectExtended() const = 0; +- + /** + * @return Returns rectangle where item is considered "hit" during actions, such as drag and drop. + * In compact and icon views, it returns KItemListWidget::visualSelectionRect(). +diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp +index 1a2976bfeb..3530677e83 100644 +--- a/src/kitemviews/kstandarditemlistwidget.cpp ++++ b/src/kitemviews/kstandarditemlistwidget.cpp +@@ -537,7 +537,11 @@ QRectF KStandardItemListWidget::visualSelectionRect() const + if (m_layout == DetailsLayout) { + auto rect = selectionRectCore(); + if (m_highlightEntireRow) { +- rect = selectionRectExtended(); ++ if (layoutDirection() == Qt::LeftToRight) { ++ rect.setRight(leftPadding() + m_columnWidthSum); ++ } else { ++ rect.setLeft(size().width() - m_columnWidthSum - rightPadding()); ++ } + } + return rect.adjusted(-padding, 0, padding, 0); + } else { +@@ -554,17 +558,6 @@ QRectF KStandardItemListWidget::selectionRectCore() const + return result; + } + +-QRectF KStandardItemListWidget::selectionRectExtended() const +-{ +- QRectF result = selectionRectCore(); +- if (layoutDirection() == Qt::LeftToRight) { +- result.setRight(leftPadding() + m_columnWidthSum); +- } else { +- result.setLeft(size().width() - m_columnWidthSum - rightPadding()); +- } +- return result; +-} +- + QRectF KStandardItemListWidget::hitTargetRect() const + { + if (m_layout == DetailsLayout) { +diff --git a/src/kitemviews/kstandarditemlistwidget.h b/src/kitemviews/kstandarditemlistwidget.h +index b9deed7454..a85e4ee11c 100644 +--- a/src/kitemviews/kstandarditemlistwidget.h ++++ b/src/kitemviews/kstandarditemlistwidget.h +@@ -109,7 +109,6 @@ public: + QRectF textFocusRect() const override; + QRectF visualSelectionRect() const override; + QRectF selectionRectCore() const override; +- QRectF selectionRectExtended() const override; + QRectF hitTargetRect() const override; + QRectF expansionToggleRect() const override; + QRectF selectionToggleRect() const override; +-- +GitLab + + +From 18aec3429bc384e3fe8e80a95fb64e513d8d1442 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Thu, 12 Jun 2025 18:21:01 +0300 +Subject: [PATCH 58/69] Rename visualSelectionRect to selectionRectFull + +--- + src/kitemviews/kitemlistcontroller.cpp | 2 +- + src/kitemviews/kitemlistview.cpp | 2 +- + src/kitemviews/kitemlistwidget.cpp | 6 +++--- + src/kitemviews/kitemlistwidget.h | 6 +++--- + src/kitemviews/kstandarditemlistwidget.cpp | 6 +++--- + src/kitemviews/kstandarditemlistwidget.h | 2 +- + 6 files changed, 12 insertions(+), 12 deletions(-) + +diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp +index cb97f484d3..66b5cb9696 100644 +--- a/src/kitemviews/kitemlistcontroller.cpp ++++ b/src/kitemviews/kitemlistcontroller.cpp +@@ -1370,7 +1370,7 @@ void KItemListController::slotRubberBandChanged() + const QRectF widgetRect = m_view->itemRect(index); + if (widgetRect.intersects(rubberBandRect)) { + // Select the full row intersecting with the rubberband rectangle +- const QRectF selectionRect = widget->visualSelectionRect().translated(widgetRect.topLeft()); ++ const QRectF selectionRect = widget->selectionRectFull().translated(widgetRect.topLeft()); + if (selectionRect.intersects(rubberBandRect)) { + selectedItems.insert(index); + } +diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp +index 90f2beebb9..5394e7341e 100644 +--- a/src/kitemviews/kitemlistview.cpp ++++ b/src/kitemviews/kitemlistview.cpp +@@ -542,7 +542,7 @@ QRectF KItemListView::itemContextRect(int index) const + + const KItemListWidget *widget = m_visibleItems.value(index); + if (widget) { +- contextRect = widget->visualSelectionRect(); ++ contextRect = widget->selectionRectFull(); + contextRect.translate(itemRect(index).topLeft()); + } + +diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp +index 0f15aa883b..2abf9fe067 100644 +--- a/src/kitemviews/kitemlistwidget.cpp ++++ b/src/kitemviews/kitemlistwidget.cpp +@@ -408,7 +408,7 @@ bool KItemListWidget::contains(const QPointF &point) const + return false; + } + +- return visualSelectionRect().contains(point) || expansionToggleRect().contains(point) || selectionToggleRect().contains(point); ++ return selectionRectFull().contains(point) || expansionToggleRect().contains(point) || selectionToggleRect().contains(point); + } + + QRectF KItemListWidget::textFocusRect() const +@@ -629,9 +629,9 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + viewItemOption.viewItemPosition = QStyleOptionViewItem::OnlyOne; + viewItemOption.showDecorationSelected = true; + // TODO: check if we're extended or not +- viewItemOption.rect = visualSelectionRect().toRect(); ++ viewItemOption.rect = selectionRectFull().toRect(); + QPainterPath path; +- path.addRoundedRect(visualSelectionRect().adjusted(penWidth, penWidth, -penWidth, -penWidth), roundness, roundness); ++ path.addRoundedRect(selectionRectFull().adjusted(penWidth, penWidth, -penWidth, -penWidth), roundness, roundness); + QColor backgroundColor{widget->palette().color(QPalette::Accent)}; + painter->setRenderHint(QPainter::Antialiasing); + bool current = m_current && styleState & QStyle::State_Active; +diff --git a/src/kitemviews/kitemlistwidget.h b/src/kitemviews/kitemlistwidget.h +index 2287683570..f277e18a73 100644 +--- a/src/kitemviews/kitemlistwidget.h ++++ b/src/kitemviews/kitemlistwidget.h +@@ -140,7 +140,7 @@ public: + int iconSize() const; + + /** +- * @return True if \a point is inside KItemListWidget::visualSelectionRect(), ++ * @return True if \a point is inside KItemListWidget::selectionRectFull(), + * KItemListWidget::selectionToggleRect() + * or KItemListWidget::expansionToggleRect(). + * @reimp +@@ -167,7 +167,7 @@ public: + * + * @return The rectangle around selection, depending on if it's full width or not. + */ +- virtual QRectF visualSelectionRect() const = 0; ++ virtual QRectF selectionRectFull() const = 0; + + /** + * @return Rectangle around icon and it's text: The core area of the item. +@@ -176,7 +176,7 @@ public: + + /** + * @return Returns rectangle where item is considered "hit" during actions, such as drag and drop. +- * In compact and icon views, it returns KItemListWidget::visualSelectionRect(). ++ * In compact and icon views, it returns KItemListWidget::selectionRectFull(). + * In details view, it returns KItemListWidget::selectionRectCore(). + */ + virtual QRectF hitTargetRect() const = 0; +diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp +index 3530677e83..ba3465c041 100644 +--- a/src/kitemviews/kstandarditemlistwidget.cpp ++++ b/src/kitemviews/kstandarditemlistwidget.cpp +@@ -530,7 +530,7 @@ QRectF KStandardItemListWidget::textFocusRect() const + return m_textRect; + } + +-QRectF KStandardItemListWidget::visualSelectionRect() const ++QRectF KStandardItemListWidget::selectionRectFull() const + { + const_cast(this)->triggerCacheRefreshing(); + const int padding = styleOption().padding; +@@ -563,7 +563,7 @@ QRectF KStandardItemListWidget::hitTargetRect() const + if (m_layout == DetailsLayout) { + return selectionRectCore(); + } else { +- return visualSelectionRect(); ++ return selectionRectFull(); + } + } + +@@ -586,7 +586,7 @@ QRectF KStandardItemListWidget::selectionToggleRect() const + } + + const int padding = styleOption().padding; +- const QRectF selectionRectMinusPadding = visualSelectionRect().adjusted(padding, padding, -padding, -padding); ++ const QRectF selectionRectMinusPadding = selectionRectFull().adjusted(padding, padding, -padding, -padding); + QPointF pos = selectionRectMinusPadding.topLeft(); + if (QApplication::isRightToLeft()) { + pos.setX(selectionRectMinusPadding.right() - (pos.x() + toggleSize - selectionRectMinusPadding.left())); +diff --git a/src/kitemviews/kstandarditemlistwidget.h b/src/kitemviews/kstandarditemlistwidget.h +index a85e4ee11c..f2b7a49438 100644 +--- a/src/kitemviews/kstandarditemlistwidget.h ++++ b/src/kitemviews/kstandarditemlistwidget.h +@@ -107,7 +107,7 @@ public: + + QRectF textRect() const override; + QRectF textFocusRect() const override; +- QRectF visualSelectionRect() const override; ++ QRectF selectionRectFull() const override; + QRectF selectionRectCore() const override; + QRectF hitTargetRect() const override; + QRectF expansionToggleRect() const override; +-- +GitLab + + +From c1aba1674385f07858c51fc9d16376f4fab10066 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Thu, 12 Jun 2025 18:24:44 +0300 +Subject: [PATCH 59/69] rename clickHighlight to isPressed, add comment, move + items + +--- + src/kitemviews/kitemlistcontroller.cpp | 6 +++--- + src/kitemviews/kitemlistwidget.cpp | 4 ++-- + src/kitemviews/kitemlistwidget.h | 7 ++++--- + src/kitemviews/kstandarditemlistwidget.cpp | 2 +- + 4 files changed, 10 insertions(+), 9 deletions(-) + +diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp +index 66b5cb9696..1729a65cba 100644 +--- a/src/kitemviews/kitemlistcontroller.cpp ++++ b/src/kitemviews/kitemlistcontroller.cpp +@@ -676,7 +676,7 @@ bool KItemListController::mouseReleaseEvent(QGraphicsSceneMouseEvent *event, con + } + + for (KItemListWidget *widget : m_view->visibleItemListWidgets()) { +- widget->setClickHighlight(false); ++ widget->setPressed(false); + } + + if (m_view->m_tapAndHoldIndicator->isActive()) { +@@ -1033,7 +1033,7 @@ bool KItemListController::hoverLeaveEvent(QGraphicsSceneHoverEvent *event, const + + const auto widgets = m_view->visibleItemListWidgets(); + for (KItemListWidget *widget : widgets) { +- widget->setClickHighlight(false); ++ widget->setPressed(false); + if (widget->isHovered()) { + widget->setHovered(false); + Q_EMIT itemUnhovered(widget->index()); +@@ -1734,7 +1734,7 @@ bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifier + bool createRubberBand = (hitTargetIsRowEmptyRegion && m_selectionManager->selectedItems().isEmpty()); + + if (leftClick) { +- row->setClickHighlight(true); ++ row->setPressed(true); + } + + if (rightClick && hitTargetIsRowEmptyRegion) { +diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp +index 2abf9fe067..a47f98ae18 100644 +--- a/src/kitemviews/kitemlistwidget.cpp ++++ b/src/kitemviews/kitemlistwidget.cpp +@@ -605,12 +605,12 @@ void KItemListWidget::clearHoverCache() + m_hoverCache = nullptr; + } + +-bool KItemListWidget::isClickHighlighted() const ++bool KItemListWidget::isPressed() const + { + return m_clickHighlighted; + } + +-void KItemListWidget::setClickHighlight(bool enabled) ++void KItemListWidget::setPressed(bool enabled) + { + if (m_clickHighlighted != enabled) { + m_clickHighlighted = enabled; +diff --git a/src/kitemviews/kitemlistwidget.h b/src/kitemviews/kitemlistwidget.h +index f277e18a73..7e6b8f9c93 100644 +--- a/src/kitemviews/kitemlistwidget.h ++++ b/src/kitemviews/kitemlistwidget.h +@@ -98,6 +98,10 @@ public: + void setHovered(bool hovered); + bool isHovered() const; + ++ /** Sets a purely visual pressed highlight effect. */ ++ void setPressed(bool enabled); ++ bool isPressed() const; ++ + void setExpansionAreaHovered(bool hover); + bool expansionAreaHovered() const; + +@@ -109,9 +113,6 @@ public: + void setEnabledSelectionToggle(bool enabled); + bool enabledSelectionToggle() const; + +- bool isClickHighlighted() const; +- void setClickHighlight(bool enabled); +- + /** + * Sets the sibling information for the item and all of its parents. + * The sibling information of the upper most parent is represented by +diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp +index ba3465c041..3511666704 100644 +--- a/src/kitemviews/kstandarditemlistwidget.cpp ++++ b/src/kitemviews/kstandarditemlistwidget.cpp +@@ -705,7 +705,7 @@ QFont KStandardItemListWidget::customizedFont(const QFont &baseFont) const + + QPalette::ColorRole KStandardItemListWidget::normalTextColorRole() const + { +- if (isClickHighlighted()) { ++ if (isPressed()) { + return QPalette::HighlightedText; + } else { + return QPalette::Text; +-- +GitLab + + +From 4af2e8ce7dfe422bfc79073e57aa0774b0bf62c0 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Thu, 12 Jun 2025 18:25:54 +0300 +Subject: [PATCH 60/69] remove todo + +--- + src/kitemviews/kitemlistwidget.cpp | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp +index a47f98ae18..3eb246e940 100644 +--- a/src/kitemviews/kitemlistwidget.cpp ++++ b/src/kitemviews/kitemlistwidget.cpp +@@ -628,7 +628,6 @@ void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QS + viewItemOption.state = styleState; + viewItemOption.viewItemPosition = QStyleOptionViewItem::OnlyOne; + viewItemOption.showDecorationSelected = true; +- // TODO: check if we're extended or not + viewItemOption.rect = selectionRectFull().toRect(); + QPainterPath path; + path.addRoundedRect(selectionRectFull().adjusted(penWidth, penWidth, -penWidth, -penWidth), roundness, roundness); +-- +GitLab + + +From 6e4705e98dc1f00cbcd6a51d3797a39a8e7bd1c4 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Fri, 13 Jun 2025 11:34:13 +0300 +Subject: [PATCH 61/69] Remove hitTargetRect + +--- + src/kitemviews/kitemlistcontroller.cpp | 7 +++++-- + src/kitemviews/kitemlistwidget.h | 7 ------- + src/kitemviews/kstandarditemlistwidget.cpp | 9 --------- + src/kitemviews/kstandarditemlistwidget.h | 1 - + 4 files changed, 5 insertions(+), 19 deletions(-) + +diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp +index 1729a65cba..1a11d4e882 100644 +--- a/src/kitemviews/kitemlistcontroller.cpp ++++ b/src/kitemviews/kitemlistcontroller.cpp +@@ -1490,7 +1490,7 @@ KItemListWidget *KItemListController::widgetForDropPos(const QPointF &pos) const + const auto widgets = m_view->visibleItemListWidgets(); + for (KItemListWidget *widget : widgets) { + const QPointF mappedPos = widget->mapFromItem(m_view, pos); +- if (widget->hitTargetRect().contains(mappedPos)) { ++ if (widget->selectionRectCore().contains(mappedPos)) { + return widget; + } + } +@@ -1728,7 +1728,10 @@ bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifier + if (m_pressedIndex.has_value()) { + // The hover highlight area of an item is being pressed. + const auto row = m_view->m_visibleItems.value(m_pressedIndex.value()); // anything outside of row.contains() will be the empty region of the row rect +- const bool hitTargetIsRowEmptyRegion = !row->hitTargetRect().contains(row->mapFromItem(m_view, pos)); ++ ++ // When full row is highlighted, we want to be able to start dragging from anywhere except the core of the item. ++ const auto hitRect = m_view->highlightEntireRow() ? row->selectionRectCore() : row->selectionRectFull(); ++ const bool hitTargetIsRowEmptyRegion = !hitRect.contains(row->mapFromItem(m_view, pos)); + // again, when this method returns false, a rubberBand selection is created as the event is not consumed; + // createRubberBand here tells us whether to return true or false. + bool createRubberBand = (hitTargetIsRowEmptyRegion && m_selectionManager->selectedItems().isEmpty()); +diff --git a/src/kitemviews/kitemlistwidget.h b/src/kitemviews/kitemlistwidget.h +index 7e6b8f9c93..d75a4db549 100644 +--- a/src/kitemviews/kitemlistwidget.h ++++ b/src/kitemviews/kitemlistwidget.h +@@ -175,13 +175,6 @@ public: + */ + virtual QRectF selectionRectCore() const = 0; + +- /** +- * @return Returns rectangle where item is considered "hit" during actions, such as drag and drop. +- * In compact and icon views, it returns KItemListWidget::selectionRectFull(). +- * In details view, it returns KItemListWidget::selectionRectCore(). +- */ +- virtual QRectF hitTargetRect() const = 0; +- + /** + * @return Rectangle for the selection-toggle that is used to select or deselect an item. + * Per default an empty rectangle is returned which means that no selection-toggle +diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp +index 3511666704..0a2cfb5968 100644 +--- a/src/kitemviews/kstandarditemlistwidget.cpp ++++ b/src/kitemviews/kstandarditemlistwidget.cpp +@@ -558,15 +558,6 @@ QRectF KStandardItemListWidget::selectionRectCore() const + return result; + } + +-QRectF KStandardItemListWidget::hitTargetRect() const +-{ +- if (m_layout == DetailsLayout) { +- return selectionRectCore(); +- } else { +- return selectionRectFull(); +- } +-} +- + QRectF KStandardItemListWidget::expansionToggleRect() const + { + const_cast(this)->triggerCacheRefreshing(); +diff --git a/src/kitemviews/kstandarditemlistwidget.h b/src/kitemviews/kstandarditemlistwidget.h +index f2b7a49438..cabe3a8c96 100644 +--- a/src/kitemviews/kstandarditemlistwidget.h ++++ b/src/kitemviews/kstandarditemlistwidget.h +@@ -109,7 +109,6 @@ public: + QRectF textFocusRect() const override; + QRectF selectionRectFull() const override; + QRectF selectionRectCore() const override; +- QRectF hitTargetRect() const override; + QRectF expansionToggleRect() const override; + QRectF selectionToggleRect() const override; + QPixmap createDragPixmap(const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; +-- +GitLab + + +From 7e05aa4ccc0121e415ed29dce09cbd116cd186f5 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Fri, 13 Jun 2025 19:17:19 +0300 +Subject: [PATCH 62/69] Modify selectionRectCore to return fullRect unless + dragging in details view + +--- + src/kitemviews/kitemlistcontroller.cpp | 4 +--- + src/kitemviews/kstandarditemlistwidget.cpp | 10 +++++++--- + 2 files changed, 8 insertions(+), 6 deletions(-) + +diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp +index 1a11d4e882..2afcf212e6 100644 +--- a/src/kitemviews/kitemlistcontroller.cpp ++++ b/src/kitemviews/kitemlistcontroller.cpp +@@ -1729,9 +1729,7 @@ bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifier + // The hover highlight area of an item is being pressed. + const auto row = m_view->m_visibleItems.value(m_pressedIndex.value()); // anything outside of row.contains() will be the empty region of the row rect + +- // When full row is highlighted, we want to be able to start dragging from anywhere except the core of the item. +- const auto hitRect = m_view->highlightEntireRow() ? row->selectionRectCore() : row->selectionRectFull(); +- const bool hitTargetIsRowEmptyRegion = !hitRect.contains(row->mapFromItem(m_view, pos)); ++ const bool hitTargetIsRowEmptyRegion = !row->selectionRectCore().contains(row->mapFromItem(m_view, pos)); + // again, when this method returns false, a rubberBand selection is created as the event is not consumed; + // createRubberBand here tells us whether to return true or false. + bool createRubberBand = (hitTargetIsRowEmptyRegion && m_selectionManager->selectedItems().isEmpty()); +diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp +index 0a2cfb5968..d078b06573 100644 +--- a/src/kitemviews/kstandarditemlistwidget.cpp ++++ b/src/kitemviews/kstandarditemlistwidget.cpp +@@ -535,7 +535,7 @@ QRectF KStandardItemListWidget::selectionRectFull() const + const_cast(this)->triggerCacheRefreshing(); + const int padding = styleOption().padding; + if (m_layout == DetailsLayout) { +- auto rect = selectionRectCore(); ++ auto rect = m_iconRect | m_textRect; + if (m_highlightEntireRow) { + if (layoutDirection() == Qt::LeftToRight) { + rect.setRight(leftPadding() + m_columnWidthSum); +@@ -554,8 +554,12 @@ QRectF KStandardItemListWidget::selectionRectFull() const + + QRectF KStandardItemListWidget::selectionRectCore() const + { +- QRectF result = m_iconRect | m_textRect; +- return result; ++ // Allow dragging from selection area in details view. ++ if (m_layout == DetailsLayout && highlightEntireRow() && !isSelected()) { ++ QRectF result = m_iconRect | m_textRect; ++ return result; ++ } ++ return selectionRectFull(); + } + + QRectF KStandardItemListWidget::expansionToggleRect() const +-- +GitLab + + +From bf8220e6bac6d4f0148c465af012ff98f16ff73e Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Mon, 16 Jun 2025 11:07:05 +0300 +Subject: [PATCH 63/69] Remove unneeded check + +--- + src/kitemviews/kitemlistcontroller.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp +index 2afcf212e6..10edf64a66 100644 +--- a/src/kitemviews/kitemlistcontroller.cpp ++++ b/src/kitemviews/kitemlistcontroller.cpp +@@ -1764,7 +1764,7 @@ bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifier + // We rule out the latter, if the item is not clicked directly and was unselected previously. + const auto row = m_view->m_visibleItems.value(m_pressedIndex.value()); + const auto mappedPos = row->mapFromItem(m_view, pos); +- if (!row->selectionRectCore().contains(mappedPos) && !pressedItemAlreadySelected) { ++ if (!row->selectionRectCore().contains(mappedPos)) { + createRubberBand = true; + } else { + m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Toggle); +-- +GitLab + + +From bc5a187f34f1566a7b5f7dacaac851519dfe17c1 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Mon, 16 Jun 2025 11:07:48 +0300 +Subject: [PATCH 64/69] add const back + +--- + src/kitemviews/kitemlistcontroller.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp +index 10edf64a66..96f34f1a11 100644 +--- a/src/kitemviews/kitemlistcontroller.cpp ++++ b/src/kitemviews/kitemlistcontroller.cpp +@@ -1364,7 +1364,7 @@ void KItemListController::slotRubberBandChanged() + + // Select all visible items that intersect with the rubberband + const auto widgets = m_view->visibleItemListWidgets(); +- for (KItemListWidget *widget : widgets) { ++ for (const KItemListWidget *widget : widgets) { + const int index = widget->index(); + + const QRectF widgetRect = m_view->itemRect(index); +-- +GitLab + + +From 73977c43f0a6c6d82f5fc8992132138402be8f32 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Mon, 16 Jun 2025 11:09:09 +0300 +Subject: [PATCH 65/69] remove selectionToggleRect check + +--- + src/kitemviews/kitemlistwidget.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp +index 3eb246e940..c06e46339f 100644 +--- a/src/kitemviews/kitemlistwidget.cpp ++++ b/src/kitemviews/kitemlistwidget.cpp +@@ -408,7 +408,7 @@ bool KItemListWidget::contains(const QPointF &point) const + return false; + } + +- return selectionRectFull().contains(point) || expansionToggleRect().contains(point) || selectionToggleRect().contains(point); ++ return selectionRectFull().contains(point) || expansionToggleRect().contains(point); + } + + QRectF KItemListWidget::textFocusRect() const +-- +GitLab + + +From 0f477b912347b5661add69dc5a8cf9ee44ecdbfb Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Mon, 16 Jun 2025 11:11:24 +0300 +Subject: [PATCH 66/69] remove presseditemalreadyselected check + +--- + src/kitemviews/kitemlistcontroller.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp +index 96f34f1a11..5f668ce3ab 100644 +--- a/src/kitemviews/kitemlistcontroller.cpp ++++ b/src/kitemviews/kitemlistcontroller.cpp +@@ -1666,7 +1666,7 @@ bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifier + if (selectedItemsCount > 1 && m_pressedIndex.has_value()) { + const auto row = m_view->m_visibleItems.value(m_pressedIndex.value()); + const auto mappedPos = row->mapFromItem(m_view, pos); +- if (pressedItemAlreadySelected || row->selectionRectCore().contains(mappedPos)) { ++ if (row->selectionRectCore().contains(mappedPos)) { + // we are indeed inside the text/icon rect, keep m_pressedIndex what it is + // and short-circuit for single-click activation (it will then propagate to onRelease and activate the item) + // or we just keep going for double-click activation +-- +GitLab + + +From 60a92ae27e28c162fd3f4a926b4b00b818134151 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Mon, 16 Jun 2025 11:16:09 +0300 +Subject: [PATCH 67/69] add setPressed with more than one selection + +--- + src/kitemviews/kitemlistcontroller.cpp | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp +index 5f668ce3ab..fdde48abcd 100644 +--- a/src/kitemviews/kitemlistcontroller.cpp ++++ b/src/kitemviews/kitemlistcontroller.cpp +@@ -1681,6 +1681,9 @@ bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifier + m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Toggle); + } + } ++ if (leftClick) { ++ row->setPressed(true); ++ } + return true; // event handled, don't create rubber band + } + } else { +-- +GitLab + + +From b3e1a4918e7165c3a19f5a9188e45ec6371fd9fa Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Mon, 16 Jun 2025 11:19:48 +0300 +Subject: [PATCH 68/69] set itemContextRect to selectionRectCore + +--- + src/kitemviews/kitemlistview.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp +index 5394e7341e..3ed4df3e7b 100644 +--- a/src/kitemviews/kitemlistview.cpp ++++ b/src/kitemviews/kitemlistview.cpp +@@ -542,7 +542,7 @@ QRectF KItemListView::itemContextRect(int index) const + + const KItemListWidget *widget = m_visibleItems.value(index); + if (widget) { +- contextRect = widget->selectionRectFull(); ++ contextRect = widget->selectionRectCore(); + contextRect.translate(itemRect(index).topLeft()); + } + +-- +GitLab + + +From 65b8c32380f9a7f8a8bf0296adfea9c976682b57 Mon Sep 17 00:00:00 2001 +From: Akseli Lahtinen +Date: Mon, 16 Jun 2025 11:20:34 +0300 +Subject: [PATCH 69/69] fix doc + +--- + src/kitemviews/kitemlistwidget.h | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/kitemviews/kitemlistwidget.h b/src/kitemviews/kitemlistwidget.h +index d75a4db549..b87a3b34e6 100644 +--- a/src/kitemviews/kitemlistwidget.h ++++ b/src/kitemviews/kitemlistwidget.h +@@ -166,12 +166,12 @@ public: + * Used for drawing the visuals, and situations where we want the behavior of the + * selection to match the visuals. + * +- * @return The rectangle around selection, depending on if it's full width or not. ++ * @return The rectangle around selection. + */ + virtual QRectF selectionRectFull() const = 0; + + /** +- * @return Rectangle around icon and it's text: The core area of the item. ++ * @return The core area of the item. All of it reacts exactly the same way to mouse clicks. + */ + virtual QRectF selectionRectCore() const = 0; + +-- +GitLab + diff --git a/roles/kde/patches/kwin/patches.txt b/roles/kde/patches/kwin/patches.txt new file mode 100644 index 0000000..352c63d --- /dev/null +++ b/roles/kde/patches/kwin/patches.txt @@ -0,0 +1,7 @@ +Plasma 6.5.0: +Pr 3612 https://invent.kde.org/plasma/kwin/-/merge_requests/3612 +Pr 7823 https://invent.kde.org/plasma/kwin/-/merge_requests/7823 + +Plasma 6.4.2: +Pr 7822 https://invent.kde.org/plasma/kwin/-/merge_requests/7822 +Pr 7829 https://invent.kde.org/plasma/kwin/-/merge_requests/7829 diff --git a/roles/kde/patches/kwin/pr3612.patch b/roles/kde/patches/kwin/pr3612.patch new file mode 100644 index 0000000..8a42f11 --- /dev/null +++ b/roles/kde/patches/kwin/pr3612.patch @@ -0,0 +1,2101 @@ +From 677241efbba5f2a725b247baf86a822127571225 Mon Sep 17 00:00:00 2001 +From: Vlad Zahorodnii +Date: Thu, 16 Feb 2023 10:16:17 +0200 +Subject: [PATCH 1/2] wayland: Implement xx-pip-v1 + +The xx-pip-v1 protocol provides clients a way to create floating windows +with miniature contents, for example a video (a movie or a video call), +a map with directions, a timer countdown, etc. + +These windows are placed in the overlay layer above all windows, including +fullscreen windows. + +This is an experimental version of the protocol. +--- + CMakeLists.txt | 4 + + src/CMakeLists.txt | 2 + + src/placement.cpp | 18 ++ + src/placement.h | 1 + + src/wayland/CMakeLists.txt | 2 + + src/wayland/protocols/xx-pip-v1.xml | 305 +++++++++++++++++++++++++++ + src/wayland/xdgshell.cpp | 2 +- + src/wayland/xdgshell_p.h | 2 + + src/wayland/xxpip_v1.cpp | 312 ++++++++++++++++++++++++++++ + src/wayland/xxpip_v1.h | 159 ++++++++++++++ + src/wayland_server.cpp | 7 + + src/window.cpp | 3 + + src/window.h | 6 + + src/xxpipv1integration.cpp | 43 ++++ + src/xxpipv1integration.h | 28 +++ + src/xxpipv1window.cpp | 179 ++++++++++++++++ + src/xxpipv1window.h | 48 +++++ + tests/CMakeLists.txt | 4 + + tests/pip/CMakeLists.txt | 22 ++ + tests/pip/main.cpp | 19 ++ + tests/pip/pip.cpp | 227 ++++++++++++++++++++ + tests/pip/pip.h | 102 +++++++++ + tests/pip/pipshellsurface.cpp | 156 ++++++++++++++ + tests/pip/pipshellsurface.h | 70 +++++++ + tests/pip/window.cpp | 19 ++ + tests/pip/window.h | 22 ++ + 26 files changed, 1761 insertions(+), 1 deletion(-) + create mode 100644 src/wayland/protocols/xx-pip-v1.xml + create mode 100644 src/wayland/xxpip_v1.cpp + create mode 100644 src/wayland/xxpip_v1.h + create mode 100644 src/xxpipv1integration.cpp + create mode 100644 src/xxpipv1integration.h + create mode 100644 src/xxpipv1window.cpp + create mode 100644 src/xxpipv1window.h + create mode 100644 tests/pip/CMakeLists.txt + create mode 100644 tests/pip/main.cpp + create mode 100644 tests/pip/pip.cpp + create mode 100644 tests/pip/pip.h + create mode 100644 tests/pip/pipshellsurface.cpp + create mode 100644 tests/pip/pipshellsurface.h + create mode 100644 tests/pip/window.cpp + create mode 100644 tests/pip/window.h + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 8d4d03b8459..6ac395557c8 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -84,6 +84,10 @@ endif() + + if (BUILD_TESTING) + find_package(KPipeWire) ++ ++ if (Qt6WaylandClient_VERSION VERSION_GREATER_EQUAL "6.10.0") ++ find_package(Qt6WaylandClientPrivate ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE) ++ endif() + endif() + + # required frameworks by Core +diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt +index 48fb847ca7c..978c98f7ff3 100644 +--- a/src/CMakeLists.txt ++++ b/src/CMakeLists.txt +@@ -211,6 +211,8 @@ target_sources(kwin PRIVATE + xdgshellintegration.cpp + xdgshellwindow.cpp + xkb.cpp ++ xxpipv1integration.cpp ++ xxpipv1window.cpp + ) + + target_link_libraries(kwin +diff --git a/src/placement.cpp b/src/placement.cpp +index 6c984282e5e..8890da56159 100644 +--- a/src/placement.cpp ++++ b/src/placement.cpp +@@ -48,6 +48,8 @@ std::optional Placement::place(const Window *c, const QRectF & + return placeOnScreenDisplay(c, area.toRect()); + } else if (c->isTransient() && c->surface()) { + return placeDialog(c, area.toRect(), options->placement()); ++ } else if (c->isPictureInPicture()) { ++ return placePictureInPicture(c, area.toRect()); + } else { + return place(c, area, options->placement()); + } +@@ -403,6 +405,22 @@ std::optional Placement::placeDialog(const Window *c, const QR + return placeOnMainWindow(c, area, nextPlacement); + } + ++std::optional Placement::placePictureInPicture(const Window *c, const QRect &area) ++{ ++ Q_ASSERT(area.isValid()); ++ ++ const QSizeF size = c->size(); ++ if (size.isEmpty()) { ++ return std::nullopt; ++ } ++ ++ const qreal x = area.x() + area.width() - size.width(); ++ const qreal y = area.y() + area.height() - size.height(); ++ ++ return QPointF(x, y); ++} ++ ++ + std::optional Placement::placeUnderMouse(const Window *c, const QRect &area, PlacementPolicy /*next*/) + { + const QSizeF size = c->size(); +diff --git a/src/placement.h b/src/placement.h +index 63be9df117f..e0da0f51ace 100644 +--- a/src/placement.h ++++ b/src/placement.h +@@ -44,6 +44,7 @@ private: + std::optional placeDialog(const Window *c, const QRect &area, PlacementPolicy next = PlacementUnknown); + std::optional placeUtility(const Window *c, const QRect &area, PlacementPolicy next = PlacementUnknown); + std::optional placeOnScreenDisplay(const Window *c, const QRect &area); ++ std::optional placePictureInPicture(const Window *c, const QRect &area); + }; + + } // namespace +diff --git a/src/wayland/CMakeLists.txt b/src/wayland/CMakeLists.txt +index fde7fc50348..7261395ecf9 100644 +--- a/src/wayland/CMakeLists.txt ++++ b/src/wayland/CMakeLists.txt +@@ -38,6 +38,7 @@ ecm_add_qtwayland_server_protocol_kde(WaylandProtocols_xml + ${PROJECT_SOURCE_DIR}/src/wayland/protocols/drm.xml + ${PROJECT_SOURCE_DIR}/src/wayland/protocols/frog-color-management-v1.xml + ${PROJECT_SOURCE_DIR}/src/wayland/protocols/wlr-layer-shell-unstable-v1.xml ++ ${PROJECT_SOURCE_DIR}/src/wayland/protocols/xx-pip-v1.xml + ${PROJECT_SOURCE_DIR}/src/wayland/protocols/xx-session-management-v1.xml + + ${WaylandProtocols_DATADIR}/stable/presentation-time/presentation-time.xml +@@ -174,6 +175,7 @@ target_sources(kwin PRIVATE + xdgtopleveltag_v1.cpp + xwaylandkeyboardgrab_v1.cpp + xwaylandshell_v1.cpp ++ xxpip_v1.cpp + ) + + install(FILES +diff --git a/src/wayland/protocols/xx-pip-v1.xml b/src/wayland/protocols/xx-pip-v1.xml +new file mode 100644 +index 00000000000..c5eb7636ee9 +--- /dev/null ++++ b/src/wayland/protocols/xx-pip-v1.xml +@@ -0,0 +1,305 @@ ++ ++ ++ ++ Copyright © 2025 Vlad Zahorodnii ++ ++ Permission is hereby granted, free of charge, to any person obtaining a ++ copy of this software and associated documentation files (the "Software"), ++ to deal in the Software without restriction, including without limitation ++ the rights to use, copy, modify, merge, publish, distribute, sublicense, ++ and/or sell copies of the Software, and to permit persons to whom the ++ Software is furnished to do so, subject to the following conditions: ++ ++ The above copyright notice and this permission notice (including the next ++ paragraph) shall be included in all copies or substantial portions of the ++ Software. ++ ++ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ++ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ++ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING ++ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER ++ DEALINGS IN THE SOFTWARE. ++ ++ ++ ++ ++ The xx_pip_shell_v1 interface provides a way to create picture-in-picture ++ windows. ++ ++ Use cases are for example playing a video in a separate floating window. ++ ++ Warning! The protocol described in this file is currently in the testing ++ phase. Backward compatible changes may be added together with the ++ corresponding interface version bump. Backward incompatible changes can ++ only be done by creating a new major version of the extension. ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ Destroy this xx_pip_shell_v1 object. Objects that have been created ++ through this instance are unaffected. ++ ++ ++ ++ ++ ++ This creates an xx_pip_v1 for the given xdg_surface and gives the ++ associated wl_surface the xx_pip_v1 role. ++ ++ If the wl_surface already has a role assigned, a role protocol error ++ will be raised. ++ ++ Creating a picture-in-picture surface from a wl_surface which has a ++ buffer attached or committed is a client error, and any attempts by ++ a client to attach or manipulate a buffer prior to the first ++ xx_pip_v1.configure event must also be treated as errors. ++ ++ After creating an xx_pip_v1 object and setting it up, the client ++ must perform an initial commit without any buffer attached. ++ The compositor will reply with a xx_pip_v1.configure event. ++ The client must acknowledge it and is then allowed to attach a buffer ++ to map the surface. ++ ++ The compositor may deny showing the picture-in-picture surface, in ++ which case it will send the closed event before the first configure ++ event. ++ ++ See the documentation of xdg_surface for more details about what an ++ xdg_surface is and how it is used. ++ ++ ++ ++ ++ ++ ++ ++ ++ This interface defines an xdg_surface role which represents a floating ++ window with some miniature contents, for example a video. ++ ++ The picture-in-picture window will be placed above all other windows. ++ Compositor-specific policies may override or customize the behavior ++ and the placement of the xx_pip_v1. For example, the compositor may ++ choose to put the xx_pip_v1 in a screen corner, etc. ++ ++ Unmapping an xx_pip_v1 means that the surface cannot be shown ++ by the compositor until it is explicitly mapped again. ++ All active operations (e.g., move, resize) are canceled and all ++ attributes (e.g. title, state, stacking, ...) are discarded for ++ an xx_pip_v1 surface when it is unmapped. The xx_pip_v1 returns to ++ the state it had right after xx_pip_shell_v1.get_pip. The client ++ can re-map the pip by perfoming a commit without any buffer ++ attached, waiting for a configure event and handling it as usual (see ++ xdg_surface description). ++ ++ Attaching a null buffer to a picture-in-picture unmaps the surface. ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ This request destroys the role surface and unmaps the surface. ++ ++ ++ ++ ++ ++ Set an application identifier for the surface. ++ ++ The app ID identifies the general class of applications to which ++ the surface belongs. The compositor can use this to group multiple ++ surfaces together, or to determine how to launch a new application. ++ ++ For D-Bus activatable applications, the app ID is used as the D-Bus ++ service name. ++ ++ The compositor shell will try to group application surfaces together ++ by their app ID. As a best practice, it is suggested to select app ++ ID's that match the basename of the application's .desktop file. ++ For example, "org.freedesktop.FooViewer" where the .desktop file is ++ "org.freedesktop.FooViewer.desktop". ++ ++ Like other properties, a set_app_id request can be sent after the ++ xx_pip_v1 has been mapped to update the property. ++ ++ See the desktop-entry specification [0] for more details on ++ application identifiers and how they relate to well-known D-Bus ++ names and .desktop files. ++ ++ [0] http://standards.freedesktop.org/desktop-entry-spec/ ++ ++ ++ ++ ++ ++ ++ Set the origin surface for the picture-in-picture surface. ++ ++ The origin surface is an optional property that specifies a surface ++ from which the picture-in-picture surface has been launched. If set, ++ the compositor may use this hint to play an animation when the ++ picture-in-picture surface is mapped or unmapped. For example, smoothly ++ move the surface from the origin to a screen corner. ++ ++ If the specified origin surface is the same as the picture-in-picture ++ surface, the invalid_origin protocol error will be posted. ++ ++ The origin surface is double-buffered state, see wl_surface.commit. ++ ++ ++ ++ ++ ++ ++ Set the origin rect within the origin surface for the picture-in-picture ++ surface. ++ ++ The origin rect is an optional property that specifies the launch ++ rectangle within the origin surface. The compositor may use this hint ++ to play an animation when the picture-in-picture surface is mapped or ++ unmapped. For example, smoothly move the surface from the origin rect ++ to a screen corner. ++ ++ The origin rect is specified in the surface-local coordinate space. ++ ++ The compositor ignores the parts of the origin rect that fall outside ++ of the origin surface. ++ ++ The origin rect is double-buffered state, see wl_surface.commit. ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ Start an interactive, user-driven move of the surface. ++ ++ This request must be used in response to some sort of user action ++ like a button press, key press, or touch down event. The passed ++ serial is used to determine the type of interactive move (touch, ++ pointer, etc). ++ ++ The server may ignore move requests depending on the state of ++ the surface, or if the passed serial is no longer valid. ++ ++ If triggered, the surface will lose the focus of the device ++ (wl_pointer, wl_touch, etc) used for the move. It is up to the ++ compositor to visually indicate that the move is taking place, such as ++ updating a pointer cursor, during the move. There is no guarantee ++ that the device focus will return when the move is completed. ++ ++ ++ ++ ++ ++ ++ ++ These values are used to indicate which edge of a surface ++ is being dragged in a resize operation. ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ Start a user-driven, interactive resize of the surface. ++ ++ This request must be used in response to some sort of user action ++ like a button press, key press, or touch down event. The passed ++ serial is used to determine the type of interactive resize (touch, ++ pointer, etc). ++ ++ The server may ignore resize requests depending on the state of ++ the surface, or if the passed serial is no longer valid. ++ ++ If triggered, the surface also will lose the focus of the device ++ (wl_pointer, wl_touch, etc) used for the resize. It is up to the ++ compositor to visually indicate that the resize is taking place, ++ such as updating a pointer cursor, during the resize. There is no ++ guarantee that the device focus will return when the resize is ++ completed. ++ ++ The edges parameter specifies how the surface should be resized, ++ and is one of the values of the resize_edge enum. The compositor ++ may use this information to update the surface position for ++ example when dragging the top left corner. The compositor may also ++ use this information to adapt its behavior, e.g. choose an ++ appropriate cursor image. ++ ++ ++ ++ ++ ++ ++ ++ ++ The closed event is sent by the compositor when the surface will ++ no longer be shown. Further changes to the surface will be ignored. ++ The client should destroy the resource after receiving this event. ++ ++ ++ ++ ++ ++ The configure_bounds event may be sent prior to a xx_pip_v1.configure ++ event to communicate the bounds a surface size must be constrained to. ++ ++ The passed width and height are in surface coordinate space. ++ ++ If the surface width or the surface height is greater than the specified ++ surface size bounds, an invalid_size protocol error will be posted. ++ ++ The surface bounds subject to compositor policies. ++ ++ The bounds may change at any point, and in such a case, a new ++ xx_pip_v1.configure_bounds will be sent, followed by xx_pip_v1.configure and ++ xdg_surface.configure. ++ ++ ++ ++ ++ ++ ++ ++ This configure event asks the client to resize its pip surface. ++ The configured state should not be applied immediately. See ++ xdg_surface.configure for details. ++ ++ The width and height arguments specify a hint to the window ++ about how its surface should be resized in window geometry ++ coordinates. See set_window_geometry. ++ ++ If the width or height arguments are zero, it means the client ++ should decide its own window dimension. ++ ++ Clients must send an ack_configure in response to this event. See ++ xdg_surface.configure and xdg_surface.ack_configure for details. ++ ++ ++ ++ ++ ++ +diff --git a/src/wayland/xdgshell.cpp b/src/wayland/xdgshell.cpp +index 282678c0938..f1217e9d37a 100644 +--- a/src/wayland/xdgshell.cpp ++++ b/src/wayland/xdgshell.cpp +@@ -186,7 +186,7 @@ void XdgSurfaceInterfacePrivate::xdg_surface_destroy_resource(Resource *resource + + void XdgSurfaceInterfacePrivate::xdg_surface_destroy(Resource *resource) + { +- if (toplevel || popup) { ++ if (!toplevel.isNull() || !popup.isNull() || !pip.isNull()) { + qWarning() << "Tried to destroy xdg_surface before its role object"; + } + wl_resource_destroy(resource->handle); +diff --git a/src/wayland/xdgshell_p.h b/src/wayland/xdgshell_p.h +index 46244b23523..e48dfd8d78d 100644 +--- a/src/wayland/xdgshell_p.h ++++ b/src/wayland/xdgshell_p.h +@@ -16,6 +16,7 @@ + namespace KWin + { + class XdgToplevelDecorationV1Interface; ++class XXPipV1Interface; + + class XdgShellInterfacePrivate : public QtWaylandServer::xdg_wm_base + { +@@ -118,6 +119,7 @@ public: + XdgShellInterface *shell = nullptr; + QPointer toplevel; + QPointer popup; ++ QPointer pip; + QPointer surface; + QRect windowGeometry; + bool firstBufferAttached = false; +diff --git a/src/wayland/xxpip_v1.cpp b/src/wayland/xxpip_v1.cpp +new file mode 100644 +index 00000000000..e0b1f36134f +--- /dev/null ++++ b/src/wayland/xxpip_v1.cpp +@@ -0,0 +1,312 @@ ++/* ++ SPDX-FileCopyrightText: 2023 Vlad Zahorodnii ++ ++ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL ++*/ ++ ++#include "wayland/xxpip_v1.h" ++#include "utils/resource.h" ++#include "wayland/display.h" ++#include "wayland/seat.h" ++#include "wayland/surface.h" ++#include "wayland/xdgshell_p.h" ++ ++#include "qwayland-server-xx-pip-v1.h" ++ ++namespace KWin ++{ ++ ++static const int s_version = 1; ++ ++class XXPipShellV1InterfacePrivate : public QtWaylandServer::xx_pip_shell_v1 ++{ ++public: ++ explicit XXPipShellV1InterfacePrivate(XXPipShellV1Interface *q, Display *display); ++ ++ XXPipShellV1Interface *q; ++ Display *display; ++ ++protected: ++ void xx_pip_shell_v1_destroy(Resource *resource) override; ++ void xx_pip_shell_v1_get_pip(Resource *resource, uint32_t id, struct ::wl_resource *xdg_surface) override; ++}; ++ ++XXPipShellV1InterfacePrivate::XXPipShellV1InterfacePrivate(XXPipShellV1Interface *q, Display *display) ++ : QtWaylandServer::xx_pip_shell_v1(*display, s_version) ++ , q(q) ++ , display(display) ++{ ++} ++ ++void XXPipShellV1InterfacePrivate::xx_pip_shell_v1_destroy(Resource *resource) ++{ ++ wl_resource_destroy(resource->handle); ++} ++ ++void XXPipShellV1InterfacePrivate::xx_pip_shell_v1_get_pip(Resource *resource, uint32_t id, struct ::wl_resource *xdg_surface) ++{ ++ XdgSurfaceInterface *xdgSurface = XdgSurfaceInterface::get(xdg_surface); ++ ++ if (const SurfaceRole *role = xdgSurface->surface()->role()) { ++ if (role != XXPipV1Interface::role()) { ++ wl_resource_post_error(resource->handle, error_already_constructed, "the surface already has a role assigned %s", role->name().constData()); ++ return; ++ } ++ } else { ++ xdgSurface->surface()->setRole(XXPipV1Interface::role()); ++ } ++ ++ wl_resource *pipResource = wl_resource_create(resource->client(), &xx_pip_v1_interface, resource->version(), id); ++ auto pip = new XXPipV1Interface(q, xdgSurface, pipResource); ++ ++ Q_EMIT q->pipCreated(pip); ++} ++ ++XXPipShellV1Interface::XXPipShellV1Interface(Display *display, QObject *parent) ++ : QObject(parent) ++ , d(std::make_unique(this, display)) ++{ ++} ++ ++XXPipShellV1Interface::~XXPipShellV1Interface() ++{ ++} ++ ++Display *XXPipShellV1Interface::display() const ++{ ++ return d->display; ++} ++ ++class XXPipV1Commit : public SurfaceAttachedState, public XdgSurfaceCommit ++{ ++public: ++ QPointer origin; ++ QRect originRect; ++}; ++ ++class XXPipV1InterfacePrivate : public SurfaceExtension, public QtWaylandServer::xx_pip_v1 ++{ ++public: ++ XXPipV1InterfacePrivate(XXPipV1Interface *q, XXPipShellV1Interface *shell, XdgSurfaceInterface *xdgSurface); ++ ++ void apply(XXPipV1Commit *comit); ++ void reset(); ++ ++ XXPipV1Interface *q; ++ XXPipShellV1Interface *shell; ++ XdgSurfaceInterface *xdgSurface; ++ QString applicationId; ++ QPointer origin; ++ QRect originRect; ++ ++protected: ++ void xx_pip_v1_destroy_resource(Resource *resource) override; ++ void xx_pip_v1_destroy(Resource *resource) override; ++ void xx_pip_v1_set_app_id(Resource *resource, const QString &app_id) override; ++ void xx_pip_v1_set_origin(Resource *resource, struct ::wl_resource *origin) override; ++ void xx_pip_v1_set_origin_rect(Resource *resource, int32_t x, int32_t y, uint32_t width, uint32_t height) override; ++ void xx_pip_v1_move(Resource *resource, struct ::wl_resource *seat, uint32_t serial) override; ++ void xx_pip_v1_resize(Resource *resource, struct ::wl_resource *seat, uint32_t serial, uint32_t edges) override; ++}; ++ ++XXPipV1InterfacePrivate::XXPipV1InterfacePrivate(XXPipV1Interface *q, XXPipShellV1Interface *shell, XdgSurfaceInterface *xdgSurface) ++ : SurfaceExtension(xdgSurface->surface()) ++ , q(q) ++ , shell(shell) ++ , xdgSurface(xdgSurface) ++{ ++} ++ ++void XXPipV1InterfacePrivate::apply(XXPipV1Commit *commit) ++{ ++ auto xdgSurfacePrivate = XdgSurfaceInterfacePrivate::get(xdgSurface); ++ if (xdgSurfacePrivate->firstBufferAttached && !xdgSurfacePrivate->surface->buffer()) { ++ reset(); ++ return; ++ } ++ ++ if (!commit->origin.isNull()) { ++ origin = commit->origin; ++ } ++ if (!commit->originRect.isNull()) { ++ originRect = commit->originRect; ++ } ++ ++ xdgSurfacePrivate->apply(commit); ++ ++ if (!xdgSurfacePrivate->isConfigured) { ++ Q_EMIT q->initializeRequested(); ++ } ++} ++ ++void XXPipV1InterfacePrivate::reset() ++{ ++ auto xdgSurfacePrivate = XdgSurfaceInterfacePrivate::get(xdgSurface); ++ xdgSurfacePrivate->reset(); ++ ++ Q_EMIT q->resetOccurred(); ++} ++ ++void XXPipV1InterfacePrivate::xx_pip_v1_destroy_resource(Resource *resource) ++{ ++ Q_EMIT q->aboutToBeDestroyed(); ++ delete q; ++} ++ ++void XXPipV1InterfacePrivate::xx_pip_v1_destroy(Resource *resource) ++{ ++ wl_resource_destroy(resource->handle); ++} ++ ++void XXPipV1InterfacePrivate::xx_pip_v1_set_app_id(Resource *resource, const QString &app_id) ++{ ++ if (applicationId != app_id) { ++ applicationId = app_id; ++ Q_EMIT q->applicationIdChanged(); ++ } ++} ++ ++void XXPipV1InterfacePrivate::xx_pip_v1_set_origin(Resource *resource, struct ::wl_resource *origin_resource) ++{ ++ SurfaceInterface *origin = SurfaceInterface::get(origin_resource); ++ if (origin == xdgSurface->surface()) { ++ wl_resource_post_error(resource->handle, error_invalid_origin, "pip surface cannot be its own origin"); ++ return; ++ } ++ ++ pending->origin = origin; ++} ++ ++void XXPipV1InterfacePrivate::xx_pip_v1_set_origin_rect(Resource *resource, int32_t x, int32_t y, uint32_t width, uint32_t height) ++{ ++ pending->originRect = QRect(x, y, width, height); ++} ++ ++void XXPipV1InterfacePrivate::xx_pip_v1_move(Resource *resource, struct ::wl_resource *seat_resource, uint32_t serial) ++{ ++ auto xdgSurfacePrivate = XdgSurfaceInterfacePrivate::get(xdgSurface); ++ if (!xdgSurfacePrivate->isConfigured) { ++ wl_resource_post_error(resource->handle, QtWaylandServer::xdg_surface::error_not_constructed, "surface has not been configured yet"); ++ return; ++ } ++ ++ Q_EMIT q->moveRequested(SeatInterface::get(seat_resource), serial); ++} ++ ++void XXPipV1InterfacePrivate::xx_pip_v1_resize(Resource *resource, struct ::wl_resource *seat_resource, uint32_t serial, uint32_t edges) ++{ ++ auto xdgSurfacePrivate = XdgSurfaceInterfacePrivate::get(xdgSurface); ++ if (!xdgSurfacePrivate->isConfigured) { ++ wl_resource_post_error(resource->handle, QtWaylandServer::xdg_surface::error_not_constructed, "surface has not been configured yet"); ++ return; ++ } ++ ++ Gravity gravity; ++ switch (edges) { ++ case resize_edge_none: ++ gravity = Gravity::None; ++ break; ++ case resize_edge_top: ++ gravity = Gravity::Top; ++ break; ++ case resize_edge_bottom: ++ gravity = Gravity::Bottom; ++ break; ++ case resize_edge_left: ++ gravity = Gravity::Left; ++ break; ++ case resize_edge_top_left: ++ gravity = Gravity::TopLeft; ++ break; ++ case resize_edge_bottom_left: ++ gravity = Gravity::BottomLeft; ++ break; ++ case resize_edge_right: ++ gravity = Gravity::Right; ++ break; ++ case resize_edge_top_right: ++ gravity = Gravity::TopRight; ++ break; ++ case resize_edge_bottom_right: ++ gravity = Gravity::BottomRight; ++ break; ++ default: ++ wl_resource_post_error(resource->handle, error_invalid_resize_edge, "invalid resize edge"); ++ return; ++ } ++ ++ Q_EMIT q->resizeRequested(SeatInterface::get(seat_resource), gravity, serial); ++} ++ ++XXPipV1Interface::XXPipV1Interface(XXPipShellV1Interface *shell, XdgSurfaceInterface *xdgSurface, wl_resource *resource) ++ : d(std::make_unique(this, shell, xdgSurface)) ++{ ++ XdgSurfaceInterfacePrivate *surfacePrivate = XdgSurfaceInterfacePrivate::get(xdgSurface); ++ surfacePrivate->pip = this; ++ surfacePrivate->pending = d->pending; ++ ++ d->init(resource); ++} ++ ++XXPipV1Interface::~XXPipV1Interface() ++{ ++} ++ ++SurfaceRole *XXPipV1Interface::role() ++{ ++ static SurfaceRole role(QByteArrayLiteral("xx_pip_v1")); ++ return &role; ++} ++ ++bool XXPipV1Interface::isConfigured() const ++{ ++ return d->xdgSurface->isConfigured(); ++} ++ ++XdgSurfaceInterface *XXPipV1Interface::xdgSurface() const ++{ ++ return d->xdgSurface; ++} ++ ++SurfaceInterface *XXPipV1Interface::surface() const ++{ ++ return d->xdgSurface->surface(); ++} ++ ++QString XXPipV1Interface::applicationId() const ++{ ++ return d->applicationId; ++} ++ ++quint32 XXPipV1Interface::sendConfigureSize(const QSizeF &size) ++{ ++ const quint32 serial = d->shell->display()->nextSerial(); ++ ++ d->send_configure_size(size.width(), size.height()); ++ ++ auto xdgSurfacePrivate = XdgSurfaceInterfacePrivate::get(xdgSurface()); ++ xdgSurfacePrivate->send_configure(serial); ++ xdgSurfacePrivate->isConfigured = true; ++ ++ return serial; ++} ++ ++void XXPipV1Interface::sendClosed() ++{ ++ d->send_closed(); ++} ++ ++void XXPipV1Interface::sendConfigureBounds(const QSizeF &size) ++{ ++ d->send_configure_bounds(size.width(), size.height()); ++} ++ ++XXPipV1Interface *XXPipV1Interface::get(::wl_resource *resource) ++{ ++ if (auto pipPrivate = resource_cast(resource)) { ++ return pipPrivate->q; ++ } ++ return nullptr; ++} ++ ++} // namespace KWin +diff --git a/src/wayland/xxpip_v1.h b/src/wayland/xxpip_v1.h +new file mode 100644 +index 00000000000..c2737f2ec26 +--- /dev/null ++++ b/src/wayland/xxpip_v1.h +@@ -0,0 +1,159 @@ ++/* ++ SPDX-FileCopyrightText: 2023 Vlad Zahorodnii ++ ++ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL ++*/ ++ ++#pragma once ++ ++#include "kwin_export.h" ++ ++#include ++ ++#include ++ ++struct wl_resource; ++ ++namespace KWin ++{ ++ ++class Display; ++class SeatInterface; ++class SurfaceInterface; ++class SurfaceRole; ++class XXPipV1Interface; ++class XXPipV1InterfacePrivate; ++class XdgSurfaceInterface; ++class XXPipShellV1InterfacePrivate; ++ ++enum class Gravity; ++ ++/** ++ * The XXPipShellV1Interface extension provides clients a way to create picture-in-picture ++ * surfaces. ++ */ ++class KWIN_EXPORT XXPipShellV1Interface : public QObject ++{ ++ Q_OBJECT ++ ++public: ++ explicit XXPipShellV1Interface(Display *display, QObject *parent = nullptr); ++ ~XXPipShellV1Interface() override; ++ ++ Display *display() const; ++ ++Q_SIGNALS: ++ void pipCreated(XXPipV1Interface *pip); ++ ++private: ++ std::unique_ptr d; ++}; ++ ++/** ++ * The XXPipV1Interface class represents a picture-in-picture surface. ++ * ++ * XXPipV1Interface corresponds to the Wayland interface \c xx_pip_v1. ++ */ ++class KWIN_EXPORT XXPipV1Interface : public QObject ++{ ++ Q_OBJECT ++ ++public: ++ XXPipV1Interface(XXPipShellV1Interface *shell, XdgSurfaceInterface *xdgSurface, wl_resource *resource); ++ ~XXPipV1Interface() override; ++ ++ static SurfaceRole *role(); ++ ++ /** ++ * Returns \c true if the popup has been configured; otherwise returns \c false. ++ */ ++ bool isConfigured() const; ++ ++ /** ++ * Returns the XdgSurfaceInterface associated with the XXPipV1Interface. ++ */ ++ XdgSurfaceInterface *xdgSurface() const; ++ ++ /** ++ * Returns the SurfaceInterface associated with the XXPipV1Interface. ++ */ ++ SurfaceInterface *surface() const; ++ ++ /** ++ * Returns the desktop file name of the pip surface. ++ */ ++ QString applicationId() const; ++ ++ /** ++ * Returns the surface from which the picture-in-picture surface has been launched, or \c null. ++ */ ++ SurfaceInterface *origin() const; ++ ++ /** ++ * Specifies the bounds within the origin surface from which the picture-in-picture surface has ++ * been launched. ++ */ ++ QRect originRect() const; ++ ++ /** ++ * Sends a configure event to the client. \a size specifies the new window geometry size. A size ++ * of zero means the client should decide its own window dimensions. ++ */ ++ quint32 sendConfigureSize(const QSizeF &size); ++ ++ /** ++ * Sends a close event to the client. The client may choose to ignore this request. ++ */ ++ void sendClosed(); ++ ++ /** ++ * Sends an event to the client specifying the maximum bounds for the surface size. Must be ++ * called before sendConfigure(). ++ */ ++ void sendConfigureBounds(const QSizeF &size); ++ ++ /** ++ * Returns the XXPipV1Interface for the specified wayland resource object \a resource. ++ */ ++ static XXPipV1Interface *get(::wl_resource *resource); ++ ++Q_SIGNALS: ++ /** ++ * This signal is emitted when the xx-pip-v1 is about to be destroyed. ++ */ ++ void aboutToBeDestroyed(); ++ ++ /** ++ * This signal is emitted when the xx-pip-v1 has commited the initial state and wants to ++ * be configured. After initializing the pip surface, you must send a configure event. ++ */ ++ void initializeRequested(); ++ ++ /** ++ * This signal is emitted when the pip surface has been unmapped and its state has been reset. ++ */ ++ void resetOccurred(); ++ ++ /** ++ * This signal is emitted when the pip wants to be interactively moved. The \a seat and ++ * the \a serial indicate the user action in response to which this request has been issued. ++ */ ++ void moveRequested(SeatInterface *seat, quint32 serial); ++ ++ /** ++ * This signal is emitted when the pip wants to be interactively resized with ++ * the specified \a gravity. The \a seat and the \a serial indicate the user action ++ * in response to which this request has been issued. ++ */ ++ void resizeRequested(SeatInterface *seat, Gravity anchor, quint32 serial); ++ ++ /** ++ * This signal is emitted when the application id changes. ++ */ ++ void applicationIdChanged(); ++ ++private: ++ std::unique_ptr d; ++}; ++ ++} // namespace KWin +diff --git a/src/wayland_server.cpp b/src/wayland_server.cpp +index 24f7fa71ebe..0d54680313a 100644 +--- a/src/wayland_server.cpp ++++ b/src/wayland_server.cpp +@@ -91,6 +91,7 @@ + #include "xdgactivationv1.h" + #include "xdgshellintegration.h" + #include "xdgshellwindow.h" ++#include "xxpipv1integration.h" + #if KWIN_BUILD_X11 + #include "wayland/xwaylandkeyboardgrab_v1.h" + #include "wayland/xwaylandshell_v1.h" +@@ -581,6 +582,12 @@ void WaylandServer::initWorkspace() + connect(layerShellV1Integration, &LayerShellV1Integration::windowCreated, + this, &WaylandServer::registerWindow); + ++ if (qEnvironmentVariableIntValue("KWIN_WAYLAND_SUPPORT_XX_PIP_V1") == 1) { ++ auto pipV1Integration = new XXPipV1Integration(this); ++ connect(pipV1Integration, &XXPipV1Integration::windowCreated, ++ this, &WaylandServer::registerWindow); ++ } ++ + new KeyStateInterface(m_display, m_display); + + VirtualDesktopManager::self()->setVirtualDesktopManagement(m_virtualDesktopManagement); +diff --git a/src/window.cpp b/src/window.cpp +index 591950ab74a..f5a4be143b8 100644 +--- a/src/window.cpp ++++ b/src/window.cpp +@@ -563,6 +563,9 @@ Layer Window::belongsToLayer() const + if (isUnmanaged() || isInternal()) { + return OverlayLayer; + } ++ if (isPictureInPicture()) { ++ return OverlayLayer; ++ } + if (isLockScreen() && !waylandServer()) { + return OverlayLayer; + } +diff --git a/src/window.h b/src/window.h +index 57be2c6891e..9ae5758de85 100644 +--- a/src/window.h ++++ b/src/window.h +@@ -781,6 +781,7 @@ public: + virtual bool isClient() const; + bool isDeleted() const; + virtual bool isUnmanaged() const; ++ virtual bool isPictureInPicture() const; + + bool isLockScreenOverlay() const; + void setLockScreenOverlay(bool allowed); +@@ -2086,6 +2087,11 @@ inline bool Window::isInternal() const + return false; + } + ++inline bool Window::isPictureInPicture() const ++{ ++ return false; ++} ++ + inline WindowItem *Window::windowItem() const + { + return m_windowItem.get(); +diff --git a/src/xxpipv1integration.cpp b/src/xxpipv1integration.cpp +new file mode 100644 +index 00000000000..b72f2cba140 +--- /dev/null ++++ b/src/xxpipv1integration.cpp +@@ -0,0 +1,43 @@ ++/* ++ SPDX-FileCopyrightText: 2023 Vlad Zahorodnii ++ ++ SPDX-License-Identifier: GPL-2.0-or-later ++*/ ++ ++#include "xxpipv1integration.h" ++#include "wayland/xxpip_v1.h" ++#include "wayland_server.h" ++#include "workspace.h" ++#include "xxpipv1window.h" ++ ++namespace KWin ++{ ++ ++XXPipV1Integration::XXPipV1Integration(QObject *parent) ++ : WaylandShellIntegration(parent) ++{ ++ XXPipShellV1Interface *shell = new XXPipShellV1Interface(waylandServer()->display(), this); ++ connect(shell, &XXPipShellV1Interface::pipCreated, ++ this, &XXPipV1Integration::registerPipV1Surface); ++} ++ ++void XXPipV1Integration::registerPipV1Surface(XXPipV1Interface *pip) ++{ ++ createPipV1Window(pip); ++ connect(pip, &XXPipV1Interface::resetOccurred, this, [this, pip] { ++ createPipV1Window(pip); ++ }); ++} ++ ++void XXPipV1Integration::createPipV1Window(XXPipV1Interface *pip) ++{ ++ if (!workspace()) { ++ qCWarning(KWIN_CORE, "An xx-pip-v1 surface has been created while the compositor " ++ "is still not fully initialized. That is a compositor bug!"); ++ return; ++ } ++ ++ Q_EMIT windowCreated(new XXPipV1Window(pip)); ++} ++ ++} // namespace KWin +diff --git a/src/xxpipv1integration.h b/src/xxpipv1integration.h +new file mode 100644 +index 00000000000..8d2be310fe5 +--- /dev/null ++++ b/src/xxpipv1integration.h +@@ -0,0 +1,28 @@ ++/* ++ SPDX-FileCopyrightText: 2023 Vlad Zahorodnii ++ ++ SPDX-License-Identifier: GPL-2.0-or-later ++*/ ++ ++#pragma once ++ ++#include "waylandshellintegration.h" ++ ++namespace KWin ++{ ++ ++class XXPipV1Interface; ++ ++class XXPipV1Integration : public WaylandShellIntegration ++{ ++ Q_OBJECT ++ ++public: ++ explicit XXPipV1Integration(QObject *parent = nullptr); ++ ++private: ++ void registerPipV1Surface(XXPipV1Interface *pip); ++ void createPipV1Window(XXPipV1Interface *pip); ++}; ++ ++} // namespace KWin +diff --git a/src/xxpipv1window.cpp b/src/xxpipv1window.cpp +new file mode 100644 +index 00000000000..0d6b6bafc8b +--- /dev/null ++++ b/src/xxpipv1window.cpp +@@ -0,0 +1,179 @@ ++/* ++ SPDX-FileCopyrightText: 2023 Vlad Zahorodnii ++ ++ SPDX-License-Identifier: GPL-2.0-or-later ++*/ ++ ++#include "xxpipv1window.h" ++#include "input.h" ++#include "wayland/seat.h" ++#include "wayland/surface.h" ++#include "wayland/tablet_v2.h" ++#include "wayland_server.h" ++#include "workspace.h" ++ ++namespace KWin ++{ ++ ++XXPipV1Window::XXPipV1Window(XXPipV1Interface *shellSurface) ++ : XdgSurfaceWindow(shellSurface->xdgSurface()) ++ , m_shellSurface(shellSurface) ++{ ++ setOutput(workspace()->activeOutput()); ++ setMoveResizeOutput(workspace()->activeOutput()); ++ setOnAllDesktops(true); ++ setOnAllActivities(true); ++ ++ connect(shellSurface, &XXPipV1Interface::initializeRequested, ++ this, &XXPipV1Window::initialize); ++ connect(shellSurface, &XXPipV1Interface::aboutToBeDestroyed, ++ this, &XXPipV1Window::destroyWindow); ++ connect(shellSurface, &XXPipV1Interface::moveRequested, ++ this, &XXPipV1Window::handleMoveRequested); ++ connect(shellSurface, &XXPipV1Interface::resizeRequested, ++ this, &XXPipV1Window::handleResizeRequested); ++ connect(shellSurface, &XXPipV1Interface::applicationIdChanged, ++ this, &XXPipV1Window::handleApplicationIdChanged); ++} ++ ++void XXPipV1Window::initialize() ++{ ++ scheduleConfigure(); ++} ++ ++bool XXPipV1Window::isPictureInPicture() const ++{ ++ return true; ++} ++ ++bool XXPipV1Window::isResizable() const ++{ ++ return true; ++} ++ ++bool XXPipV1Window::isMovable() const ++{ ++ return true; ++} ++ ++bool XXPipV1Window::isMovableAcrossScreens() const ++{ ++ return true; ++} ++ ++bool XXPipV1Window::isCloseable() const ++{ ++ return true; ++} ++ ++void XXPipV1Window::closeWindow() ++{ ++ m_shellSurface->sendClosed(); ++} ++ ++bool XXPipV1Window::wantsInput() const ++{ ++ return false; ++} ++ ++bool XXPipV1Window::takeFocus() ++{ ++ return false; ++} ++ ++bool XXPipV1Window::acceptsFocus() const ++{ ++ return false; ++} ++ ++XdgSurfaceConfigure *XXPipV1Window::sendRoleConfigure() const ++{ ++ surface()->setPreferredBufferScale(nextTargetScale()); ++ surface()->setPreferredBufferTransform(preferredBufferTransform()); ++ surface()->setPreferredColorDescription(preferredColorDescription()); ++ ++ const QRectF geometry = moveResizeGeometry(); ++ if (geometry.isEmpty()) { ++ const QRectF workArea = workspace()->clientArea(PlacementArea, this, moveResizeOutput()); ++ m_shellSurface->sendConfigureBounds(workArea.size() * 0.25); ++ } ++ ++ XdgSurfaceConfigure *configureEvent = new XdgSurfaceConfigure(); ++ configureEvent->bounds = moveResizeGeometry(); ++ configureEvent->serial = m_shellSurface->sendConfigureSize(geometry.size()); ++ ++ return configureEvent; ++} ++ ++void XXPipV1Window::handleRoleDestroyed() ++{ ++ m_shellSurface->disconnect(this); ++ ++ XdgSurfaceWindow::handleRoleDestroyed(); ++} ++ ++void XXPipV1Window::handleApplicationIdChanged() ++{ ++ setResourceClass(resourceName(), m_shellSurface->applicationId()); ++ setDesktopFileName(m_shellSurface->applicationId()); ++} ++ ++void XXPipV1Window::handleMoveRequested(SeatInterface *seat, quint32 serial) ++{ ++ if (const auto anchor = input()->implicitGrabPositionBySerial(seat, serial)) { ++ performMousePressCommand(Options::MouseMove, *anchor); ++ } ++} ++ ++void XXPipV1Window::handleResizeRequested(SeatInterface *seat, Gravity gravity, quint32 serial) ++{ ++ const auto anchor = input()->implicitGrabPositionBySerial(seat, serial); ++ if (!anchor) { ++ return; ++ } ++ if (isInteractiveMoveResize()) { ++ finishInteractiveMoveResize(false); ++ } ++ setInteractiveMoveResizePointerButtonDown(true); ++ setInteractiveMoveResizeAnchor(*anchor); ++ setInteractiveMoveResizeModifiers(Qt::KeyboardModifiers()); ++ setInteractiveMoveOffset(QPointF((anchor->x() - x()) / width(), (anchor->y() - y()) / height())); ++ setUnrestrictedInteractiveMoveResize(false); ++ setInteractiveMoveResizeGravity(gravity); ++ if (!startInteractiveMoveResize()) { ++ setInteractiveMoveResizePointerButtonDown(false); ++ } ++ updateCursor(); ++} ++ ++void XXPipV1Window::doSetNextTargetScale() ++{ ++ if (isDeleted()) { ++ return; ++ } ++ if (m_shellSurface->isConfigured()) { ++ scheduleConfigure(); ++ } ++} ++ ++void XXPipV1Window::doSetPreferredBufferTransform() ++{ ++ if (isDeleted()) { ++ return; ++ } ++ if (m_shellSurface->isConfigured()) { ++ scheduleConfigure(); ++ } ++} ++ ++void XXPipV1Window::doSetPreferredColorDescription() ++{ ++ if (isDeleted()) { ++ return; ++ } ++ if (m_shellSurface->isConfigured()) { ++ scheduleConfigure(); ++ } ++} ++ ++} // namespace KWin +diff --git a/src/xxpipv1window.h b/src/xxpipv1window.h +new file mode 100644 +index 00000000000..15873e3b25f +--- /dev/null ++++ b/src/xxpipv1window.h +@@ -0,0 +1,48 @@ ++/* ++ SPDX-FileCopyrightText: 2023 Vlad Zahorodnii ++ ++ SPDX-License-Identifier: GPL-2.0-or-later ++*/ ++ ++#pragma once ++ ++#include "wayland/xxpip_v1.h" ++#include "xdgshellwindow.h" ++ ++namespace KWin ++{ ++ ++class XXPipV1Window final : public XdgSurfaceWindow ++{ ++ Q_OBJECT ++ ++public: ++ explicit XXPipV1Window(XXPipV1Interface *shellSurface); ++ ++ bool isPictureInPicture() const override; ++ bool isResizable() const override; ++ bool isMovable() const override; ++ bool isMovableAcrossScreens() const override; ++ bool isCloseable() const override; ++ void closeWindow() override; ++ bool wantsInput() const override; ++ bool takeFocus() override; ++ ++protected: ++ bool acceptsFocus() const override; ++ XdgSurfaceConfigure *sendRoleConfigure() const override; ++ void handleRoleDestroyed() override; ++ void doSetNextTargetScale() override; ++ void doSetPreferredBufferTransform() override; ++ void doSetPreferredColorDescription() override; ++ ++private: ++ void initialize(); ++ void handleApplicationIdChanged(); ++ void handleMoveRequested(SeatInterface *seat, quint32 serial); ++ void handleResizeRequested(SeatInterface *seat, Gravity gravity, quint32 serial); ++ ++ XXPipV1Interface *m_shellSurface; ++}; ++ ++} // namespace KWin +diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt +index 134f5416975..824cd67083c 100644 +--- a/tests/CMakeLists.txt ++++ b/tests/CMakeLists.txt +@@ -1,3 +1,7 @@ ++if (Qt6_VERSION VERSION_GREATER_EQUAL "6.9.0") ++ add_subdirectory(pip) ++endif() ++ + if(KWIN_BUILD_X11) + set(normalhintsbasesizetest_SRCS normalhintsbasesizetest.cpp) + add_executable(normalhintsbasesizetest ${normalhintsbasesizetest_SRCS}) +diff --git a/tests/pip/CMakeLists.txt b/tests/pip/CMakeLists.txt +new file mode 100644 +index 00000000000..9135a5ab072 +--- /dev/null ++++ b/tests/pip/CMakeLists.txt +@@ -0,0 +1,22 @@ ++add_executable(piptest) ++ ++target_sources(piptest PRIVATE ++ main.cpp ++ pipshellsurface.cpp ++ pip.cpp ++ window.cpp ++) ++ ++qt6_generate_wayland_protocol_client_sources(piptest ++ PRIVATE_CODE ++ FILES ++ ${PROJECT_SOURCE_DIR}/src/wayland/protocols/xx-pip-v1.xml ++ ${WaylandProtocols_DATADIR}/stable/xdg-shell/xdg-shell.xml ++ ${Wayland_DATADIR}/wayland.xml ++) ++ ++target_link_libraries(piptest PRIVATE ++ Qt::Gui ++ Qt::WaylandClientPrivate ++ Qt::Widgets ++) +diff --git a/tests/pip/main.cpp b/tests/pip/main.cpp +new file mode 100644 +index 00000000000..6d7c4f5b3b4 +--- /dev/null ++++ b/tests/pip/main.cpp +@@ -0,0 +1,19 @@ ++/* ++ SPDX-FileCopyrightText: 2025 Vlad Zahorodnii ++ ++ SPDX-License-Identifier: GPL-2.0-or-later ++*/ ++ ++#include ++ ++#include "window.h" ++ ++int main(int argc, char **argv) ++{ ++ QApplication app(argc, argv); ++ ++ Window w; ++ w.show(); ++ ++ return app.exec(); ++} +diff --git a/tests/pip/pip.cpp b/tests/pip/pip.cpp +new file mode 100644 +index 00000000000..ed14f5f98d4 +--- /dev/null ++++ b/tests/pip/pip.cpp +@@ -0,0 +1,227 @@ ++/* ++ SPDX-FileCopyrightText: 2025 Vlad Zahorodnii ++ ++ SPDX-License-Identifier: GPL-2.0-or-later ++*/ ++ ++#include "pip.h" ++#include "pipshellsurface.h" ++ ++#include ++#include ++ ++PipPin::PipPin(QWidget *parent) ++ : QWidget(parent) ++{ ++ resize(100, 50); ++} ++ ++bool PipPin::isPinned() const ++{ ++ return m_pinned; ++} ++ ++void PipPin::setPinned(bool pinned) ++{ ++ if (m_pinned != pinned) { ++ m_pinned = pinned; ++ update(); ++ } ++} ++ ++void PipPin::paintEvent(QPaintEvent *event) ++{ ++ QPainter painter(this); ++ painter.setClipRegion(event->region()); ++ ++ if (m_hovered) { ++ painter.setOpacity(1.0); ++ } else { ++ painter.setOpacity(0.5); ++ } ++ ++ painter.fillRect(rect(), Qt::black); ++ painter.setPen(Qt::white); ++ painter.drawText(rect(), Qt::AlignCenter, m_pinned ? QStringLiteral("Unpin") : QStringLiteral("Pin")); ++} ++ ++void PipPin::mousePressEvent(QMouseEvent *event) ++{ ++ if (event->button() == Qt::LeftButton) { ++ event->accept(); ++ Q_EMIT clicked(); ++ } ++} ++ ++void PipPin::enterEvent(QEnterEvent *event) ++{ ++ m_hovered = true; ++ update(); ++} ++ ++void PipPin::leaveEvent(QEvent *event) ++{ ++ m_hovered = false; ++ update(); ++} ++ ++Media::Media(QWidget *parent) ++ : QWidget(parent) ++{ ++ m_pip = std::make_unique(); ++ connect(m_pip.get(), &Pip::pinned, this, [this]() { ++ m_pin->setPinned(true); ++ }); ++ connect(m_pip.get(), &Pip::unpinned, this, [this]() { ++ m_pin->setPinned(false); ++ }); ++ ++ m_pin = new PipPin(this); ++ connect(m_pin, &PipPin::clicked, this, [this]() { ++ if (m_pin->isPinned()) { ++ m_pip->show(); ++ } else { ++ m_pip->hide(); ++ } ++ }); ++} ++ ++void Media::paintEvent(QPaintEvent *event) ++{ ++ QPainter painter(this); ++ painter.setClipRegion(event->region()); ++ painter.fillRect(rect(), QColor(0, 0, 0, 128)); ++} ++ ++void Media::resizeEvent(QResizeEvent *event) ++{ ++ m_pin->move(width() - m_pin->width() - 50, height() - m_pin->height() - 50); ++ m_pip->resize(width(), height()); ++} ++ ++PipResizeHandle::PipResizeHandle(Qt::Edges edges, QWidget *parent) ++ : QWidget(parent) ++ , m_edges(edges) ++{ ++ switch (edges) { ++ case Qt::LeftEdge: ++ case Qt::RightEdge: ++ setCursor(Qt::SizeHorCursor); ++ break; ++ case Qt::TopEdge: ++ case Qt::BottomEdge: ++ setCursor(Qt::SizeVerCursor); ++ break; ++ case Qt::TopEdge | Qt::LeftEdge: ++ case Qt::BottomEdge | Qt::RightEdge: ++ setCursor(Qt::SizeFDiagCursor); ++ break; ++ case Qt::TopEdge | Qt::RightEdge: ++ case Qt::BottomEdge | Qt::LeftEdge: ++ setCursor(Qt::SizeBDiagCursor); ++ break; ++ default: ++ Q_UNREACHABLE(); ++ } ++} ++ ++void PipResizeHandle::enterEvent(QEnterEvent *event) ++{ ++ m_hovered = true; ++ update(); ++} ++ ++void PipResizeHandle::leaveEvent(QEvent *event) ++{ ++ m_hovered = false; ++ update(); ++} ++ ++void PipResizeHandle::mousePressEvent(QMouseEvent *event) ++{ ++ if (event->button() == Qt::LeftButton) { ++ event->accept(); ++ window()->windowHandle()->startSystemResize(m_edges); ++ } ++} ++ ++void PipResizeHandle::paintEvent(QPaintEvent *event) ++{ ++ QPainter painter(this); ++ painter.setClipRegion(event->region()); ++ painter.fillRect(rect(), QColor(222, 137, 190, m_hovered ? 128 : 0)); ++} ++ ++Pip::Pip(QWidget *parent) ++ : QWidget(parent) ++{ ++ m_closeButton = new QPushButton(this); ++ m_closeButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close"))); ++ m_closeButton->setText(QStringLiteral("Close")); ++ connect(m_closeButton, &QPushButton::clicked, this, &Pip::hide); ++ ++ m_topLeftResizeHandle = new PipResizeHandle(Qt::TopEdge | Qt::LeftEdge, this); ++ m_topResizeHandle = new PipResizeHandle(Qt::TopEdge, this); ++ m_topRightResizeHandle = new PipResizeHandle(Qt::TopEdge | Qt::RightEdge, this); ++ m_rightResizeHandle = new PipResizeHandle(Qt::RightEdge, this); ++ m_bottomRightResizeHandle = new PipResizeHandle(Qt::BottomEdge | Qt::BottomEdge, this); ++ m_bottomResizeHandle = new PipResizeHandle(Qt::BottomEdge, this); ++ m_bottomLeftResizeHandle = new PipResizeHandle(Qt::BottomEdge | Qt::LeftEdge, this); ++ m_leftResizeHandle = new PipResizeHandle(Qt::LeftEdge, this); ++ ++ winId(); ++ PipShellIntegration::assignPipRole(windowHandle()); ++} ++ ++void Pip::layout() ++{ ++ const int gridUnit = 5; ++ const int resizeZone = 2 * gridUnit; ++ ++ m_topLeftResizeHandle->setGeometry(0, 0, resizeZone, resizeZone); ++ m_topResizeHandle->setGeometry(resizeZone, 0, width() - 2 * resizeZone, resizeZone); ++ m_topRightResizeHandle->setGeometry(width() - resizeZone, 0, resizeZone, resizeZone); ++ m_rightResizeHandle->setGeometry(width() - resizeZone, resizeZone, resizeZone, height() - 2 * resizeZone); ++ m_bottomRightResizeHandle->setGeometry(width() - resizeZone, height() - resizeZone, resizeZone, resizeZone); ++ m_bottomResizeHandle->setGeometry(resizeZone, height() - resizeZone, width() - 2 * resizeZone, resizeZone); ++ m_bottomLeftResizeHandle->setGeometry(0, height() - resizeZone, resizeZone, resizeZone); ++ m_leftResizeHandle->setGeometry(0, resizeZone, resizeZone, height() - 2 * resizeZone); ++ ++ m_closeButton->move(width() - resizeZone - gridUnit - m_closeButton->width(), resizeZone + gridUnit); ++} ++ ++void Pip::paintEvent(QPaintEvent *event) ++{ ++ QPainter painter(this); ++ painter.setClipRegion(event->region()); ++ painter.fillRect(rect(), QColor(64, 67, 78)); ++} ++ ++void Pip::resizeEvent(QResizeEvent *event) ++{ ++ layout(); ++} ++ ++void Pip::mousePressEvent(QMouseEvent *event) ++{ ++ switch (event->button()) { ++ case Qt::LeftButton: ++ event->accept(); ++ windowHandle()->startSystemMove(); ++ break; ++ default: ++ break; ++ } ++} ++ ++void Pip::showEvent(QShowEvent *event) ++{ ++ Q_EMIT unpinned(); ++} ++ ++void Pip::hideEvent(QHideEvent *event) ++{ ++ Q_EMIT pinned(); ++} ++ ++#include "moc_pip.cpp" +diff --git a/tests/pip/pip.h b/tests/pip/pip.h +new file mode 100644 +index 00000000000..2f6e4a87d86 +--- /dev/null ++++ b/tests/pip/pip.h +@@ -0,0 +1,102 @@ ++/* ++ SPDX-FileCopyrightText: 2025 Vlad Zahorodnii ++ ++ SPDX-License-Identifier: GPL-2.0-or-later ++*/ ++ ++#pragma once ++ ++#include ++#include ++ ++class PipResizeHandle : public QWidget ++{ ++ Q_OBJECT ++ ++public: ++ explicit PipResizeHandle(Qt::Edges edges, QWidget *parent = nullptr); ++ ++protected: ++ void enterEvent(QEnterEvent *event) override; ++ void leaveEvent(QEvent *event) override; ++ void mousePressEvent(QMouseEvent *event) override; ++ void paintEvent(QPaintEvent *event) override; ++ ++private: ++ Qt::Edges m_edges; ++ bool m_hovered = false; ++}; ++ ++class Pip : public QWidget ++{ ++ Q_OBJECT ++ ++public: ++ explicit Pip(QWidget *parent = nullptr); ++ ++Q_SIGNALS: ++ void pinned(); ++ void unpinned(); ++ ++protected: ++ void paintEvent(QPaintEvent *event) override; ++ void resizeEvent(QResizeEvent *event) override; ++ void mousePressEvent(QMouseEvent *event) override; ++ void showEvent(QShowEvent *event) override; ++ void hideEvent(QHideEvent *event) override; ++ ++private: ++ void layout(); ++ ++ QPushButton *m_closeButton = nullptr; ++ PipResizeHandle *m_topLeftResizeHandle = nullptr; ++ PipResizeHandle *m_topResizeHandle = nullptr; ++ PipResizeHandle *m_topRightResizeHandle = nullptr; ++ PipResizeHandle *m_rightResizeHandle = nullptr; ++ PipResizeHandle *m_bottomRightResizeHandle = nullptr; ++ PipResizeHandle *m_bottomResizeHandle = nullptr; ++ PipResizeHandle *m_bottomLeftResizeHandle = nullptr; ++ PipResizeHandle *m_leftResizeHandle = nullptr; ++}; ++ ++class PipPin : public QWidget ++{ ++ Q_OBJECT ++ ++public: ++ explicit PipPin(QWidget *parent = nullptr); ++ ++ bool isPinned() const; ++ void setPinned(bool pinned); ++ ++Q_SIGNALS: ++ void clicked(); ++ ++protected: ++ void paintEvent(QPaintEvent *event) override; ++ void mousePressEvent(QMouseEvent *event) override; ++ void enterEvent(QEnterEvent *event) override; ++ void leaveEvent(QEvent *event) override; ++ ++private: ++ bool m_pinned = true; ++ bool m_hovered = false; ++}; ++ ++class Media : public QWidget ++{ ++ Q_OBJECT ++ ++public: ++ explicit Media(QWidget *parent = nullptr); ++ ++protected: ++ void paintEvent(QPaintEvent *event) override; ++ void resizeEvent(QResizeEvent *event) override; ++ ++private: ++ void layout(); ++ ++ std::unique_ptr m_pip; ++ PipPin *m_pin = nullptr; ++}; +diff --git a/tests/pip/pipshellsurface.cpp b/tests/pip/pipshellsurface.cpp +new file mode 100644 +index 00000000000..082a51a3c24 +--- /dev/null ++++ b/tests/pip/pipshellsurface.cpp +@@ -0,0 +1,156 @@ ++/* ++ SPDX-FileCopyrightText: 2025 Vlad Zahorodnii ++ ++ SPDX-License-Identifier: GPL-2.0-or-later ++*/ ++ ++#include "pipshellsurface.h" ++ ++#include ++#include ++ ++XdgWmBase::XdgWmBase() ++ : QWaylandClientExtensionTemplate(6) ++{ ++ initialize(); ++ if (!isActive()) { ++ qFatal("The xdg-shell protocol is unsupported by the compositor"); ++ } ++} ++ ++XXPipShell::XXPipShell() ++ : QWaylandClientExtensionTemplate(1) ++{ ++ initialize(); ++ if (!isActive()) { ++ qFatal("The xx-pip-v1 protocol is unsupported by the compositor"); ++ } ++} ++ ++void PipShellIntegration::assignPipRole(QWindow *window) ++{ ++ window->create(); ++ ++ auto waylandWindow = dynamic_cast(window->handle()); ++ if (!waylandWindow) { ++ return; ++ } ++ ++ static PipShellIntegration *shellIntegration = nullptr; ++ if (!shellIntegration) { ++ shellIntegration = new PipShellIntegration(); ++ } ++ ++ waylandWindow->setShellIntegration(shellIntegration); ++} ++ ++PipShellIntegration::PipShellIntegration() ++ : m_xdgWmBase(std::make_unique()) ++ , m_xxPipShell(std::make_unique()) ++{ ++} ++ ++bool PipShellIntegration::initialize(QtWaylandClient::QWaylandDisplay *display) ++{ ++ return m_xdgWmBase->isInitialized() && m_xxPipShell->isInitialized(); ++} ++ ++QtWaylandClient::QWaylandShellSurface *PipShellIntegration::createShellSurface(QtWaylandClient::QWaylandWindow *window) ++{ ++ ::xdg_surface *xdgSurface = m_xdgWmBase->get_xdg_surface(window->wlSurface()); ++ ::xx_pip_v1 *xxPip = m_xxPipShell->get_pip(xdgSurface); ++ return new PipShellSurface(xdgSurface, xxPip, window); ++} ++ ++PipShellSurface::PipShellSurface(::xdg_surface *xdgSurface, ::xx_pip_v1 *xxPip, QtWaylandClient::QWaylandWindow *window) ++ : QWaylandShellSurface(window) ++ , QtWayland::xdg_surface(xdgSurface) ++ , QtWayland::xx_pip_v1(xxPip) ++{ ++} ++ ++PipShellSurface::~PipShellSurface() ++{ ++ xx_pip_v1::destroy(); ++ xdg_surface::destroy(); ++} ++ ++bool PipShellSurface::isExposed() const ++{ ++ return m_configured; ++} ++ ++void PipShellSurface::applyConfigure() ++{ ++ QSize size = window()->windowContentGeometry().size(); ++ if (m_pendingSize.width() > 0) { ++ size.setWidth(m_pendingSize.width()); ++ } ++ if (m_pendingSize.height() > 0) { ++ size.setHeight(m_pendingSize.height()); ++ } ++ ++ window()->resizeFromApplyConfigure(size); ++} ++ ++void PipShellSurface::setWindowGeometry(const QRect &rect) ++{ ++ if (window()->isExposed()) { ++ xdg_surface::set_window_geometry(rect.x(), rect.y(), rect.width(), rect.height()); ++ } ++} ++ ++bool PipShellSurface::move(QtWaylandClient::QWaylandInputDevice *inputDevice) ++{ ++ if (!m_configured) { ++ return false; ++ } ++ xx_pip_v1::move(inputDevice->wl_seat(), inputDevice->serial()); ++ return true; ++} ++ ++bool PipShellSurface::resize(QtWaylandClient::QWaylandInputDevice *inputDevice, Qt::Edges edges) ++{ ++ if (!m_configured) { ++ return false; ++ } ++ ++ const resize_edge edge = static_cast( ++ ((edges & Qt::TopEdge) ? resize_edge_top : 0) ++ | ((edges & Qt::BottomEdge) ? resize_edge_bottom : 0) ++ | ((edges & Qt::LeftEdge) ? resize_edge_left : 0) ++ | ((edges & Qt::RightEdge) ? resize_edge_right : 0)); ++ ++ xx_pip_v1::resize(inputDevice->wl_seat(), inputDevice->serial(), edge); ++ return true; ++} ++ ++void PipShellSurface::xdg_surface_configure(uint32_t serial) ++{ ++ xdg_surface::ack_configure(serial); ++ ++ if (!m_configured) { ++ m_configured = true; ++ applyConfigure(); ++ } else { ++ window()->applyConfigureWhenPossible(); ++ } ++ ++ window()->updateExposure(); ++} ++ ++void PipShellSurface::xx_pip_v1_configure_bounds(int32_t width, int32_t height) ++{ ++} ++ ++void PipShellSurface::xx_pip_v1_configure_size(int32_t width, int32_t height) ++{ ++ m_pendingSize = QSize(width, height); ++} ++ ++void PipShellSurface::xx_pip_v1_closed() ++{ ++ QWindowSystemInterface::handleCloseEvent(window()->window()); ++} ++ ++#include "moc_pipshellsurface.cpp" +diff --git a/tests/pip/pipshellsurface.h b/tests/pip/pipshellsurface.h +new file mode 100644 +index 00000000000..b02e83b3fc9 +--- /dev/null ++++ b/tests/pip/pipshellsurface.h +@@ -0,0 +1,70 @@ ++/* ++ SPDX-FileCopyrightText: 2025 Vlad Zahorodnii ++ ++ SPDX-License-Identifier: GPL-2.0-or-later ++*/ ++ ++#pragma once ++ ++#include ++#include ++#include ++#include ++ ++#include "qwayland-xdg-shell.h" ++#include "qwayland-xx-pip-v1.h" ++ ++class XdgWmBase : public QWaylandClientExtensionTemplate, public QtWayland::xdg_wm_base ++{ ++ Q_OBJECT ++ ++public: ++ XdgWmBase(); ++}; ++ ++class XXPipShell : public QWaylandClientExtensionTemplate, public QtWayland::xx_pip_shell_v1 ++{ ++ Q_OBJECT ++ ++public: ++ XXPipShell(); ++}; ++ ++class PipShellIntegration : public QtWaylandClient::QWaylandShellIntegration ++{ ++public: ++ PipShellIntegration(); ++ ++ bool initialize(QtWaylandClient::QWaylandDisplay *display) override; ++ QtWaylandClient::QWaylandShellSurface *createShellSurface(QtWaylandClient::QWaylandWindow *window) override; ++ ++ static void assignPipRole(QWindow *window); ++ ++private: ++ std::unique_ptr m_xdgWmBase; ++ std::unique_ptr m_xxPipShell; ++}; ++ ++class PipShellSurface : public QtWaylandClient::QWaylandShellSurface, public QtWayland::xdg_surface, public QtWayland::xx_pip_v1 ++{ ++ Q_OBJECT ++ ++public: ++ PipShellSurface(::xdg_surface *xdgSurface, ::xx_pip_v1 *xxPip, QtWaylandClient::QWaylandWindow *window); ++ ~PipShellSurface() override; ++ ++ bool isExposed() const override; ++ void applyConfigure() override; ++ void setWindowGeometry(const QRect &rect) override; ++ bool move(QtWaylandClient::QWaylandInputDevice *inputDevice) override; ++ bool resize(QtWaylandClient::QWaylandInputDevice *inputDevice, Qt::Edges edges) override; ++ ++private: ++ void xdg_surface_configure(uint32_t serial) override; ++ void xx_pip_v1_closed() override; ++ void xx_pip_v1_configure_bounds(int32_t width, int32_t height) override; ++ void xx_pip_v1_configure_size(int32_t width, int32_t height) override; ++ ++ QSize m_pendingSize; ++ bool m_configured = false; ++}; +diff --git a/tests/pip/window.cpp b/tests/pip/window.cpp +new file mode 100644 +index 00000000000..2ac98bde845 +--- /dev/null ++++ b/tests/pip/window.cpp +@@ -0,0 +1,19 @@ ++/* ++ SPDX-FileCopyrightText: 2025 Vlad Zahorodnii ++ ++ SPDX-License-Identifier: GPL-2.0-or-later ++*/ ++ ++#include "window.h" ++#include "pip.h" ++ ++Window::Window(QWidget *parent) ++ : QWidget(parent) ++{ ++ resize(800, 600); ++ ++ m_media = new Media(this); ++ m_media->setGeometry(100, 100, 400, 300); ++} ++ ++#include "moc_window.cpp" +diff --git a/tests/pip/window.h b/tests/pip/window.h +new file mode 100644 +index 00000000000..bfd83e9d467 +--- /dev/null ++++ b/tests/pip/window.h +@@ -0,0 +1,22 @@ ++/* ++ SPDX-FileCopyrightText: 2025 Vlad Zahorodnii ++ ++ SPDX-License-Identifier: GPL-2.0-or-later ++*/ ++ ++#pragma once ++ ++#include ++ ++class Media; ++ ++class Window : public QWidget ++{ ++ Q_OBJECT ++ ++public: ++ explicit Window(QWidget *parent = nullptr); ++ ++private: ++ Media *m_media = nullptr; ++}; +-- +GitLab + + +From cab39d37c8cb7d1c37d04a76cc7fa7af39fb7908 Mon Sep 17 00:00:00 2001 +From: Vlad Zahorodnii +Date: Thu, 12 Jun 2025 14:22:55 +0000 +Subject: [PATCH 2/2] Apply 2 suggestion(s) to 1 file(s) + +Co-authored-by: Xaver Hugl +--- + src/wayland/xxpip_v1.cpp | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/wayland/xxpip_v1.cpp b/src/wayland/xxpip_v1.cpp +index e0b1f36134f..bc1b65d0212 100644 +--- a/src/wayland/xxpip_v1.cpp ++++ b/src/wayland/xxpip_v1.cpp +@@ -282,7 +282,7 @@ quint32 XXPipV1Interface::sendConfigureSize(const QSizeF &size) + { + const quint32 serial = d->shell->display()->nextSerial(); + +- d->send_configure_size(size.width(), size.height()); ++ d->send_configure_size(std::round(size.width()), std::round(size.height())); + + auto xdgSurfacePrivate = XdgSurfaceInterfacePrivate::get(xdgSurface()); + xdgSurfacePrivate->send_configure(serial); +@@ -298,7 +298,7 @@ void XXPipV1Interface::sendClosed() + + void XXPipV1Interface::sendConfigureBounds(const QSizeF &size) + { +- d->send_configure_bounds(size.width(), size.height()); ++ d->send_configure_bounds(std::round(size.width()), std::round(size.height())); + } + + XXPipV1Interface *XXPipV1Interface::get(::wl_resource *resource) +-- +GitLab + diff --git a/roles/kde/patches/kwin/pr7822.patch b/roles/kde/patches/kwin/pr7822.patch new file mode 100644 index 0000000..aca6909 --- /dev/null +++ b/roles/kde/patches/kwin/pr7822.patch @@ -0,0 +1,40 @@ +From 3ac297b99540eae81568edca1e550ca7d1d6c01f Mon Sep 17 00:00:00 2001 +From: Ismael Asensio +Date: Tue, 24 Jun 2025 17:08:00 +0200 +Subject: [PATCH] plugins/fadingpopups: Blacklist spectacle popup menus from + fading effect + +Spectacle can trigger a screenshot from a popup menu. This menu needs +to get hidden immediately with no effects, to avoid appearing on the +screenshot. + +BUG: 505803 +FIXED-IN: 6.4.2 + + +(cherry picked from commit 99f6418e3c2e044eb1f4e8bedcdccac55f1a01d4) + +Co-authored-by: Ismael Asensio +--- + src/plugins/fadingpopups/package/contents/code/main.js | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/src/plugins/fadingpopups/package/contents/code/main.js b/src/plugins/fadingpopups/package/contents/code/main.js +index f0764b42ac6..7c00640ae8f 100644 +--- a/src/plugins/fadingpopups/package/contents/code/main.js ++++ b/src/plugins/fadingpopups/package/contents/code/main.js +@@ -17,7 +17,10 @@ var blacklist = [ + // The lockscreen isn't a popup window + "kscreenlocker_greet kscreenlocker_greet", + // KDE Plasma splash screen has to be animated only by the login effect. +- "ksplashqml ksplashqml" ++ "ksplashqml ksplashqml", ++ // Spectacle can trigger a screenshot from a popup menu ++ "spectacle org.kde.spectacle", ++ "spectacle spectacle", + ]; + + function isPopupWindow(window) { +-- +GitLab + diff --git a/roles/kde/patches/kwin/pr7823.patch b/roles/kde/patches/kwin/pr7823.patch new file mode 100644 index 0000000..5e54a43 --- /dev/null +++ b/roles/kde/patches/kwin/pr7823.patch @@ -0,0 +1,26 @@ +From a1868942fbc2ad5de4c053dd17335dff34d81779 Mon Sep 17 00:00:00 2001 +From: Vlad Zahorodnii +Date: Wed, 25 Jun 2025 14:10:18 +0300 +Subject: [PATCH] plugins/login: Reduce animation duration + +This makes the startup feel a bit faster. +--- + src/plugins/login/package/contents/code/main.js | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/plugins/login/package/contents/code/main.js b/src/plugins/login/package/contents/code/main.js +index 7f849a6e2e6..e3ad0b76c07 100644 +--- a/src/plugins/login/package/contents/code/main.js ++++ b/src/plugins/login/package/contents/code/main.js +@@ -11,7 +11,7 @@ + "use strict"; + + var loginEffect = { +- duration: animationTime(1000), ++ duration: animationTime(500), + isFadeToBlack: false, + loadConfig: function () { + loginEffect.isFadeToBlack = effect.readConfig("FadeToBlack", false); +-- +GitLab + diff --git a/roles/kde/patches/kwin/pr7829.patch b/roles/kde/patches/kwin/pr7829.patch new file mode 100644 index 0000000..fe92562 --- /dev/null +++ b/roles/kde/patches/kwin/pr7829.patch @@ -0,0 +1,85 @@ +From 4ddb4a23d5f453cf890c9f1b1aa5429150991db3 Mon Sep 17 00:00:00 2001 +From: David Redondo +Date: Thu, 26 Jun 2025 17:00:07 +0200 +Subject: [PATCH] scene: Skip visibility check for the Item itself in + framePainted + +Fixes offscreen rendering and window thumbnails for hidden windows +not udpating. +Co-authored-by: Vlad Zahorodnii +--- + src/scene/item.cpp | 9 +++++---- + src/scene/item.h | 2 +- + src/scene/rootitem.cpp | 9 --------- + src/scene/rootitem.h | 2 -- + 4 files changed, 6 insertions(+), 16 deletions(-) + +diff --git a/src/scene/item.cpp b/src/scene/item.cpp +index 2637a131dbd..eb2b6b46977 100644 +--- a/src/scene/item.cpp ++++ b/src/scene/item.cpp +@@ -648,12 +648,13 @@ void Item::removeEffect() + + void Item::framePainted(Output *output, OutputFrame *frame, std::chrono::milliseconds timestamp) + { +- if (!isVisible() || workspace()->outputAt(mapToScene(boundingRect()).center()) != output) { +- return; +- } ++ // The visibility of the item itself is not checked here to be able to paint hidden items for ++ // things like screncasts or thumbnails + handleFramePainted(output, frame, timestamp); + for (const auto child : std::as_const(m_childItems)) { +- child->framePainted(output, frame, timestamp); ++ if (child->explicitVisible() && workspace()->outputAt(child->mapToScene(child->boundingRect()).center()) == output) { ++ child->framePainted(output, frame, timestamp); ++ } + } + } + +diff --git a/src/scene/item.h b/src/scene/item.h +index acb29994328..d42b32abf7b 100644 +--- a/src/scene/item.h ++++ b/src/scene/item.h +@@ -144,7 +144,7 @@ public: + void addEffect(); + void removeEffect(); + +- virtual void framePainted(Output *output, OutputFrame *frame, std::chrono::milliseconds timestamp); ++ void framePainted(Output *output, OutputFrame *frame, std::chrono::milliseconds timestamp); + + bool isAncestorOf(const Item *item) const; + +diff --git a/src/scene/rootitem.cpp b/src/scene/rootitem.cpp +index fe0cecc76e3..466a25ce828 100644 +--- a/src/scene/rootitem.cpp ++++ b/src/scene/rootitem.cpp +@@ -14,13 +14,4 @@ RootItem::RootItem(Scene *scene) + setScene(scene); + } + +-void RootItem::framePainted(Output *output, OutputFrame *frame, std::chrono::milliseconds timestamp) +-{ +- handleFramePainted(output, frame, timestamp); +- const auto children = childItems(); +- for (const auto child : children) { +- child->framePainted(output, frame, timestamp); +- } +-} +- + } // namespace KWin +diff --git a/src/scene/rootitem.h b/src/scene/rootitem.h +index 45fa1359c84..43b3cdb90e9 100644 +--- a/src/scene/rootitem.h ++++ b/src/scene/rootitem.h +@@ -20,8 +20,6 @@ class KWIN_EXPORT RootItem : public Item + + public: + explicit RootItem(Scene *scene); +- +- void framePainted(Output *output, OutputFrame *frame, std::chrono::milliseconds timestamp) override; + }; + + } // namespace KWin +-- +GitLab + diff --git a/roles/kde/patches/plasma-nm/patches.txt b/roles/kde/patches/plasma-nm/patches.txt new file mode 100644 index 0000000..c5e71a7 --- /dev/null +++ b/roles/kde/patches/plasma-nm/patches.txt @@ -0,0 +1,4 @@ +Plasma 6.5.0: + +Pr 439 https://invent.kde.org/plasma/plasma-nm/-/merge_requests/439 +Pr 442 https://invent.kde.org/plasma/plasma-nm/-/merge_requests/442 diff --git a/roles/kde/patches/plasma-nm/pr439.patch b/roles/kde/patches/plasma-nm/pr439.patch new file mode 100644 index 0000000..885e3dd --- /dev/null +++ b/roles/kde/patches/plasma-nm/pr439.patch @@ -0,0 +1,140 @@ +From ac32824009397188131aab4fcc3394fcc4551c5c Mon Sep 17 00:00:00 2001 +From: Nate Graham +Date: Tue, 10 Jun 2025 15:43:13 -0600 +Subject: [PATCH 1/2] applet: handle some more states + +1. Looking for Wi-Fi networks but haven't found any yet +2. NetworkManager isn't running + +FEATURE: 485982 +BUG: 462454 +FIXED-IN: 6.5.0 +--- + applet/contents/ui/ConnectionListPage.qml | 15 ++++++++++++--- + applet/contents/ui/Toolbar.qml | 3 ++- + applet/metadata.json | 1 - + libs/networkstatus.h | 3 +-- + 4 files changed, 15 insertions(+), 7 deletions(-) + +diff --git a/applet/contents/ui/ConnectionListPage.qml b/applet/contents/ui/ConnectionListPage.qml +index b9a949d87..056f52de3 100644 +--- a/applet/contents/ui/ConnectionListPage.qml ++++ b/applet/contents/ui/ConnectionListPage.qml +@@ -101,9 +101,12 @@ ColumnLayout { + if (toolbar.displayplaneModeMessage) { + return "network-flightmode-on" + } +- if (toolbar.displayWifiMessage) { ++ if (toolbar.displayWifiOffMessage) { + return "network-wireless-off" + } ++ if (toolbar.displayWifiConnectingMessage) { ++ return "view-refresh-symbolic" ++ } + if (toolbar.displayWwanMessage) { + return "network-mobile-off" + } +@@ -113,19 +116,25 @@ ColumnLayout { + if (toolbar.displayplaneModeMessage) { + return i18n("Airplane mode is enabled") + } +- if (toolbar.displayWifiMessage) { ++ if (toolbar.displayWifiOffMessage) { + if (toolbar.displayWwanMessage) { + return i18n("Wireless and mobile networks are deactivated") + } + return i18n("Wireless is deactivated") + } ++ if (toolbar.displayWifiConnectingMessage) { ++ return i18n("Looking for wireless networks") ++ } + if (toolbar.displayWwanMessage) { + return i18n("Mobile network is deactivated") + } + if (toolbar.searchTextField.text.length > 0) { + return i18n("No matches") + } +- return i18n("No available connections") ++ if (connectionListPage.nmStatus.connectivity === NMQt.NetworkManager.Full) { ++ return i18n("No available connections") ++ } ++ return nmStatus.checkUnknownReason() + } + } + } +diff --git a/applet/contents/ui/Toolbar.qml b/applet/contents/ui/Toolbar.qml +index 87b3f1654..0d382109b 100644 +--- a/applet/contents/ui/Toolbar.qml ++++ b/applet/contents/ui/Toolbar.qml +@@ -16,7 +16,8 @@ import org.kde.kcmutils as KCMUtils + RowLayout { + id: toolbar + +- readonly property var displayWifiMessage: !wifiSwitchButton.checked && wifiSwitchButton.visible ++ readonly property var displayWifiOffMessage: !wifiSwitchButton.checked && wifiSwitchButton.visible ++ readonly property var displayWifiConnectingMessage: wifiSwitchButton.checked && wifiSwitchButton.visible + readonly property var displayWwanMessage: !wwanSwitchButton.checked && wwanSwitchButton.visible + readonly property var displayplaneModeMessage: planeModeSwitchButton.checked && planeModeSwitchButton.visible + +diff --git a/applet/metadata.json b/applet/metadata.json +index 0ca36a2b7..2f12891b5 100644 +--- a/applet/metadata.json ++++ b/applet/metadata.json +@@ -210,6 +210,5 @@ + "X-KDE-Keywords[zh_CN]": "network,internet,ethernet,wireless,wifi,wlan,vpn,wangluo,hulianwang,yitaiwang,juyuwang,wuxianwangluo,xunizhuanyongwangluo,网络,互联网,以太网,局域网,无线网络,虚拟专用网络", + "X-KDE-Keywords[zh_TW]": "網路,網絡,網際網路,乙太網路,以太網路,無線", + "X-Plasma-API-Minimum-Version": "6.0", +- "X-Plasma-DBusActivationService": "org.freedesktop.NetworkManager", + "X-Plasma-NotificationAreaCategory": "Hardware" + } +diff --git a/libs/networkstatus.h b/libs/networkstatus.h +index aa88bf7c5..a885f04dd 100644 +--- a/libs/networkstatus.h ++++ b/libs/networkstatus.h +@@ -59,6 +59,7 @@ public: + QString activeConnections() const; + QString networkStatus() const; + NetworkManager::Connectivity connectivity() const; ++ Q_INVOKABLE QString checkUnknownReason() const; + + private Q_SLOTS: + void activeConnectionsChanged(); +@@ -75,8 +76,6 @@ private: + QString m_activeConnections; + QString m_networkStatus; + NetworkManager::Connectivity m_connectivity = NetworkManager::UnknownConnectivity; +- +- QString checkUnknownReason() const; + }; + + #endif // PLAMA_NM_NETWORK_STATUS_H +-- +GitLab + + +From a45534cd9c19f267075fe4261087045f1f3a9318 Mon Sep 17 00:00:00 2001 +From: Nate Graham +Date: Wed, 11 Jun 2025 10:14:40 -0600 +Subject: [PATCH 2/2] Rephrase "NetworkManager not running" message to be more + user-friendly + +--- + libs/networkstatus.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/libs/networkstatus.cpp b/libs/networkstatus.cpp +index c89ca9fca..b778eedda 100644 +--- a/libs/networkstatus.cpp ++++ b/libs/networkstatus.cpp +@@ -253,7 +253,7 @@ QString NetworkStatus::checkUnknownReason() const + { + // Check if NetworkManager is running. + if (!QDBusConnection::systemBus().interface()->isServiceRegistered(QStringLiteral(NM_DBUS_INTERFACE))) { +- return i18n("NetworkManager not running"); ++ return i18nc("@info:status", "NetworkManager service is not running"); + } + + // Check for compatible NetworkManager version. +-- +GitLab + diff --git a/roles/kde/patches/plasma-nm/pr442.patch b/roles/kde/patches/plasma-nm/pr442.patch new file mode 100644 index 0000000..cd6dda6 --- /dev/null +++ b/roles/kde/patches/plasma-nm/pr442.patch @@ -0,0 +1,168 @@ +From 63a9d544dc4f5160f28e5f3203a2c0a5e639e5ac Mon Sep 17 00:00:00 2001 +From: Nate Graham +Date: Fri, 13 Jun 2025 12:24:22 -0600 +Subject: [PATCH] applet: use standard section headers + +We have them, so we might as well use them. This lets us get rid of a +bunch of complex and fragile code that's responsible for the existing +separator. +--- + applet/contents/ui/ConnectionItem.qml | 14 ----- + applet/contents/ui/ConnectionListPage.qml | 10 ++-- + applet/contents/ui/ListItem.qml | 64 ----------------------- + libs/models/networkmodelitem.cpp | 8 ++- + 4 files changed, 12 insertions(+), 84 deletions(-) + delete mode 100644 applet/contents/ui/ListItem.qml + +diff --git a/applet/contents/ui/ConnectionItem.qml b/applet/contents/ui/ConnectionItem.qml +index ed154e633..5bb5c530c 100644 +--- a/applet/contents/ui/ConnectionItem.qml ++++ b/applet/contents/ui/ConnectionItem.qml +@@ -321,20 +321,6 @@ PlasmaExtras.ExpandableListItem { + } + } + +- onDeactivatedChanged: { +- /* Separator is part of section, which is visible only when available connections exist. Need to determine +- if there is a connection in use, to show Separator. Otherwise need to hide it from the top of the list. +- Connections in use are always on top, only need to check the first one. */ +- if (appletProxyModel.data(appletProxyModel.index(0, 0), PlasmaNM.NetworkModel.SectionRole) !== "Available connections") { +- if (connectionView.showSeparator != true) { +- connectionView.showSeparator = true +- } +- return +- } +- connectionView.showSeparator = false +- return +- } +- + onItemCollapsed: { + connectionItem.customExpandedViewContent = detailsComponent; + setDelayModelUpdates(false); +diff --git a/applet/contents/ui/ConnectionListPage.qml b/applet/contents/ui/ConnectionListPage.qml +index b62da252c..d8ebd2c54 100644 +--- a/applet/contents/ui/ConnectionListPage.qml ++++ b/applet/contents/ui/ConnectionListPage.qml +@@ -8,7 +8,7 @@ import QtQuick 2.15 + import QtQuick.Layouts 1.2 + import org.kde.plasma.components 3.0 as PlasmaComponents3 + import org.kde.kirigami 2.20 as Kirigami +-import org.kde.plasma.extras 2.0 as PlasmaExtras ++import org.kde.plasma.extras as PlasmaExtras + import org.kde.plasma.networkmanagement as PlasmaNM + import org.kde.networkmanager as NMQt + +@@ -55,7 +55,6 @@ ColumnLayout { + id: connectionView + + property int currentVisibleButtonIndex: -1 +- property bool showSeparator: false + + Keys.onDownPressed: event => { + connectionView.incrementCurrentIndex(); +@@ -80,9 +79,10 @@ ColumnLayout { + model: appletProxyModel + currentIndex: -1 + boundsBehavior: Flickable.StopAtBounds +- section.property: showSeparator ? "Section" : "" +- section.delegate: ListItem { +- separator: true ++ section.property: "Section" ++ section.delegate: PlasmaExtras.ListSectionHeader { ++ width: connectionView.width - connectionView.leftMargin - connectionView.rightMargin ++ text: section + } + highlight: PlasmaExtras.Highlight { } + highlightMoveDuration: Kirigami.Units.shortDuration +diff --git a/applet/contents/ui/ListItem.qml b/applet/contents/ui/ListItem.qml +deleted file mode 100644 +index bfe1390d1..000000000 +--- a/applet/contents/ui/ListItem.qml ++++ /dev/null +@@ -1,64 +0,0 @@ +-/* +- SPDX-FileCopyrightText: 2010 Marco Martin +- SPDX-FileCopyrightText: 2016 Jan Grulich +- SPDX-FileCopyrightText: 2020 George Vogiatzis +- +- SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +-*/ +- +-import QtQuick 2.1 +-import org.kde.kirigami 2.20 as Kirigami +-import org.kde.ksvg 1.0 as KSvg +- +-/** +- * Ignores the theme's listItem margins, and uses custom highlight(pressed) area. +- * Could break some themes but the majority look fine. +- * Also includes a separator to be used in sections. +- */ +-MouseArea { +- id: listItem +- +- property bool checked: false +- property bool separator: false +- property rect highlightRect: Qt.rect(0, 0, width, height) +- +- width: parent.width +- +- // Sections have spacing above but not below. Will use 2 of them below. +- height: separator ? separatorLine.height + Kirigami.Units.smallSpacing * 3 : parent.height +- hoverEnabled: true +- +- KSvg.SvgItem { +- id: separatorLine +- anchors { +- horizontalCenter: parent.horizontalCenter +- top: parent.top +- topMargin: Kirigami.Units.smallSpacing +- } +- imagePath: "widgets/line" +- elementId: "horizontal-line" +- width: parent.width - Kirigami.Units.gridUnit * 2 +- visible: listItem.separator +- } +- +- KSvg.FrameSvgItem { +- id: background +- imagePath: "widgets/listitem" +- prefix: "normal" +- anchors.fill: parent +- visible: listItem.separator ? false : true +- } +- +- KSvg.FrameSvgItem { +- id: pressed +- imagePath: "widgets/listitem" +- prefix: "pressed" +- opacity: listItem.checked ? 1 : 0 +- Behavior on opacity { NumberAnimation { duration: Kirigami.Units.shortDuration } } +- +- x: listItem.highlightRect.x +- y: listItem.highlightRect.y +- height: listItem.highlightRect.height +- width: listItem.highlightRect.width +- } +-} +diff --git a/libs/models/networkmodelitem.cpp b/libs/models/networkmodelitem.cpp +index b92ec0df2..4d7cbd77d 100644 +--- a/libs/models/networkmodelitem.cpp ++++ b/libs/models/networkmodelitem.cpp +@@ -344,7 +344,13 @@ QString NetworkModelItem::originalName() const + QString NetworkModelItem::sectionType() const + { + if (m_connectionState == NetworkManager::ActiveConnection::Deactivated) { +- return QStringLiteral("Available connections"); ++ return i18nc("@title:column header for list of available network connections", "Available"); ++ // clang-format off ++ } else if (m_connectionState == NetworkManager::ActiveConnection::Activating ++ || m_connectionState == NetworkManager::ActiveConnection::Activated ++ || m_connectionState == NetworkManager::ActiveConnection::Deactivating) { ++ // clang-format on ++ return i18nc("@title:column header for list of connected network connections", "Connected"); + } else { + return {}; + } +-- +GitLab + diff --git a/roles/kde/patches/plasma-workspace/patches.txt b/roles/kde/patches/plasma-workspace/patches.txt new file mode 100644 index 0000000..d704604 --- /dev/null +++ b/roles/kde/patches/plasma-workspace/patches.txt @@ -0,0 +1,6 @@ +Plasma 6.5.0: + +Pr 5589 https://invent.kde.org/plasma/plasma-workspace/-/merge_requests/5589 +Pr 5626 https://invent.kde.org/plasma/plasma-workspace/-/merge_requests/5626 +Pr 5627 https://invent.kde.org/plasma/plasma-workspace/-/merge_requests/5627 +Pr 5628 https://invent.kde.org/plasma/plasma-workspace/-/merge_requests/5628 diff --git a/roles/kde/patches/plasma-workspace/pr5589.patch b/roles/kde/patches/plasma-workspace/pr5589.patch new file mode 100644 index 0000000..b305b9f --- /dev/null +++ b/roles/kde/patches/plasma-workspace/pr5589.patch @@ -0,0 +1,37 @@ +From 2278398309c68ce401a8e35b193fca3782391a4a Mon Sep 17 00:00:00 2001 +From: Nate Graham +Date: Thu, 12 Jun 2025 09:15:15 -0600 +Subject: [PATCH] applets/devicenotifier: use standard section header + +No need for a custom header here when we have a standard one. + +CCBUG: 442724 +--- + .../package/contents/ui/FullRepresentation.qml | 10 ++-------- + 1 file changed, 2 insertions(+), 8 deletions(-) + +diff --git a/applets/devicenotifier/package/contents/ui/FullRepresentation.qml b/applets/devicenotifier/package/contents/ui/FullRepresentation.qml +index 967223e0a1a..a0c28df45fe 100644 +--- a/applets/devicenotifier/package/contents/ui/FullRepresentation.qml ++++ b/applets/devicenotifier/package/contents/ui/FullRepresentation.qml +@@ -132,15 +132,9 @@ PlasmaExtras.Representation { + + section { + property: "deviceType" +- delegate: Item { +- height: Math.floor(childrenRect.height) ++ delegate: PlasmaExtras.ListSectionHeader { + width: notifierDialog.width - (scrollView.PlasmaComponents3.ScrollBar.vertical.visible ? Kirigami.Units.largeSpacing * 2 : 0) +- Kirigami.Heading { +- level: 3 +- opacity: 0.6 +- text: section +- textFormat: Text.PlainText +- } ++ text: section + } + } + +-- +GitLab + diff --git a/roles/kde/patches/plasma-workspace/pr5626.patch b/roles/kde/patches/plasma-workspace/pr5626.patch new file mode 100644 index 0000000..6ad487f --- /dev/null +++ b/roles/kde/patches/plasma-workspace/pr5626.patch @@ -0,0 +1,102 @@ +From 9fabf42c39a25308739dd3483881cc889243bf58 Mon Sep 17 00:00:00 2001 +From: Kristen McWilliam +Date: Tue, 24 Jun 2025 17:44:33 -0400 +Subject: [PATCH] applets/notifications: Add actions to missed notifications + notification + +When a notification is displayed informing the user that notifications were missed while in Do Not +Disturb, now clicking the button or the notification itself will open the notifications applet to +show the missed notifications. + +BUG: 502423 +--- + .../package/contents/ui/main.qml | 10 ++++++ + libnotificationmanager/notifications.cpp | 34 ++++++++++++++----- + libnotificationmanager/notifications.h | 7 ++++ + 3 files changed, 42 insertions(+), 9 deletions(-) + +diff --git a/applets/notifications/package/contents/ui/main.qml b/applets/notifications/package/contents/ui/main.qml +index c96afa0558d..ab66a52f45e 100644 +--- a/applets/notifications/package/contents/ui/main.qml ++++ b/applets/notifications/package/contents/ui/main.qml +@@ -268,4 +268,14 @@ PlasmoidItem { + Component.onDestruction: { + Globals.forget(root); + } ++ ++ Connections { ++ target: Globals.popupNotificationsModel ++ ++ // The user requested to show the notifications popup, probably by ++ // clicking the "Missed Notifications in Do Not Disturb" notification. ++ function onShowNotificationsRequested(): void { ++ root.expanded = true; ++ } ++ } + } +diff --git a/libnotificationmanager/notifications.cpp b/libnotificationmanager/notifications.cpp +index 2cba1360b10..7b5cd9d68c4 100644 +--- a/libnotificationmanager/notifications.cpp ++++ b/libnotificationmanager/notifications.cpp +@@ -903,15 +903,31 @@ void Notifications::showInhibitionSummary(Urgency urgency, const QStringList &bl + return; + } + +- KNotification::event(u"inhibitionSummary"_s, +- i18ncp("@title", "Unread Notification", "Unread Notifications", inhibited), +- i18ncp("@info", +- "%1 notification was received while Do Not Disturb was active.", +- "%1 notifications were received while Do Not Disturb was active.", +- inhibited), +- u"preferences-desktop-notification-bell"_s, +- KNotification::CloseOnTimeout, +- u"libnotificationmanager"_s); ++ KNotification *notification = new KNotification(u"inhibitionSummary"_s); ++ notification->setTitle(i18ncp("@title", "Unread Notification", "Unread Notifications", inhibited)); ++ notification->setText(i18ncp("@info", ++ "%1 notification was received while Do Not Disturb was active.", ++ "%1 notifications were received while Do Not Disturb was active.", ++ inhibited)); ++ notification->setIconName(u"preferences-desktop-notification-bell"_s); ++ notification->setFlags(KNotification::CloseOnTimeout); ++ notification->setComponentName(u"libnotificationmanager"_s); ++ ++ const QString showNotificationsText = i18nc( // ++ "@action:button Show the notifications popup; translate this in as short a form as possible", ++ "Show Notifications"); ++ ++ const KNotificationAction *defaultShowNotificationsAction = notification->addDefaultAction(showNotificationsText); ++ connect(defaultShowNotificationsAction, &KNotificationAction::activated, this, [this]() { ++ Q_EMIT showNotificationsRequested(); ++ }); ++ ++ const KNotificationAction *showNotificationsAction = notification->addAction(showNotificationsText); ++ connect(showNotificationsAction, &KNotificationAction::activated, this, [this]() { ++ Q_EMIT showNotificationsRequested(); ++ }); ++ ++ notification->sendEvent(); + } + + QVariant Notifications::data(const QModelIndex &index, int role) const +diff --git a/libnotificationmanager/notifications.h b/libnotificationmanager/notifications.h +index e927c472c8f..8544b6271ae 100644 +--- a/libnotificationmanager/notifications.h ++++ b/libnotificationmanager/notifications.h +@@ -597,6 +597,13 @@ Q_SIGNALS: + void jobsPercentageChanged(); + void windowChanged(QWindow *window); + ++ /** ++ * Emitted when the user has requested to show the notifications popup. ++ * ++ * This is typically connected to a button in the "Missed Notifications in Do Not Disturb" notification. ++ */ ++ void showNotificationsRequested(); ++ + protected: + void classBegin() override; + void componentComplete() override; +-- +GitLab + diff --git a/roles/kde/patches/plasma-workspace/pr5627.patch b/roles/kde/patches/plasma-workspace/pr5627.patch new file mode 100644 index 0000000..dc61149 --- /dev/null +++ b/roles/kde/patches/plasma-workspace/pr5627.patch @@ -0,0 +1,157 @@ +From 1641ea3897d565d672e29a7524ce4171ddbcfd32 Mon Sep 17 00:00:00 2001 +From: Vlad Zahorodnii +Date: Wed, 25 Jun 2025 14:00:05 +0300 +Subject: [PATCH 1/2] shell: Set desktop ksplash stage when all desktop views + are ready + +If there are two outputs and the wallpaper for the first one is ready +but for the second one is not, the ksplash will be notified about the +desktop stage. + +This changes fixes checkAllDesktopsUiReady() so it sets the desktop +stage only if all desktop views are ready. +--- + shell/shellcorona.cpp | 28 +++++++++++++++------------- + shell/shellcorona.h | 2 +- + 2 files changed, 16 insertions(+), 14 deletions(-) + +diff --git a/shell/shellcorona.cpp b/shell/shellcorona.cpp +index 37d93d05353..65c8ccc272e 100644 +--- a/shell/shellcorona.cpp ++++ b/shell/shellcorona.cpp +@@ -1338,6 +1338,8 @@ void ShellCorona::removeDesktop(DesktopView *desktopView) + + desktopView->destroy(); + desktopView->containment()->reactToScreenChange(); ++ ++ checkAllDesktopsUiReady(); + } + + PanelView *ShellCorona::panelView(Plasma::Containment *containment) const +@@ -1529,22 +1531,22 @@ void ShellCorona::addOutput(QScreen *screen) + #endif + } + +-void ShellCorona::checkAllDesktopsUiReady(bool ready) ++void ShellCorona::checkAllDesktopsUiReady() + { +- if (!ready) ++ const bool ready = std::ranges::all_of(std::as_const(m_desktopViewForScreen), [](const DesktopView *view) { ++ return view->containment()->isUiReady(); ++ }); ++ if (!ready) { + return; +- for (auto v : std::as_const(m_desktopViewForScreen)) { +- if (!v->containment()->isUiReady()) +- return; +- +- qCDebug(PLASMASHELL) << "Plasma Shell startup completed"; +- QDBusMessage ksplashProgressMessage = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KSplash"), +- QStringLiteral("/KSplash"), +- QStringLiteral("org.kde.KSplash"), +- QStringLiteral("setStage")); +- ksplashProgressMessage.setArguments(QList() << QStringLiteral("desktop")); +- QDBusConnection::sessionBus().asyncCall(ksplashProgressMessage); + } ++ ++ qCDebug(PLASMASHELL) << "Plasma Shell startup completed"; ++ QDBusMessage ksplashProgressMessage = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KSplash"), ++ QStringLiteral("/KSplash"), ++ QStringLiteral("org.kde.KSplash"), ++ QStringLiteral("setStage")); ++ ksplashProgressMessage.setArguments(QList() << QStringLiteral("desktop")); ++ QDBusConnection::sessionBus().asyncCall(ksplashProgressMessage); + } + + Plasma::Containment *ShellCorona::createContainmentForActivity(const QString &activity, int screenNum) +diff --git a/shell/shellcorona.h b/shell/shellcorona.h +index 0f544ca266f..7b6c6a559a1 100644 +--- a/shell/shellcorona.h ++++ b/shell/shellcorona.h +@@ -275,7 +275,7 @@ private: + DesktopView *desktopForScreen(QScreen *screen) const; + void setupWaylandIntegration(); + void executeSetupPlasmoidScript(Plasma::Containment *containment, Plasma::Applet *applet); +- void checkAllDesktopsUiReady(bool ready); ++ void checkAllDesktopsUiReady(); + void activateLauncherMenu(const QString &screenName); + void handleColorRequestedFromDBus(const QDBusMessage &msg); + +-- +GitLab + + +From 00bd19ecaccbb10d5cfcc6006b06a9714c615741 Mon Sep 17 00:00:00 2001 +From: Vlad Zahorodnii +Date: Wed, 25 Jun 2025 14:03:28 +0300 +Subject: [PATCH 2/2] shell: Create panel views after desktop views are ready + +With the current code, the splash screen will be hidden as soon as the +wallpapers are loaded. + +However, the splash screnn is actually notified about the desktop stage +about 1-1.5 second later after the wallpaper plugin resets the loading +property. + +The reason for that is that the panel is loaded between +`wallpaper.loading = false` in the wallpaper and ShellCorona::checkAllDesktopsUiReady(). + +This change re-arranges the startup sequence so the panels are loaded +after the desktop views become ready. It reduces plasma startup time a bit. + +In long term, we should look for making panel loading as async as +possible so the main thread doesn't get blocked for too long. +--- + shell/shellcorona.cpp | 15 +++++---------- + 1 file changed, 5 insertions(+), 10 deletions(-) + +diff --git a/shell/shellcorona.cpp b/shell/shellcorona.cpp +index 65c8ccc272e..6a59683dba7 100644 +--- a/shell/shellcorona.cpp ++++ b/shell/shellcorona.cpp +@@ -868,10 +868,6 @@ void ShellCorona::load() + connect(m_screenPool, &ScreenPool::screenOrderChanged, this, &ShellCorona::handleScreenOrderChanged, Qt::UniqueConnection); + connect(m_screenPool, &ScreenPool::screenRemoved, this, &ShellCorona::handleScreenRemoved, Qt::UniqueConnection); + +- if (!m_waitingPanels.isEmpty()) { +- m_waitingPanelsTimer.start(); +- } +- + if (config()->isImmutable() || !KAuthorized::authorize(QStringLiteral("plasma/plasmashell/unlockedDesktop"))) { + setImmutability(Plasma::Types::SystemImmutable); + } else { +@@ -1517,11 +1513,6 @@ void ShellCorona::addOutput(QScreen *screen) + // in the list. We still don't want to have an invisible view added. + containment->reactToScreenChange(); + +- // were there any panels for this screen before it popped up? +- if (!m_waitingPanels.isEmpty()) { +- m_waitingPanelsTimer.start(); +- } +- + if (!m_screenReorderInProgress) { + Q_EMIT availableScreenRectChanged(m_screenPool->idForScreen(screen)); + } +@@ -1547,6 +1538,10 @@ void ShellCorona::checkAllDesktopsUiReady() + QStringLiteral("setStage")); + ksplashProgressMessage.setArguments(QList() << QStringLiteral("desktop")); + QDBusConnection::sessionBus().asyncCall(ksplashProgressMessage); ++ ++ if (!m_waitingPanels.isEmpty()) { ++ m_waitingPanelsTimer.start(); ++ } + } + + Plasma::Containment *ShellCorona::createContainmentForActivity(const QString &activity, int screenNum) +@@ -1604,7 +1599,7 @@ void ShellCorona::createWaitingPanels() + + QScreen *screen = m_screenPool->screenForId(requestedScreen); + DesktopView *desktopView = desktopForScreen(screen); +- if (!screen || !desktopView) { ++ if (!screen || !desktopView || !desktopView->containment()->isUiReady()) { + stillWaitingPanels << cont; + continue; + } +-- +GitLab + diff --git a/roles/kde/patches/plasma-workspace/pr5628.patch b/roles/kde/patches/plasma-workspace/pr5628.patch new file mode 100644 index 0000000..aa2a3dd --- /dev/null +++ b/roles/kde/patches/plasma-workspace/pr5628.patch @@ -0,0 +1,277 @@ +From 9171f24afe6be37e1ac384b8ef03ff89b552be7a Mon Sep 17 00:00:00 2001 +From: Vlad Zahorodnii +Date: Wed, 25 Jun 2025 17:25:37 +0300 +Subject: [PATCH 1/2] startkde: Remove Before=plasma-ksplash-ready.service + +This line makes ksplash wait for unrelated services such as powerdevil +and baloo. + +This change reduces plasma startup time on my machine to 2-3 seconds. +--- + startkde/systemd/plasma-workspace.target | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/startkde/systemd/plasma-workspace.target b/startkde/systemd/plasma-workspace.target +index a62db5e252a..a9113f49112 100644 +--- a/startkde/systemd/plasma-workspace.target ++++ b/startkde/systemd/plasma-workspace.target +@@ -17,7 +17,6 @@ Wants=xdg-desktop-autostart.target + BindsTo=graphical-session.target + Before=graphical-session.target + Before=xdg-desktop-autostart.target +-Before=plasma-ksplash-ready.service + Before=plasma-restoresession.service + RefuseManualStart=yes + StopWhenUnneeded=true +-- +GitLab + + +From 8202ba92b610c691b8bc6bab8ad5a1c3b9ac73da Mon Sep 17 00:00:00 2001 +From: Vlad Zahorodnii +Date: Wed, 25 Jun 2025 17:29:19 +0300 +Subject: [PATCH 2/2] startkde: Drop ready stage + +In order to hide the splash screen, it is sufficient just to see the +wallpaper. If more desktop environment components are loaded soon +afterwards, it is okay. + +With systemd boot, the way the ready stage is integrated is also kind +of a hack. +--- + appiumtests/CMakeLists.txt | 1 - + appiumtests/ksplash/CMakeLists.txt | 14 ---- + appiumtests/ksplash/ksplashtest.py | 79 ------------------- + ksplash/ksplashqml/splashapp.cpp | 5 +- + startkde/plasma-session/startup.cpp | 11 --- + startkde/plasma-session/startup.h | 1 - + startkde/systemd/CMakeLists.txt | 3 - + .../systemd/plasma-ksplash-ready.service.in | 10 --- + startkde/systemd/plasma-workspace.target | 1 - + 9 files changed, 2 insertions(+), 123 deletions(-) + delete mode 100644 appiumtests/ksplash/CMakeLists.txt + delete mode 100755 appiumtests/ksplash/ksplashtest.py + delete mode 100644 startkde/systemd/plasma-ksplash-ready.service.in + +diff --git a/appiumtests/CMakeLists.txt b/appiumtests/CMakeLists.txt +index 68d0b6895ba..22234aeb031 100644 +--- a/appiumtests/CMakeLists.txt ++++ b/appiumtests/CMakeLists.txt +@@ -20,5 +20,4 @@ add_subdirectory(applets) + add_subdirectory(components_tests) + add_subdirectory(kcms) + add_subdirectory(krunner) +-add_subdirectory(ksplash) + add_subdirectory(wallpapers) +diff --git a/appiumtests/ksplash/CMakeLists.txt b/appiumtests/ksplash/CMakeLists.txt +deleted file mode 100644 +index 3bea5174f5a..00000000000 +--- a/appiumtests/ksplash/CMakeLists.txt ++++ /dev/null +@@ -1,14 +0,0 @@ +-# SPDX-FileCopyrightText: 2024 Fushan Wen +-# SPDX-License-Identifier: BSD-3-Clause +- +-add_test( +- NAME ksplashtest_wayland +- COMMAND sh -c "mkdir -p /tmp/appium/ksplashtest_wayland;dbus-launch selenium-webdriver-at-spi-run ${CMAKE_CURRENT_SOURCE_DIR}/ksplashtest.py --failfast" +-) +-set_tests_properties(ksplashtest_wayland PROPERTIES TIMEOUT 60 ENVIRONMENT "XDG_RUNTIME_DIR=/tmp/appium/ksplashtest_wayland;FLASK_PORT=5701") +- +-add_test( +- NAME ksplashtest_x11 +- COMMAND sh -c "mkdir -p /tmp/appium/ksplashtest_x11;dbus-launch selenium-webdriver-at-spi-run ${CMAKE_CURRENT_SOURCE_DIR}/ksplashtest.py --failfast" +-) +-set_tests_properties(ksplashtest_x11 PROPERTIES TIMEOUT 60 ENVIRONMENT "XDG_RUNTIME_DIR=/tmp/appium/ksplashtest_x11;FLASK_PORT=5702;TEST_WITH_KWIN_WAYLAND=0") +diff --git a/appiumtests/ksplash/ksplashtest.py b/appiumtests/ksplash/ksplashtest.py +deleted file mode 100755 +index b7ca43c0519..00000000000 +--- a/appiumtests/ksplash/ksplashtest.py ++++ /dev/null +@@ -1,79 +0,0 @@ +-#!/usr/bin/env python3 +- +-# SPDX-FileCopyrightText: 2024 Fushan Wen +-# SPDX-License-Identifier: MIT +- +-# pylint: disable=too-many-arguments +- +-import os +-import subprocess +-import sys +-import time +-import unittest +- +-from appium import webdriver +-from appium.options.common.base import AppiumOptions +-from appium.webdriver.common.appiumby import AppiumBy +-from gi.repository import Gio, GLib +- +- +-class KSplashTest(unittest.TestCase): +- +- driver: webdriver.Remote +- +- @classmethod +- def setUpClass(cls) -> None: +- options = AppiumOptions() +- options.set_capability("app", "ksplashqml --window") +- options.set_capability("environ", { +- "LC_ALL": "en_US.UTF-8", +- "QT_FATAL_WARNINGS": "1", +- "QT_LOGGING_RULES": "qt.accessibility.atspi.warning=false;kf.plasma.core.warning=false;kf.windowsystem.warning=false;kf.kirigami.platform.warning=false;org.kde.plasma.ksplashqml.debug=true", +- }) +- options.set_capability("timeouts", {'implicit': 10000}) +- cls.driver = webdriver.Remote(command_executor=f'http://127.0.0.1:{os.getenv("FLASK_PORT", "4723")}', options=options) +- +- def tearDown(self) -> None: +- """ +- Take screenshot when the current test fails +- """ +- if not self._outcome.result.wasSuccessful(): +- self.driver.get_screenshot_as_file(f"failed_test_shot_ksplash_#{self.id()}.png") +- +- def test_1_bug494840_setStage(self) -> None: +- """ +- Checks if the setStage method is ever called after starting plasma-ksplash-ready.service. +- """ +- if os.getenv("TEST_WITH_KWIN_WAYLAND", "1") == "0": +- stages = ("wm", "kcminit", "ksmserver", "startPlasma", "desktop") +- else: +- stages = ("kcminit", "ksmserver", "startPlasma", "desktop") +- +- session_bus = Gio.bus_get_sync(Gio.BusType.SESSION) +- for stage in stages: +- message: Gio.DBusMessage = Gio.DBusMessage.new_method_call("org.kde.KSplash", "/KSplash", "org.kde.KSplash", "setStage") +- message.set_body(GLib.Variant("(s)", [stage])) +- session_bus.send_message_with_reply_sync(message, Gio.DBusSendMessageFlags.NONE, 3000) +- +- self.driver.find_element(AppiumBy.NAME, "Plasma made by KDE") +- +- with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir, os.pardir, "startkde", "systemd", "plasma-ksplash-ready.service.in"), encoding="utf-8") as handler: +- for line in handler: +- if line.startswith("ExecStart="): +- command = line.removeprefix("ExecStart=").strip().split(" ") +- subprocess.check_call(command, stdout=sys.stderr, stderr=sys.stderr) +- break +- +- success = False +- for _ in range(10): +- try: +- subprocess.check_call(["pidof", "ksplashqml"]) +- except subprocess.CalledProcessError: +- success = True +- break +- time.sleep(1) +- self.assertTrue(success) +- +- +-if __name__ == '__main__': +- unittest.main() +diff --git a/ksplash/ksplashqml/splashapp.cpp b/ksplash/ksplashqml/splashapp.cpp +index b60a58724be..2262503b1c3 100644 +--- a/ksplash/ksplashqml/splashapp.cpp ++++ b/ksplash/ksplashqml/splashapp.cpp +@@ -26,13 +26,12 @@ + #define TEST_STEP_INTERVAL 2000 + + /** +- * There are 7 stages in ksplash ++ * There are 6 stages in ksplash + * - initial (from this class) + * - startPlasma (from startplasma) + * - kcminit + * - ksmserver + * - wm (for X11 from KWin, for Wayland from this class) +- * - ready (from plasma-session startup) + * - desktop (from shellcorona) + */ + +@@ -114,7 +113,7 @@ void SplashApp::setStage(const QString &stage) + void SplashApp::setStage(int stage) + { + m_stage = stage; +- if (m_stage == 7) { ++ if (m_stage == 6) { + QGuiApplication::exit(EXIT_SUCCESS); + } + for (SplashWindow *w : std::as_const(m_windows)) { +diff --git a/startkde/plasma-session/startup.cpp b/startkde/plasma-session/startup.cpp +index a731c7b2791..0567e00881f 100644 +--- a/startkde/plasma-session/startup.cpp ++++ b/startkde/plasma-session/startup.cpp +@@ -206,20 +206,9 @@ Startup::Startup(QObject *parent) + // app will be closed when all KJobs finish thanks to the QEventLoopLocker in each KJob + } + +-void Startup::upAndRunning(const QString &msg) +-{ +- QDBusMessage ksplashProgressMessage = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KSplash"), +- QStringLiteral("/KSplash"), +- QStringLiteral("org.kde.KSplash"), +- QStringLiteral("setStage")); +- ksplashProgressMessage.setArguments(QList() << msg); +- QDBusConnection::sessionBus().asyncCall(ksplashProgressMessage); +-} +- + void Startup::finishStartup() + { + qCDebug(PLASMA_SESSION) << "Finished"; +- upAndRunning(QStringLiteral("ready")); + + playStartupSound(); + new SessionTrack(m_processes); +diff --git a/startkde/plasma-session/startup.h b/startkde/plasma-session/startup.h +index 6ef4fee9bdd..876a1439fce 100644 +--- a/startkde/plasma-session/startup.h ++++ b/startkde/plasma-session/startup.h +@@ -20,7 +20,6 @@ class Startup : public QObject + Q_OBJECT + public: + Startup(QObject *parent); +- void upAndRunning(const QString &msg); + void finishStartup(); + + static Startup *self() +diff --git a/startkde/systemd/CMakeLists.txt b/startkde/systemd/CMakeLists.txt +index 2f5d30e8456..c3455ebae81 100644 +--- a/startkde/systemd/CMakeLists.txt ++++ b/startkde/systemd/CMakeLists.txt +@@ -1,6 +1,3 @@ +-ecm_install_configured_files(INPUT plasma-ksplash-ready.service.in @ONLY +- DESTINATION ${KDE_INSTALL_SYSTEMDUSERUNITDIR}) +- + install(FILES plasma-core.target DESTINATION ${KDE_INSTALL_SYSTEMDUSERUNITDIR}) + install(FILES plasma-workspace.target DESTINATION ${KDE_INSTALL_SYSTEMDUSERUNITDIR}) + install(FILES plasma-workspace-wayland.target DESTINATION ${KDE_INSTALL_SYSTEMDUSERUNITDIR}) +diff --git a/startkde/systemd/plasma-ksplash-ready.service.in b/startkde/systemd/plasma-ksplash-ready.service.in +deleted file mode 100644 +index 1e903130a96..00000000000 +--- a/startkde/systemd/plasma-ksplash-ready.service.in ++++ /dev/null +@@ -1,10 +0,0 @@ +-[Unit] +-Description=KSplash "ready" Stage +-Wants=plasma-core.target +-After=plasma-core.target +-PartOf=graphical-session.target +- +-[Service] +-Type=oneshot +-ExecStart=dbus-send --session --reply-timeout=1 --type=method_call --dest=org.kde.KSplash /KSplash org.kde.KSplash.setStage string:ready +-Slice=session.slice +diff --git a/startkde/systemd/plasma-workspace.target b/startkde/systemd/plasma-workspace.target +index a9113f49112..4cc8d9330c3 100644 +--- a/startkde/systemd/plasma-workspace.target ++++ b/startkde/systemd/plasma-workspace.target +@@ -6,7 +6,6 @@ Wants=plasma-restoresession.service + Wants=plasma-xembedsniproxy.service + Wants=plasma-gmenudbusmenuproxy.service + Wants=plasma-powerdevil.service +-Wants=plasma-ksplash-ready.service + Wants=plasma-polkit-agent.service + Wants=kde-baloo.service + Wants=plasma-foreground-booster.service +-- +GitLab + diff --git a/roles/kde/patches/spectacle/patches.txt b/roles/kde/patches/spectacle/patches.txt new file mode 100644 index 0000000..587da02 --- /dev/null +++ b/roles/kde/patches/spectacle/patches.txt @@ -0,0 +1,3 @@ +Plasma 6.5.0: + +Pr 460 https://invent.kde.org/plasma/spectacle/-/merge_requests/460 diff --git a/roles/kde/patches/spectacle/pr460.patch b/roles/kde/patches/spectacle/pr460.patch new file mode 100644 index 0000000..63c6cfa --- /dev/null +++ b/roles/kde/patches/spectacle/pr460.patch @@ -0,0 +1,228 @@ +From 97f209559c00acc1ea6d0736bb318ac0254a3e37 Mon Sep 17 00:00:00 2001 +From: Noah Davis +Date: Wed, 4 Jun 2025 16:06:06 -0400 +Subject: [PATCH 1/2] Say which shortcut can stop recording in recording + notification + +It will say Meta+R by default, but will show the shortcut used to initiate the recording instead if Meta+R isn't assigned to region recording. + +BUG: 505081 +--- + src/Platforms/VideoPlatform.cpp | 16 +++++++++++++++ + src/Platforms/VideoPlatform.h | 4 ++++ + src/Platforms/VideoPlatformWayland.cpp | 6 ++++-- + src/SpectacleCore.cpp | 28 +++++++++++++++++++++++++- + 4 files changed, 51 insertions(+), 3 deletions(-) + +diff --git a/src/Platforms/VideoPlatform.cpp b/src/Platforms/VideoPlatform.cpp +index 592539048..333692326 100644 +--- a/src/Platforms/VideoPlatform.cpp ++++ b/src/Platforms/VideoPlatform.cpp +@@ -69,6 +69,11 @@ VideoPlatform::RecordingState VideoPlatform::recordingState() const + return m_recordingState; + } + ++VideoPlatform::RecordingMode VideoPlatform::recordingMode() const ++{ ++ return m_recordingMode; ++} ++ + void VideoPlatform::setRecordingState(RecordingState state) + { + if (state == m_recordingState) { +@@ -91,9 +96,20 @@ void VideoPlatform::setRecordingState(RecordingState state) + m_elapsedTimer.invalidate(); + m_basicTimer.stop(); + } ++ if (state != RecordingState::Recording) { ++ setRecordingMode(NoRecordingModes); ++ } + m_recordingState = state; + Q_EMIT recordingStateChanged(state); + Q_EMIT recordedTimeChanged(); + } + ++void VideoPlatform::setRecordingMode(RecordingMode mode) ++{ ++ if (m_recordingMode == mode) { ++ return; ++ } ++ m_recordingMode = mode; ++} ++ + #include "moc_VideoPlatform.cpp" +diff --git a/src/Platforms/VideoPlatform.h b/src/Platforms/VideoPlatform.h +index 4f5fba720..eea81e32a 100644 +--- a/src/Platforms/VideoPlatform.h ++++ b/src/Platforms/VideoPlatform.h +@@ -129,8 +129,11 @@ public: + + RecordingState recordingState() const; + ++ RecordingMode recordingMode() const; ++ + protected: + void setRecordingState(RecordingState state); ++ void setRecordingMode(RecordingMode mode); + void timerEvent(QTimerEvent *event) override; + + public Q_SLOTS: +@@ -157,6 +160,7 @@ private: + QBasicTimer m_basicTimer; + qint64 m_recordedTime = 0; + RecordingState m_recordingState = RecordingState::NotRecording; ++ RecordingMode m_recordingMode = RecordingMode::NoRecordingModes; + }; + + Q_DECLARE_OPERATORS_FOR_FLAGS(VideoPlatform::RecordingModes) +diff --git a/src/Platforms/VideoPlatformWayland.cpp b/src/Platforms/VideoPlatformWayland.cpp +index 648f8adbf..984ede0eb 100644 +--- a/src/Platforms/VideoPlatformWayland.cpp ++++ b/src/Platforms/VideoPlatformWayland.cpp +@@ -245,11 +245,12 @@ void VideoPlatformWayland::startRecording(const QUrl &fileUrl, RecordingMode rec + m_recorder->setNodeId(0); + + Q_ASSERT(stream); +- connect(stream, &ScreencastingStream::created, this, [this, stream] { ++ connect(stream, &ScreencastingStream::created, this, [this, stream, recordingMode] { + m_recorder->setNodeId(stream->nodeId()); + if (!m_recorder->output().isEmpty()) { + m_recorder->start(); + } ++ setRecordingMode(recordingMode); + setRecordingState(VideoPlatform::RecordingState::Recording); + }); + connect(stream, &ScreencastingStream::failed, this, [this](const QString &error) { +@@ -302,7 +303,7 @@ void VideoPlatformWayland::startRecording(const QUrl &fileUrl, RecordingMode rec + m_recorder->start(); + } + +- connect(m_recorder.get(), &PipeWireRecord::stateChanged, this, [this] { ++ connect(m_recorder.get(), &PipeWireRecord::stateChanged, this, [this, recordingMode] { + if (m_recorder->state() == PipeWireRecord::Idle) { + m_memoryTimer.stop(); + if (recordingState() != RecordingState::NotRecording && recordingState() != RecordingState::Finished) { +@@ -311,6 +312,7 @@ void VideoPlatformWayland::startRecording(const QUrl &fileUrl, RecordingMode rec + } + } else if (m_recorder->state() == PipeWireRecord::Recording) { + m_memoryTimer.start(5000, Qt::CoarseTimer, this); ++ setRecordingMode(recordingMode); + setRecordingState(VideoPlatform::RecordingState::Recording); + } else if (m_recorder->state() == PipeWireRecord::Rendering) { + m_memoryTimer.stop(); +diff --git a/src/SpectacleCore.cpp b/src/SpectacleCore.cpp +index 8033751c9..aa2e8c6e1 100644 +--- a/src/SpectacleCore.cpp ++++ b/src/SpectacleCore.cpp +@@ -281,7 +281,33 @@ SpectacleCore::SpectacleCore(QObject *parent) + SpectacleCore::instance()->finishRecording(); + }); + const auto messageTitle = i18nc("recording notification title", "Spectacle is Recording"); +- const auto messageBody = i18nc("recording notification message", "Click the system tray icon to finish recording"); ++ auto getSimpleDefaultShortcut = [] { ++ const auto shortcuts = KGlobalAccel::self()->shortcut(ShortcutActions::self()->recordRegionAction()); ++ if (shortcuts.contains(QKeySequence{Qt::META | Qt::Key_R})) { ++ return QKeySequence{Qt::META | Qt::Key_R}; ++ } ++ return QKeySequence{}; ++ }; ++ auto getShortcut = [](const auto &list) { ++ auto it = std::find_if(list.cbegin(), list.cend(), [](const QKeySequence &shortcut) { ++ return !shortcut.isEmpty(); ++ }); ++ return it != list.cend() ? *it : QKeySequence{}; ++ }; ++ QKeySequence stopShortcut = getSimpleDefaultShortcut(); ++ if (stopShortcut.isEmpty()) { ++ auto mode = m_videoPlatform->recordingMode(); ++ if (mode == VideoPlatform::Screen) { ++ stopShortcut = getShortcut(KGlobalAccel::self()->shortcut(ShortcutActions::self()->recordScreenAction())); ++ } else if (mode == VideoPlatform::Window) { ++ stopShortcut = getShortcut(KGlobalAccel::self()->shortcut(ShortcutActions::self()->recordWindowAction())); ++ } else if (mode == VideoPlatform::Region) { ++ stopShortcut = getShortcut(KGlobalAccel::self()->shortcut(ShortcutActions::self()->recordRegionAction())); ++ } ++ } ++ const auto messageBody = stopShortcut.isEmpty() ++ ? i18nc("recording notification message without shortcut", "To finish the recording, click the pulsing red System Tray icon.") ++ : xi18nc("recording notification message with shortcut", "To finish the recording, click the pulsing red System Tray icon or press %1.", stopShortcut.toString(QKeySequence::NativeText)); + auto notification = new KNotification(u"notification"_s, KNotification::CloseOnTimeout | KNotification::DefaultEvent, this); + notification->setTitle(messageTitle); + notification->setText(messageBody); +-- +GitLab + + +From 7fd2fe158408b6285f9855f7fcf34e77d4ecb764 Mon Sep 17 00:00:00 2001 +From: Noah Davis +Date: Thu, 12 Jun 2025 16:12:48 -0400 +Subject: [PATCH 2/2] Rename shortcuts to show that they can start or stop + recording + +BUG: 505081 +--- + desktop/org.kde.spectacle.desktop.cmake | 6 +++--- + src/ShortcutActions.cpp | 6 +++--- + 2 files changed, 6 insertions(+), 6 deletions(-) + +diff --git a/desktop/org.kde.spectacle.desktop.cmake b/desktop/org.kde.spectacle.desktop.cmake +index 4e36b18e6..800fcb62b 100644 +--- a/desktop/org.kde.spectacle.desktop.cmake ++++ b/desktop/org.kde.spectacle.desktop.cmake +@@ -474,7 +474,7 @@ Exec=${KDE_INSTALL_FULL_BINDIR}/spectacle -u + X-KDE-Shortcuts=Meta+Ctrl+Print + + [Desktop Action RecordRegion] +-Name=Record Rectangular Region ++Name=Start/Stop Region Recording + Name[ar]=يسجل منطقة مستطيلة + Name[az]=Düzbucaqlı sahəni yazmaq + Name[bg]=Заснемане на правоъгълен регион +@@ -522,7 +522,7 @@ Exec=${KDE_INSTALL_FULL_BINDIR}/spectacle -R region + X-KDE-Shortcuts=Meta+Shift+R,Meta+R + + [Desktop Action RecordScreen] +-Name=Record Screen ++Name=Start/Stop Screen Recording + Name[ar]=سجّل الشاشة + Name[az]=Ekranı yazmaq + Name[bg]=Запис на екрана +@@ -570,7 +570,7 @@ Exec=${KDE_INSTALL_FULL_BINDIR}/spectacle -R screen + X-KDE-Shortcuts=Meta+Alt+R + + [Desktop Action RecordWindow] +-Name=Record Window ++Name=Start/Stop Window Recording + Name[ar]=سجل النافذة + Name[az]=Pəncərəni yazmaq + Name[bg]=Запис на прозорец +diff --git a/src/ShortcutActions.cpp b/src/ShortcutActions.cpp +index ee73bcfe5..a1464d06d 100644 +--- a/src/ShortcutActions.cpp ++++ b/src/ShortcutActions.cpp +@@ -73,19 +73,19 @@ ShortcutActions::ShortcutActions() + mActions.addAction(action->objectName(), action); + } + { +- QAction *action = new QAction(i18nc("@action global shortcut", "Record Screen"), &mActions); ++ QAction *action = new QAction(i18nc("@action global shortcut", "Start/Stop Screen Recording"), &mActions); + action->setObjectName(u"RecordScreen"_s); + action->setProperty("isConfigurationAction", true); + mActions.addAction(action->objectName(), action); + } + { +- QAction *action = new QAction(i18nc("@action global shortcut", "Record Window"), &mActions); ++ QAction *action = new QAction(i18nc("@action global shortcut", "Start/Stop Window Recording"), &mActions); + action->setObjectName(u"RecordWindow"_s); + action->setProperty("isConfigurationAction", true); + mActions.addAction(action->objectName(), action); + } + { +- QAction *action = new QAction(i18nc("@action global shortcut", "Record Rectangular Region"), &mActions); ++ QAction *action = new QAction(i18nc("@action global shortcut", "Start/Stop Region Recording"), &mActions); + action->setObjectName(u"RecordRegion"_s); + action->setProperty("isConfigurationAction", true); + mActions.addAction(action->objectName(), action); +-- +GitLab +