2022-11-23 11:49:45 +02:00
|
|
|
// Copyright (C) 2022 The Qt Company Ltd.
|
2023-01-04 08:52:22 +01:00
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
2022-10-07 19:49:52 +03:00
|
|
|
|
|
|
|
import QtQuick
|
|
|
|
import QtQuick.Controls
|
2022-11-23 11:49:45 +02:00
|
|
|
import HelperWidgets as HelperWidgets
|
2022-10-07 19:49:52 +03:00
|
|
|
import StudioControls as StudioControls
|
2023-09-19 17:14:24 +02:00
|
|
|
import StudioTheme as StudioTheme
|
2023-03-07 16:51:02 +01:00
|
|
|
import AssetsLibraryBackend
|
2022-10-07 19:49:52 +03:00
|
|
|
|
2022-11-23 11:49:45 +02:00
|
|
|
TreeView {
|
|
|
|
id: root
|
2024-11-21 10:38:04 +02:00
|
|
|
|
2022-10-07 19:49:52 +03:00
|
|
|
clip: true
|
2023-02-07 15:26:00 +02:00
|
|
|
interactive: verticalScrollBar.visible && !root.contextMenu.opened && !rootView.isDragging
|
2022-11-23 11:49:45 +02:00
|
|
|
reuseItems: false
|
|
|
|
boundsBehavior: Flickable.StopAtBounds
|
|
|
|
rowSpacing: 5
|
|
|
|
|
2023-08-25 16:53:39 +02:00
|
|
|
property bool adsFocus: false
|
|
|
|
// objectName is used by the dock widget to find this particular ScrollView
|
|
|
|
// and set the ads focus on it.
|
|
|
|
objectName: "__mainSrollView"
|
|
|
|
|
2023-03-07 16:51:02 +01:00
|
|
|
property var assetsModel: AssetsLibraryBackend.assetsModel
|
|
|
|
property var rootView: AssetsLibraryBackend.rootView
|
|
|
|
property var tooltipBackend: AssetsLibraryBackend.tooltipBackend
|
|
|
|
|
2022-11-23 11:49:45 +02:00
|
|
|
required property Item assetsRoot
|
|
|
|
required property StudioControls.Menu contextMenu
|
|
|
|
property alias verticalScrollBar: verticalScrollBar
|
|
|
|
|
|
|
|
property var selectedAssets: ({})
|
2023-02-02 21:03:17 +02:00
|
|
|
// the latest file that was clicked, or changed to via Up or Down keys
|
|
|
|
property string currentFilePath: ""
|
2022-11-23 11:49:45 +02:00
|
|
|
|
|
|
|
// used to see if the op requested is to expand or to collapse.
|
|
|
|
property int lastRowCount: -1
|
|
|
|
// we need this to know if we need to expand further, while we're in onRowsChanged()
|
|
|
|
property bool requestedExpandAll: true
|
|
|
|
// used to compute the visual depth of the items we show to the user.
|
|
|
|
property int rootPathDepth: 0
|
|
|
|
property int rootPathRow: 0
|
|
|
|
// i.e. first child of the root path
|
|
|
|
readonly property int firstRow: root.rootPathRow + 1
|
2023-02-02 21:03:17 +02:00
|
|
|
readonly property int lastRow: root.rows - 1
|
2022-11-23 15:47:34 +02:00
|
|
|
property var __createdDirectories: []
|
2022-11-23 11:49:45 +02:00
|
|
|
|
2024-12-19 11:16:18 +02:00
|
|
|
onExpanded:(row) => {
|
|
|
|
let index = root.__modelIndex(row)
|
|
|
|
assetsModel.saveExpandState(assetsModel.filePath(index), true)
|
|
|
|
}
|
|
|
|
|
|
|
|
onCollapsed:(row) => {
|
|
|
|
let index = root.__modelIndex(row)
|
|
|
|
assetsModel.saveExpandState(assetsModel.filePath(index), false)
|
|
|
|
}
|
|
|
|
|
2022-11-23 11:49:45 +02:00
|
|
|
rowHeightProvider: (row) => {
|
|
|
|
if (row <= root.rootPathRow)
|
|
|
|
return 0
|
|
|
|
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
|
2023-08-25 16:53:39 +02:00
|
|
|
HoverHandler { id: hoverHandler }
|
|
|
|
|
2023-09-19 17:14:24 +02:00
|
|
|
ScrollBar.vertical: StudioControls.TransientScrollBar {
|
2022-11-23 11:49:45 +02:00
|
|
|
id: verticalScrollBar
|
2023-09-19 17:14:24 +02:00
|
|
|
style: StudioTheme.Values.viewStyle
|
2023-08-25 16:53:39 +02:00
|
|
|
parent: root
|
|
|
|
x: root.width - verticalScrollBar.width
|
|
|
|
y: 0
|
|
|
|
height: root.availableHeight
|
|
|
|
orientation: Qt.Vertical
|
|
|
|
|
|
|
|
show: (hoverHandler.hovered || root.adsFocus || verticalScrollBar.inUse)
|
|
|
|
&& verticalScrollBar.isNeeded
|
2022-11-23 11:49:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
model: assetsModel
|
|
|
|
|
|
|
|
onRowsChanged: {
|
2024-10-09 20:43:31 +03:00
|
|
|
if (root.rows > root.rootPathRow + 1 && assetsModel.isEmpty ||
|
|
|
|
root.rows <= root.rootPathRow + 1 && !assetsModel.isEmpty) {
|
|
|
|
assetsModel.syncIsEmpty()
|
2022-11-23 11:49:45 +02:00
|
|
|
}
|
|
|
|
|
2023-02-09 09:17:37 +01:00
|
|
|
root.updateRows()
|
2022-11-23 11:49:45 +02:00
|
|
|
}
|
2022-10-07 19:49:52 +03:00
|
|
|
|
2022-11-23 11:49:45 +02:00
|
|
|
Timer {
|
|
|
|
id: updateRowsTimer
|
|
|
|
interval: 200
|
|
|
|
repeat: false
|
|
|
|
|
2024-08-29 22:27:13 +03:00
|
|
|
onTriggered: root.updateRows()
|
2022-11-23 11:49:45 +02:00
|
|
|
}
|
2022-10-07 19:49:52 +03:00
|
|
|
|
2022-11-23 11:49:45 +02:00
|
|
|
Connections {
|
|
|
|
target: rootView
|
|
|
|
|
|
|
|
function onDirectoryCreated(path)
|
|
|
|
{
|
2022-11-23 15:47:34 +02:00
|
|
|
root.__createdDirectories.push(path)
|
2022-11-23 11:49:45 +02:00
|
|
|
updateRowsTimer.restart()
|
|
|
|
}
|
2023-02-02 21:03:17 +02:00
|
|
|
|
|
|
|
function onDeleteSelectedAssetsRequested()
|
|
|
|
{
|
|
|
|
let selectedPaths = root.selectedPathsAsList()
|
|
|
|
if (!selectedPaths.length)
|
|
|
|
return
|
|
|
|
|
|
|
|
let deleted = assetsModel.requestDeleteFiles(selectedPaths)
|
|
|
|
if (!deleted) {
|
|
|
|
confirmDeleteFiles.files = selectedPaths
|
|
|
|
confirmDeleteFiles.open()
|
|
|
|
}
|
|
|
|
}
|
2022-11-23 11:49:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
Connections {
|
|
|
|
target: assetsModel
|
|
|
|
function onDirectoryLoaded(path)
|
|
|
|
{
|
|
|
|
// updating rows for safety: the rows might have been created before the
|
|
|
|
// directory (esp. the root path) has been loaded, so we must make sure all rows are
|
|
|
|
// expanded -- otherwise, the tree may not become visible.
|
|
|
|
updateRowsTimer.restart()
|
|
|
|
}
|
|
|
|
|
|
|
|
function onRootPathChanged()
|
|
|
|
{
|
|
|
|
// when we switch from one project to another, we need to reset the state of the
|
|
|
|
// view: make sure we will do an "expand all" (otherwise, the whole tree might
|
|
|
|
// be collapsed, and with our visible root not being the actual root of the tree,
|
|
|
|
// the entire tree would be invisible)
|
|
|
|
root.lastRowCount = -1
|
|
|
|
root.requestedExpandAll = true
|
|
|
|
}
|
|
|
|
|
|
|
|
function onFileChanged(filePath)
|
|
|
|
{
|
|
|
|
rootView.invalidateThumbnail(filePath)
|
|
|
|
|
|
|
|
let index = assetsModel.indexForPath(filePath)
|
|
|
|
let cell = root.cellAtIndex(index)
|
|
|
|
let fileItem = root.itemAtCell(cell)
|
|
|
|
|
|
|
|
if (fileItem)
|
|
|
|
fileItem.reloadImage()
|
|
|
|
}
|
|
|
|
|
|
|
|
} // Connections
|
|
|
|
|
|
|
|
function addCreatedFolder(path)
|
|
|
|
{
|
2022-11-23 15:47:34 +02:00
|
|
|
root.__createdDirectories.push(path)
|
2022-11-23 11:49:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function selectedPathsAsList()
|
|
|
|
{
|
|
|
|
return Object.keys(root.selectedAssets)
|
|
|
|
.filter(itemPath => root.selectedAssets[itemPath])
|
|
|
|
}
|
|
|
|
|
|
|
|
// workaround for a bug -- might be fixed by https://codereview.qt-project.org/c/qt/qtdeclarative/+/442721
|
|
|
|
function resetVerticalScrollPosition()
|
|
|
|
{
|
|
|
|
root.contentY = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
function updateRows()
|
|
|
|
{
|
|
|
|
if (root.rows <= 0)
|
|
|
|
return
|
|
|
|
|
2022-11-23 15:47:34 +02:00
|
|
|
while (root.__createdDirectories.length > 0) {
|
|
|
|
let dirPath = root.__createdDirectories.pop()
|
2022-11-23 11:49:45 +02:00
|
|
|
let index = assetsModel.indexForPath(dirPath)
|
|
|
|
let row = root.rowAtIndex(index)
|
|
|
|
|
2024-10-09 14:02:12 +03:00
|
|
|
if (row > 0) {
|
2022-11-23 11:49:45 +02:00
|
|
|
root.expand(row)
|
2024-10-09 14:02:12 +03:00
|
|
|
} else if (row === -1 && assetsModel.indexIsValid(index)) {
|
2022-11-23 11:49:45 +02:00
|
|
|
// It is possible that this directory, dirPath, was created inside of a parent
|
|
|
|
// directory that was not yet expanded in the TreeView. This can happen with the
|
|
|
|
// bridge plugin. In such a situation, we don't have a "row" for it yet, so we have
|
|
|
|
// to expand its parents, from root to our `index`
|
|
|
|
let parents = assetsModel.parentIndices(index);
|
|
|
|
parents.reverse().forEach(idx => {
|
|
|
|
let row = root.rowAtIndex(idx)
|
|
|
|
if (row > 0)
|
|
|
|
root.expand(row)
|
|
|
|
})
|
2022-10-07 19:49:52 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-23 11:49:45 +02:00
|
|
|
// we have no way to know beyond doubt here if updateRows() was called due
|
|
|
|
// to a request to expand or to collapse rows - but it should be safe to
|
|
|
|
// assume that, if we have more rows now than the last time, then it's an expand
|
|
|
|
var expanding = (root.rows >= root.lastRowCount)
|
|
|
|
|
|
|
|
if (expanding) {
|
|
|
|
if (root.requestedExpandAll)
|
2022-11-23 15:47:34 +02:00
|
|
|
root.__doExpandAll()
|
2022-11-23 11:49:45 +02:00
|
|
|
} else {
|
|
|
|
// on collapsing, set expandAll flag to false.
|
|
|
|
root.requestedExpandAll = false;
|
2022-10-07 19:49:52 +03:00
|
|
|
}
|
2022-11-23 11:49:45 +02:00
|
|
|
|
|
|
|
root.lastRowCount = root.rows
|
|
|
|
}
|
|
|
|
|
2022-11-23 15:47:34 +02:00
|
|
|
function __doExpandAll()
|
2022-11-23 11:49:45 +02:00
|
|
|
{
|
|
|
|
let expandedAny = false
|
2024-10-09 14:02:12 +03:00
|
|
|
for (let r = 0; r < root.rows; ++r) {
|
|
|
|
let index = root.__modelIndex(r)
|
|
|
|
if (assetsModel.isDirectory(index) && !root.isExpanded(r)) {
|
|
|
|
root.expand(r)
|
2022-11-23 11:49:45 +02:00
|
|
|
expandedAny = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!expandedAny)
|
|
|
|
Qt.callLater(root.forceLayout)
|
|
|
|
}
|
|
|
|
|
|
|
|
function expandAll()
|
|
|
|
{
|
2022-11-23 15:47:34 +02:00
|
|
|
// In order for __doExpandAll() to be called repeatedly (every time a new node is
|
2022-11-23 11:49:45 +02:00
|
|
|
// loaded, and then, expanded), we need to set requestedExpandAll to true.
|
|
|
|
root.requestedExpandAll = true
|
2022-11-23 15:47:34 +02:00
|
|
|
root.__doExpandAll()
|
2022-11-23 11:49:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function collapseAll()
|
|
|
|
{
|
|
|
|
root.resetVerticalScrollPosition()
|
|
|
|
|
|
|
|
// collapse all, except for the root path - from the last item (leaves) up to the root
|
|
|
|
for (let nRow = root.rows - 1; nRow >= 0; --nRow) {
|
2022-11-23 15:47:34 +02:00
|
|
|
let index = root.__modelIndex(nRow)
|
2022-11-23 11:49:45 +02:00
|
|
|
// we don't want to collapse the root path, because doing so will hide the contents
|
|
|
|
// of the tree.
|
|
|
|
if (assetsModel.filePath(index) === assetsModel.rootPath())
|
|
|
|
break
|
|
|
|
|
|
|
|
root.collapse(nRow)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// workaround for a bug -- might be fixed by https://codereview.qt-project.org/c/qt/qtdeclarative/+/442721
|
|
|
|
onContentHeightChanged: {
|
|
|
|
if (root.contentHeight <= root.height) {
|
|
|
|
let first = root.itemAtCell(0, root.firstRow)
|
|
|
|
if (!first)
|
|
|
|
root.contentY = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function computeAllExpandedState()
|
|
|
|
{
|
|
|
|
var dirsWithChildren = [...Array(root.rows).keys()].filter(row => {
|
2022-11-23 15:47:34 +02:00
|
|
|
let index = root.__modelIndex(row)
|
2022-11-23 11:49:45 +02:00
|
|
|
return assetsModel.isDirectory(index) && assetsModel.hasChildren(index)
|
|
|
|
})
|
|
|
|
|
|
|
|
var countExpanded = dirsWithChildren.filter(row => root.isExpanded(row)).length
|
|
|
|
|
|
|
|
if (countExpanded === dirsWithChildren.length)
|
|
|
|
return "all_expanded"
|
|
|
|
|
|
|
|
if (countExpanded === 0)
|
|
|
|
return "all_collapsed"
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
function isAssetSelected(itemPath)
|
|
|
|
{
|
|
|
|
return root.selectedAssets[itemPath] ? true : false
|
|
|
|
}
|
|
|
|
|
|
|
|
function clearSelectedAssets()
|
|
|
|
{
|
|
|
|
root.selectedAssets = {}
|
|
|
|
}
|
|
|
|
|
|
|
|
function setAssetSelected(itemPath, selected)
|
|
|
|
{
|
|
|
|
root.selectedAssets[itemPath] = selected
|
|
|
|
root.selectedAssetsChanged()
|
|
|
|
}
|
|
|
|
|
2022-11-23 15:47:34 +02:00
|
|
|
function __getDelegateParentForIndex(index)
|
2022-11-23 11:49:45 +02:00
|
|
|
{
|
|
|
|
let parentIndex = assetsModel.parentDirIndex(index)
|
|
|
|
let parentCell = root.cellAtIndex(parentIndex)
|
|
|
|
return root.itemAtCell(parentCell)
|
|
|
|
}
|
|
|
|
|
2022-11-23 15:47:34 +02:00
|
|
|
function __modelIndex(row)
|
2022-11-23 11:49:45 +02:00
|
|
|
{
|
|
|
|
// The modelIndex() function exists since 6.3. In Qt 6.3, this modelIndex() function was a
|
2023-03-09 17:17:03 +02:00
|
|
|
// member of the TreeView, while in Qt6.4 it was moved to TableView. In Qt 6.4, the order of
|
|
|
|
// the arguments was changed, and in Qt 6.5 the order was changed again. Due to this mess,
|
|
|
|
// the whole function was deprecated in Qt 6.4.3 and replaced with index() function.
|
|
|
|
if (assetsRoot.qtVersion >= 0x060403)
|
|
|
|
return root.index(row, 0)
|
|
|
|
else if (assetsRoot.qtVersion >= 0x060400)
|
2022-11-23 11:49:45 +02:00
|
|
|
return root.modelIndex(0, row)
|
|
|
|
else
|
|
|
|
return root.modelIndex(row, 0)
|
|
|
|
}
|
|
|
|
|
2023-02-02 21:03:17 +02:00
|
|
|
function __selectRow(row: int)
|
|
|
|
{
|
|
|
|
let index = root.__modelIndex(row)
|
|
|
|
let filePath = assetsModel.filePath(index)
|
|
|
|
|
|
|
|
root.clearSelectedAssets()
|
|
|
|
root.setAssetSelected(filePath, true)
|
|
|
|
root.currentFilePath = filePath
|
|
|
|
}
|
|
|
|
|
2023-08-04 13:48:15 +03:00
|
|
|
function moveSelection(amount)
|
|
|
|
{
|
2024-10-09 20:43:31 +03:00
|
|
|
if (assetsModel.isEmpty || !amount)
|
2023-02-02 21:03:17 +02:00
|
|
|
return
|
|
|
|
|
2023-08-04 13:48:15 +03:00
|
|
|
let index = root.currentFilePath ? assetsModel.indexForPath(root.currentFilePath)
|
|
|
|
: root.__modelIndex(root.firstRow)
|
2023-02-02 21:03:17 +02:00
|
|
|
let row = root.rowAtIndex(index)
|
2024-11-04 11:41:00 +02:00
|
|
|
let nextRow = row + amount
|
2023-02-02 21:03:17 +02:00
|
|
|
|
2024-11-04 11:41:00 +02:00
|
|
|
if ((amount < 0 && nextRow < root.firstRow) || (amount > 0 && nextRow > root.lastRow))
|
|
|
|
return
|
2023-02-02 21:03:17 +02:00
|
|
|
|
|
|
|
root.__selectRow(nextRow)
|
|
|
|
root.positionViewAtRow(nextRow, TableView.Contain)
|
|
|
|
}
|
|
|
|
|
2023-08-04 13:48:15 +03:00
|
|
|
Keys.enabled: true
|
2023-02-02 21:03:17 +02:00
|
|
|
|
2023-08-04 13:48:15 +03:00
|
|
|
Keys.onUpPressed: {
|
|
|
|
moveSelection(-1)
|
|
|
|
}
|
2023-02-02 21:03:17 +02:00
|
|
|
|
2023-08-04 13:48:15 +03:00
|
|
|
Keys.onDownPressed: {
|
|
|
|
moveSelection(1)
|
2023-02-02 21:03:17 +02:00
|
|
|
}
|
|
|
|
|
2024-11-04 11:41:00 +02:00
|
|
|
Keys.onRightPressed: {
|
2024-10-09 14:02:12 +03:00
|
|
|
root.expandFolder(true)
|
2024-11-04 11:41:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
Keys.onLeftPressed: {
|
2024-10-09 14:02:12 +03:00
|
|
|
root.expandFolder(false)
|
2024-11-04 11:41:00 +02:00
|
|
|
}
|
|
|
|
|
2024-10-09 14:02:12 +03:00
|
|
|
function expandFolder(expand) {
|
2024-11-04 11:41:00 +02:00
|
|
|
let index = root.currentFilePath ? assetsModel.indexForPath(root.currentFilePath)
|
|
|
|
: root.__modelIndex(root.firstRow)
|
|
|
|
|
|
|
|
if (!assetsModel.isDirectory(index))
|
|
|
|
return
|
|
|
|
|
|
|
|
let row = root.rowAtIndex(index)
|
|
|
|
|
2024-10-09 14:02:12 +03:00
|
|
|
if (expand)
|
2024-11-04 11:41:00 +02:00
|
|
|
root.expand(row)
|
2024-10-09 14:02:12 +03:00
|
|
|
else
|
2024-11-04 11:41:00 +02:00
|
|
|
root.collapse(row)
|
|
|
|
}
|
|
|
|
|
2023-02-02 21:03:17 +02:00
|
|
|
ConfirmDeleteFilesDialog {
|
|
|
|
id: confirmDeleteFiles
|
|
|
|
parent: root
|
|
|
|
files: []
|
|
|
|
|
|
|
|
onAccepted: root.clearSelectedAssets()
|
|
|
|
onClosed: confirmDeleteFiles.files = []
|
|
|
|
}
|
|
|
|
|
2022-11-23 11:49:45 +02:00
|
|
|
delegate: AssetDelegate {
|
|
|
|
assetsView: root
|
|
|
|
assetsRoot: root.assetsRoot
|
2024-06-03 17:37:38 +03:00
|
|
|
indentation: 10
|
2022-10-07 19:49:52 +03:00
|
|
|
}
|
2022-11-23 11:49:45 +02:00
|
|
|
} // TreeView
|