aerothemeplasma/plasma/plasmoids/io.gitgud.wackyideas.desktopcontainment/contents/ui/FolderItemDelegate.qml
wackyideas 8d6fbd7bdb overhaul: prepare repo for Plasma 6.6 release
This commit overhauls the repository structure such that it separates
the project into multiple repositories, one for each subproject. This
repository will be moved to the AeroShell group, and most importantly,
the installation method changes from locally installed modifications, to
CMake-based installation, in preparation for AUR packages, and possibly
packages for other distros.

Migration details are in INSTALL.md, which are highly recommended, if
not required to uninstall any old instance of AeroThemePlasma.
2026-02-21 21:15:07 +01:00

663 lines
25 KiB
QML

/*
SPDX-FileCopyrightText: 2014-2015 Eike Hein <hein@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.15
import QtQuick.Window 2.15
import Qt5Compat.GraphicalEffects
import org.kde.plasma.plasmoid 2.0
import org.kde.plasma.core as PlasmaCore
import org.kde.plasma.extras as PlasmaExtras
import org.kde.plasma.components as PlasmaComponents
import org.kde.kirigami 2.20 as Kirigami
import org.kde.ksvg 1.0 as KSvg
import org.kde.kquickcontrolsaddons 2.0
Item {
id: main
property int index: model.index
property string name: model.blank ? "" : model.display
property string nameWrapped: model.blank ? "" : model.displayWrapped
property bool blank: model.blank
property bool selected: model.blank ? false : model.selected
property bool isDir: loader.item ? loader.item.isDir : false
property QtObject popupDialog: loader.item ? loader.item.popupDialog : null
property Item iconArea: loader.item ? loader.item.iconArea : null
property Item label: loader.item ? loader.item.label : null
property Item labelArea: loader.item ? loader.item.labelArea : null
property Item actionsOverlay: loader.item ? loader.item.actionsOverlay : null
property Item hoverArea: loader.item ? loader.item.hoverArea : null
property Item frame: loader.item ? loader.item.frame : null
property Item toolTip: loader.item ? loader.item.toolTip : null
property bool shouldShowToolTip: false
Accessible.name: name
Accessible.role: Accessible.Canvas
// This MouseArea exists to intercept press and hold; preventing edit mode
// from being triggered when pressing and holding on an icon (if there is one).
MouseArea {
anchors.fill: parent
visible: !main.blank
}
function openPopup() {
if (isDir) {
loader.item.openPopup();
}
}
function closePopup() {
if (popupDialog) {
popupDialog.requestDestroy();
loader.item.popupDialog = null;
}
}
Loader {
id: loader
// On the desktop we pad our cellSize to avoid a gap at the right/bottom of the screen.
// The padding per item is quite small and causes the delegate to be positioned on fractional pixels
// leading to blurry rendering. The Loader is offset to account for this.
x: -main.x % 1
y: -main.y % 1
width: parent.width
height: parent.height
visible: status === Loader.Ready
active: !model.blank
sourceComponent: delegateImplementation
asynchronous: true
}
function updateDragImage() {
if (selected && !blank) {
loader.grabToImage(result => {
dir.addItemDragImage(positioner.map(index), main.x + loader.x, main.y + loader.y, loader.width, loader.height, result.image);
});
}
}
Component {
id: delegateImplementation
Item {
id: impl
anchors.fill: parent
property bool blank: model.blank
property bool selected: model.blank ? false : model.selected
property bool isDir: model.blank ? false : model.isDir
property bool hovered: (main.GridView.view.hoveredItem === main)
property QtObject popupDialog: null
property Item iconArea: icon
property Item label: label
property Item labelArea: label
property Item actionsOverlay: actions
property Item hoverArea: toolTip
property Item frame: frameLoader
property Item toolTip: toolTip
property Item selectionButton: null
property Item popupButton: null
readonly property bool iconAndLabelsShouldlookSelected: impl.hovered
// When a drop happens, a new item is created, and is set to selected
// grabToImagebefore it gets the final width, making grabToImage fail because it's still 0x0
onSelectedChanged: {
Qt.callLater(updateDragImage)
if(selected && (!toolTip.containsMouse) && main.shouldShowToolTip) {
toolTipTimer.start();
main.shouldShowToolTip = false;
} else {
toolTipTimer.stop();
toolTip.hideImmediately();
}
}
function updateDragImage() {
if (selected && !blank) {
frameLoader.grabToImage(result => {
dir.addItemDragImage(positioner.map(index), main.x + frameLoader.x, main.y + frameLoader.y, frameLoader.width, frameLoader.height, result.image);
});
}
}
Connections {
target: model
function onSelectedChanged() {
if (dir.usedByContainment && model.selected) {
gridView.currentIndex = model.index;
}
}
}
onHoveredChanged: {
if (hovered) {
// In list view, it behaves more like a menu, and menus always activate their items on a single click
if (Plasmoid.configuration.selectionMarkers && (Qt.styleHints.singleClickActivation || root.useListViewMode)) {
selectionButton = selectionButtonComponent.createObject(actions);
}
if (model.isDir) {
if (!main.GridView.view.isRootView || root.containsDrag) {
hoverActivateTimer.restart();
}
if (Plasmoid.configuration.popups && !root.useListViewMode) {
popupButton = popupButtonComponent.createObject(actions);
}
}
} else if (!hovered) {
if (popupDialog != null) {
closePopup();
}
if (selectionButton) {
selectionButton.destroy();
selectionButton = null;
}
if (popupButton) {
popupButton.destroy();
popupButton = null;
}
}
}
function openPopup() {
if (folderViewDialogComponent.status === Component.Ready) {
impl.popupDialog = folderViewDialogComponent.createObject(impl);
impl.popupDialog.visualParent = icon;
impl.popupDialog.url = model.linkDestinationUrl;
impl.popupDialog.visible = true;
}
}
Timer {
id: toolTipTimer
interval: 700
onTriggered: {
toolTip.updateToolTip();
toolTip.showToolTip();
}
}
Loader {
id: frameLoader
x: 0//root.useListViewMode ? 0 : Kirigami.Units.smallSpacing
y: root.useListViewMode ? 0 : Kirigami.Units.smallSpacing
property Item iconShadow: null
property string prefix: ""
sourceComponent: frameComponent
active: impl.iconAndLabelsShouldlookSelected || model.selected
asynchronous: true
width: parent.width
height: root.useListViewMode
? parent.height
// the smallSpacings are for padding
: icon.height + (Kirigami.Units.iconSizes.small * label.lineCount) + (Kirigami.Units.smallSpacing * 3)
Kirigami.Icon {
id: iconHighlight
z: 3
states: [
State { // icon view
when: !root.useListViewMode
AnchorChanges {
target: iconHighlight
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
}
},
State { // list view
when: root.useListViewMode
AnchorChanges {
target: iconHighlight
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
}
}
]
anchors {
topMargin: Kirigami.Units.smallSpacing
leftMargin: Kirigami.Units.smallSpacing
}
width: root.useListViewMode ? main.GridView.view.iconSize : (parent.width - 2 * Kirigami.Units.smallSpacing)
height: main.GridView.view.iconSize
isMask: true
color: "#c2e7ed"
opacity: 0.5
animated: false
source: model.decoration
visible: model.selected && Plasmoid.configuration.selectionStyle
}
Kirigami.Icon {
id: icon
z: 2
states: [
State { // icon view
when: !root.useListViewMode
AnchorChanges {
target: icon
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
}
},
State { // list view
when: root.useListViewMode
AnchorChanges {
target: icon
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
}
}
]
anchors {
topMargin: Kirigami.Units.smallSpacing
leftMargin: Kirigami.Units.smallSpacing
}
width: root.useListViewMode ? main.GridView.view.iconSize : (parent.width - 2 * Kirigami.Units.smallSpacing)
height: main.GridView.view.iconSize
opacity: {
if (root.useListViewMode && selectionButton) {
return 0.3;
}
if (model.isHidden) {
return 0.6;
}
return 1.0;
}
animated: false
source: model.decoration
}
PlasmaComponents.Label {
id: label
renderType: Text.NativeRendering
font.hintingPreference: Font.PreferFullHinting
z: 2 // So it's always above the highlight effect
font.kerning: Screen.devicePixelRatio == 1.0
Timer {
id: hidpi_hack
interval: 50
onTriggered: {
front.anchors.rightMargin += 1;
front.anchors.rightMargin -= 1;
behind.anchors.rightMargin += 1;
behind.anchors.rightMargin -= 1;
}
}
// Hacks to improve font rendering to increase contrast and text brightness
// This is done to get darker subpixel rendering, closer to ClearType
PlasmaComponents.Label {
id: behind
z: -1
anchors.fill: parent
anchors.rightMargin: 1
anchors.leftMargin: -1
color: model.selected && Plasmoid.configuration.selectionStyle ? "black" : "#F9000000"
renderType: Text.NativeRendering
font.hintingPreference: Font.PreferFullHinting
font.kerning: Screen.devicePixelRatio == 1.0
text: parent.text
elide: Text.ElideRight
maximumLineCount: parent.maximumLineCount
wrapMode: (maximumLineCount === 1) ? Text.NoWrap : Text.Wrap
horizontalAlignment: Text.AlignHCenter
visible: Plasmoid.configuration.textShadows
}
PlasmaComponents.Label {
id: front
z: -2
anchors.fill: parent
anchors.rightMargin: 0
color: model.selected && Plasmoid.configuration.selectionStyle ? "black" : "#ffffffff"
renderType: Text.NativeRendering
font.hintingPreference: Font.PreferFullHinting
font.kerning: Screen.devicePixelRatio == 1.0
text: parent.text
elide: Text.ElideRight
maximumLineCount: parent.maximumLineCount
wrapMode: (maximumLineCount === 1) ? Text.NoWrap : Text.Wrap
horizontalAlignment: Text.AlignHCenter
visible: Plasmoid.configuration.textShadows
}
states: [
State { // icon view
when: !root.useListViewMode
PropertyChanges {
target: label
anchors.topMargin: Kirigami.Units.smallSpacing
// Ffs
width: Math.floor((parent.width - Kirigami.Units.smallSpacing + (Screen.devicePixelRatio != 1.0) * 2 * Screen.devicePixelRatio) * Screen.devicePixelRatio) / Screen.devicePixelRatio
maximumLineCount: Plasmoid.configuration.textLines
horizontalAlignment: Text.AlignHCenter
}
AnchorChanges {
target: label
anchors.top: icon.bottom
anchors.horizontalCenter: parent.horizontalCenter
}
AnchorChanges {
target: behind
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
}
AnchorChanges {
target: front
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
}
PropertyChanges {
target: behind
anchors.rightMargin: 1
anchors.leftMargin: -1
}
StateChangeScript {
script: {
hidpi_hack.start();
}
}
},
State { // list view
when: root.useListViewMode
AnchorChanges {
target: label
anchors.left: icon.right
anchors.verticalCenter: parent.verticalCenter
}
PropertyChanges {
target: label
anchors.leftMargin: Kirigami.Units.smallSpacing * 2
anchors.rightMargin: Kirigami.Units.smallSpacing * 2
width: parent.width - icon.width - (Kirigami.Units.smallSpacing * 4)
maximumLineCount: 1
horizontalAlignment: Text.AlignLeft
}
}
]
color: {
if (Plasmoid.isContainment) {
if (model.selected && Plasmoid.configuration.selectionStyle) {
return "gray"
} else return "white"
}
return Kirigami.Theme.textColor;
}
opacity: model.isHidden ? 0.6 : 1
text: main.nameWrapped
elide: Text.ElideRight
wrapMode: (maximumLineCount === 1) ? Text.NoWrap : Text.Wrap
horizontalAlignment: Text.AlignHCenter
TextMetrics {
id: textMetrics
text: main.nameWrapped
}
Rectangle {
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
width: textMetrics.advanceWidth + Kirigami.Units.smallSpacing
height: parent.height
color: "#c2e7ed"
visible: model.selected && Plasmoid.configuration.selectionStyle
z: -1
}
layer.enabled: true
layer.effect: DropShadow {
anchors.fill: label
z: 1
horizontalOffset: 1
verticalOffset: 1
radius: 4.0
samples: radius * 2
spread: 0.38
color: Plasmoid.configuration.textShadows ? (model.selected && Plasmoid.configuration.selectionStyle ? "transparent" : "#c8080808") : "transparent"
opacity: (model.isHidden ? 0.6 : 1)
source: label
visible: (Plasmoid.isContainment && (!editor || editor.targetItem !== main))
}
}
Component {
id: frameComponent
KSvg.FrameSvgItem {
imagePath: "widgets/viewitem"
visible: this === frameLoader.item && !Plasmoid.configuration.selectionStyle
property bool hovered: impl.iconAndLabelsShouldlookSelected
property bool pressed: model.selected
prefix: {
if(hovered && pressed) return "selected+hover";
if(hovered) return "hover";
if(pressed) return "selected";
return "normal";
}
}
}
Component {
id: selectionButtonComponent
FolderItemActionButton {
element: model.selected ? "remove" : "add"
onClicked: {
dir.toggleSelected(positioner.map(index));
main.GridView.view.currentIndex = index;
}
}
}
Component {
id: popupButtonComponent
FolderItemActionButton {
visible: main.GridView.view.isRootView && (popupDialog == null)
element: "open"
onClicked: {
dir.setSelected(positioner.map(index));
main.GridView.view.currentIndex = index;
openPopup();
}
}
}
Component {
id: iconShadowComponent
DropShadow {
anchors.fill: icon
z: 1
verticalOffset: 1
radius: 5.0
samples: radius * 2 + 1
spread: 0.05
color: "black"
opacity: model.isHidden ? 0.3 : 0.6
visible: Plasmoid.configuration.iconShadows
source: icon
}
}
}
PlasmaCore.ToolTipArea {
id: toolTip
active: (Plasmoid.configuration.toolTips || label.truncated)
&& popupDialog === null
&& !model.blank
interactive: false
location: {
if(toolTip.containsMouse) {
return PlasmaCore.Types.Floating | PlasmaCore.Types.Desktop
} else {
return root.useListViewMode ? (Plasmoid.location === PlasmaCore.Types.LeftEdge ? PlasmaCore.Types.LeftEdge : PlasmaCore.Types.RightEdge) : Plasmoid.location
}
}
z: 999
function updateToolTip() {
if (toolTip.active && !model.blank) {
toolTip.textFormat = Text.RichText;
toolTip.mainText = model.display;
if (model.size !== undefined) {
toolTip.subText = model.type + "<br>" + "Size: " + model.size;
} else {
toolTip.subText = model.type;
}
}
}
MouseArea {
id: toolTipMA
anchors.fill: parent
hoverEnabled: true
onPositionChanged: {
if (containsMouse) {
toolTip.updateToolTip();
main.GridView.view.hoveredItem = main;
} else if(!containsMouse && main.GridView.view.hoveredItem === main) {
toolTip.hideImmediately();
}
}
}
states: [
State { // icon view
when: !root.useListViewMode
PropertyChanges {
target: toolTip
x: frameLoader.x
y: frameLoader.y
width: frameLoader.width
height: frameLoader.height
}
},
State { // list view
when: root.useListViewMode
AnchorChanges {
target: toolTip
anchors.horizontalCenter: undefined
}
PropertyChanges {
target: toolTip
x: frameLoader.x
y: frameLoader.y
width: frameLoader.width
height: frameLoader.height
}
}
]
}
Column {
id: actions
visible: {
if (main.GridView.view.isRootView && root.containsDrag) {
return false;
}
if (!main.GridView.view.isRootView && main.GridView.view.dialog && main.GridView.view.dialog.containsDrag) {
return false;
}
if (popupDialog) {
return false;
}
return true;
}
anchors {
left: frameLoader.left
top: frameLoader.top
leftMargin: root.useListViewMode ? (icon.x + (icon.width / 2)) - (width / 2) : 0
topMargin: root.useListViewMode ? (icon.y + (icon.height / 2)) - (height / 2) : 0
}
width: implicitWidth
height: implicitHeight
}
Component.onCompleted: {
if (Plasmoid.isContainment && main.GridView.view.isRootView && root.GraphicsInfo.api === GraphicsInfo.OpenGL) {
frameLoader.iconShadow = iconShadowComponent.createObject(frameLoader);
}
}
}
}
}