Files
qt-creator/share/qtcreator/qmldesigner/assetsLibraryQmlSources/AssetDelegate.qml
Miikka Heikkinen ede7969ea3 QmlDesigner: Make imported 3D scenes available via assets
A placeholder .q3d file is created under content for imported 3D
components found under Generated/QtQuick3D on asset view attach and
every time new import is done. .q3d file contains a project root
relative path to component's import folder.

.q3d files get generated preview as icon in assets view.

Imported 3D items are no longer shown in Components view.

Removing .q3d file will remove the corresponding module as well
as all model nodes created from that asset.

Removing last model node of asset will remove the import statement
on next document save.

Fixes: QDS-12193
Fixes: QDS-14565
Change-Id: If01546ca4c78334bac73b055ed156276f6f8f2a4
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
Reviewed-by: Marco Bubke <marco.bubke@qt.io>
2025-02-13 09:22:06 +00:00

407 lines
14 KiB
QML

// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import QtQuick
import QtQuick.Templates as T
import StudioTheme as StudioTheme
import StudioControls as StudioControls
import AssetsLibraryBackend
T.TreeViewDelegate {
id: root
property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle
required property Item assetsView
required property Item assetsRoot
property var assetsModel: AssetsLibraryBackend.assetsModel
property var rootView: AssetsLibraryBackend.rootView
property bool hasChildWithDropHover: false
property bool isHighlighted: false
property bool isDelegateEmpty: false
readonly property string suffix: model.fileName.substr(-4)
readonly property bool isFont: root.suffix === ".ttf" || root.suffix === ".otf"
readonly property bool isEffect: root.suffix === ".qep"
property bool currFileSelected: false
property int initialDepth: -1
property bool __isDirectory: assetsModel.isDirectory(model.filePath)
property int __currentRow: model.index
property string __itemPath: model.filePath
readonly property int __fileItemHeight: thumbnailImage.height + 2 * StudioTheme.Values.border
readonly property int __dirItemHeight: 21
implicitHeight: root.__isDirectory ? root.__dirItemHeight : root.__fileItemHeight
implicitWidth: root.assetsView.width
Component.onCompleted: {
// the depth of the root path will become available before we get to the actual
// items we display, so it's safe to set assetsView.rootPathDepth here. All other
// tree items (below the root) will have the indentation (basically, depth) adjusted.
if (model.filePath === assetsModel.rootPath()) {
root.assetsView.rootPathDepth = root.depth
root.assetsView.rootPathRow = root.__currentRow
} else if (model.filePath.includes(assetsModel.rootPath())) {
root.depth -= root.assetsView.rootPathDepth
root.initialDepth = root.depth
}
// expand/collapse folder based on its stored expanded state
if (root.__isDirectory) {
// if the folder expand state is not stored yet, stores it as true (expanded)
root.assetsModel.initializeExpandState(root.__itemPath)
let expandState = assetsModel.folderExpandState(root.__itemPath)
if (expandState)
root.assetsView.expand(root.__currentRow)
else
root.assetsView.collapse(root.__currentRow)
root.isDelegateEmpty = assetsModel.isDelegateEmpty(root.__itemPath)
}
}
// workaround for a bug -- might be fixed by https://codereview.qt-project.org/c/qt/qtdeclarative/+/442721
onYChanged: {
if (root.__currentRow === root.assetsView.firstRow) {
if (root.y > root.assetsView.contentY) {
let item = root.assetsView.itemAtCell(0, root.assetsView.rootPathRow)
if (!item)
root.assetsView.contentY = root.y
}
}
}
onDepthChanged: {
if (root.depth > root.initialDepth && root.initialDepth >= 0)
root.depth = root.initialDepth
}
indicator: Item {
id: arrowIndicator
implicitWidth: 10
implicitHeight: root.implicitHeight
anchors.left: bg.left
anchors.leftMargin: 5
Image {
id: arrow
width: 8
height: 4
visible: !root.isDelegateEmpty
source: "image://icons/down-arrow"
anchors.centerIn: parent
rotation: root.expanded ? 0 : -90
}
}
background: Rectangle {
id: bg
x: root.indentation * (root.depth - 1)
width: root.implicitWidth - bg.x
color: {
if (root.__isDirectory && (root.isHighlighted || root.hasChildWithDropHover))
return StudioTheme.Values.themeInteraction
if (!root.__isDirectory && root.assetsView.selectedAssets[root.__itemPath])
return StudioTheme.Values.themeSectionHeadBackground
if (mouseArea.containsMouse)
return StudioTheme.Values.themeSectionHeadBackground
return root.__isDirectory
? StudioTheme.Values.themeSectionHeadBackground
: "transparent"
}
border.width: StudioTheme.Values.border
border.color: root.assetsView.selectedAssets[root.__itemPath] ? StudioTheme.Values.themeInteraction
: "transparent"
}
contentItem: Text {
id: assetLabel
text: assetLabel.__computeText()
color: StudioTheme.Values.themeTextColor
font.pixelSize: StudioTheme.Values.baseFontSize
verticalAlignment: Qt.AlignVCenter
anchors.left: root.__isDirectory ? arrowIndicator.right : thumbnailImage.right
anchors.leftMargin: 8
function __computeText() {
return root.__isDirectory
? (root.isDelegateEmpty
? model.display.toUpperCase() + qsTr(" (empty)")
: model.display.toUpperCase())
: model.display
}
}
MouseArea {
id: mouseArea
property bool allowTooltip: true
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton
onExited: AssetsLibraryBackend.tooltipBackend.hideTooltip()
onEntered: mouseArea.allowTooltip = true
onCanceled: {
AssetsLibraryBackend.tooltipBackend.hideTooltip()
mouseArea.allowTooltip = true
}
onPositionChanged: AssetsLibraryBackend.tooltipBackend.reposition()
onPressed: (mouse) => {
mouseArea.forceActiveFocus()
mouseArea.allowTooltip = false
AssetsLibraryBackend.tooltipBackend.hideTooltip()
var ctrlDown = mouse.modifiers & Qt.ControlModifier
if (mouse.button === Qt.LeftButton) {
if (root.__isDirectory) {
// ensure only one directory can be selected
root.assetsView.clearSelectedAssets()
root.currFileSelected = true
} else {
if (!root.assetsView.isAssetSelected(root.__itemPath) && !ctrlDown)
root.assetsView.clearSelectedAssets()
root.currFileSelected = ctrlDown ? !root.assetsView.isAssetSelected(root.__itemPath) : true
}
root.assetsView.setAssetSelected(root.__itemPath, root.currFileSelected)
if (root.currFileSelected) {
let selectedPaths = root.assetsView.selectedPathsAsList()
AssetsLibraryBackend.rootView.startDragAsset(selectedPaths, mapToGlobal(mouse.x, mouse.y))
}
} else {
if (!root.assetsView.isAssetSelected(root.__itemPath) && !ctrlDown)
root.assetsView.clearSelectedAssets()
root.currFileSelected = root.assetsView.isAssetSelected(root.__itemPath) || !ctrlDown
root.assetsView.setAssetSelected(root.__itemPath, root.currFileSelected)
}
}
onReleased: (mouse) => {
mouseArea.allowTooltip = true
if (root.__isDirectory)
return
if (mouse.button === Qt.LeftButton) {
if (!(mouse.modifiers & Qt.ControlModifier))
root.assetsView.selectedAssets = {}
root.assetsView.selectedAssets[root.__itemPath] = root.currFileSelected
root.assetsView.selectedAssetsChanged()
}
}
onDoubleClicked: (mouse) => {
mouseArea.forceActiveFocus()
mouseArea.allowTooltip = false
AssetsLibraryBackend.tooltipBackend.hideTooltip()
if (mouse.button === Qt.LeftButton && root.isEffect)
AssetsLibraryBackend.rootView.openEffectComposer(filePath)
}
StudioControls.ToolTip {
id: assetTooltip
visible: !root.isFont && mouseArea.containsMouse && !root.assetsView.contextMenu.visible
text: assetTooltip.__computeText()
delay: 1000
function __computeText() {
let filePath = model.filePath.replace(assetsModel.contentDirPath(), "")
let fileSize = rootView.assetFileSize(model.filePath)
let fileExtMatches = model.filePath.match(/\.(.*)$/)
let fileExt = fileExtMatches ? "(" + fileExtMatches[1] + ")" : ""
if (root.__isDirectory)
return filePath
if (rootView.assetIsImageOrTexture(model.filePath)) {
let size = rootView.imageSize(model.filePath)
return filePath + "\n"
+ size.width + " x " + size.height
+ "\n" + fileSize
+ " " + fileExt
} else if (rootView.assetIsImported3d(model.filePath)) {
return filePath + "\n"
+ fileExt
} else {
return filePath + "\n"
+ fileSize
+ " " + fileExt
}
}
function refresh() {
assetTooltip.text = assetTooltip.__computeText()
}
}
Timer {
interval: 1000
running: mouseArea.containsMouse && mouseArea.allowTooltip
onTriggered: {
if (root.isFont) {
AssetsLibraryBackend.tooltipBackend.name = model.fileName
AssetsLibraryBackend.tooltipBackend.path = model.filePath
AssetsLibraryBackend.tooltipBackend.showTooltip()
}
}
}
onClicked: (mouse) => {
if (mouse.button === Qt.LeftButton) {
root.__toggleExpandCurrentRow()
root.assetsView.currentFilePath = root.__itemPath
} else {
root.__openContextMenuForCurrentRow()
}
}
}
function getDirPath() {
if (root.__isDirectory)
return model.filePath
else
return assetsModel.parentDirPath(model.filePath)
}
function __openContextMenuForCurrentRow() {
let modelIndex = assetsModel.indexForPath(model.filePath)
function onFolderCreated(path) {
if (path)
root.assetsView.addCreatedFolder(path)
}
if (root.__isDirectory) {
var allExpandedState = root.assetsView.computeAllExpandedState()
root.assetsView.contextMenu.openContextMenuForDir(modelIndex, model.filePath,
model.fileName, allExpandedState, onFolderCreated)
} else {
let parentDirIndex = assetsModel.parentDirIndex(model.filePath)
let selectedPaths = root.assetsView.selectedPathsAsList()
root.assetsView.contextMenu.openContextMenuForFile(modelIndex, parentDirIndex,
selectedPaths, onFolderCreated)
}
}
function __toggleExpandCurrentRow() {
if (!root.__isDirectory || root.isDelegateEmpty)
return
let index = root.assetsView.__modelIndex(root.__currentRow)
// if the user manually clicked on a directory, then this is definitely not a
// an automatic request to expand all.
root.assetsView.requestedExpandAll = false
if (root.assetsView.isExpanded(root.__currentRow)) {
root.assetsView.requestedExpandAll = false
root.assetsView.collapse(root.__currentRow)
} else {
root.assetsView.expand(root.__currentRow)
}
assetsModel.saveExpandState(root.__itemPath, root.expanded)
}
function reloadImage() {
if (root.__isDirectory)
return
thumbnailImage.source = ""
thumbnailImage.source = thumbnailImage.__computeSource()
}
Image {
id: thumbnailImage
visible: !root.__isDirectory
y: StudioTheme.Values.border
x: bg.x + StudioTheme.Values.border
width: 48
height: 48
cache: false
sourceSize.width: 48
sourceSize.height: 48
asynchronous: true
fillMode: Image.Pad
source: thumbnailImage.__computeSource()
function __computeSource() {
return root.__isDirectory
? ""
: "image://qmldesigner_assets/" + model.filePath
}
onStatusChanged: {
if (thumbnailImage.status === Image.Ready)
assetTooltip.refresh()
}
}
DropArea {
id: dropArea
anchors.fill: parent
anchors.bottomMargin: -assetsView.rowSpacing
function updateParentHighlight(highlight) {
let index = root.assetsView.__modelIndex(root.__currentRow)
let parentItem = assetsView.__getDelegateParentForIndex(index)
if (parentItem)
parentItem.isHighlighted = highlight
// highlights the root folder canvas area when dragging over child
if (root.depth === 1 && !root.__isDirectory)
root.assetsRoot.highlightCanvas = highlight
}
onEntered: (drag) => {
root.assetsRoot.updateDropExtFiles(drag)
drag.accepted |= drag.formats[0] === "application/vnd.qtdesignstudio.assets"
&& !root.assetsModel.isSameOrDescendantPath(drag.urls[0], root.__itemPath)
if (root.__isDirectory)
root.isHighlighted = drag.accepted
else
dropArea.updateParentHighlight(drag.accepted)
}
onDropped: (drag) => {
if (drag.formats[0] === "application/vnd.qtdesignstudio.assets") {
root.rootView.invokeAssetsDrop(drag.urls, root.getDirPath())
} else {
root.rootView.emitExtFilesDrop(root.assetsRoot.dropSimpleExtFiles,
root.assetsRoot.dropComplexExtFiles,
root.getDirPath())
}
root.isHighlighted = false
dropArea.updateParentHighlight(false)
}
onExited: {
root.isHighlighted = false
dropArea.updateParentHighlight(false)
}
}
}