// SPDX-FileCopyrightText: 2026, Shaun Reed // // SPDX-License-Identifier: GNU General Public License v3.0 or later import QtQuick import QtQuick.Controls import QtQuick.Layouts import clide.module 1.0 Rectangle { id: root color: RustColors.explorer_background required property string rootDirectory signal fileClicked(string filePath) // https://doc.qt.io/qt-6/qml-qtquick-treeview.html TreeView { id: fileSystemTreeView anchors.margins: 15 property int lastIndex: -1 model: FileSystemSortProxyModel { id: fs } anchors.fill: parent boundsBehavior: Flickable.StopAtBounds boundsMovement: Flickable.StopAtBounds clip: true Component.onCompleted: { fs.setDirectory(root.rootDirectory) fileSystemTreeView.expandRecursively(0, -1) } // The delegate represents a single entry in the filesystem. delegate: TreeViewDelegate { id: treeDelegate indentation: 8 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 } } } HoverHandler { id: hoverHandler } TapHandler { acceptedButtons: Qt.LeftButton | Qt.RightButton onSingleTapped: (eventPoint, button) => { switch (button) { case Qt.LeftButton: fileSystemTreeView.toggleExpanded(treeDelegate.row) fileSystemTreeView.lastIndex = treeDelegate.index // If this model item doesn't have children, it means it's // representing a file. if (!treeDelegate.hasChildren) root.fileClicked(treeDelegate.filePath) break; case Qt.RightButton: if (treeDelegate.hasChildren) contextMenu.popup(); break; } } } Menu { id: contextMenu Action { text: qsTr("Set as root index") onTriggered: { console.log("Setting directory: " + treeDelegate.filePath) FileSystemSortProxyModel.setDirectory(treeDelegate.filePath) } } Action { text: qsTr("Reset root index") onTriggered: { FileSystemSortProxyModel.setDirectory("") } } } } // 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 } } } } } }