// SPDX-FileCopyrightText: 2026, Shaun Reed // // SPDX-License-Identifier: GNU General Public License v3.0 or later import QtQuick import QtQuick.Effects import QtQuick.Controls import clide.module 1.0 import Logger 1.0 TreeView { id: fileSystemTreeView property int lastIndex: -1 required property string originalRootDirectory property string rootDirectory signal fileClicked(string filePath) boundsBehavior: Flickable.StopAtBounds boundsMovement: Flickable.StopAtBounds clip: true leftMargin: 5 model: FileSystem rootIndex: FileSystem.setDirectory(fileSystemTreeView.rootDirectory) // Provide our own custom ScrollIndicator for the TreeView. ScrollIndicator.vertical: ScrollIndicator { active: true implicitWidth: 15 contentItem: Rectangle { color: RustColors.scrollbar implicitHeight: 6 implicitWidth: 6 opacity: fileSystemTreeView.movingVertically ? 0.5 : 0.0 Behavior on opacity { OpacityAnimator { duration: 500 } } } } // The delegate represents a single entry in the filesystem. delegate: TreeViewDelegate { id: treeDelegate required property string fileName required property url filePath required property int index implicitHeight: 25 implicitWidth: fileSystemTreeView.width > 0 ? fileSystemTreeView.width : 250 indentation: 12 background: Rectangle { color: current ? RustColors.explorer_text_selected : "transparent" radius: 2.5 } contentItem: Text { color: RustColors.explorer_text text: treeDelegate.fileName } indicator: Image { id: directoryIcon function setSourceImage() { let folderOpen = "data:image/svg+xml;utf8,"; let folderClosed = "data:image/svg+xml;utf8,"; let file = "data:image/svg+xml;utf8,"; // If the item has children, it's a directory. if (treeDelegate.hasChildren) { return treeDelegate.expanded ? folderOpen : folderClosed; } return file; } anchors.verticalCenter: parent.verticalCenter antialiasing: true asynchronous: true fillMode: Image.PreserveAspectFit smooth: true source: setSourceImage() sourceSize.height: 15 sourceSize.width: 15 x: treeDelegate.leftMargin + (treeDelegate.depth * treeDelegate.indentation) } MultiEffect { id: iconOverlay anchors.fill: directoryIcon brightness: 1.0 colorization: 1.0 colorizationColor: { const isFile = !treeDelegate.hasChildren; if (isFile) return Qt.lighter(RustColors.explorer_folder, 2); const isExpandedFolder = treeDelegate.expanded && treeDelegate.hasChildren; if (isExpandedFolder) return Qt.darker(RustColors.explorer_folder, 2); else return RustColors.explorer_folder; } source: directoryIcon } HoverHandler { id: hoverHandler acceptedDevices: PointerDevice.Mouse } TapHandler { acceptedButtons: Qt.LeftButton | Qt.RightButton onSingleTapped: (eventPoint, button) => { switch (button) { case Qt.LeftButton: if (treeDelegate.hasChildren) { fileSystemTreeView.toggleExpanded(treeDelegate.row); // fileSystemTreeView.lastIndex = treeDelegate.index } else { // If this model item doesn't have children, it means it's // representing a file. fileSystemTreeView.fileClicked(treeDelegate.filePath); } break; case Qt.RightButton: contextMenu.popup(); break; } } } Menu { id: contextMenu Action { enabled: treeDelegate.hasChildren text: qsTr("Set as root index") onTriggered: { Logger.debug("Setting new root directory: " + treeDelegate.filePath); fileSystemTreeView.rootDirectory = treeDelegate.filePath; } } Action { text: qsTr("Reset root index") onTriggered: { Logger.log("Resetting root directory: " + fileSystemTreeView.originalRootDirectory); fileSystemTreeView.rootDirectory = fileSystemTreeView.originalRootDirectory; } } } } selectionModel: ItemSelectionModel { } }