// 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 TreeView { id: fileSystemTreeView model: FileSystem property int lastIndex: -1 required property string originalRootDirectory property string rootDirectory signal fileClicked(string filePath) rootIndex: FileSystem.setDirectory(fileSystemTreeView.rootDirectory) leftMargin: 5 boundsBehavior: Flickable.StopAtBounds boundsMovement: Flickable.StopAtBounds clip: true // The delegate represents a single entry in the filesystem. delegate: TreeViewDelegate { id: treeDelegate indentation: 12 implicitWidth: fileSystemTreeView.width > 0 ? fileSystemTreeView.width : 250 implicitHeight: 25 required property int index required property url filePath required property string 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; } else { return file } } x: treeDelegate.leftMargin + (treeDelegate.depth * treeDelegate.indentation) anchors.verticalCenter: parent.verticalCenter source: setSourceImage() sourceSize.width: 15 sourceSize.height: 15 fillMode: Image.PreserveAspectFit smooth: true antialiasing: true asynchronous: true } contentItem: Text { text: treeDelegate.fileName color: RustColors.explorer_text } background: Rectangle { // TODO: Fix flickering from color transition on states here. color: (treeDelegate.index === fileSystemTreeView.lastIndex) ? RustColors.explorer_text_selected : (hoverHandler.hovered ? RustColors.explorer_hovered : "transparent") radius: 2.5 opacity: hoverHandler.hovered ? 0.75 : 1.0 Behavior on color { ColorAnimation { duration: 300 } } } MultiEffect { id: iconOverlay anchors.fill: directoryIcon source: directoryIcon colorization: 1.0 brightness: 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 } } HoverHandler { id: hoverHandler acceptedDevices: PointerDevice.Mouse } TapHandler { acceptedButtons: Qt.LeftButton | Qt.RightButton onSingleTapped: (eventPoint, button) => { switch (button) { case Qt.LeftButton: fileSystemTreeView.toggleExpanded(treeDelegate.row) // If this model item doesn't have children, it means it's // representing a file. if (!treeDelegate.hasChildren) fileSystemTreeView.fileClicked(treeDelegate.filePath) break; case Qt.RightButton: contextMenu.popup(); break; } } } Menu { id: contextMenu Action { text: qsTr("Set as root index") enabled: treeDelegate.hasChildren onTriggered: { console.log("Setting new root directory: " + treeDelegate.filePath) fileSystemTreeView.rootDirectory = treeDelegate.filePath } } Action { text: qsTr("Reset root index") onTriggered: { console.log("Resetting root directory: " + fileSystemTreeView.originalRootDirectory) fileSystemTreeView.rootDirectory = fileSystemTreeView.originalRootDirectory } } } } // Provide our own custom ScrollIndicator for the TreeView. ScrollIndicator.vertical: ScrollIndicator { active: true implicitWidth: 15 contentItem: Rectangle { implicitWidth: 6 implicitHeight: 6 color: RustColors.scrollbar opacity: fileSystemTreeView.movingVertically ? 0.5 : 0.0 Behavior on opacity { OpacityAnimator { duration: 500 } } } } }