forked from qt-creator/qt-creator
Merge "Merge remote-tracking branch 'origin/qds/dev'"
This commit is contained in:
@@ -196,9 +196,7 @@ function(qtc_enable_sanitize _target _sanitize_flags)
|
||||
endfunction()
|
||||
|
||||
function(qtc_deeper_concept_diagnostic_depth _target)
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
target_compile_options("${_target}" PUBLIC -fconcepts-diagnostics-depth=8)
|
||||
endif()
|
||||
target_compile_options("${_target}" PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,GNU>:-fconcepts-diagnostics-depth=8>)
|
||||
endfunction()
|
||||
|
||||
function(qtc_add_link_flags_no_undefined target)
|
||||
|
@@ -55,7 +55,6 @@ set(DESIGNSTUDIO_PLUGINS
|
||||
Texteditor
|
||||
UpdateInfo
|
||||
VcsBase
|
||||
assetexporterplugin
|
||||
componentsplugin
|
||||
qmlpreviewplugin
|
||||
qtquickplugin)
|
||||
|
@@ -141,6 +141,10 @@
|
||||
\externalpage https://doc.qt.io/qtdesignstudio/qtquick-positioning.html#using-layouts
|
||||
\title Using layouts
|
||||
*/
|
||||
/*!
|
||||
\externalpage https://www.qt.io/blog/qt-design-studio-4.7-released
|
||||
\title Qt Design Studio 4.7 released
|
||||
*/
|
||||
/*!
|
||||
\externalpage https://www.qt.io/blog/qt-design-studio-4.6-released
|
||||
\title Qt Design Studio 4.6 released
|
||||
|
@@ -14,8 +14,6 @@
|
||||
to \QDS and to edit them to create a UI.
|
||||
|
||||
The following sections describe some of the main points of the webinar.
|
||||
Select the \uicontrol Tutorials tab to watch the webinar video for the
|
||||
full details.
|
||||
|
||||
\section1 Exporting from Adobe Photoshop
|
||||
|
||||
|
@@ -16,7 +16,7 @@
|
||||
|
||||
\list
|
||||
\li In the \uicontrol 3D or \uicontrol Navigator view, right-click a 3D component and select
|
||||
\uicontrol {Export Component}.
|
||||
\uicontrol {Export Bundle}.
|
||||
\li In the \uicontrol {Material Browser} view, right-click a material and select
|
||||
\uicontrol {Export Material}.
|
||||
\endlist
|
||||
@@ -25,7 +25,7 @@
|
||||
To import a 3D component or material bundle, do one of the following:
|
||||
\list
|
||||
\li In the \uicontrol {3D}, \uicontrol {2D}, or \uicontrol {Navigator} view, right-click
|
||||
and select \uicontrol {Import Component}. If you use this method to import a bundle of 3D
|
||||
and select \uicontrol {Import Bundle}. If you use this method to import a bundle of 3D
|
||||
components, the 3D components are added to the 3D scene.
|
||||
\li In \uicontrol {Content Library} > \uicontrol {User Assets}, right-click and select
|
||||
\uicontrol {Import Bundle}. If you use this method to import a bundle of 3D
|
||||
|
@@ -9,11 +9,12 @@
|
||||
|
||||
Read the \QDS release blog posts to see what's new in every version.
|
||||
|
||||
\section1 \QDS Release blog posts
|
||||
\section1 \QDS release blog posts
|
||||
|
||||
\section2 \QDS 4
|
||||
|
||||
\list
|
||||
\li \l{Qt Design Studio 4.7 released}
|
||||
\li \l{Qt Design Studio 4.6 released}
|
||||
\li \l{Qt Design Studio 4.5.1 released}
|
||||
\li \l{Qt Design Studio 4.5 released}
|
||||
|
@@ -141,6 +141,15 @@ StudioControls.Menu {
|
||||
onTriggered: root.rootView.editAssetComponent(root.__selectedAssetPathsList[0])
|
||||
}
|
||||
|
||||
StudioControls.MenuItem {
|
||||
id: updateComponent
|
||||
text: qsTr("Reimport 3D Asset")
|
||||
visible: root.__fileIndex && root.__selectedAssetPathsList.length === 1
|
||||
&& root.rootView.assetIsImported3d(root.__selectedAssetPathsList[0])
|
||||
height: editComponent.visible ? editComponent.implicitHeight : 0
|
||||
onTriggered: root.rootView.updateAssetComponent(root.__selectedAssetPathsList[0])
|
||||
}
|
||||
|
||||
StudioControls.MenuItem {
|
||||
id: addTexturesItem
|
||||
text: qsTr("Add Texture")
|
||||
|
@@ -5,6 +5,7 @@ import QtQuick
|
||||
import QtQuick.Controls
|
||||
import StudioControls as StudioControls
|
||||
import StudioTheme as StudioTheme
|
||||
import HelperWidgets as HelperWidgets
|
||||
|
||||
Column {
|
||||
id: root
|
||||
@@ -21,13 +22,13 @@ Column {
|
||||
Row {
|
||||
spacing: root.horizontalSpacing
|
||||
|
||||
PopupLabel {
|
||||
HelperWidgets.PopupLabel {
|
||||
width: root.columnWidth
|
||||
text: qsTr("From")
|
||||
tooltip: qsTr("Sets the component and its property from which the value is copied.")
|
||||
}
|
||||
|
||||
PopupLabel {
|
||||
HelperWidgets.PopupLabel {
|
||||
width: root.columnWidth
|
||||
text: qsTr("To")
|
||||
tooltip: qsTr("Sets the property of the selected component to which the copied value is assigned.")
|
||||
@@ -49,7 +50,7 @@ Column {
|
||||
onCurrentTypeIndexChanged: sourceNode.currentIndex = sourceNode.currentTypeIndex
|
||||
}
|
||||
|
||||
PopupLabel {
|
||||
HelperWidgets.PopupLabel {
|
||||
width: root.columnWidth
|
||||
text: backend.targetNode
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
@@ -49,8 +49,6 @@ StudioControls.PopupDialog {
|
||||
ConnectionsDialogForm {
|
||||
id: form
|
||||
|
||||
parentWindow: root.window
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
function onPopupShouldClose() {
|
||||
|
@@ -6,6 +6,8 @@ import QtQuick.Controls
|
||||
import HelperWidgets as HelperWidgets
|
||||
import StudioControls as StudioControls
|
||||
import StudioTheme as StudioTheme
|
||||
import ScriptsEditor as ScriptsEditor
|
||||
import ScriptEditorBackend
|
||||
|
||||
Column {
|
||||
id: root
|
||||
@@ -16,8 +18,7 @@ Column {
|
||||
|
||||
property var backend
|
||||
|
||||
property bool keepOpen: expressionDialogLoader.visible
|
||||
property Window parentWindow: null
|
||||
property bool keepOpen: scriptEditor.keepOpen
|
||||
|
||||
width: parent.width
|
||||
spacing: root.verticalSpacing
|
||||
@@ -29,13 +30,13 @@ Column {
|
||||
Row {
|
||||
spacing: root.horizontalSpacing
|
||||
|
||||
PopupLabel {
|
||||
HelperWidgets.PopupLabel {
|
||||
width: root.columnWidth
|
||||
text: qsTr("Signal")
|
||||
tooltip: qsTr("Sets an interaction method that connects to the <b>Target</b> component.")
|
||||
}
|
||||
|
||||
PopupLabel {
|
||||
HelperWidgets.PopupLabel {
|
||||
width: root.columnWidth
|
||||
text: qsTr("Action")
|
||||
tooltip: qsTr("Sets an action that is associated with the selected <b>Target</b> component's <b>Signal</b>.")
|
||||
@@ -58,259 +59,40 @@ Column {
|
||||
onCurrentTypeIndexChanged: signal.currentIndex = signal.currentTypeIndex
|
||||
}
|
||||
|
||||
StudioControls.TopLevelComboBox {
|
||||
ScriptsEditor.ActionsComboBox {
|
||||
id: action
|
||||
style: StudioTheme.Values.connectionPopupControlStyle
|
||||
|
||||
width: root.columnWidth
|
||||
textRole: "text"
|
||||
valueRole: "value"
|
||||
///model.getData(currentIndex, "role")
|
||||
property int indexFromBackend: indexOfValue(backend.actionType)
|
||||
onIndexFromBackendChanged: action.currentIndex = action.indexFromBackend
|
||||
onActivated: backend.changeActionType(action.currentValue)
|
||||
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
value: ConnectionModelStatementDelegate.CallFunction
|
||||
text: qsTr("Call Function")
|
||||
enabled: true
|
||||
}
|
||||
ListElement {
|
||||
value: ConnectionModelStatementDelegate.Assign
|
||||
text: qsTr("Assign")
|
||||
enabled: true
|
||||
}
|
||||
ListElement {
|
||||
value: ConnectionModelStatementDelegate.ChangeState
|
||||
text: qsTr("Change State")
|
||||
enabled: true
|
||||
}
|
||||
ListElement {
|
||||
value: ConnectionModelStatementDelegate.SetProperty
|
||||
text: qsTr("Set Property")
|
||||
enabled: true
|
||||
}
|
||||
ListElement {
|
||||
value: ConnectionModelStatementDelegate.PrintMessage
|
||||
text: qsTr("Print Message")
|
||||
enabled: true
|
||||
}
|
||||
ListElement {
|
||||
value: ConnectionModelStatementDelegate.Custom
|
||||
text: qsTr("Custom")
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StatementEditor {
|
||||
width: root.width
|
||||
actionType: action.currentValue ?? ConnectionModelStatementDelegate.Custom
|
||||
horizontalSpacing: root.horizontalSpacing
|
||||
columnWidth: root.columnWidth
|
||||
statement: backend.okStatement
|
||||
backend: root.backend
|
||||
spacing: root.verticalSpacing
|
||||
}
|
||||
|
||||
HelperWidgets.AbstractButton {
|
||||
style: StudioTheme.Values.connectionPopupButtonStyle
|
||||
width: 160
|
||||
buttonIcon: qsTr("Add Condition")
|
||||
tooltip: qsTr("Sets a logical condition for the selected <b>Signal</b>. It works with the properties of the <b>Target</b> component.")
|
||||
iconSize: StudioTheme.Values.baseFontSize
|
||||
iconFontFamily: StudioTheme.Constants.font.family
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: action.currentValue !== ConnectionModelStatementDelegate.Custom && !backend.hasCondition
|
||||
|
||||
onClicked: backend.addCondition()
|
||||
}
|
||||
|
||||
HelperWidgets.AbstractButton {
|
||||
style: StudioTheme.Values.connectionPopupButtonStyle
|
||||
width: 160
|
||||
buttonIcon: qsTr("Remove Condition")
|
||||
tooltip: qsTr("Removes the logical condition for the <b>Target</b> component.")
|
||||
iconSize: StudioTheme.Values.baseFontSize
|
||||
iconFontFamily: StudioTheme.Constants.font.family
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: action.currentValue !== ConnectionModelStatementDelegate.Custom && backend.hasCondition
|
||||
|
||||
onClicked: backend.removeCondition()
|
||||
}
|
||||
|
||||
ExpressionBuilder {
|
||||
style: StudioTheme.Values.connectionPopupControlStyle
|
||||
width: root.width
|
||||
|
||||
visible: backend.hasCondition
|
||||
model: backend.conditionListModel
|
||||
|
||||
onRemove: function(index) {
|
||||
//console.log("remove", index)
|
||||
backend.conditionListModel.removeToken(index)
|
||||
}
|
||||
|
||||
onUpdate: function(index, value) {
|
||||
//console.log("update", index, value)
|
||||
backend.conditionListModel.updateToken(index, value)
|
||||
}
|
||||
|
||||
onAdd: function(value) {
|
||||
//console.log("add", value)
|
||||
backend.conditionListModel.appendToken(value)
|
||||
}
|
||||
|
||||
onInsert: function(index, value, type) {
|
||||
//console.log("insert", index, value, type)
|
||||
if (type === ConditionListModel.Intermediate)
|
||||
backend.conditionListModel.insertIntermediateToken(index, value)
|
||||
else if (type === ConditionListModel.Shadow)
|
||||
backend.conditionListModel.insertShadowToken(index, value)
|
||||
else
|
||||
backend.conditionListModel.insertToken(index, value)
|
||||
}
|
||||
|
||||
onSetValue: function(index, value) {
|
||||
//console.log("setValue", index, value)
|
||||
backend.conditionListModel.setShadowToken(index, value)
|
||||
}
|
||||
}
|
||||
|
||||
HelperWidgets.AbstractButton {
|
||||
style: StudioTheme.Values.connectionPopupButtonStyle
|
||||
width: 160
|
||||
buttonIcon: qsTr("Add Else Statement")
|
||||
tooltip: qsTr("Sets an alternate condition for the previously defined logical condition.")
|
||||
iconSize: StudioTheme.Values.baseFontSize
|
||||
iconFontFamily: StudioTheme.Constants.font.family
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: action.currentValue !== ConnectionModelStatementDelegate.Custom
|
||||
&& backend.hasCondition && !backend.hasElse
|
||||
ScriptsEditor.ScriptEditorForm {
|
||||
id: scriptEditor
|
||||
|
||||
onClicked: backend.addElse()
|
||||
}
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
HelperWidgets.AbstractButton {
|
||||
style: StudioTheme.Values.connectionPopupButtonStyle
|
||||
width: 160
|
||||
buttonIcon: qsTr("Remove Else Statement")
|
||||
tooltip: qsTr("Removes the alternate logical condition for the previously defined logical condition.")
|
||||
iconSize: StudioTheme.Values.baseFontSize
|
||||
iconFontFamily: StudioTheme.Constants.font.family
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: action.currentValue !== ConnectionModelStatementDelegate.Custom
|
||||
&& backend.hasCondition && backend.hasElse
|
||||
|
||||
onClicked: backend.removeElse()
|
||||
}
|
||||
|
||||
//Else Statement
|
||||
StatementEditor {
|
||||
width: root.width
|
||||
actionType: action.currentValue ?? ConnectionModelStatementDelegate.Custom
|
||||
horizontalSpacing: root.horizontalSpacing
|
||||
verticalSpacing: root.verticalSpacing
|
||||
columnWidth: root.columnWidth
|
||||
statement: backend.koStatement
|
||||
spacing: root.spacing
|
||||
|
||||
backend: root.backend
|
||||
spacing: root.verticalSpacing
|
||||
visible: action.currentValue !== ConnectionModelStatementDelegate.Custom
|
||||
&& backend.hasCondition && backend.hasElse
|
||||
}
|
||||
|
||||
// code preview toolbar
|
||||
Column {
|
||||
id: miniToolbarEditor
|
||||
width: parent.width
|
||||
spacing: -2
|
||||
currentAction: action.currentValue ?? StatementDelegate.Custom
|
||||
|
||||
Rectangle {
|
||||
id: miniToolbar
|
||||
width: parent.width
|
||||
height: editorButton.height + 2
|
||||
radius: 4
|
||||
z: -1
|
||||
color: StudioTheme.Values.themeConnectionEditorMicroToolbar
|
||||
|
||||
Row {
|
||||
spacing: 2
|
||||
HelperWidgets.AbstractButton {
|
||||
id: editorButton
|
||||
style: StudioTheme.Values.microToolbarButtonStyle
|
||||
buttonIcon: StudioTheme.Constants.codeEditor_medium
|
||||
tooltip: qsTr("Write the conditions for the components and the signals manually.")
|
||||
onClicked: expressionDialogLoader.show()
|
||||
}
|
||||
HelperWidgets.AbstractButton {
|
||||
id: jumpToCodeButton
|
||||
style: StudioTheme.Values.microToolbarButtonStyle
|
||||
buttonIcon: StudioTheme.Constants.jumpToCode_medium
|
||||
tooltip: qsTr("Jump to the code.")
|
||||
onClicked: backend.jumpToCode()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Editor
|
||||
Rectangle {
|
||||
id: editor
|
||||
width: parent.width
|
||||
height: 150
|
||||
color: StudioTheme.Values.themeConnectionCodeEditor
|
||||
|
||||
Text {
|
||||
id: code
|
||||
anchors.fill: parent
|
||||
anchors.margins: 4
|
||||
text: backend.indentedSource
|
||||
color: StudioTheme.Values.themeTextColor
|
||||
font.pixelSize: StudioTheme.Values.myFontSize
|
||||
wrapMode: Text.Wrap
|
||||
horizontalAlignment: code.lineCount === 1 ? Text.AlignHCenter : Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: expressionDialogLoader
|
||||
parent: editor
|
||||
anchors.fill: parent
|
||||
visible: false
|
||||
active: visible
|
||||
|
||||
function show() {
|
||||
expressionDialogLoader.visible = true
|
||||
}
|
||||
|
||||
sourceComponent: Item {
|
||||
id: bindingEditorParent
|
||||
|
||||
Component.onCompleted: {
|
||||
bindingEditor.showWidget()
|
||||
bindingEditor.text = backend.source
|
||||
bindingEditor.showControls(false)
|
||||
bindingEditor.setMultilne(true)
|
||||
bindingEditor.updateWindowName()
|
||||
}
|
||||
|
||||
ActionEditor {
|
||||
id: bindingEditor
|
||||
|
||||
onRejected: {
|
||||
bindingEditor.hideWidget()
|
||||
expressionDialogLoader.visible = false
|
||||
}
|
||||
|
||||
onAccepted: {
|
||||
backend.setNewSource(bindingEditor.text)
|
||||
bindingEditor.hideWidget()
|
||||
expressionDialogLoader.visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
itemTooltip: qsTr("Sets the component that is affected by the action of the <b>Target</b> component's <b>Signal</b>.")
|
||||
methodTooltip: qsTr("Sets the item component's method that is affected by the <b>Target</b> component's <b>Signal</b>.")
|
||||
fromTooltip: qsTr("Sets the component and its property from which the value is copied when the <b>Target</b> component initiates the <b>Signal</b>.")
|
||||
toTooltip: qsTr("Sets the component and its property to which the copied value is assigned when the <b>Target</b> component initiates the <b>Signal</b>.")
|
||||
addConditionTooltip: qsTr("Sets a logical condition for the selected <b>Signal</b>. It works with the properties of the <b>Target</b> component.")
|
||||
removeConditionTooltip: qsTr("Removes the logical condition for the <b>Target</b> component.")
|
||||
stateGroupTooltip: qsTr("Sets a <b>State Group</b> that is accessed when the <b>Target</b> component initiates the <b>Signal</b>.")
|
||||
stateTooltip: qsTr("Sets a <b>State</b> within the assigned <b>State Group</b> that is accessed when the <b>Target</b> component initiates the <b>Signal</b>.")
|
||||
propertyTooltip: qsTr("Sets the property of the component that is affected by the action of the <b>Target</b> component's <b>Signal</b>.")
|
||||
valueTooltip: qsTr("Sets the value of the property of the component that is affected by the action of the <b>Target</b> component's <b>Signal</b>.")
|
||||
messageTooltip: qsTr("Sets a text that is printed when the <b>Signal</b> of the <b>Target</b> component initiates.")
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -5,6 +5,7 @@ import QtQuick
|
||||
import QtQuick.Controls
|
||||
import StudioControls as StudioControls
|
||||
import StudioTheme as StudioTheme
|
||||
import HelperWidgets as HelperWidgets
|
||||
|
||||
Column {
|
||||
id: root
|
||||
@@ -18,7 +19,7 @@ Column {
|
||||
width: parent.width
|
||||
spacing: root.verticalSpacing
|
||||
|
||||
PopupLabel {
|
||||
HelperWidgets.PopupLabel {
|
||||
text: qsTr("Type")
|
||||
tooltip: qsTr("Sets the category of the <b>Local Custom Property</b>.")
|
||||
}
|
||||
@@ -37,13 +38,13 @@ Column {
|
||||
Row {
|
||||
spacing: root.horizontalSpacing
|
||||
|
||||
PopupLabel {
|
||||
HelperWidgets.PopupLabel {
|
||||
width: root.columnWidth
|
||||
text: qsTr("Name")
|
||||
tooltip: qsTr("Sets a name for the <b>Local Custom Property</b>.")
|
||||
}
|
||||
|
||||
PopupLabel {
|
||||
HelperWidgets.PopupLabel {
|
||||
width: root.columnWidth
|
||||
text: qsTr("Value")
|
||||
tooltip: qsTr("Sets a valid <b>Local Custom Property</b> value.")
|
||||
|
@@ -109,29 +109,35 @@ HelperWidgets.ScrollView {
|
||||
id: infoText
|
||||
|
||||
text: {
|
||||
if (!ContentLibraryBackend.rootView.isQt6Project)
|
||||
if (!ContentLibraryBackend.rootView.isQt6Project) {
|
||||
qsTr("<b>Content Library</b> effects are not supported in Qt5 projects.")
|
||||
else if (!ContentLibraryBackend.rootView.hasQuick3DImport)
|
||||
qsTr('To use <b>Content Library</b>, first <a href="#add_import" style="text-decoration:none;color:%1">
|
||||
add the QtQuick3D module</a> in the <b>Components</b> view.')
|
||||
.arg(StudioTheme.Values.themeInteraction)
|
||||
else if (!ContentLibraryBackend.effectsModel.hasRequiredQuick3DImport)
|
||||
} else if (!ContentLibraryBackend.rootView.hasQuick3DImport) {
|
||||
qsTr('To use <b>Content Library</b> effects, add the <b>QtQuick3D</b> module and the <b>View3D</b>
|
||||
component in the <b>Components</b> view, or click
|
||||
<a href=\"#add_import\"><span style=\"text-decoration:none;color:%1\">
|
||||
here</span></a>.').arg(StudioTheme.Values.themeInteraction)
|
||||
} else if (!ContentLibraryBackend.effectsModel.hasRequiredQuick3DImport) {
|
||||
qsTr("To use <b>Content Library</b>, version 6.4 or later of the QtQuick3D module is required.")
|
||||
else if (!ContentLibraryBackend.rootView.hasMaterialLibrary)
|
||||
} else if (!ContentLibraryBackend.rootView.hasMaterialLibrary) {
|
||||
qsTr("<b>Content Library</b> is disabled inside a non-visual component.")
|
||||
else if (!ContentLibraryBackend.effectsModel.bundleExists)
|
||||
} else if (!ContentLibraryBackend.effectsModel.bundleExists) {
|
||||
qsTr("No effects available.")
|
||||
else if (!searchBox.isEmpty())
|
||||
} else if (!searchBox.isEmpty()) {
|
||||
qsTr("No match found.")
|
||||
else
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
textFormat: Text.RichText
|
||||
color: StudioTheme.Values.themeTextColor
|
||||
font.pixelSize: StudioTheme.Values.baseFontSize
|
||||
topPadding: 10
|
||||
leftPadding: 10
|
||||
rightPadding: 10
|
||||
visible: ContentLibraryBackend.effectsModel.isEmpty
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WordWrap
|
||||
width: root.width
|
||||
|
||||
onLinkActivated: ContentLibraryBackend.rootView.addQtQuick3D()
|
||||
}
|
||||
|
@@ -114,30 +114,34 @@ HelperWidgets.ScrollView {
|
||||
id: infoText
|
||||
|
||||
text: {
|
||||
if (!ContentLibraryBackend.rootView.isQt6Project)
|
||||
if (!ContentLibraryBackend.rootView.isQt6Project) {
|
||||
qsTr("<b>Content Library</b> materials are not supported in Qt5 projects.")
|
||||
else if (!ContentLibraryBackend.rootView.hasQuick3DImport)
|
||||
qsTr('To use <b>Content Library</b>, first <a href="#add_import" style="text-decoration:none;color:%1">
|
||||
add the QtQuick3D module</a> in the <b>Components</b> view.')
|
||||
.arg(StudioTheme.Values.themeInteraction)
|
||||
else if (!root.materialsModel.hasRequiredQuick3DImport)
|
||||
} else if (!ContentLibraryBackend.rootView.hasQuick3DImport) {
|
||||
qsTr('To use <b>Content Library</b> materials, add the <b>QtQuick3D</b> module and the <b>View3D</b>
|
||||
component in the <b>Components</b> view, or click
|
||||
<a href=\"#add_import\"><span style=\"text-decoration:none;color:%1\">
|
||||
here</span></a>.').arg(StudioTheme.Values.themeInteraction)
|
||||
} else if (!root.materialsModel.hasRequiredQuick3DImport) {
|
||||
qsTr("To use <b>Content Library</b>, version 6.3 or later of the QtQuick3D module is required.")
|
||||
else if (!ContentLibraryBackend.rootView.hasMaterialLibrary)
|
||||
} else if (!ContentLibraryBackend.rootView.hasMaterialLibrary) {
|
||||
qsTr("<b>Content Library</b> is disabled inside a non-visual component.")
|
||||
else if (!root.materialsModel.bundleExists)
|
||||
} else if (!root.materialsModel.bundleExists) {
|
||||
qsTr("No materials available. Make sure you have an internet connection.")
|
||||
else if (!searchBox.isEmpty())
|
||||
} else if (!searchBox.isEmpty()) {
|
||||
qsTr("No match found.")
|
||||
else
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
textFormat: Text.RichText
|
||||
color: StudioTheme.Values.themeTextColor
|
||||
font.pixelSize: StudioTheme.Values.baseFontSize
|
||||
topPadding: 10
|
||||
leftPadding: 10
|
||||
rightPadding: 10
|
||||
wrapMode: Text.WordWrap
|
||||
width: root.width - x
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
|
||||
onLinkActivated: ContentLibraryBackend.rootView.addQtQuick3D()
|
||||
}
|
||||
|
@@ -213,26 +213,33 @@ Item {
|
||||
text: {
|
||||
let categoryName = (categoryTitle === "3D") ? categoryTitle + " assets"
|
||||
: categoryTitle.toLowerCase()
|
||||
if (!ContentLibraryBackend.rootView.isQt6Project)
|
||||
if (!ContentLibraryBackend.rootView.isQt6Project) {
|
||||
qsTr("<b>Content Library</b> is not supported in Qt5 projects.")
|
||||
else if (!ContentLibraryBackend.rootView.hasQuick3DImport && categoryTitle !== "Textures")
|
||||
qsTr(`To use %1, first <a href="#add_import" style="text-decoration:none;color:%2">
|
||||
add the <b>QtQuick3D</b> module</a> in the <b>Components</b> view.`)
|
||||
} else if (!ContentLibraryBackend.rootView.hasQuick3DImport && categoryTitle !== "Textures") {
|
||||
qsTr('To use %1, add the <b>QtQuick3D</b> module and the <b>View3D</b>
|
||||
component in the <b>Components</b> view, or click
|
||||
<a href=\"#add_import\"><span style=\"text-decoration:none;color:%2\">
|
||||
here</span></a>.')
|
||||
.arg(categoryName)
|
||||
.arg(StudioTheme.Values.themeInteraction)
|
||||
else if (!ContentLibraryBackend.rootView.hasMaterialLibrary && categoryTitle !== "Textures")
|
||||
} else if (!ContentLibraryBackend.rootView.hasMaterialLibrary && categoryTitle !== "Textures") {
|
||||
qsTr("<b>Content Library</b> is disabled inside a non-visual component.")
|
||||
else if (categoryEmpty)
|
||||
} else if (categoryEmpty) {
|
||||
qsTr("There are no "+ categoryName + " in the <b>User Assets</b>.")
|
||||
else
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
textFormat: Text.RichText
|
||||
color: StudioTheme.Values.themeTextColor
|
||||
font.pixelSize: StudioTheme.Values.baseFontSize
|
||||
topPadding: 10
|
||||
leftPadding: 10
|
||||
rightPadding: 10
|
||||
visible: infoText.text !== ""
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WordWrap
|
||||
width: root.width
|
||||
|
||||
onLinkActivated: ContentLibraryBackend.rootView.addQtQuick3D()
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,62 @@
|
||||
// Copyright (C) 2024 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
|
||||
|
||||
Item {
|
||||
id: control
|
||||
|
||||
property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle
|
||||
property alias icon: icon
|
||||
|
||||
property bool hover: mouseArea.containsMouse//false
|
||||
property bool pressed: false
|
||||
property bool forceVisible: false
|
||||
|
||||
implicitWidth: control.style.actionIndicatorSize.width
|
||||
implicitHeight: control.style.actionIndicatorSize.height
|
||||
|
||||
signal clicked
|
||||
z: 10
|
||||
|
||||
T.Label {
|
||||
id: icon
|
||||
anchors.fill: parent
|
||||
text: StudioTheme.Constants.actionIcon
|
||||
color: control.style.icon.idle
|
||||
font.family: StudioTheme.Constants.iconFont.family
|
||||
font.pixelSize: control.style.baseIconFontSize
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "hover"
|
||||
when: control.hover && !control.pressed && control.enabled
|
||||
PropertyChanges {
|
||||
target: icon
|
||||
scale: 1.2
|
||||
visible: true
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "disable"
|
||||
when: !control.enabled
|
||||
PropertyChanges {
|
||||
target: icon
|
||||
color: control.style.icon.disabled
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
//onContainsMouseChanged: control.hover = mouseArea.containsMouse
|
||||
onClicked: control.clicked()
|
||||
}
|
||||
}
|
@@ -0,0 +1,365 @@
|
||||
// Copyright (C) 2025 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 StudioQuickUtils
|
||||
|
||||
T.SpinBox {
|
||||
id: control
|
||||
|
||||
property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle
|
||||
|
||||
property real realFrom: 0.0
|
||||
property real realTo: 99.0
|
||||
property real realValue: 1.0
|
||||
property real realStepSize: 1.0
|
||||
|
||||
property alias labelColor: spinBoxInput.color
|
||||
|
||||
property int decimals: 0
|
||||
|
||||
property real minStepSize: {
|
||||
var tmpMinStepSize = Number((control.realStepSize * 0.1).toFixed(control.decimals))
|
||||
return (tmpMinStepSize) ? tmpMinStepSize : control.realStepSize
|
||||
}
|
||||
property real maxStepSize: {
|
||||
var tmpMaxStepSize = Number((control.realStepSize * 10.0).toFixed(control.decimals))
|
||||
return (tmpMaxStepSize < control.realTo) ? tmpMaxStepSize : control.realStepSize
|
||||
}
|
||||
|
||||
property bool edit: spinBoxInput.activeFocus
|
||||
// This property is used to indicate the global hover state
|
||||
property bool hover: (spinBoxInput.hover || spinBoxIndicatorUp.hover || spinBoxIndicatorDown.hover)
|
||||
&& control.enabled
|
||||
|
||||
property bool dirty: false // user modification flag
|
||||
|
||||
property bool spinBoxIndicatorVisible: true
|
||||
property real __spinBoxIndicatorWidth: control.style.spinBoxIndicatorSize.width
|
||||
property real __spinBoxIndicatorHeight: control.height / 2 - control.style.borderWidth
|
||||
|
||||
property alias compressedValueTimer: myTimer
|
||||
|
||||
property string preFocusText: ""
|
||||
|
||||
signal realValueModified
|
||||
signal compressedRealValueModified
|
||||
signal indicatorPressed
|
||||
|
||||
locale: Utils.locale
|
||||
|
||||
// Use custom wheel handling due to bugs
|
||||
property bool __wheelEnabled: false
|
||||
wheelEnabled: false
|
||||
hoverEnabled: true
|
||||
|
||||
width: control.style.controlSize.width
|
||||
height: control.style.controlSize.height
|
||||
|
||||
leftPadding: spinBoxIndicatorDown.x + spinBoxIndicatorDown.width
|
||||
rightPadding: control.style.borderWidth
|
||||
|
||||
font.pixelSize: control.style.baseFontSize
|
||||
editable: true
|
||||
|
||||
// Leave this in for now
|
||||
from: -99
|
||||
value: 0
|
||||
to: 99
|
||||
|
||||
function checkAndClearFocus() {
|
||||
if (!spinBoxIndicatorUp.activeFocus && !spinBoxIndicatorDown.activeFocus && !spinBoxInput.activeFocus)
|
||||
control.focus = false
|
||||
}
|
||||
|
||||
DoubleValidator {
|
||||
id: doubleValidator
|
||||
locale: control.locale
|
||||
notation: DoubleValidator.StandardNotation
|
||||
decimals: control.decimals
|
||||
bottom: Math.min(control.realFrom, control.realTo)
|
||||
top: Math.max(control.realFrom, control.realTo)
|
||||
}
|
||||
|
||||
IntValidator {
|
||||
id: intValidator
|
||||
locale: control.locale
|
||||
bottom: Math.round(Math.min(control.realFrom, control.realTo))
|
||||
top: Math.round(Math.max(control.realFrom, control.realTo))
|
||||
}
|
||||
|
||||
validator: control.decimals === 0 ? intValidator : doubleValidator
|
||||
|
||||
up.indicator: SpinBoxIndicator {
|
||||
id: spinBoxIndicatorUp
|
||||
style: control.style
|
||||
parentHover: control.hover
|
||||
parentEdit: control.edit
|
||||
iconFlip: -1
|
||||
visible: control.spinBoxIndicatorVisible
|
||||
onRealPressed: control.indicatorPressed()
|
||||
onRealReleased: control.realIncrease()
|
||||
onRealPressAndHold: control.realIncrease()
|
||||
x: control.style.borderWidth
|
||||
y: control.style.borderWidth
|
||||
width: control.spinBoxIndicatorVisible ? control.__spinBoxIndicatorWidth : 0
|
||||
height: control.spinBoxIndicatorVisible ? control.__spinBoxIndicatorHeight : 0
|
||||
|
||||
realEnabled: (control.realFrom < control.realTo) ? (control.realValue < control.realTo)
|
||||
: (control.realValue > control.realTo)
|
||||
}
|
||||
|
||||
down.indicator: SpinBoxIndicator {
|
||||
id: spinBoxIndicatorDown
|
||||
style: control.style
|
||||
parentHover: control.hover
|
||||
parentEdit: control.edit
|
||||
visible: control.spinBoxIndicatorVisible
|
||||
onRealPressed: control.indicatorPressed()
|
||||
onRealReleased: control.realDecrease()
|
||||
onRealPressAndHold: control.realDecrease()
|
||||
x: control.style.borderWidth
|
||||
y: spinBoxIndicatorUp.y + spinBoxIndicatorUp.height
|
||||
width: control.spinBoxIndicatorVisible ? control.__spinBoxIndicatorWidth : 0
|
||||
height: control.spinBoxIndicatorVisible ? control.__spinBoxIndicatorHeight : 0
|
||||
|
||||
realEnabled: (control.realFrom < control.realTo) ? (control.realValue > control.realFrom)
|
||||
: (control.realValue < control.realFrom)
|
||||
}
|
||||
|
||||
contentItem: SpinBoxInput {
|
||||
id: spinBoxInput
|
||||
style: control.style
|
||||
validator: control.validator
|
||||
font: control.font
|
||||
readOnly: !control.editable
|
||||
inputMethodHints: control.inputMethodHints
|
||||
|
||||
function handleEditingFinished() {
|
||||
control.checkAndClearFocus()
|
||||
|
||||
// Keep the dirty state before calling setValueFromInput(),
|
||||
// it will be set to false (cleared) internally
|
||||
var valueModified = control.dirty
|
||||
|
||||
control.setValueFromInput()
|
||||
myTimer.stop()
|
||||
|
||||
// Only trigger the signal, if the value was modified
|
||||
if (valueModified)
|
||||
control.compressedRealValueModified()
|
||||
}
|
||||
|
||||
onEditingFinished: {
|
||||
spinBoxInput.focus = false
|
||||
spinBoxInput.handleEditingFinished()
|
||||
}
|
||||
|
||||
onTextEdited: control.dirty = true
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
id: spinBoxBackground
|
||||
color: control.style.background.idle
|
||||
border.color: control.style.border.idle
|
||||
border.width: control.style.borderWidth
|
||||
x: 0
|
||||
width: control.width
|
||||
height: control.height
|
||||
}
|
||||
|
||||
textFromValue: function (value, locale) {
|
||||
// -128 is the value of QLocale::FloatingPointShortest
|
||||
return Number(control.realValue).toLocaleString(locale, 'f', -128)
|
||||
}
|
||||
|
||||
valueFromText: function (text, locale) {
|
||||
control.setRealValue(Number.fromLocaleString(locale, spinBoxInput.text))
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "default"
|
||||
when: control.enabled && !control.hover && !control.hovered
|
||||
&& !control.edit
|
||||
PropertyChanges {
|
||||
target: control
|
||||
__wheelEnabled: false
|
||||
}
|
||||
PropertyChanges {
|
||||
target: spinBoxInput
|
||||
selectByMouse: false
|
||||
}
|
||||
PropertyChanges {
|
||||
target: spinBoxBackground
|
||||
color: control.style.background.idle
|
||||
border.color: control.style.border.idle
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "hover"
|
||||
when: control.enabled && control.hover && control.hovered
|
||||
&& !control.edit
|
||||
PropertyChanges {
|
||||
target: spinBoxBackground
|
||||
border.color: control.style.border.hover
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "edit"
|
||||
when: control.edit
|
||||
PropertyChanges {
|
||||
target: control
|
||||
__wheelEnabled: true
|
||||
}
|
||||
PropertyChanges {
|
||||
target: spinBoxInput
|
||||
selectByMouse: true
|
||||
}
|
||||
PropertyChanges {
|
||||
target: spinBoxBackground
|
||||
border.color: control.style.border.interaction
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "disable"
|
||||
when: !control.enabled
|
||||
PropertyChanges {
|
||||
target: spinBoxBackground
|
||||
border.color: control.style.border.disabled
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
Timer {
|
||||
id: myTimer
|
||||
repeat: false
|
||||
running: false
|
||||
interval: 400
|
||||
onTriggered: control.compressedRealValueModified()
|
||||
}
|
||||
|
||||
onRealValueChanged: {
|
||||
control.setRealValue(control.realValue) // sanitize and clamp realValue
|
||||
spinBoxInput.text = control.textFromValue(control.realValue, control.locale)
|
||||
control.value = 0 // Without setting value back to 0, it can happen that one of
|
||||
// the indicator will be disabled due to range logic.
|
||||
}
|
||||
onRealValueModified: myTimer.restart()
|
||||
onFocusChanged: {
|
||||
if (control.focus)
|
||||
control.dirty = false
|
||||
}
|
||||
onDisplayTextChanged: spinBoxInput.text = control.displayText
|
||||
onActiveFocusChanged: {
|
||||
if (control.activeFocus) { // QTBUG-75862 && mySpinBox.focusReason === Qt.TabFocusReason)
|
||||
control.preFocusText = spinBoxInput.text
|
||||
spinBoxInput.selectAll()
|
||||
} else {
|
||||
// Make sure displayed value is correct after focus loss, as onEditingFinished
|
||||
// doesn't trigger when value is something validator doesn't accept.
|
||||
if (spinBoxInput.text === "")
|
||||
spinBoxInput.text = control.textFromValue(control.realValue, control.locale)
|
||||
|
||||
if (control.dirty)
|
||||
spinBoxInput.handleEditingFinished()
|
||||
}
|
||||
}
|
||||
onDecimalsChanged: spinBoxInput.text = control.textFromValue(control.realValue, control.locale)
|
||||
|
||||
Keys.onPressed: function(event) {
|
||||
if (event.key === Qt.Key_Up || event.key === Qt.Key_Down) {
|
||||
event.accepted = true
|
||||
|
||||
// Store current step size
|
||||
var currStepSize = control.realStepSize
|
||||
|
||||
// Set realStepSize according to used modifier key
|
||||
if (event.modifiers & Qt.ControlModifier)
|
||||
control.realStepSize = control.minStepSize
|
||||
|
||||
if (event.modifiers & Qt.ShiftModifier)
|
||||
control.realStepSize = control.maxStepSize
|
||||
|
||||
if (event.key === Qt.Key_Up)
|
||||
control.realIncrease()
|
||||
else
|
||||
control.realDecrease()
|
||||
|
||||
// Reset realStepSize
|
||||
control.realStepSize = currStepSize
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
spinBoxInput.text = control.preFocusText
|
||||
control.dirty = true
|
||||
spinBoxInput.handleEditingFinished()
|
||||
}
|
||||
}
|
||||
|
||||
function clamp(v, lo, hi) {
|
||||
return (v < lo || v > hi) ? Math.min(Math.max(lo, v), hi) : v
|
||||
}
|
||||
|
||||
function setValueFromInput() {
|
||||
if (!control.dirty)
|
||||
return
|
||||
|
||||
// FIX: This is a temporary fix for QTBUG-74239
|
||||
var currValue = control.realValue
|
||||
|
||||
// Call the function but don't use return value. The realValue property
|
||||
// will be implicitly set inside the function/procedure.
|
||||
control.valueFromText(spinBoxInput.text, control.locale)
|
||||
|
||||
if (control.realValue !== currValue) {
|
||||
control.realValueModified()
|
||||
} else {
|
||||
// Check if input text differs in format from the current value
|
||||
var tmpInputValue = control.textFromValue(control.realValue, control.locale)
|
||||
|
||||
if (tmpInputValue !== spinBoxInput.text)
|
||||
spinBoxInput.text = tmpInputValue
|
||||
}
|
||||
|
||||
control.dirty = false
|
||||
}
|
||||
|
||||
function setRealValue(value) {
|
||||
if (Number.isNaN(value))
|
||||
value = 0
|
||||
|
||||
if (control.decimals === 0)
|
||||
value = Math.round(value)
|
||||
|
||||
control.realValue = control.clamp(value,
|
||||
control.validator.bottom,
|
||||
control.validator.top)
|
||||
}
|
||||
|
||||
function realDecrease() {
|
||||
// Store the current value for comparison
|
||||
var currValue = control.realValue
|
||||
control.valueFromText(spinBoxInput.text, control.locale)
|
||||
|
||||
control.setRealValue(control.realValue - control.realStepSize)
|
||||
|
||||
if (control.realValue !== currValue)
|
||||
control.realValueModified()
|
||||
}
|
||||
|
||||
function realIncrease() {
|
||||
// Store the current value for comparison
|
||||
var currValue = control.realValue
|
||||
control.valueFromText(spinBoxInput.text, control.locale)
|
||||
|
||||
control.setRealValue(control.realValue + control.realStepSize)
|
||||
|
||||
if (control.realValue !== currValue)
|
||||
control.realValueModified()
|
||||
}
|
||||
}
|
@@ -0,0 +1,247 @@
|
||||
// Copyright (C) 2025 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
|
||||
|
||||
Rectangle {
|
||||
id: control
|
||||
|
||||
property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle
|
||||
|
||||
property bool hover: spinBoxIndicatorMouseArea.containsMouse
|
||||
property bool pressed: spinBoxIndicatorMouseArea.containsPress
|
||||
property bool released: false
|
||||
property bool realEnabled: true
|
||||
|
||||
property bool parentHover: false
|
||||
property bool parentEdit: false
|
||||
|
||||
signal realPressed
|
||||
signal realPressAndHold
|
||||
signal realReleased
|
||||
|
||||
property alias iconFlip: spinBoxIndicatorIconScale.yScale
|
||||
|
||||
|
||||
color: control.style.background.idle
|
||||
border.width: 0
|
||||
|
||||
onEnabledChanged: control.syncEnabled()
|
||||
onRealEnabledChanged: {
|
||||
control.syncEnabled()
|
||||
if (control.realEnabled === false) {
|
||||
pressAndHoldTimer.stop()
|
||||
spinBoxIndicatorMouseArea.pressedAndHeld = false
|
||||
}
|
||||
}
|
||||
|
||||
// This function is meant to synchronize enabled with realEnabled to avoid
|
||||
// the internal logic messing with the actual state.
|
||||
function syncEnabled() {
|
||||
control.enabled = control.realEnabled
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: pressAndHoldTimer
|
||||
repeat: true
|
||||
running: false
|
||||
interval: 100
|
||||
onTriggered: control.realPressAndHold()
|
||||
}
|
||||
|
||||
// This MouseArea is a workaround to avoid some hover state related bugs
|
||||
// when using the actual signal 'up.hovered'. QTBUG-74688
|
||||
MouseArea {
|
||||
id: spinBoxIndicatorMouseArea
|
||||
|
||||
property bool pressedAndHeld: false
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
pressAndHoldInterval: 500
|
||||
onPressed: function(mouse) {
|
||||
//if (control.__parentControl.activeFocus)
|
||||
// control.forceActiveFocus()
|
||||
|
||||
control.realPressed()
|
||||
mouse.accepted = true
|
||||
}
|
||||
onPressAndHold: {
|
||||
pressAndHoldTimer.restart()
|
||||
spinBoxIndicatorMouseArea.pressedAndHeld = true
|
||||
}
|
||||
onReleased: function(mouse) {
|
||||
// Only trigger real released when pressAndHold isn't active
|
||||
if (!pressAndHoldTimer.running && containsMouse)
|
||||
control.realReleased()
|
||||
pressAndHoldTimer.stop()
|
||||
mouse.accepted = true
|
||||
spinBoxIndicatorMouseArea.pressedAndHeld = false
|
||||
}
|
||||
onEntered: {
|
||||
if (spinBoxIndicatorMouseArea.pressedAndHeld)
|
||||
pressAndHoldTimer.restart()
|
||||
}
|
||||
onExited: {
|
||||
if (pressAndHoldTimer.running)
|
||||
pressAndHoldTimer.stop()
|
||||
}
|
||||
}
|
||||
|
||||
T.Label {
|
||||
id: spinBoxIndicatorIcon
|
||||
text: StudioTheme.Constants.upDownSquare2
|
||||
color: control.style.icon.idle
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.pixelSize: control.style.smallIconFontSize
|
||||
font.family: StudioTheme.Constants.iconFont.family
|
||||
anchors.fill: parent
|
||||
transform: Scale {
|
||||
id: spinBoxIndicatorIconScale
|
||||
origin.x: 0
|
||||
origin.y: spinBoxIndicatorIcon.height / 2
|
||||
yScale: 1
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "default"
|
||||
when: control.enabled && !control.hover && !control.parentEdit && !control.parentHover
|
||||
PropertyChanges {
|
||||
target: spinBoxIndicatorIcon
|
||||
color: control.style.icon.idle
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "globalHover"
|
||||
when: control.enabled && !control.hover && !control.parentEdit && control.parentHover
|
||||
PropertyChanges {
|
||||
target: spinBoxIndicatorIcon
|
||||
color: control.style.icon.idle
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "hover"
|
||||
when: control.enabled && control.hover && !control.pressed && control.parentHover
|
||||
PropertyChanges {
|
||||
target: spinBoxIndicatorIcon
|
||||
color: control.style.icon.hover
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "press"
|
||||
when: control.enabled && control.pressed
|
||||
PropertyChanges {
|
||||
target: spinBoxIndicatorIcon
|
||||
color: control.style.icon.idle
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "parentEdit"
|
||||
when: control.parentEdit && control.enabled
|
||||
PropertyChanges {
|
||||
target: spinBoxIndicatorIcon
|
||||
color: control.style.icon.idle
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "disable"
|
||||
when: !control.enabled
|
||||
PropertyChanges {
|
||||
target: spinBoxIndicatorIcon
|
||||
color: control.style.icon.disabled
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "default"
|
||||
when: !control.parentEdit && !control.hover && !control.parentHover
|
||||
PropertyChanges {
|
||||
target: spinBoxIndicatorIcon
|
||||
visible: false
|
||||
}
|
||||
PropertyChanges {
|
||||
target: control
|
||||
color: control.style.background.idle
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "globalHover"
|
||||
when: control.enabled && !control.hover && !control.parentEdit && control.parentHover
|
||||
PropertyChanges {
|
||||
target: spinBoxIndicatorIcon
|
||||
visible: true
|
||||
}
|
||||
PropertyChanges {
|
||||
target: control
|
||||
color: control.style.background.globalHover
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "hover"
|
||||
when: control.enabled && control.hover && !control.pressed && control.parentHover
|
||||
PropertyChanges {
|
||||
target: spinBoxIndicatorIcon
|
||||
visible: true
|
||||
}
|
||||
PropertyChanges {
|
||||
target: control
|
||||
color: control.style.background.hover
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "press"
|
||||
when: control.enabled && control.pressed
|
||||
PropertyChanges {
|
||||
target: spinBoxIndicatorIcon
|
||||
visible: true
|
||||
}
|
||||
PropertyChanges {
|
||||
target: control
|
||||
color: control.style.interaction
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "parentEdit"
|
||||
when: control.parentEdit && control.enabled
|
||||
PropertyChanges {
|
||||
target: spinBoxIndicatorIcon
|
||||
visible: true
|
||||
}
|
||||
PropertyChanges {
|
||||
target: control
|
||||
color: control.style.background.idle
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "disable"
|
||||
when: !control.enabled
|
||||
PropertyChanges {
|
||||
target: spinBoxIndicatorIcon
|
||||
visible: false
|
||||
}
|
||||
PropertyChanges {
|
||||
target: control
|
||||
color: control.style.background.disabled
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "limit"
|
||||
when: !control.enabled && !control.realEnabled && control.parentHover
|
||||
PropertyChanges {
|
||||
target: spinBoxIndicatorIcon
|
||||
visible: true
|
||||
}
|
||||
PropertyChanges {
|
||||
target: control
|
||||
color: control.style.background.idle
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@@ -0,0 +1,100 @@
|
||||
// Copyright (C) 2025 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
|
||||
|
||||
TextInput {
|
||||
id: control
|
||||
|
||||
property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle
|
||||
|
||||
property bool edit: control.activeFocus
|
||||
property bool drag: false
|
||||
property bool hover: hoverHandler.hovered && control.enabled
|
||||
|
||||
z: 2
|
||||
color: control.style.text.idle
|
||||
selectionColor: control.style.text.selection
|
||||
selectedTextColor: control.style.text.selectedText
|
||||
|
||||
horizontalAlignment: Qt.AlignLeft
|
||||
verticalAlignment: Qt.AlignVCenter
|
||||
leftPadding: control.style.inputHorizontalPadding
|
||||
rightPadding: control.style.inputHorizontalPadding
|
||||
|
||||
selectByMouse: false
|
||||
activeFocusOnPress: false
|
||||
clip: true
|
||||
|
||||
// TextInput focus needs to be set to activeFocus whenever it changes,
|
||||
// otherwise TextInput will get activeFocus whenever the parent SpinBox gets
|
||||
// activeFocus. This will lead to weird side effects.
|
||||
onActiveFocusChanged: control.focus = control.activeFocus
|
||||
|
||||
HoverHandler { id: hoverHandler }
|
||||
|
||||
Rectangle {
|
||||
id: textInputBackground
|
||||
x: 0
|
||||
y: control.style.borderWidth
|
||||
z: -1
|
||||
width: control.width
|
||||
height: control.height - (control.style.borderWidth * 2)
|
||||
color: control.style.background.idle
|
||||
border.width: 0
|
||||
}
|
||||
|
||||
// Ensure that we get Up and Down key press events first
|
||||
Keys.onShortcutOverride: function(event) {
|
||||
event.accepted = (event.key === Qt.Key_Up || event.key === Qt.Key_Down)
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "default"
|
||||
when: control.enabled && !control.edit && !control.hover
|
||||
PropertyChanges {
|
||||
target: textInputBackground
|
||||
color: control.style.background.idle
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "globalHover"
|
||||
when: !control.hover && !control.edit
|
||||
PropertyChanges {
|
||||
target: textInputBackground
|
||||
color: control.style.background.globalHover
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "hover"
|
||||
when: control.hover && !control.edit
|
||||
PropertyChanges {
|
||||
target: textInputBackground
|
||||
color: control.style.background.hover
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "edit"
|
||||
when: control.edit
|
||||
PropertyChanges {
|
||||
target: textInputBackground
|
||||
color: control.style.background.interaction
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "disable"
|
||||
when: !control.enabled
|
||||
PropertyChanges {
|
||||
target: textInputBackground
|
||||
color: control.style.background.disabled
|
||||
}
|
||||
PropertyChanges {
|
||||
target: control
|
||||
color: control.style.text.disabled
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@@ -4,7 +4,7 @@
|
||||
import QtQuick
|
||||
import QtQuick.Templates as T
|
||||
|
||||
import StudioTheme 1.0 as StudioTheme
|
||||
import StudioTheme as StudioTheme
|
||||
|
||||
T.Switch {
|
||||
id: control
|
||||
@@ -14,6 +14,7 @@ T.Switch {
|
||||
// This property is used to indicate the global hover state
|
||||
property bool hover: control.hovered && control.enabled
|
||||
property bool edit: false
|
||||
property bool readonly: false
|
||||
|
||||
property alias labelVisible: label.visible
|
||||
property alias labelColor: label.color
|
||||
@@ -33,6 +34,8 @@ T.Switch {
|
||||
hoverEnabled: true
|
||||
activeFocusOnTab: false
|
||||
|
||||
enabled: !control.readonly
|
||||
|
||||
indicator: Rectangle {
|
||||
id: switchBackground
|
||||
x: 0
|
||||
@@ -60,6 +63,7 @@ T.Switch {
|
||||
color: control.style.icon.idle
|
||||
border.width: 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
contentItem: T.Label {
|
||||
@@ -133,7 +137,7 @@ T.Switch {
|
||||
},
|
||||
State {
|
||||
name: "disable"
|
||||
when: !control.enabled && !control.checked
|
||||
when: !control.enabled && !control.checked && !control.readonly
|
||||
PropertyChanges {
|
||||
target: switchBackground
|
||||
color: control.style.background.disabled
|
||||
@@ -186,8 +190,19 @@ T.Switch {
|
||||
},
|
||||
State {
|
||||
name: "disableChecked"
|
||||
when: !control.enabled && control.checked
|
||||
when: !control.enabled && control.checked && !control.readonly
|
||||
extend: "disable"
|
||||
},
|
||||
|
||||
State {
|
||||
name: "readonly"
|
||||
when: !control.enabled && !control.checked && control.readonly
|
||||
extend: "default"
|
||||
},
|
||||
State {
|
||||
name: "readonlyChecked"
|
||||
when: !control.enabled && control.checked && control.readonly
|
||||
extend: "defaultChecked"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@@ -1,3 +1,7 @@
|
||||
BindingIndicator 1.0 BindingIndicator.qml
|
||||
MenuItem 1.0 MenuItem.qml
|
||||
SpinBox 1.0 SpinBox.qml
|
||||
SpinBoxIndicator 1.0 SpinBoxIndicator.qml
|
||||
SpinBoxInput 1.0 SpinBoxInput.qml
|
||||
Switch 1.0 Switch.qml
|
||||
TextField 1.0 TextField.qml
|
||||
|
@@ -596,21 +596,27 @@ Item {
|
||||
anchors.centerIn: parent
|
||||
|
||||
text: {
|
||||
if (!materialBrowserModel.isQt6Project)
|
||||
if (!materialBrowserModel.isQt6Project) {
|
||||
qsTr("<b>Material Browser</b> is not supported in Qt5 projects.")
|
||||
else if (!materialBrowserModel.hasQuick3DImport)
|
||||
qsTr("To use <b>Material Browser</b>, first add the QtQuick3D module in the <b>Components</b> view.")
|
||||
else if (!materialBrowserModel.hasMaterialLibrary)
|
||||
} else if (!materialBrowserModel.hasQuick3DImport) {
|
||||
qsTr('To use the <b>Material Browser</b>, add the <b>QtQuick3D</b> module and the <b>View3D</b>
|
||||
component in the <b>Components</b> view, or click
|
||||
<a href=\"#add_import\"><span style=\"text-decoration:none;color:%1\">
|
||||
here</span></a>.').arg(StudioTheme.Values.themeInteraction)
|
||||
} else if (!materialBrowserModel.hasMaterialLibrary) {
|
||||
qsTr("<b>Material Browser</b> is disabled inside a non-visual component.")
|
||||
else
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
textFormat: Text.RichText
|
||||
color: StudioTheme.Values.themeTextColor
|
||||
font.pixelSize: StudioTheme.Values.mediumFontSize
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
onLinkActivated: rootView.addQtQuick3D()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -186,6 +186,8 @@ Section {
|
||||
spacing: 1
|
||||
|
||||
Section {
|
||||
readonly property bool __isInEffectsSection: true // used by property search logic
|
||||
|
||||
sectionHeight: 37
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
@@ -232,6 +234,8 @@ Section {
|
||||
}
|
||||
|
||||
Section {
|
||||
readonly property bool __isInEffectsSection: true // used by property search logic
|
||||
|
||||
sectionHeight: 37
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
@@ -305,6 +309,8 @@ Section {
|
||||
Section {
|
||||
id: delegate
|
||||
|
||||
readonly property bool __isInEffectsSection: true // used by property search logic
|
||||
|
||||
property QtObject wrapper: modelNodeBackend.registerSubSelectionWrapper(modelData)
|
||||
property bool wasExpanded: false
|
||||
|
||||
|
@@ -20,7 +20,7 @@ PropertyEditorPane {
|
||||
}
|
||||
|
||||
DynamicPropertiesSection {
|
||||
propertiesModel: SelectionDynamicPropertiesModel {}
|
||||
propertiesModel: PropertyEditorDynamicPropertiesModel {}
|
||||
visible: !hasMultiSelection
|
||||
}
|
||||
|
||||
@@ -90,9 +90,17 @@ PropertyEditorPane {
|
||||
|
||||
StudioControls.TabButton {
|
||||
text: backendValues.__classNamePrivateInternal.value
|
||||
onClicked: () => {
|
||||
if (itemPane.searchBar.hasDoneSearch)
|
||||
itemPane.searchBar.search();
|
||||
}
|
||||
}
|
||||
StudioControls.TabButton {
|
||||
text: qsTr("Layout")
|
||||
onClicked: () => {
|
||||
if (itemPane.searchBar.hasDoneSearch)
|
||||
itemPane.searchBar.search();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -12,7 +12,7 @@ PropertyEditorPane {
|
||||
ComponentSection {}
|
||||
|
||||
DynamicPropertiesSection {
|
||||
propertiesModel: SelectionDynamicPropertiesModel {}
|
||||
propertiesModel: PropertyEditorDynamicPropertiesModel {}
|
||||
visible: !hasMultiSelection
|
||||
}
|
||||
|
||||
|
@@ -12,4 +12,9 @@ Column {
|
||||
AnimationSection {
|
||||
showDuration: false
|
||||
}
|
||||
|
||||
ScriptSection {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,102 @@
|
||||
// Copyright (C) 2025 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.Controls
|
||||
import HelperWidgets
|
||||
import StudioControls as StudioControls
|
||||
import StudioTheme as StudioTheme
|
||||
import ScriptEditorBackend
|
||||
import ScriptsEditor as ScriptsEditor
|
||||
|
||||
Section {
|
||||
id: root
|
||||
caption: qsTr("Script")
|
||||
|
||||
SectionLayout {
|
||||
PropertyLabel {
|
||||
text: ""
|
||||
tooltip: ""
|
||||
}
|
||||
|
||||
SecondColumnLayout {
|
||||
Spacer { implicitWidth: StudioTheme.Values.actionIndicatorWidth }
|
||||
|
||||
AbstractButton {
|
||||
id: editScriptButton
|
||||
implicitWidth: StudioTheme.Values.singleControlColumnWidth
|
||||
width: StudioTheme.Values.singleControlColumnWidth
|
||||
buttonIcon: qsTr("Edit script")
|
||||
iconFontFamily: StudioTheme.Constants.font.family
|
||||
onClicked: dialog.show(root)
|
||||
}
|
||||
|
||||
ExpandingSpacer {}
|
||||
}
|
||||
}
|
||||
|
||||
data: [
|
||||
StudioControls.PopupDialog {
|
||||
id: dialog
|
||||
|
||||
readonly property real horizontalSpacing: 10
|
||||
readonly property real verticalSpacing: 12
|
||||
readonly property real columnWidth: (dialog.width - dialog.horizontalSpacing - (2 * dialog.anchorGap)) / 2
|
||||
|
||||
property var backend: ScriptEditorBackend
|
||||
|
||||
titleBar: Row {
|
||||
id: titleBarRow
|
||||
spacing: 30
|
||||
anchors.fill: parent
|
||||
|
||||
Text {
|
||||
color: StudioTheme.Values.themeTextColor
|
||||
text: qsTr("Action")
|
||||
font.pixelSize: StudioTheme.Values.myFontSize
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
ToolTipArea {
|
||||
anchors.fill: parent
|
||||
tooltip: qsTr("Sets an action that is associated with the selected <b>ScriptAction</b>.")
|
||||
}
|
||||
}
|
||||
|
||||
ScriptsEditor.ActionsComboBox {
|
||||
id: action
|
||||
style: StudioTheme.Values.connectionPopupControlStyle
|
||||
width: 180
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
backend: dialog.backend
|
||||
}
|
||||
}
|
||||
|
||||
ScriptsEditor.ScriptEditorForm {
|
||||
id: scriptEditor
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
horizontalSpacing: dialog.horizontalSpacing
|
||||
verticalSpacing: dialog.verticalSpacing
|
||||
columnWidth: dialog.columnWidth
|
||||
spacing: dialog.verticalSpacing
|
||||
|
||||
backend: dialog.backend
|
||||
|
||||
currentAction: action.currentValue ?? StatementDelegate.Custom
|
||||
}
|
||||
},
|
||||
|
||||
Connections {
|
||||
target: modelNodeBackend
|
||||
function onSelectionChanged() {
|
||||
dialog.backend.update()
|
||||
}
|
||||
},
|
||||
|
||||
TapHandler {
|
||||
onTapped: scriptEditor.forceActiveFocus()
|
||||
}
|
||||
]
|
||||
}
|
@@ -13,6 +13,11 @@ Rectangle {
|
||||
height: 400
|
||||
color: StudioTheme.Values.themePanelBackground
|
||||
|
||||
// Called from C++ to clear the search when the selected node changes
|
||||
function clearSearch() {
|
||||
// The function is empty, because it is a placeholder to match other panes
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: mainColumn
|
||||
anchors.fill: parent
|
||||
|
@@ -1,15 +0,0 @@
|
||||
// Copyright (C) 2022 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import HelperWidgets 2.0
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
|
||||
// CubeMapTexture inherits Texture but doesn't provide any extra properties itself
|
||||
TextureSection {
|
||||
width: parent.width
|
||||
}
|
||||
}
|
@@ -11,8 +11,4 @@ Column {
|
||||
CustomMaterialSection {
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
MaterialSection {
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
|
@@ -11,8 +11,4 @@ Column {
|
||||
DefaultMaterialSection {
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
MaterialSection {
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,62 @@
|
||||
// Copyright (C) 2025 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
import QtQuick
|
||||
import StudioControls as StudioControls
|
||||
|
||||
StudioControls.PopupDialog {
|
||||
id: colorPopup
|
||||
|
||||
property QtObject loaderItem: loader.item
|
||||
property color originalColor
|
||||
required property color currentColor
|
||||
|
||||
signal activateColor(color : color)
|
||||
|
||||
width: 260
|
||||
|
||||
onOriginalColorChanged: loader.updateOriginalColor()
|
||||
onClosing: loader.active = false
|
||||
|
||||
function open(showItem) {
|
||||
loader.ensureActive()
|
||||
colorPopup.show(showItem)
|
||||
|
||||
loader.updateOriginalColor()
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: loader
|
||||
|
||||
function ensureActive() {
|
||||
if (!loader.active)
|
||||
loader.active = true
|
||||
}
|
||||
|
||||
function updateOriginalColor() {
|
||||
if (loader.status === Loader.Ready)
|
||||
loader.item.originalColor = colorPopup.originalColor
|
||||
}
|
||||
|
||||
sourceComponent: StudioControls.ColorEditorPopup {
|
||||
width: colorPopup.contentWidth
|
||||
visible: colorPopup.visible
|
||||
|
||||
onActivateColor: (color) => {
|
||||
colorPopup.activateColor(color)
|
||||
}
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: loader.item
|
||||
property: "color"
|
||||
value: colorPopup.currentColor
|
||||
when: loader.status === Loader.Ready
|
||||
}
|
||||
|
||||
onLoaded: {
|
||||
loader.updateOriginalColor()
|
||||
colorPopup.titleBar = loader.item.titleBarContent
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,266 @@
|
||||
// Copyright (C) 2025 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
import QtQuick
|
||||
import HelperWidgets 2.0 as HelperWidgets
|
||||
import StudioControls as StudioControls
|
||||
import StudioTheme as StudioTheme
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property HelperWidgets.QmlMaterialNodeProxy backend: materialNodeBackend
|
||||
property alias pinned: pinButton.checked
|
||||
property alias showPinButton: pinButton.visible
|
||||
|
||||
property StudioTheme.ControlStyle buttonStyle: StudioTheme.ViewBarButtonStyle {
|
||||
// This is how you can override stuff from the control styles
|
||||
baseIconFontSize: StudioTheme.Values.bigIconFontSize
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: HelperWidgets.Controller
|
||||
|
||||
function onCloseContextMenu() {
|
||||
root.closeContextMenu()
|
||||
}
|
||||
}
|
||||
|
||||
implicitHeight: image.height
|
||||
|
||||
clip: true
|
||||
color: "#000000"
|
||||
|
||||
// Called from C++ to close context menu on focus out
|
||||
function closeContextMenu()
|
||||
{
|
||||
modelMenu.close()
|
||||
envMenu.close()
|
||||
}
|
||||
|
||||
function refreshPreview()
|
||||
{
|
||||
image.source = ""
|
||||
image.source = "image://nodeInstance/preview"
|
||||
}
|
||||
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
|
||||
function onPreviewEnvChanged() {
|
||||
envMenu.updateEnvParams(backend.previewEnv)
|
||||
root.refreshPreview()
|
||||
}
|
||||
|
||||
function onPreviewModelChanged() {
|
||||
root.refreshPreview()
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
id: image
|
||||
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectFit
|
||||
|
||||
source: "image://nodeInstance/preview"
|
||||
cache: false
|
||||
smooth: true
|
||||
|
||||
sourceSize.width: image.width
|
||||
sourceSize.height: image.height
|
||||
|
||||
Rectangle {
|
||||
id: toolbarRect
|
||||
|
||||
radius: 10
|
||||
color: StudioTheme.Values.themeToolbarBackground
|
||||
width: optionsToolbar.width + 2 * toolbarRect.radius
|
||||
height: optionsToolbar.height + toolbarRect.radius
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: -toolbarRect.radius
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Column {
|
||||
id: optionsToolbar
|
||||
|
||||
spacing: 5
|
||||
anchors.centerIn: parent
|
||||
anchors.horizontalCenterOffset: optionsToolbar.spacing
|
||||
|
||||
HelperWidgets.AbstractButton {
|
||||
id: pinButton
|
||||
|
||||
style: root.buttonStyle
|
||||
buttonIcon: pinButton.checked ? StudioTheme.Constants.pin : StudioTheme.Constants.unpin
|
||||
checkable: true
|
||||
}
|
||||
|
||||
HelperWidgets.AbstractButton {
|
||||
id: previewEnvMenuButton
|
||||
|
||||
style: root.buttonStyle
|
||||
buttonIcon: StudioTheme.Constants.textures_medium
|
||||
tooltip: qsTr("Select preview environment.")
|
||||
onClicked: envMenu.popup()
|
||||
}
|
||||
|
||||
HelperWidgets.AbstractButton {
|
||||
id: previewModelMenuButton
|
||||
|
||||
style: root.buttonStyle
|
||||
buttonIcon: StudioTheme.Constants.cube_medium
|
||||
tooltip: qsTr("Select preview model.")
|
||||
onClicked: modelMenu.popup()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StudioControls.Menu {
|
||||
id: modelMenu
|
||||
|
||||
closePolicy: StudioControls.Menu.CloseOnEscape | StudioControls.Menu.CloseOnPressOutside
|
||||
|
||||
ListModel {
|
||||
id: modelMenuModel
|
||||
ListElement {
|
||||
modelName: qsTr("Cone")
|
||||
modelStr: "#Cone"
|
||||
}
|
||||
ListElement {
|
||||
modelName: qsTr("Cube")
|
||||
modelStr: "#Cube"
|
||||
}
|
||||
ListElement {
|
||||
modelName: qsTr("Cylinder")
|
||||
modelStr: "#Cylinder"
|
||||
}
|
||||
ListElement {
|
||||
modelName: qsTr("Sphere")
|
||||
modelStr: "#Sphere"
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: modelMenuModel
|
||||
StudioControls.MenuItemWithIcon {
|
||||
text: modelName
|
||||
onClicked: root.backend.previewModel = modelStr
|
||||
checkable: true
|
||||
checked: root.backend.previewModel === modelStr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StudioControls.Menu {
|
||||
id: envMenu
|
||||
|
||||
property string previewEnvName
|
||||
property string previewEnvValue
|
||||
|
||||
signal envParametersChanged()
|
||||
|
||||
closePolicy: StudioControls.Menu.CloseOnEscape | StudioControls.Menu.CloseOnPressOutside
|
||||
|
||||
Component.onCompleted: envMenu.updateEnvParams(root.backend.previewEnv)
|
||||
|
||||
function updateEnvParams(str: string) {
|
||||
let eqFound = str.lastIndexOf("=")
|
||||
let newEnvName = (eqFound > 0) ? str.substr(0, eqFound) : str
|
||||
let newEnvValue = (eqFound > 0) ? str.substr(eqFound + 1, str.length - eqFound) : ""
|
||||
|
||||
if (envMenu.previewEnvName !== newEnvName
|
||||
|| envMenu.previewEnvValue !== newEnvValue) {
|
||||
envMenu.previewEnvName = newEnvName
|
||||
envMenu.previewEnvValue = newEnvValue
|
||||
envMenu.envParametersChanged()
|
||||
}
|
||||
}
|
||||
|
||||
EnvMenuItem {
|
||||
envName: qsTr("Basic")
|
||||
envStr: "Basic"
|
||||
}
|
||||
|
||||
EnvMenuItem {
|
||||
id: colorItem
|
||||
|
||||
property color color
|
||||
property bool colorIsValid: false
|
||||
|
||||
envName: qsTr("Color")
|
||||
envStr: "Color"
|
||||
checked: false
|
||||
|
||||
Component.onCompleted: update()
|
||||
onColorIsValidChanged: updatePopupOriginalColor()
|
||||
|
||||
onClicked: {
|
||||
colorItem.updatePopupOriginalColor()
|
||||
colorPopup.open(colorItem)
|
||||
}
|
||||
|
||||
onColorChanged: {
|
||||
colorItem.envStr = colorItem.checked
|
||||
? "Color=" + color.toString()
|
||||
: "Color"
|
||||
colorItem.commit()
|
||||
}
|
||||
|
||||
function updatePopupOriginalColor() {
|
||||
if (colorItem.colorIsValid)
|
||||
colorPopup.originalColor = colorItem.color
|
||||
}
|
||||
|
||||
function update() {
|
||||
colorItem.checked = envMenu.previewEnvName === "Color"
|
||||
if (colorItem.checked && envMenu.previewEnvValue) {
|
||||
colorItem.color = envMenu.previewEnvValue
|
||||
colorItem.colorIsValid = true
|
||||
} else {
|
||||
colorItem.colorIsValid = false
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: envMenu
|
||||
function onEnvParametersChanged() {
|
||||
colorItem.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EnvMenuItem {
|
||||
envName: qsTr("Studio")
|
||||
envStr: "SkyBox=preview_studio"
|
||||
}
|
||||
|
||||
EnvMenuItem {
|
||||
envName: qsTr("Landscape")
|
||||
envStr: "SkyBox=preview_landscape"
|
||||
}
|
||||
}
|
||||
|
||||
ColorEditorPopup {
|
||||
id: colorPopup
|
||||
|
||||
currentColor: colorItem.color
|
||||
onActivateColor: (color) => colorItem.color = color
|
||||
}
|
||||
|
||||
component EnvMenuItem: StudioControls.MenuItemWithIcon {
|
||||
required property string envName
|
||||
property string envStr
|
||||
|
||||
function commit() {
|
||||
root.backend.previewEnv = envStr
|
||||
}
|
||||
|
||||
text: envName
|
||||
onClicked: commit()
|
||||
checkable: false
|
||||
checked: root.backend.previewEnv === envStr
|
||||
}
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
// Copyright (C) 2025 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
import QtQuick
|
||||
import HelperWidgets 2.0
|
||||
import HelperWidgets 2.0 as HelperWidgets
|
||||
import StudioTheme as StudioTheme
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property HelperWidgets.QmlMaterialNodeProxy backend: materialNodeBackend
|
||||
|
||||
color: StudioTheme.Values.themeToolbarBackground
|
||||
height: StudioTheme.Values.toolbarHeight
|
||||
|
||||
Row {
|
||||
id: row
|
||||
spacing: StudioTheme.Values.toolbarSpacing
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
leftPadding: 6
|
||||
|
||||
HelperWidgets.AbstractButton {
|
||||
style: StudioTheme.Values.viewBarButtonStyle
|
||||
buttonIcon: StudioTheme.Constants.apply_medium
|
||||
tooltip: qsTr("Apply material to selected model.")
|
||||
onClicked: root.backend.toolBarAction(QmlMaterialNodeProxy.ApplyToSelected)
|
||||
}
|
||||
|
||||
HelperWidgets.AbstractButton {
|
||||
style: StudioTheme.Values.viewBarButtonStyle
|
||||
buttonIcon: StudioTheme.Constants.create_medium
|
||||
tooltip: qsTr("Create new material.")
|
||||
onClicked: backend.toolBarAction(QmlMaterialNodeProxy.AddNewMaterial)
|
||||
}
|
||||
|
||||
HelperWidgets.AbstractButton {
|
||||
style: StudioTheme.Values.viewBarButtonStyle
|
||||
buttonIcon: StudioTheme.Constants.delete_medium
|
||||
tooltip: qsTr("Delete current material.")
|
||||
onClicked: backend.toolBarAction(QmlMaterialNodeProxy.DeleteCurrentMaterial)
|
||||
}
|
||||
|
||||
HelperWidgets.AbstractButton {
|
||||
style: StudioTheme.Values.viewBarButtonStyle
|
||||
buttonIcon: StudioTheme.Constants.materialBrowser_medium
|
||||
tooltip: qsTr("Open material browser.")
|
||||
onClicked: backend.toolBarAction(QmlMaterialNodeProxy.OpenMaterialBrowser)
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,93 @@
|
||||
// Copyright (C) 2025 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.Controls
|
||||
import HelperWidgets 2.0 as HelperWidgets
|
||||
import StudioControls as StudioControls
|
||||
import StudioTheme as StudioTheme
|
||||
|
||||
StudioControls.SplitView {
|
||||
id: root
|
||||
|
||||
property HelperWidgets.QmlMaterialNodeProxy backend: materialNodeBackend
|
||||
property alias showImage: previewLoader.active
|
||||
property Component previewComponent: null
|
||||
|
||||
width: parent.width
|
||||
implicitHeight: showImage ? previewLoader.implicitHeight + nameSection.implicitHeight : nameSection.implicitHeight
|
||||
|
||||
orientation: Qt.Vertical
|
||||
|
||||
Loader {
|
||||
id: previewLoader
|
||||
|
||||
SplitView.fillWidth: true
|
||||
SplitView.minimumWidth: 152
|
||||
SplitView.preferredHeight: previewLoader.visible ? Math.min(root.width * 0.75, 400) : 0
|
||||
SplitView.minimumHeight: previewLoader.visible ? 150 : 0
|
||||
SplitView.maximumHeight: previewLoader.visible ? 600 : 0
|
||||
|
||||
visible: previewLoader.active && previewLoader.item
|
||||
|
||||
sourceComponent: root.previewComponent
|
||||
}
|
||||
|
||||
HelperWidgets.Section {
|
||||
id: nameSection
|
||||
|
||||
// Section with hidden header is used so properties are aligned with the other sections' properties
|
||||
hideHeader: true
|
||||
SplitView.fillWidth: true
|
||||
SplitView.preferredHeight: implicitHeight
|
||||
SplitView.maximumHeight: implicitHeight
|
||||
bottomPadding: StudioTheme.Values.sectionPadding * 2
|
||||
collapsible: false
|
||||
|
||||
HelperWidgets.SectionLayout {
|
||||
HelperWidgets.PropertyLabel { text: qsTr("Name") }
|
||||
|
||||
HelperWidgets.SecondColumnLayout {
|
||||
HelperWidgets.Spacer { implicitWidth: StudioTheme.Values.actionIndicatorWidth }
|
||||
|
||||
HelperWidgets.LineEdit {
|
||||
id: matName
|
||||
|
||||
implicitWidth: StudioTheme.Values.singleControlColumnWidth
|
||||
width: StudioTheme.Values.singleControlColumnWidth
|
||||
placeholderText: qsTr("Material name")
|
||||
showTranslateCheckBox: false
|
||||
showExtendedFunctionButton: false
|
||||
|
||||
Timer {
|
||||
running: true
|
||||
interval: 0
|
||||
onTriggered: matName.backendValue = backendValues.objectName
|
||||
// backendValues.objectName is not available yet without the Timer
|
||||
}
|
||||
|
||||
// allow only alphanumeric characters, underscores, no space at start, and 1 space between words
|
||||
validator: RegularExpressionValidator { regularExpression: /^(\w+\s)*\w+$/ }
|
||||
}
|
||||
|
||||
HelperWidgets.ExpandingSpacer {}
|
||||
}
|
||||
|
||||
HelperWidgets.PropertyLabel { text: qsTr("Type") }
|
||||
|
||||
HelperWidgets.SecondColumnLayout {
|
||||
HelperWidgets.Spacer { implicitWidth: StudioTheme.Values.actionIndicatorWidth }
|
||||
|
||||
HelperWidgets.ComboBox {
|
||||
currentIndex: backend.possibleTypeIndex
|
||||
model: backend.possibleTypes
|
||||
showExtendedFunctionButton: false
|
||||
implicitWidth: StudioTheme.Values.singleControlColumnWidth
|
||||
enabled: backend.possibleTypes.length > 1
|
||||
|
||||
onActivated: changeTypeName(currentValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,137 @@
|
||||
// Copyright (C) 2025 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
import QtCore
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import HelperWidgets 2.0
|
||||
import StudioControls 1.0 as StudioControls
|
||||
import "Material" as Material
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
width: 420
|
||||
height: 420
|
||||
|
||||
// invoked from C++ to refresh material preview image
|
||||
signal refreshPreview()
|
||||
|
||||
// Called from C++ to close context menu on focus out
|
||||
function closeContextMenu()
|
||||
{
|
||||
Controller.closeContextMenu()
|
||||
}
|
||||
|
||||
Material.Toolbar {
|
||||
id: toolbar
|
||||
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
Settings {
|
||||
id: settings
|
||||
|
||||
property var topSection
|
||||
property bool dockMode
|
||||
}
|
||||
|
||||
StudioControls.SplitView {
|
||||
id: splitView
|
||||
|
||||
readonly property bool isHorizontal: splitView.orientation == Qt.Horizontal
|
||||
|
||||
anchors.top: toolbar.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
width: parent.width
|
||||
orientation: splitView.width > 1000 ? Qt.Horizontal : Qt.Vertical
|
||||
clip: true
|
||||
|
||||
Loader {
|
||||
id: leftSideView
|
||||
|
||||
SplitView.fillWidth: leftSideView.visible
|
||||
SplitView.fillHeight: leftSideView.visible
|
||||
SplitView.minimumWidth: leftSideView.visible ? 300 : 0
|
||||
SplitView.minimumHeight: leftSideView.visible ? 300 : 0
|
||||
|
||||
active: splitView.isHorizontal
|
||||
visible: leftSideView.active && leftSideView.item
|
||||
|
||||
sourceComponent: PreviewComponent {}
|
||||
}
|
||||
|
||||
PropertyEditorPane {
|
||||
id: itemPane
|
||||
|
||||
clip: true
|
||||
SplitView.fillWidth: !leftSideView.visible
|
||||
SplitView.fillHeight: true
|
||||
SplitView.minimumWidth: leftSideView.visible ? 400 : 0
|
||||
SplitView.maximumWidth: leftSideView.visible ? 800 : -1
|
||||
|
||||
headerDocked: !leftSideView.visible && settings.dockMode
|
||||
|
||||
headerComponent: Material.TopSection {
|
||||
id: topSection
|
||||
|
||||
Component.onCompleted: topSection.restoreState(settings.topSection)
|
||||
Component.onDestruction: settings.topSection = topSection.saveState()
|
||||
previewComponent: PreviewComponent {}
|
||||
showImage: !splitView.isHorizontal
|
||||
}
|
||||
|
||||
DynamicPropertiesSection {
|
||||
propertiesModel: PropertyEditorDynamicPropertiesModel {}
|
||||
visible: !hasMultiSelection
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: specificsTwo
|
||||
|
||||
property string theSource: specificQmlData
|
||||
|
||||
width: itemPane.width
|
||||
visible: specificsTwo.theSource !== ""
|
||||
sourceComponent: specificQmlComponent
|
||||
|
||||
onTheSourceChanged: {
|
||||
specificsTwo.active = false
|
||||
specificsTwo.active = true
|
||||
}
|
||||
}
|
||||
|
||||
Item { // spacer
|
||||
width: 1
|
||||
height: 10
|
||||
visible: specificsTwo.visible
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: specificsOne
|
||||
width: itemPane.width
|
||||
source: specificsUrl
|
||||
}
|
||||
|
||||
MaterialSection {
|
||||
width: itemPane.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component PreviewComponent : Material.Preview {
|
||||
id: previewItem
|
||||
|
||||
pinned: settings.dockMode
|
||||
showPinButton: !leftSideView.visible
|
||||
onPinnedChanged: settings.dockMode = previewItem.pinned
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
|
||||
function onRefreshPreview() {
|
||||
previewItem.refreshPreview()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -18,7 +18,7 @@ PropertyEditorPane {
|
||||
anchors.right: parent.right
|
||||
|
||||
DynamicPropertiesSection {
|
||||
propertiesModel: SelectionDynamicPropertiesModel {}
|
||||
propertiesModel: PropertyEditorDynamicPropertiesModel {}
|
||||
visible: !hasMultiSelection
|
||||
}
|
||||
|
||||
|
@@ -11,8 +11,4 @@ Column {
|
||||
PrincipledMaterialSection {
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
MaterialSection {
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
|
@@ -11,8 +11,4 @@ Column {
|
||||
SpecularGlossyMaterialSection {
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
MaterialSection {
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,54 @@
|
||||
// Copyright (C) 2025 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
import QtQuick
|
||||
import HelperWidgets 2.0
|
||||
import StudioTheme as StudioTheme
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property QmlTextureNodeProxy backend: textureNodeBackend
|
||||
|
||||
color: StudioTheme.Values.themeToolbarBackground
|
||||
implicitHeight: StudioTheme.Values.toolbarHeight
|
||||
|
||||
Row {
|
||||
id: row
|
||||
spacing: StudioTheme.Values.toolbarSpacing
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
leftPadding: 6
|
||||
|
||||
AbstractButton {
|
||||
style: StudioTheme.Values.viewBarButtonStyle
|
||||
buttonIcon: StudioTheme.Constants.apply_medium
|
||||
enabled: backend.hasTexture && backend.selectedNodeAcceptsMaterial && hasQuick3DImport && hasMaterialLibrary
|
||||
tooltip: qsTr("Apply texture to selected model's material.")
|
||||
onClicked: backend.toolbarAction(QmlTextureNodeProxy.ApplyToSelected)
|
||||
}
|
||||
|
||||
AbstractButton {
|
||||
style: StudioTheme.Values.viewBarButtonStyle
|
||||
buttonIcon: StudioTheme.Constants.create_medium
|
||||
enabled: hasQuick3DImport && hasMaterialLibrary
|
||||
tooltip: qsTr("Create new texture.")
|
||||
onClicked: backend.toolbarAction(QmlTextureNodeProxy.AddNewTexture)
|
||||
}
|
||||
|
||||
AbstractButton {
|
||||
style: StudioTheme.Values.viewBarButtonStyle
|
||||
buttonIcon: StudioTheme.Constants.delete_medium
|
||||
enabled: backend.hasTexture && hasQuick3DImport && hasMaterialLibrary
|
||||
tooltip: qsTr("Delete current texture.")
|
||||
onClicked: backend.toolbarAction(QmlTextureNodeProxy.DeleteCurrentTexture)
|
||||
}
|
||||
|
||||
AbstractButton {
|
||||
style: StudioTheme.Values.viewBarButtonStyle
|
||||
buttonIcon: StudioTheme.Constants.materialBrowser_medium
|
||||
enabled: backend.hasTexture && hasQuick3DImport && hasMaterialLibrary
|
||||
tooltip: qsTr("Open material browser.")
|
||||
onClicked: backend.toolbarAction(QmlTextureNodeProxy.OpenMaterialBrowser)
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,88 @@
|
||||
// Copyright (C) 2025 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
import QtQuick
|
||||
import HelperWidgets 2.0 as HelperWidgets
|
||||
import StudioTheme as StudioTheme
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property HelperWidgets.QmlTextureNodeProxy backend: textureNodeBackend
|
||||
|
||||
readonly property string sourcePath: backendValues.source ? backendValues.source.valueToString : ""
|
||||
readonly property string previewPath: "image://qmldesigner_thumbnails/" + backend.resolveResourcePath(root.sourcePath)
|
||||
|
||||
function refreshPreview()
|
||||
{
|
||||
texturePreview.source = ""
|
||||
texturePreview.source = root.previewPath
|
||||
}
|
||||
|
||||
color: StudioTheme.Values.themePanelBackground
|
||||
implicitHeight: column.height
|
||||
|
||||
Column {
|
||||
id: column
|
||||
|
||||
Item { implicitWidth: 1; implicitHeight: 10 } // spacer
|
||||
|
||||
Rectangle {
|
||||
id: previewRect
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: 152
|
||||
height: 152
|
||||
color: "#000000"
|
||||
|
||||
Image {
|
||||
id: texturePreview
|
||||
asynchronous: true
|
||||
width: 150
|
||||
height: 150
|
||||
fillMode: Image.PreserveAspectFit
|
||||
anchors.centerIn: parent
|
||||
source: root.previewPath
|
||||
}
|
||||
}
|
||||
|
||||
HelperWidgets.Section {
|
||||
id: nameSection
|
||||
|
||||
// Section with hidden header is used so properties are aligned with the other sections' properties
|
||||
hideHeader: true
|
||||
implicitWidth: root.width
|
||||
bottomPadding: StudioTheme.Values.sectionPadding * 2
|
||||
collapsible: false
|
||||
|
||||
HelperWidgets.SectionLayout {
|
||||
HelperWidgets.PropertyLabel { text: qsTr("Name") }
|
||||
|
||||
HelperWidgets.SecondColumnLayout {
|
||||
HelperWidgets.Spacer { implicitWidth: StudioTheme.Values.actionIndicatorWidth }
|
||||
|
||||
HelperWidgets.LineEdit {
|
||||
id: texName
|
||||
|
||||
implicitWidth: StudioTheme.Values.singleControlColumnWidth
|
||||
width: StudioTheme.Values.singleControlColumnWidth
|
||||
placeholderText: qsTr("Texture name")
|
||||
showTranslateCheckBox: false
|
||||
showExtendedFunctionButton: false
|
||||
|
||||
Timer {
|
||||
running: true
|
||||
interval: 0
|
||||
onTriggered: texName.backendValue = backendValues.objectName
|
||||
// backendValues.objectName is not available yet without the Timer
|
||||
}
|
||||
|
||||
// allow only alphanumeric characters, underscores, no space at start, and 1 space between words
|
||||
validator: RegularExpressionValidator { regularExpression: /^(\w+\s)*\w+$/ }
|
||||
}
|
||||
|
||||
HelperWidgets.ExpandingSpacer {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,86 @@
|
||||
// Copyright (C) 2025 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.Layouts
|
||||
import HelperWidgets 2.0
|
||||
import StudioTheme as StudioTheme
|
||||
import "Texture" as Texture
|
||||
|
||||
Rectangle {
|
||||
id: itemPane
|
||||
|
||||
width: 420
|
||||
height: 420
|
||||
color: StudioTheme.Values.themePanelBackground
|
||||
|
||||
// invoked from C++ to refresh material preview image
|
||||
function refreshPreview()
|
||||
{
|
||||
topSection.refreshPreview()
|
||||
}
|
||||
|
||||
// Called from C++ to close context menu on focus out
|
||||
function closeContextMenu()
|
||||
{
|
||||
Controller.closeContextMenu()
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
Texture.ToolBar {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Texture.TopSection {
|
||||
id: topSection
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
PropertyEditorPane {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
DynamicPropertiesSection {
|
||||
propertiesModel: PropertyEditorDynamicPropertiesModel {}
|
||||
visible: !hasMultiSelection
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: specificsTwo
|
||||
|
||||
property string theSource: specificQmlData
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
visible: specificsTwo.theSource !== ""
|
||||
sourceComponent: specificQmlComponent
|
||||
|
||||
onTheSourceChanged: {
|
||||
specificsTwo.active = false
|
||||
specificsTwo.active = true
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 1
|
||||
height: 10
|
||||
visible: specificsTwo.visible
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: specificsOne
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
source: specificsUrl
|
||||
}
|
||||
|
||||
TextureSection {
|
||||
width: itemPane.width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,14 +0,0 @@
|
||||
// Copyright (C) 2021 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import HelperWidgets 2.0
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
|
||||
TextureSection {
|
||||
width: parent.width
|
||||
}
|
||||
}
|
@@ -507,6 +507,8 @@ Section {
|
||||
}
|
||||
|
||||
PropertyLabel {
|
||||
readonly property bool __inDynamicPropertiesSection: true
|
||||
|
||||
text: propertyName
|
||||
tooltip: propertyType
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
@@ -24,9 +24,7 @@ Item {
|
||||
property real __actionIndicatorWidth: StudioTheme.Values.squareComponentWidth
|
||||
property real __actionIndicatorHeight: StudioTheme.Values.height
|
||||
property string typeFilter: "QtQuick3D.Material"
|
||||
// This binding is a workaround to overcome the rather long adaption to new Qt versions. This
|
||||
// should actually be fixed in the ModelSection.qml by setting the textRole: "idAndName".
|
||||
property string textRole: (root.typeFilter === "QtQuick3D.Material") ? "idAndName" : "id"
|
||||
property string textRole: "id"
|
||||
property string valueRole: "id"
|
||||
property int activatedReason: ComboBox.ActivatedReason.Other
|
||||
|
||||
|
@@ -18,6 +18,7 @@ Rectangle {
|
||||
|
||||
default property alias content: mainColumn.children
|
||||
property alias scrollView: mainScrollView
|
||||
property alias searchBar: propertySearchBar
|
||||
|
||||
property bool headerDocked: false
|
||||
readonly property Item headerItem: headerDocked ? dockedHeaderLoader.item : undockedHeaderLoader.item
|
||||
@@ -29,10 +30,23 @@ Rectangle {
|
||||
Controller.closeContextMenu()
|
||||
}
|
||||
|
||||
// Called from C++ to clear the search when the selected node changes
|
||||
function clearSearch() {
|
||||
propertySearchBar.clear();
|
||||
}
|
||||
|
||||
PropertySearchBar {
|
||||
id: propertySearchBar
|
||||
|
||||
contentItem: mainColumn
|
||||
width: parent.width
|
||||
z: parent.z + 1
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: dockedHeaderLoader
|
||||
|
||||
anchors.top: itemPane.top
|
||||
anchors.top: propertySearchBar.bottom
|
||||
z: parent.z + 1
|
||||
height: item ? item.implicitHeight : 0
|
||||
width: parent.width
|
||||
@@ -123,6 +137,16 @@ Rectangle {
|
||||
HeaderBackground{}
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 10
|
||||
|
||||
visible: propertySearchBar.hasDoneSearch && !propertySearchBar.hasMatchSearch
|
||||
text: qsTr("No match found.")
|
||||
color: StudioTheme.Values.themeTextColor
|
||||
font.pixelSize: StudioTheme.Values.baseFont
|
||||
}
|
||||
|
||||
Column {
|
||||
id: mainColumn
|
||||
|
||||
|
@@ -9,10 +9,13 @@ import StudioTheme 1.0 as StudioTheme
|
||||
T.Label {
|
||||
id: label
|
||||
|
||||
readonly property bool __isPropertyLabel: true // used by property search logic
|
||||
|
||||
property alias tooltip: toolTipArea.tooltip
|
||||
|
||||
property bool blockedByContext: false
|
||||
property bool blockedByTemplate: false // MCU
|
||||
property bool searchNoMatch: false
|
||||
|
||||
width: StudioTheme.Values.propertyLabelWidth
|
||||
color: StudioTheme.Values.themeTextColor
|
||||
@@ -34,6 +37,14 @@ T.Label {
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "searchNoMatch"
|
||||
when: searchNoMatch
|
||||
PropertyChanges {
|
||||
target: label
|
||||
visible: false
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "disabled"
|
||||
when: !label.enabled && !(label.blockedByContext || label.blockedByTemplate)
|
||||
|
@@ -0,0 +1,135 @@
|
||||
// Copyright (C) 2025 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
import QtQuick
|
||||
import StudioControls as StudioControls
|
||||
import StudioTheme as StudioTheme
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
readonly property alias hasMatchSearch: internal.matched
|
||||
readonly property alias hasDoneSearch: internal.searched
|
||||
|
||||
property Item contentItem: null
|
||||
|
||||
color: StudioTheme.Values.themeToolbarBackground
|
||||
height: StudioTheme.Values.toolbarHeight
|
||||
|
||||
function clear() {
|
||||
internal.clear();
|
||||
searchBox.text = "";
|
||||
internal.timer.stop();
|
||||
}
|
||||
|
||||
function search() {
|
||||
internal.search();
|
||||
}
|
||||
|
||||
StudioControls.SearchBox {
|
||||
id: searchBox
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.bottomMargin: StudioTheme.Values.toolbarVerticalMargin
|
||||
anchors.topMargin: StudioTheme.Values.toolbarVerticalMargin
|
||||
anchors.leftMargin: StudioTheme.Values.toolbarHorizontalMargin
|
||||
anchors.rightMargin: StudioTheme.Values.toolbarHorizontalMargin
|
||||
style: StudioTheme.Values.searchControlStyle
|
||||
onSearchChanged: internal.timer.restart()
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: internal
|
||||
|
||||
readonly property var reverts: []
|
||||
readonly property Timer timer: Timer {
|
||||
interval: 300
|
||||
repeat: false
|
||||
|
||||
onTriggered: internal.search()
|
||||
}
|
||||
property bool matched: false
|
||||
property bool searched: false
|
||||
|
||||
function clear() {
|
||||
internal.reverts.forEach(revert => revert());
|
||||
internal.reverts.length = 0;
|
||||
internal.matched = false;
|
||||
internal.searched = false;
|
||||
}
|
||||
|
||||
function search() {
|
||||
internal.clear();
|
||||
const searchText = searchBox.text.toLowerCase();
|
||||
if (searchText.length > 0) {
|
||||
internal.traverse(root.contentItem, searchText);
|
||||
internal.searched = true;
|
||||
}
|
||||
}
|
||||
|
||||
function disableSearchNoMatchAction(item) {
|
||||
item.searchNoMatch = true;
|
||||
internal.reverts.push(() => {
|
||||
item.searchNoMatch = false;
|
||||
});
|
||||
}
|
||||
|
||||
function disableVisibleAction(item) {
|
||||
item.visible = false;
|
||||
internal.reverts.push(() => {
|
||||
item.visible = true;
|
||||
});
|
||||
}
|
||||
|
||||
function enableSearchHideAction(item) {
|
||||
item.searchHide = true;
|
||||
internal.reverts.push(() => {
|
||||
item.searchHide = false;
|
||||
});
|
||||
}
|
||||
|
||||
function expandSectionAction(item) {
|
||||
internal.matched = true;
|
||||
const prevValue = item.expanded;
|
||||
item.expanded = true;
|
||||
internal.reverts.push(() => {
|
||||
item.expanded = prevValue;
|
||||
});
|
||||
}
|
||||
|
||||
function traverse(item, searchText) {
|
||||
let hideSection = true;
|
||||
let hideParentSection = true;
|
||||
item.children.forEach((child, index, arr) => {
|
||||
if (!child.visible)
|
||||
return;
|
||||
|
||||
if (child.__isPropertyLabel) {
|
||||
const propertyLabel = child;
|
||||
const text = propertyLabel.text.toLowerCase();
|
||||
if (!text.includes(searchText)) {
|
||||
internal.disableSearchNoMatchAction(propertyLabel);
|
||||
const action = propertyLabel.__inDynamicPropertiesSection ? internal.disableVisibleAction
|
||||
: internal.disableSearchNoMatchAction;
|
||||
const nextItem = arr[index + 1];
|
||||
action(nextItem);
|
||||
} else {
|
||||
hideSection = false;
|
||||
}
|
||||
}
|
||||
hideSection &= internal.traverse(child, searchText);
|
||||
if (child.__isSection) {
|
||||
const action = hideSection ? internal.enableSearchHideAction
|
||||
: internal.expandSectionAction;
|
||||
action(child);
|
||||
|
||||
if (child.__isInEffectsSection && !hideSection)
|
||||
hideParentSection = false;
|
||||
|
||||
hideSection = true;
|
||||
}
|
||||
});
|
||||
return hideParentSection && hideSection;
|
||||
}
|
||||
}
|
||||
}
|
@@ -5,6 +5,21 @@ import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
RowLayout {
|
||||
id: root
|
||||
|
||||
property bool searchNoMatch: false
|
||||
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "searchNoMatch"
|
||||
when: searchNoMatch
|
||||
PropertyChanges {
|
||||
target: root
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@@ -10,6 +10,8 @@ import StudioTheme as StudioTheme
|
||||
Item {
|
||||
id: section
|
||||
|
||||
readonly property bool __isSection: true // used by property search logic
|
||||
|
||||
property string caption: "Title"
|
||||
property color labelColor: StudioTheme.Values.themeTextColor
|
||||
property int labelCapitalization: Font.AllUppercase
|
||||
@@ -58,6 +60,7 @@ Item {
|
||||
property bool dropEnabled: false
|
||||
property bool highlight: false
|
||||
property bool eyeEnabled: true // eye button enabled (on)
|
||||
property bool searchHide: false
|
||||
|
||||
property bool useDefaulContextMenu: true
|
||||
|
||||
@@ -343,6 +346,14 @@ Item {
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "Hide"
|
||||
when: section.searchHide
|
||||
PropertyChanges {
|
||||
target: section
|
||||
visible: false
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "Collapsed"
|
||||
when: !section.expanded
|
||||
|
@@ -1,3 +1,4 @@
|
||||
module HelperWidgets
|
||||
AbstractButton 2.0 AbstractButton.qml
|
||||
ActionIndicator 2.0 ActionIndicator.qml
|
||||
AlignmentHorizontalButtons 2.0 AlignmentHorizontalButtons.qml
|
||||
@@ -51,6 +52,7 @@ MultiIconLabel 2.0 MultiIconLabel.qml
|
||||
OriginControl 2.0 OriginControl.qml
|
||||
OriginIndicator 2.0 OriginIndicator.qml
|
||||
OriginSelector 2.0 OriginSelector.qml
|
||||
PopupLabel 2.0 PopupLabel.qml
|
||||
PropertyEditorPane 2.0 PropertyEditorPane.qml
|
||||
PropertyLabel 2.0 PropertyLabel.qml
|
||||
PaddingSection 2.0 PaddingSection.qml
|
||||
|
@@ -3,9 +3,9 @@
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Templates as T
|
||||
import StudioTheme 1.0 as StudioTheme
|
||||
import StudioTheme as StudioTheme
|
||||
|
||||
Rectangle {
|
||||
Item {
|
||||
id: control
|
||||
|
||||
property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle
|
||||
@@ -18,8 +18,6 @@ Rectangle {
|
||||
property bool pressed: false
|
||||
property bool forceVisible: false
|
||||
|
||||
color: "transparent"
|
||||
|
||||
implicitWidth: control.style.actionIndicatorSize.width
|
||||
implicitHeight: control.style.actionIndicatorSize.height
|
||||
|
||||
|
@@ -3,27 +3,29 @@
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Templates as T
|
||||
import StudioTheme 1.0 as StudioTheme
|
||||
import StudioTheme as StudioTheme
|
||||
|
||||
T.Button {
|
||||
id: control
|
||||
|
||||
property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle
|
||||
|
||||
implicitWidth: Math.max(buttonBackground ? buttonBackground.implicitWidth : 0,
|
||||
textItem.implicitWidth + leftPadding + rightPadding)
|
||||
implicitHeight: Math.max(buttonBackground ? buttonBackground.implicitHeight : 0,
|
||||
textItem.implicitHeight + topPadding + bottomPadding)
|
||||
implicitWidth: Math.max(control.style.squareControlSize.width,
|
||||
implicitBackgroundWidth + leftInset + rightInset,
|
||||
implicitContentWidth + leftPadding + rightPadding)
|
||||
implicitHeight: Math.max(control.style.squareControlSize.height,
|
||||
implicitBackgroundHeight + topInset + bottomInset,
|
||||
implicitContentHeight + topPadding + bottomPadding)
|
||||
|
||||
leftPadding: control.style.dialogPadding
|
||||
rightPadding: control.style.dialogPadding
|
||||
|
||||
background: Rectangle {
|
||||
id: buttonBackground
|
||||
implicitWidth: 70
|
||||
implicitHeight: 20
|
||||
id: controlBackground
|
||||
color: control.style.background.idle
|
||||
border.color: control.style.border.idle
|
||||
anchors.fill: parent
|
||||
border.width: control.style.borderWidth
|
||||
radius: control.style.radius
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
@@ -41,7 +43,7 @@ T.Button {
|
||||
when: control.enabled && !control.down && !control.hovered && !control.checked
|
||||
|
||||
PropertyChanges {
|
||||
target: buttonBackground
|
||||
target: controlBackground
|
||||
color: control.highlighted ? control.style.interaction
|
||||
: control.style.background.idle
|
||||
border.color: control.style.border.idle
|
||||
@@ -56,7 +58,7 @@ T.Button {
|
||||
when: control.enabled && control.hovered && !control.checked && !control.down
|
||||
|
||||
PropertyChanges {
|
||||
target: buttonBackground
|
||||
target: controlBackground
|
||||
color: control.style.background.hover
|
||||
border.color: control.style.border.hover
|
||||
}
|
||||
@@ -70,9 +72,9 @@ T.Button {
|
||||
when: control.enabled && (control.checked || control.down)
|
||||
|
||||
PropertyChanges {
|
||||
target: buttonBackground
|
||||
color: control.style.background.interaction
|
||||
border.color: control.style.border.interaction
|
||||
target: controlBackground
|
||||
color: control.style.interaction
|
||||
border.color: control.style.interaction
|
||||
}
|
||||
PropertyChanges {
|
||||
target: textItem
|
||||
@@ -83,7 +85,7 @@ T.Button {
|
||||
name: "disable"
|
||||
when: !control.enabled
|
||||
PropertyChanges {
|
||||
target: buttonBackground
|
||||
target: controlBackground
|
||||
color: control.style.background.disabled
|
||||
border.color: control.style.border.disabled
|
||||
}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
module StudioControls
|
||||
AbstractButton 1.0 AbstractButton.qml
|
||||
ActionIndicator 1.0 ActionIndicator.qml
|
||||
Button 1.0 Button.qml
|
||||
|
@@ -1,3 +1,4 @@
|
||||
module StudioTheme
|
||||
singleton Values 1.0 Values.qml
|
||||
singleton Constants 1.0 Constants.qml
|
||||
ControlStyle 1.0 ControlStyle.qml
|
||||
|
@@ -0,0 +1,54 @@
|
||||
// Copyright (C) 2025 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
import QtQuick
|
||||
import StudioControls as StudioControls
|
||||
import StudioTheme as StudioTheme
|
||||
import ScriptEditorBackend
|
||||
|
||||
StudioControls.TopLevelComboBox {
|
||||
id: action
|
||||
|
||||
style: StudioTheme.Values.connectionPopupControlStyle
|
||||
textRole: "text"
|
||||
valueRole: "value"
|
||||
|
||||
property var backend
|
||||
property int indexFromBackend: indexOfValue(backend.actionType)
|
||||
|
||||
onIndexFromBackendChanged: action.currentIndex = action.indexFromBackend
|
||||
onActivated: backend.changeActionType(action.currentValue)
|
||||
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
value: StatementDelegate.CallFunction
|
||||
text: qsTr("Call Function")
|
||||
enabled: true
|
||||
}
|
||||
ListElement {
|
||||
value: StatementDelegate.Assign
|
||||
text: qsTr("Assign")
|
||||
enabled: true
|
||||
}
|
||||
ListElement {
|
||||
value: StatementDelegate.ChangeState
|
||||
text: qsTr("Change State")
|
||||
enabled: true
|
||||
}
|
||||
ListElement {
|
||||
value: StatementDelegate.SetProperty
|
||||
text: qsTr("Set Property")
|
||||
enabled: true
|
||||
}
|
||||
ListElement {
|
||||
value: StatementDelegate.PrintMessage
|
||||
text: qsTr("Print Message")
|
||||
enabled: true
|
||||
}
|
||||
ListElement {
|
||||
value: StatementDelegate.Custom
|
||||
text: qsTr("Custom")
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
}
|
@@ -4,13 +4,16 @@
|
||||
import QtQuick
|
||||
import StudioControls as StudioControls
|
||||
import StudioTheme as StudioTheme
|
||||
import ScriptEditorBackend
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle
|
||||
|
||||
property var conditionListModel: ConnectionsEditorEditorBackend.connectionModel.delegate.conditionListModel
|
||||
property var conditionListModel: ScriptEditorBackend.conditionListModel // connect a valid model here
|
||||
property alias popupListModel: popup.listModel
|
||||
property alias popupTreeModel: popup.treeModel
|
||||
|
||||
property alias model: repeater.model
|
||||
property int shadowPillIndex: -1
|
@@ -5,6 +5,7 @@ import QtQuick
|
||||
import StudioControls as StudioControls
|
||||
import StudioTheme as StudioTheme
|
||||
import HelperWidgets as HelperWidgets
|
||||
import ScriptEditorBackend
|
||||
|
||||
FocusScope {
|
||||
id: root
|
@@ -0,0 +1,262 @@
|
||||
// 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.Controls
|
||||
import HelperWidgets as HelperWidgets
|
||||
import StudioTheme as StudioTheme
|
||||
import ScriptEditorBackend
|
||||
|
||||
Column {
|
||||
id: root
|
||||
|
||||
property real horizontalSpacing
|
||||
property real verticalSpacing
|
||||
property real columnWidth
|
||||
|
||||
property var backend
|
||||
|
||||
property alias keepOpen: expressionDialogLoader.visible
|
||||
|
||||
property int currentAction: StatementDelegate.Custom
|
||||
|
||||
property string itemTooltip: qsTr("Sets the component that is affected by the action.")
|
||||
property string methodTooltip: qsTr("Sets the item component's method.")
|
||||
property string fromTooltip: qsTr("Sets the component and its property from which the value is copied.")
|
||||
property string toTooltip: qsTr("Sets the component and its property to which the copied value is assigned.")
|
||||
property string addConditionTooltip: qsTr("Sets a logical condition for the selected action.")
|
||||
property string removeConditionTooltip: qsTr("Removes the logical condition for the action.")
|
||||
property string stateGroupTooltip: qsTr("Sets a <b>State Group</b> that is accessed when the action is initiated.")
|
||||
property string stateTooltip: qsTr("Sets a <b>State</b> within the assigned <b>State Group</b> that is accessed when the action is initiated.")
|
||||
property string propertyTooltip: qsTr("Sets the property of the component that is affected by the action.")
|
||||
property string valueTooltip: qsTr("Sets the value of the property of the component that is affected by the action.")
|
||||
property string messageTooltip: qsTr("Sets a text that is printed when the action is initiated.")
|
||||
|
||||
|
||||
StatementEditor {
|
||||
width: root.width
|
||||
actionType: root.currentAction
|
||||
horizontalSpacing: root.horizontalSpacing
|
||||
columnWidth: root.columnWidth
|
||||
statement: root.backend.okStatement
|
||||
backend: root.backend
|
||||
spacing: root.verticalSpacing
|
||||
itemTooltip: root.itemTooltip
|
||||
methodTooltip: root.methodTooltip
|
||||
fromTooltip: root.fromTooltip
|
||||
toTooltip: root.toTooltip
|
||||
stateGroupTooltip: root.stateGroupTooltip
|
||||
stateTooltip: root.stateTooltip
|
||||
propertyTooltip: root.propertyTooltip
|
||||
valueTooltip: root.valueTooltip
|
||||
messageTooltip: root.messageTooltip
|
||||
}
|
||||
|
||||
|
||||
HelperWidgets.AbstractButton {
|
||||
id: addConditionButton
|
||||
style: StudioTheme.Values.connectionPopupButtonStyle
|
||||
width: 160
|
||||
buttonIcon: qsTr("Add Condition")
|
||||
tooltip: root.addConditionTooltip
|
||||
iconSize: StudioTheme.Values.baseFontSize
|
||||
iconFontFamily: StudioTheme.Constants.font.family
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: root.currentAction !== StatementDelegate.Custom && !backend.hasCondition
|
||||
|
||||
onClicked: backend.addCondition()
|
||||
}
|
||||
|
||||
HelperWidgets.AbstractButton {
|
||||
style: StudioTheme.Values.connectionPopupButtonStyle
|
||||
width: 160
|
||||
buttonIcon: qsTr("Remove Condition")
|
||||
tooltip: root.removeConditionTooltip
|
||||
iconSize: StudioTheme.Values.baseFontSize
|
||||
iconFontFamily: StudioTheme.Constants.font.family
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: root.currentAction !== StatementDelegate.Custom && backend.hasCondition
|
||||
|
||||
onClicked: backend.removeCondition()
|
||||
}
|
||||
|
||||
ExpressionBuilder {
|
||||
style: StudioTheme.Values.connectionPopupControlStyle
|
||||
width: root.width
|
||||
|
||||
visible: backend.hasCondition
|
||||
model: backend.conditionListModel
|
||||
conditionListModel: backend.conditionListModel
|
||||
popupListModel: backend.propertyListProxyModel
|
||||
popupTreeModel: backend.propertyTreeModel
|
||||
|
||||
onRemove: function(index) {
|
||||
backend.conditionListModel.removeToken(index)
|
||||
}
|
||||
|
||||
onUpdate: function(index, value) {
|
||||
backend.conditionListModel.updateToken(index, value)
|
||||
}
|
||||
|
||||
onAdd: function(value) {
|
||||
backend.conditionListModel.appendToken(value)
|
||||
}
|
||||
|
||||
onInsert: function(index, value, type) {
|
||||
if (type === ConditionListModel.Intermediate)
|
||||
backend.conditionListModel.insertIntermediateToken(index, value)
|
||||
else if (type === ConditionListModel.Shadow)
|
||||
backend.conditionListModel.insertShadowToken(index, value)
|
||||
else
|
||||
backend.conditionListModel.insertToken(index, value)
|
||||
}
|
||||
|
||||
onSetValue: function(index, value) {
|
||||
backend.conditionListModel.setShadowToken(index, value)
|
||||
}
|
||||
}
|
||||
|
||||
HelperWidgets.AbstractButton {
|
||||
style: StudioTheme.Values.connectionPopupButtonStyle
|
||||
width: 160
|
||||
buttonIcon: qsTr("Add Else Statement")
|
||||
tooltip: qsTr("Sets an alternate condition for the previously defined logical condition.")
|
||||
iconSize: StudioTheme.Values.baseFontSize
|
||||
iconFontFamily: StudioTheme.Constants.font.family
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: root.currentAcion !== StatementDelegate.Custom
|
||||
&& backend.hasCondition && !backend.hasElse
|
||||
|
||||
onClicked: backend.addElse()
|
||||
}
|
||||
|
||||
HelperWidgets.AbstractButton {
|
||||
style: StudioTheme.Values.connectionPopupButtonStyle
|
||||
width: 160
|
||||
buttonIcon: qsTr("Remove Else Statement")
|
||||
tooltip: qsTr("Removes the alternate logical condition for the previously defined logical condition.")
|
||||
iconSize: StudioTheme.Values.baseFontSize
|
||||
iconFontFamily: StudioTheme.Constants.font.family
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: root.currentAction !== StatementDelegate.Custom
|
||||
&& backend.hasCondition && backend.hasElse
|
||||
|
||||
onClicked: backend.removeElse()
|
||||
}
|
||||
|
||||
//Else Statement
|
||||
StatementEditor {
|
||||
width: root.width
|
||||
actionType: root.currentAction
|
||||
horizontalSpacing: root.horizontalSpacing
|
||||
columnWidth: root.columnWidth
|
||||
statement: backend.koStatement
|
||||
backend: root.backend
|
||||
spacing: root.verticalSpacing
|
||||
visible: action.currentValue !== StatementDelegate.Custom
|
||||
&& backend.hasCondition && backend.hasElse
|
||||
itemTooltip: root.itemTooltip
|
||||
methodTooltip: root.methodTooltip
|
||||
fromTooltip: root.fromTooltip
|
||||
toTooltip: root.toTooltip
|
||||
stateGroupTooltip: root.stateGroupTooltip
|
||||
stateTooltip: root.stateTooltip
|
||||
propertyTooltip: root.propertyTooltip
|
||||
valueTooltip: root.valueTooltip
|
||||
messageTooltip: root.messageTooltip
|
||||
}
|
||||
|
||||
// code preview toolbar
|
||||
Column {
|
||||
id: miniToolbarEditor
|
||||
width: parent.width
|
||||
spacing: -2
|
||||
|
||||
Rectangle {
|
||||
id: miniToolbar
|
||||
width: parent.width
|
||||
height: editorButton.height + 2
|
||||
radius: 4
|
||||
z: -1
|
||||
color: StudioTheme.Values.themeConnectionEditorMicroToolbar
|
||||
|
||||
Row {
|
||||
spacing: 2
|
||||
HelperWidgets.AbstractButton {
|
||||
id: editorButton
|
||||
style: StudioTheme.Values.microToolbarButtonStyle
|
||||
buttonIcon: StudioTheme.Constants.codeEditor_medium
|
||||
tooltip: qsTr("Write the conditions manually.")
|
||||
onClicked: expressionDialogLoader.show()
|
||||
}
|
||||
HelperWidgets.AbstractButton {
|
||||
id: jumpToCodeButton
|
||||
style: StudioTheme.Values.microToolbarButtonStyle
|
||||
buttonIcon: StudioTheme.Constants.jumpToCode_medium
|
||||
tooltip: qsTr("Jump to the code.")
|
||||
onClicked: backend.jumpToCode()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Editor
|
||||
Rectangle {
|
||||
id: editor
|
||||
width: parent.width
|
||||
height: 150
|
||||
color: StudioTheme.Values.themeConnectionCodeEditor
|
||||
|
||||
Text {
|
||||
id: code
|
||||
anchors.fill: parent
|
||||
anchors.margins: 4
|
||||
text: backend.indentedSource
|
||||
color: StudioTheme.Values.themeTextColor
|
||||
font.pixelSize: StudioTheme.Values.myFontSize
|
||||
wrapMode: Text.Wrap
|
||||
horizontalAlignment: code.lineCount === 1 ? Text.AlignHCenter : Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: expressionDialogLoader
|
||||
parent: editor
|
||||
anchors.fill: parent
|
||||
visible: false
|
||||
active: visible
|
||||
|
||||
function show() {
|
||||
expressionDialogLoader.visible = true
|
||||
}
|
||||
|
||||
sourceComponent: Item {
|
||||
id: bindingEditorParent
|
||||
|
||||
Component.onCompleted: {
|
||||
bindingEditor.showWidget()
|
||||
bindingEditor.text = backend.source
|
||||
bindingEditor.showControls(false)
|
||||
bindingEditor.setMultilne(true)
|
||||
bindingEditor.updateWindowName()
|
||||
}
|
||||
|
||||
HelperWidgets.ActionEditor {
|
||||
id: bindingEditor
|
||||
|
||||
onRejected: {
|
||||
bindingEditor.hideWidget()
|
||||
expressionDialogLoader.visible = false
|
||||
}
|
||||
|
||||
onAccepted: {
|
||||
backend.setNewSource(bindingEditor.text)
|
||||
bindingEditor.hideWidget()
|
||||
expressionDialogLoader.visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,7 +6,7 @@ import QtQuick.Controls
|
||||
import HelperWidgets as HelperWidgets
|
||||
import StudioControls as StudioControls
|
||||
import StudioTheme as StudioTheme
|
||||
import ConnectionsEditorEditorBackend
|
||||
import ScriptEditorBackend
|
||||
|
||||
Column {
|
||||
id: root
|
||||
@@ -20,26 +20,37 @@ Column {
|
||||
|
||||
property var backend
|
||||
|
||||
property string itemTooltip
|
||||
property alias methodTooltip: methodPopupLabel.tooltip
|
||||
property alias fromTooltip: fromPopupLabel.tooltip
|
||||
property alias toTooltip: toPopupLabel.tooltip
|
||||
property alias stateGroupTooltip: stateGroupPopupLabel.tooltip
|
||||
property alias stateTooltip: statePopupLabel.tooltip
|
||||
property alias propertyTooltip: propertyPopupLabel.tooltip
|
||||
property alias valueTooltip: valuePopupLabel.tooltip
|
||||
property alias messageTooltip: messagePopupLabel.tooltip
|
||||
|
||||
// Call Function
|
||||
Row {
|
||||
visible: root.actionType === ConnectionModelStatementDelegate.CallFunction
|
||||
visible: root.actionType === StatementDelegate.CallFunction
|
||||
spacing: root.horizontalSpacing
|
||||
|
||||
PopupLabel {
|
||||
HelperWidgets.PopupLabel {
|
||||
id: itemPopupLabel
|
||||
width: root.columnWidth
|
||||
text: qsTr("Item")
|
||||
tooltip: qsTr("Sets the component that is affected by the action of the <b>Target</b> component's <b>Signal</b>.")
|
||||
tooltip: root.itemTooltip
|
||||
}
|
||||
|
||||
PopupLabel {
|
||||
HelperWidgets.PopupLabel {
|
||||
id: methodPopupLabel
|
||||
width: root.columnWidth
|
||||
text: qsTr("Method")
|
||||
tooltip: qsTr("Sets the item component's method that is affected by the <b>Target</b> component's <b>Signal</b>.")
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
visible: root.actionType === ConnectionModelStatementDelegate.CallFunction
|
||||
visible: root.actionType === StatementDelegate.CallFunction
|
||||
spacing: root.horizontalSpacing
|
||||
|
||||
StudioControls.TopLevelComboBox {
|
||||
@@ -68,23 +79,23 @@ Column {
|
||||
|
||||
// Assign
|
||||
Row {
|
||||
visible: root.actionType === ConnectionModelStatementDelegate.Assign
|
||||
visible: root.actionType === StatementDelegate.Assign
|
||||
spacing: root.horizontalSpacing
|
||||
|
||||
PopupLabel {
|
||||
HelperWidgets.PopupLabel {
|
||||
id: fromPopupLabel
|
||||
width: root.columnWidth
|
||||
text: qsTr("From")
|
||||
tooltip: qsTr("Sets the component and its property from which the value is copied when the <b>Target</b> component initiates the <b>Signal</b>.")
|
||||
}
|
||||
PopupLabel {
|
||||
HelperWidgets.PopupLabel {
|
||||
id: toPopupLabel
|
||||
width: root.columnWidth
|
||||
text: qsTr("To")
|
||||
tooltip: qsTr("Sets the component and its property to which the copied value is assigned when the <b>Target</b> component initiates the <b>Signal</b>.")
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
visible: root.actionType === ConnectionModelStatementDelegate.Assign
|
||||
visible: root.actionType === StatementDelegate.Assign
|
||||
spacing: root.horizontalSpacing
|
||||
|
||||
StudioControls.TopLevelComboBox {
|
||||
@@ -114,7 +125,7 @@ Column {
|
||||
}
|
||||
|
||||
Row {
|
||||
visible: root.actionType === ConnectionModelStatementDelegate.Assign
|
||||
visible: root.actionType === StatementDelegate.Assign
|
||||
spacing: root.horizontalSpacing
|
||||
|
||||
StudioControls.TopLevelComboBox {
|
||||
@@ -145,24 +156,24 @@ Column {
|
||||
|
||||
// Change State
|
||||
Row {
|
||||
visible: root.actionType === ConnectionModelStatementDelegate.ChangeState
|
||||
visible: root.actionType === StatementDelegate.ChangeState
|
||||
spacing: root.horizontalSpacing
|
||||
|
||||
PopupLabel {
|
||||
HelperWidgets.PopupLabel {
|
||||
id: stateGroupPopupLabel
|
||||
width: root.columnWidth
|
||||
text: qsTr("State Group")
|
||||
tooltip: qsTr("Sets a <b>State Group</b> that is accessed when the <b>Target</b> component initiates the <b>Signal</b>.")
|
||||
}
|
||||
|
||||
PopupLabel {
|
||||
HelperWidgets.PopupLabel {
|
||||
id: statePopupLabel
|
||||
width: root.columnWidth
|
||||
text: qsTr("State")
|
||||
tooltip: qsTr("Sets a <b>State</b> within the assigned <b>State Group</b> that is accessed when the <b>Target</b> component initiates the <b>Signal</b>.")
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
visible: root.actionType === ConnectionModelStatementDelegate.ChangeState
|
||||
visible: root.actionType === StatementDelegate.ChangeState
|
||||
spacing: root.horizontalSpacing
|
||||
|
||||
StudioControls.TopLevelComboBox {
|
||||
@@ -191,24 +202,24 @@ Column {
|
||||
|
||||
// Set Property
|
||||
Row {
|
||||
visible: root.actionType === ConnectionModelStatementDelegate.SetProperty
|
||||
visible: root.actionType === StatementDelegate.SetProperty
|
||||
spacing: root.horizontalSpacing
|
||||
|
||||
PopupLabel {
|
||||
HelperWidgets.PopupLabel {
|
||||
width: root.columnWidth
|
||||
text: qsTr("Item")
|
||||
tooltip: qsTr("Sets the component that is affected by the action of the <b>Target</b> component's <b>Signal</b>.")
|
||||
tooltip: root.itemTooltip
|
||||
}
|
||||
|
||||
PopupLabel {
|
||||
HelperWidgets.PopupLabel {
|
||||
id: propertyPopupLabel
|
||||
width: root.columnWidth
|
||||
text: qsTr("Property")
|
||||
tooltip: qsTr("Sets the property of the component that is affected by the action of the <b>Target</b> component's <b>Signal</b>.")
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
visible: root.actionType === ConnectionModelStatementDelegate.SetProperty
|
||||
visible: root.actionType === StatementDelegate.SetProperty
|
||||
spacing: root.horizontalSpacing
|
||||
|
||||
StudioControls.TopLevelComboBox {
|
||||
@@ -236,16 +247,16 @@ Column {
|
||||
}
|
||||
}
|
||||
|
||||
PopupLabel {
|
||||
HelperWidgets.PopupLabel {
|
||||
id: valuePopupLabel
|
||||
width: root.columnWidth
|
||||
visible: root.actionType === ConnectionModelStatementDelegate.SetProperty
|
||||
visible: root.actionType === StatementDelegate.SetProperty
|
||||
text: qsTr("Value")
|
||||
tooltip: qsTr("Sets the value of the property of the component that is affected by the action of the <b>Target</b> component's <b>Signal</b>.")
|
||||
}
|
||||
|
||||
StudioControls.TextField {
|
||||
id: setPropertyArgument
|
||||
visible: root.actionType === ConnectionModelStatementDelegate.SetProperty
|
||||
visible: root.actionType === StatementDelegate.SetProperty
|
||||
width: root.width
|
||||
actionIndicatorVisible: false
|
||||
translationIndicatorVisible: false
|
||||
@@ -257,16 +268,16 @@ Column {
|
||||
}
|
||||
|
||||
// Print Message
|
||||
PopupLabel {
|
||||
HelperWidgets.PopupLabel {
|
||||
id: messagePopupLabel
|
||||
width: root.columnWidth
|
||||
visible: root.actionType === ConnectionModelStatementDelegate.PrintMessage
|
||||
visible: root.actionType === StatementDelegate.PrintMessage
|
||||
text: qsTr("Message")
|
||||
tooltip: qsTr("Sets a text that is printed when the <b>Signal</b> of the <b>Target</b> component initiates.")
|
||||
}
|
||||
|
||||
StudioControls.TextField {
|
||||
id: messageString
|
||||
visible: root.actionType === ConnectionModelStatementDelegate.PrintMessage
|
||||
visible: root.actionType === StatementDelegate.PrintMessage
|
||||
width: root.width
|
||||
actionIndicatorVisible: false
|
||||
translationIndicatorVisible: false
|
||||
@@ -277,8 +288,8 @@ Column {
|
||||
}
|
||||
|
||||
// Custom
|
||||
PopupLabel {
|
||||
visible: root.actionType === ConnectionModelStatementDelegate.Custom
|
||||
HelperWidgets.PopupLabel {
|
||||
visible: root.actionType === StatementDelegate.Custom
|
||||
text: qsTr("Custom Connections can only be edited with the binding editor")
|
||||
width: root.width
|
||||
horizontalAlignment: Text.AlignHCenter
|
@@ -6,15 +6,15 @@ import QtQuick.Controls as Controls
|
||||
import HelperWidgets as HelperWidgets
|
||||
import StudioTheme as StudioTheme
|
||||
import StudioControls as StudioControls
|
||||
import ConnectionsEditorEditorBackend
|
||||
import ScriptEditorBackend
|
||||
|
||||
Controls.Popup {
|
||||
id: root
|
||||
|
||||
property StudioTheme.ControlStyle style: StudioTheme.Values.controlStyle
|
||||
|
||||
property var listModel: ConnectionsEditorEditorBackend.connectionModel.delegate.propertyListProxyModel
|
||||
property var treeModel: ConnectionsEditorEditorBackend.connectionModel.delegate.propertyTreeModel
|
||||
property var listModel: ScriptEditorBackend.propertyListProxyModel // connect a valid model here
|
||||
property var treeModel: ScriptEditorBackend.propertyTreeModel // connect a valid model here
|
||||
|
||||
signal select(var value)
|
||||
signal entered(var value)
|
@@ -0,0 +1,2 @@
|
||||
ScriptEditorForm 1.0 ScriptEditorForm.qml
|
||||
ActionsComboBox 1.0 ActionsComboBox.qml
|
@@ -26,8 +26,8 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls.Basic as Basic
|
||||
import StatesEditor
|
||||
import HelperWidgets 2.0 as HelperWidgets
|
||||
import StudioControls 1.0 as StudioControls
|
||||
import HelperWidgets as HelperWidgets
|
||||
import StudioControls as StudioControls
|
||||
import StudioTheme as StudioTheme
|
||||
import StatesEditorBackend
|
||||
|
||||
@@ -313,7 +313,7 @@ Rectangle {
|
||||
StudioControls.Dialog {
|
||||
id: editDialog
|
||||
title: qsTr("Rename state group")
|
||||
standardButtons: Dialog.Apply | Dialog.Cancel
|
||||
standardButtons: Basic.Dialog.Apply | Basic.Dialog.Cancel
|
||||
x: editButton.x - Math.max(0, editButton.x + editDialog.width - root.width)
|
||||
y: toolBar.height
|
||||
width: Math.min(300, root.width)
|
||||
@@ -328,15 +328,11 @@ Rectangle {
|
||||
anchors.fill: parent
|
||||
|
||||
onTextChanged: {
|
||||
let btn = editDialog.standardButton(Dialog.Apply)
|
||||
let btn = editDialog.standardButton(Basic.Dialog.Apply)
|
||||
if (!btn)
|
||||
return
|
||||
|
||||
if (editDialog.previousString !== editTextField.text) {
|
||||
btn.enabled = true
|
||||
} else {
|
||||
btn.enabled = false
|
||||
}
|
||||
btn.enabled = (editDialog.previousString !== editTextField.text)
|
||||
}
|
||||
|
||||
onAccepted: editDialog.accept()
|
||||
@@ -355,7 +351,7 @@ Rectangle {
|
||||
editTextField.text = StatesEditorBackend.statesEditorModel.activeStateGroup
|
||||
editDialog.previousString = StatesEditorBackend.statesEditorModel.activeStateGroup
|
||||
|
||||
let btn = editDialog.standardButton(Dialog.Apply)
|
||||
let btn = editDialog.standardButton(Basic.Dialog.Apply)
|
||||
btn.enabled = false
|
||||
}
|
||||
}
|
||||
|
@@ -526,6 +526,10 @@ protected:
|
||||
{
|
||||
out("pragma ", ast->pragmaToken);
|
||||
out(ast->name.toString());
|
||||
if (!ast->value.isEmpty()) {
|
||||
out(": ");
|
||||
out(ast->value.toString());
|
||||
}
|
||||
newLine();
|
||||
return false;
|
||||
}
|
||||
|
@@ -36,14 +36,7 @@ void insertUpdateDelete(SqliteRange &&sqliteRange,
|
||||
auto endValueIterator = values.end();
|
||||
auto lastValueIterator = endValueIterator;
|
||||
|
||||
while (true) {
|
||||
bool hasMoreValues = currentValueIterator != endValueIterator;
|
||||
bool hasMoreSqliteValues = currentSqliteIterator != endSqliteIterator;
|
||||
if (hasMoreValues && hasMoreSqliteValues) {
|
||||
auto &&sqliteValue = *currentSqliteIterator;
|
||||
auto &&value = *currentValueIterator;
|
||||
auto compare = compareKey(sqliteValue, value);
|
||||
if (compare == 0) {
|
||||
auto doUpdate = [&](const auto &sqliteValue, const auto &value) {
|
||||
UpdateChange updateChange = updateCallback(sqliteValue, value);
|
||||
switch (updateChange) {
|
||||
case UpdateChange::Update:
|
||||
@@ -55,10 +48,14 @@ void insertUpdateDelete(SqliteRange &&sqliteRange,
|
||||
}
|
||||
++currentSqliteIterator;
|
||||
++currentValueIterator;
|
||||
} else if (compare > 0) {
|
||||
};
|
||||
|
||||
auto doInsert = [&](const auto &value) {
|
||||
insertCallback(value);
|
||||
++currentValueIterator;
|
||||
} else if (compare < 0) {
|
||||
};
|
||||
|
||||
auto doDelete = [&](const auto &sqliteValue) {
|
||||
if (lastValueIterator != endValueIterator) {
|
||||
if (compareKey(sqliteValue, *lastValueIterator) != 0)
|
||||
deleteCallback(sqliteValue);
|
||||
@@ -67,20 +64,25 @@ void insertUpdateDelete(SqliteRange &&sqliteRange,
|
||||
deleteCallback(sqliteValue);
|
||||
}
|
||||
++currentSqliteIterator;
|
||||
}
|
||||
} else if (hasMoreValues) {
|
||||
insertCallback(*currentValueIterator);
|
||||
++currentValueIterator;
|
||||
} else if (hasMoreSqliteValues) {
|
||||
};
|
||||
|
||||
while (true) {
|
||||
bool hasMoreValues = currentValueIterator != endValueIterator;
|
||||
bool hasMoreSqliteValues = currentSqliteIterator != endSqliteIterator;
|
||||
if (hasMoreValues && hasMoreSqliteValues) {
|
||||
auto &&sqliteValue = *currentSqliteIterator;
|
||||
if (lastValueIterator != endValueIterator) {
|
||||
if (compareKey(sqliteValue, *lastValueIterator) != 0)
|
||||
deleteCallback(sqliteValue);
|
||||
lastValueIterator = endValueIterator;
|
||||
} else {
|
||||
deleteCallback(sqliteValue);
|
||||
}
|
||||
++currentSqliteIterator;
|
||||
auto &&value = *currentValueIterator;
|
||||
auto compare = compareKey(sqliteValue, value);
|
||||
if (compare == 0)
|
||||
doUpdate(sqliteValue, value);
|
||||
else if (compare > 0)
|
||||
doInsert(value);
|
||||
else if (compare < 0)
|
||||
doDelete(sqliteValue);
|
||||
} else if (hasMoreValues) {
|
||||
doInsert(*currentValueIterator);
|
||||
} else if (hasMoreSqliteValues) {
|
||||
doDelete(*currentSqliteIterator);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
@@ -191,7 +191,7 @@ void SqlStatementBuilder::clearSqlStatement()
|
||||
|
||||
void SqlStatementBuilder::checkIfPlaceHolderExists(Utils::SmallStringView name) const
|
||||
{
|
||||
if (name.size() < 2 || !name.startsWith('$') || !m_sqlTemplate.contains(name))
|
||||
if (name.size() < 2 || !name.starts_with('$') || !m_sqlTemplate.contains(name))
|
||||
throwException("SqlStatementBuilder::bind: placeholder name does not exist!", name.data());
|
||||
}
|
||||
|
||||
|
@@ -112,13 +112,13 @@ public:
|
||||
|
||||
~BasicSmallString() noexcept
|
||||
{
|
||||
if (Q_UNLIKELY(hasAllocatedMemory()))
|
||||
if (hasAllocatedMemory()) [[unlikely]]
|
||||
Memory::deallocate(m_data.data());
|
||||
}
|
||||
|
||||
BasicSmallString(const BasicSmallString &other) noexcept
|
||||
{
|
||||
if (Q_LIKELY(other.isShortString() || other.isReadOnlyReference()))
|
||||
if (other.isShortString() || other.isReadOnlyReference()) [[likely]]
|
||||
m_data = other.m_data;
|
||||
else
|
||||
new (this) BasicSmallString{other.data(), other.size()};
|
||||
@@ -126,10 +126,10 @@ public:
|
||||
|
||||
BasicSmallString &operator=(const BasicSmallString &other) noexcept
|
||||
{
|
||||
if (Q_LIKELY(this != &other)) {
|
||||
if (this != &other) [[likely]] {
|
||||
this->~BasicSmallString();
|
||||
|
||||
if (Q_LIKELY(other.isShortString() || other.isReadOnlyReference()))
|
||||
if (other.isShortString() || other.isReadOnlyReference()) [[likely]]
|
||||
m_data = other.m_data;
|
||||
else
|
||||
new (this) BasicSmallString{other.data(), other.size()};
|
||||
@@ -146,7 +146,7 @@ public:
|
||||
|
||||
BasicSmallString &operator=(BasicSmallString &&other) noexcept
|
||||
{
|
||||
if (Q_LIKELY(this != &other)) {
|
||||
if (this != &other) [[likely]] {
|
||||
this->~BasicSmallString();
|
||||
|
||||
m_data = std::move(other.m_data);
|
||||
@@ -174,7 +174,7 @@ public:
|
||||
|
||||
SmallStringView toStringView() const noexcept { return SmallStringView(data(), size()); }
|
||||
|
||||
operator SmallStringView() const noexcept { return SmallStringView(data(), size()); }
|
||||
constexpr operator SmallStringView() const noexcept { return SmallStringView(data(), size()); }
|
||||
|
||||
explicit operator QLatin1StringView() const noexcept
|
||||
{
|
||||
@@ -202,7 +202,7 @@ public:
|
||||
void reserve(size_type newCapacity) noexcept
|
||||
{
|
||||
if (fitsNotInCapacity(newCapacity)) {
|
||||
if (Q_UNLIKELY(hasAllocatedMemory())) {
|
||||
if (hasAllocatedMemory()) {
|
||||
m_data.setPointer(Memory::reallocate(m_data.data(), newCapacity));
|
||||
m_data.setAllocatedCapacity(newCapacity);
|
||||
} else {
|
||||
|
@@ -13,12 +13,6 @@
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#if __cpp_lib_constexpr_string >= 201907L
|
||||
#define constexpr_string constexpr
|
||||
#else
|
||||
#define constexpr_string
|
||||
#endif
|
||||
|
||||
namespace Utils {
|
||||
|
||||
template <typename String>
|
||||
@@ -36,41 +30,14 @@ class SmallStringView : public std::string_view
|
||||
public:
|
||||
using std::string_view::string_view;
|
||||
|
||||
constexpr SmallStringView(const_iterator begin, const_iterator end) noexcept
|
||||
: std::string_view{std::addressof(*begin), static_cast<std::size_t>(std::distance(begin, end))}
|
||||
{}
|
||||
|
||||
#ifdef Q_CC_MSVC
|
||||
constexpr SmallStringView(const char *const begin, const char *const end) noexcept
|
||||
: std::string_view{begin, static_cast<std::size_t>(std::distance(begin, end))}
|
||||
{}
|
||||
#endif
|
||||
|
||||
template<typename String, typename Utils::enable_if_has_char_data_pointer<String> = 0>
|
||||
constexpr SmallStringView(const String &string) noexcept
|
||||
: std::string_view{string.data(), static_cast<std::size_t>(string.size())}
|
||||
{}
|
||||
|
||||
static constexpr SmallStringView fromUtf8(const char *const characterPointer)
|
||||
{
|
||||
return SmallStringView(characterPointer);
|
||||
}
|
||||
|
||||
constexpr size_type isEmpty() const noexcept { return empty(); }
|
||||
|
||||
constexpr
|
||||
SmallStringView mid(size_type position) const noexcept
|
||||
{
|
||||
return SmallStringView(data() + position, size() - position);
|
||||
}
|
||||
|
||||
constexpr
|
||||
SmallStringView mid(size_type position, size_type length) const noexcept
|
||||
{
|
||||
return SmallStringView(data() + position, length);
|
||||
}
|
||||
|
||||
constexpr_string operator std::string() const { return std::string(data(), size()); }
|
||||
operator std::string() const { return std::string(data(), size()); }
|
||||
|
||||
explicit operator QString() const { return QString::fromUtf8(data(), int(size())); }
|
||||
|
||||
@@ -89,57 +56,18 @@ public:
|
||||
{
|
||||
return QUtf8StringView(data(), Utils::ssize(*this));
|
||||
}
|
||||
constexpr bool startsWith(SmallStringView subStringToSearch) const noexcept
|
||||
{
|
||||
if (size() >= subStringToSearch.size())
|
||||
return !std::char_traits<char>::compare(data(),
|
||||
subStringToSearch.data(),
|
||||
subStringToSearch.size());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr bool startsWith(char characterToSearch) const noexcept
|
||||
{
|
||||
return *begin() == characterToSearch;
|
||||
}
|
||||
|
||||
constexpr bool endsWith(SmallStringView ending) const noexcept
|
||||
{
|
||||
return size() >= ending.size() && std::equal(ending.rbegin(), ending.rend(), rbegin());
|
||||
}
|
||||
};
|
||||
|
||||
constexpr bool operator!=(SmallStringView first, SmallStringView second) noexcept
|
||||
inline constexpr auto operator<=>(const SmallStringView &first, const SmallStringView &second)
|
||||
{
|
||||
return std::string_view{first} != std::string_view{second};
|
||||
return std::string_view{first} <=> std::string_view{second};
|
||||
}
|
||||
|
||||
constexpr bool operator==(SmallStringView first, SmallStringView second) noexcept
|
||||
inline constexpr bool operator==(const SmallStringView &first, const SmallStringView &second)
|
||||
{
|
||||
return std::string_view{first} == std::string_view{second};
|
||||
}
|
||||
|
||||
constexpr bool operator<(SmallStringView first, SmallStringView second) noexcept
|
||||
{
|
||||
return std::string_view{first} < std::string_view{second};
|
||||
}
|
||||
|
||||
constexpr bool operator>(SmallStringView first, SmallStringView second) noexcept
|
||||
{
|
||||
return std::string_view{first} > std::string_view{second};
|
||||
}
|
||||
|
||||
constexpr bool operator<=(SmallStringView first, SmallStringView second) noexcept
|
||||
{
|
||||
return std::string_view{first} <= std::string_view{second};
|
||||
}
|
||||
|
||||
constexpr bool operator>=(SmallStringView first, SmallStringView second) noexcept
|
||||
{
|
||||
return std::string_view{first} >= std::string_view{second};
|
||||
}
|
||||
|
||||
constexpr int compare(SmallStringView first, SmallStringView second) noexcept
|
||||
{
|
||||
return first.compare(second);
|
||||
|
@@ -103,7 +103,6 @@ void InsightWidget::reloadQmlSource()
|
||||
{
|
||||
QString statesListQmlFilePath = qmlSourcesPath() + QStringLiteral("/Main.qml");
|
||||
QTC_ASSERT(QFileInfo::exists(statesListQmlFilePath), return );
|
||||
engine()->clearComponentCache();
|
||||
setSource(QUrl::fromLocalFile(statesListQmlFilePath));
|
||||
|
||||
if (!rootObject()) {
|
||||
|
@@ -100,6 +100,11 @@ DeployMcuProcessStep::DeployMcuProcessStep(ProjectExplorer::BuildStepList *bc, I
|
||||
cmdLine.addArg(directory);
|
||||
return cmdLine;
|
||||
});
|
||||
|
||||
connect(this, &BuildStep::addOutput, this, [](const QString &str, OutputFormat fmt) {
|
||||
if (fmt == OutputFormat::ErrorMessage)
|
||||
showError(str);
|
||||
});
|
||||
}
|
||||
|
||||
// Workaround for QDS-13763, when UL-10456 is completed this can be removed with the next LTS
|
||||
|
@@ -104,7 +104,6 @@ add_qtc_plugin(QmlDesigner
|
||||
qmldesignericons.h
|
||||
qmldesignerplugin.cpp qmldesignerplugin.h
|
||||
qmldesignerexternaldependencies.cpp qmldesignerexternaldependencies.h
|
||||
qmldesignerprojectmanager.cpp qmldesignerprojectmanager.h
|
||||
settingspage.cpp settingspage.h
|
||||
shortcutmanager.cpp shortcutmanager.h
|
||||
designermcumanager.cpp designermcumanager.h
|
||||
@@ -133,6 +132,16 @@ if (QTC_STATIC_BUILD AND TARGET QmlDesigner)
|
||||
extend_qtc_target(QmlDesigner PUBLIC_DEPENDS TextEditor)
|
||||
endif()
|
||||
|
||||
extend_qtc_plugin(QmlDesigner
|
||||
PUBLIC_INCLUDES project
|
||||
SOURCES_PREFIX project
|
||||
SOURCES
|
||||
qmldesignerprojectmanager.cpp
|
||||
qmldesignerprojectmanager.h
|
||||
projectstorageerrornotifier.cpp
|
||||
projectstorageerrornotifier.h
|
||||
)
|
||||
|
||||
extend_qtc_plugin(QmlDesigner
|
||||
PUBLIC_INCLUDES instances
|
||||
SOURCES_PREFIX instances
|
||||
@@ -412,7 +421,9 @@ extend_qtc_plugin(QmlDesigner
|
||||
gradientpresetdefaultlistmodel.cpp gradientpresetdefaultlistmodel.h
|
||||
gradientpresetitem.cpp gradientpresetitem.h
|
||||
gradientpresetlistmodel.cpp gradientpresetlistmodel.h
|
||||
instanceimageprovider.cpp instanceimageprovider.h
|
||||
propertyeditorcontextobject.cpp propertyeditorcontextobject.h
|
||||
propertyeditordynamicpropertiesproxymodel.cpp propertyeditordynamicpropertiesproxymodel.h
|
||||
propertyeditorqmlbackend.cpp propertyeditorqmlbackend.h
|
||||
propertyeditortransaction.cpp propertyeditortransaction.h
|
||||
propertyeditorvalue.cpp propertyeditorvalue.h
|
||||
@@ -421,7 +432,9 @@ extend_qtc_plugin(QmlDesigner
|
||||
propertynamevalidator.cpp propertynamevalidator.h
|
||||
tooltip.cpp tooltip.h
|
||||
qmlanchorbindingproxy.cpp qmlanchorbindingproxy.h
|
||||
qmlmaterialnodeproxy.cpp qmlmaterialnodeproxy.h
|
||||
qmlmodelnodeproxy.cpp qmlmodelnodeproxy.h
|
||||
qmltexturenodeproxy.cpp qmltexturenodeproxy.h
|
||||
quick2propertyeditorview.cpp quick2propertyeditorview.h
|
||||
propertyeditorutils.cpp propertyeditorutils.h
|
||||
)
|
||||
@@ -477,7 +490,6 @@ extend_qtc_plugin(QmlDesigner
|
||||
materialbrowserwidget.cpp materialbrowserwidget.h
|
||||
materialbrowsermodel.cpp materialbrowsermodel.h
|
||||
materialbrowsertexturesmodel.cpp materialbrowsertexturesmodel.h
|
||||
materialutils.cpp materialutils.h
|
||||
)
|
||||
|
||||
extend_qtc_plugin(QmlDesigner
|
||||
@@ -571,6 +583,16 @@ extend_qtc_plugin(QmlDesigner
|
||||
annotationeditor.qrc
|
||||
)
|
||||
|
||||
extend_qtc_plugin(QmlDesigner
|
||||
SOURCES_PREFIX components/scripteditor
|
||||
SOURCES
|
||||
scripteditorstatements.cpp scripteditorstatements.h
|
||||
scripteditorevaluator.cpp scripteditorevaluator.h
|
||||
scripteditorutils.cpp scripteditorutils.h
|
||||
propertytreemodel.cpp propertytreemodel.h
|
||||
scripteditorbackend.cpp scripteditorbackend.h
|
||||
)
|
||||
|
||||
extend_qtc_plugin(QmlDesigner
|
||||
SOURCES_PREFIX components/connectioneditor
|
||||
SOURCES
|
||||
@@ -578,15 +600,12 @@ extend_qtc_plugin(QmlDesigner
|
||||
bindingmodel.cpp bindingmodel.h
|
||||
bindingmodelitem.cpp bindingmodelitem.h
|
||||
connectioneditor.qrc
|
||||
connectioneditorevaluator.cpp connectioneditorevaluator.h
|
||||
connectioneditorstatements.cpp connectioneditorstatements.h
|
||||
connectionmodel.cpp connectionmodel.h
|
||||
connectionview.cpp connectionview.h
|
||||
dynamicpropertiesmodel.cpp dynamicpropertiesmodel.h
|
||||
dynamicpropertiesitem.cpp dynamicpropertiesitem.h
|
||||
connectioneditorutils.cpp connectioneditorutils.h
|
||||
selectiondynamicpropertiesproxymodel.cpp selectiondynamicpropertiesproxymodel.h
|
||||
propertytreemodel.cpp propertytreemodel.h
|
||||
connectioneditorlogging.cpp connectioneditorlogging.h
|
||||
)
|
||||
|
||||
extend_qtc_plugin(QmlDesigner
|
||||
@@ -780,39 +799,6 @@ extend_qtc_plugin(QmlDesigner
|
||||
dvconnector.cpp dvconnector.h
|
||||
)
|
||||
|
||||
add_qtc_plugin(assetexporterplugin
|
||||
PLUGIN_CLASS AssetExporterPlugin
|
||||
CONDITION TARGET QmlDesigner
|
||||
PLUGIN_DEPENDS
|
||||
Core ProjectExplorer QmlDesigner
|
||||
DEPENDS Utils Qt::Qml Qt::QuickPrivate
|
||||
PUBLIC_INCLUDES assetexporterplugin
|
||||
PLUGIN_PATH ${QmlDesignerPluginInstallPrefix}
|
||||
)
|
||||
|
||||
extend_qtc_plugin(assetexporterplugin
|
||||
CONDITION ENABLE_COMPILE_WARNING_AS_ERROR
|
||||
PROPERTIES COMPILE_WARNING_AS_ERROR ON
|
||||
)
|
||||
|
||||
extend_qtc_plugin(assetexporterplugin
|
||||
SOURCES_PREFIX assetexporterplugin
|
||||
SOURCES
|
||||
assetexportdialog.h assetexportdialog.cpp
|
||||
assetexporter.h assetexporter.cpp
|
||||
assetexporterplugin.h assetexporterplugin.cpp
|
||||
assetexporterview.h assetexporterview.cpp
|
||||
assetexportpluginconstants.h
|
||||
componentexporter.h componentexporter.cpp
|
||||
exportnotification.h exportnotification.cpp
|
||||
filepathmodel.h filepathmodel.cpp
|
||||
dumpers/assetnodedumper.h dumpers/assetnodedumper.cpp
|
||||
dumpers/itemnodedumper.h dumpers/itemnodedumper.cpp
|
||||
dumpers/nodedumper.h dumpers/nodedumper.cpp
|
||||
dumpers/textnodedumper.h dumpers/textnodedumper.cpp
|
||||
assetexporterplugin.qrc
|
||||
)
|
||||
|
||||
add_qtc_plugin(componentsplugin
|
||||
PLUGIN_CLASS ComponentsPlugin
|
||||
CONDITION TARGET QmlDesigner
|
||||
|
@@ -1,228 +0,0 @@
|
||||
// Copyright (C) 2020 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "assetexportdialog.h"
|
||||
|
||||
#include "../qmldesignertr.h"
|
||||
#include "assetexportpluginconstants.h"
|
||||
#include "filepathmodel.h"
|
||||
|
||||
#include <coreplugin/fileutils.h>
|
||||
#include <coreplugin/icore.h>
|
||||
|
||||
#include <projectexplorer/task.h>
|
||||
#include <projectexplorer/taskhub.h>
|
||||
#include <projectexplorer/project.h>
|
||||
#include <projectexplorer/projectmanager.h>
|
||||
|
||||
#include <utils/detailswidget.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <utils/outputformatter.h>
|
||||
#include <utils/pathchooser.h>
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QPushButton>
|
||||
#include <QListView>
|
||||
#include <QPlainTextEdit>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QScrollBar>
|
||||
#include <QGridLayout>
|
||||
#include <QProgressBar>
|
||||
#include <QLabel>
|
||||
#include <QStackedWidget>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
using namespace ProjectExplorer;
|
||||
using namespace Utils;
|
||||
|
||||
namespace QmlDesigner {
|
||||
|
||||
static void addFormattedMessage(OutputFormatter *formatter, const QString &str, OutputFormat format)
|
||||
{
|
||||
if (!formatter)
|
||||
return;
|
||||
|
||||
QPlainTextEdit *edit = formatter->plainTextEdit();
|
||||
QScrollBar *scroll = edit->verticalScrollBar();
|
||||
bool isAtBottom = scroll && scroll->value() == scroll->maximum();
|
||||
|
||||
QString msg = str + "\n";
|
||||
formatter->appendMessage(msg, format);
|
||||
|
||||
if (isAtBottom)
|
||||
scroll->setValue(scroll->maximum());
|
||||
}
|
||||
|
||||
AssetExportDialog::AssetExportDialog(const FilePath &exportPath,
|
||||
AssetExporter &assetExporter, FilePathModel &model,
|
||||
QWidget *parent) :
|
||||
QDialog(parent),
|
||||
m_assetExporter(assetExporter),
|
||||
m_filePathModel(model),
|
||||
m_filesView(new QListView),
|
||||
m_exportLogs(new QPlainTextEdit),
|
||||
m_outputFormatter(new Utils::OutputFormatter())
|
||||
{
|
||||
resize(768, 480);
|
||||
setWindowTitle(Tr::tr("Export Components"));
|
||||
|
||||
m_stackedWidget = new QStackedWidget;
|
||||
|
||||
m_exportProgress = new QProgressBar;
|
||||
m_exportProgress->setRange(0,0);
|
||||
|
||||
auto optionsWidget = new QWidget;
|
||||
|
||||
auto advancedOptions = new DetailsWidget;
|
||||
advancedOptions->setSummaryText(tr("Advanced Options"));
|
||||
advancedOptions->setWidget(optionsWidget);
|
||||
|
||||
m_buttonBox = new QDialogButtonBox;
|
||||
m_buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Close);
|
||||
|
||||
m_exportPath = new PathChooser;
|
||||
m_exportPath->setExpectedKind(PathChooser::Kind::SaveFile);
|
||||
m_exportPath->setFilePath(
|
||||
exportPath.pathAppended(
|
||||
ProjectExplorer::ProjectManager::startupProject()->displayName() + ".metadata"
|
||||
));
|
||||
m_exportPath->setPromptDialogTitle(tr("Choose Export File"));
|
||||
m_exportPath->setPromptDialogFilter(tr("Metadata file (*.metadata)"));
|
||||
m_exportPath->lineEdit()->setReadOnly(true);
|
||||
m_exportPath->addButton(tr("Open"), this, [this] {
|
||||
Core::FileUtils::showInGraphicalShell(m_exportPath->filePath());
|
||||
});
|
||||
|
||||
m_exportAssetsCheck = new QCheckBox(tr("Export assets"), this);
|
||||
m_exportAssetsCheck->setChecked(true);
|
||||
|
||||
m_perComponentExportCheck = new QCheckBox(tr("Export components separately"), this);
|
||||
m_perComponentExportCheck->setChecked(false);
|
||||
|
||||
m_buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false);
|
||||
|
||||
m_stackedWidget->addWidget(m_filesView);
|
||||
m_filesView->setModel(&m_filePathModel);
|
||||
|
||||
m_exportLogs->setReadOnly(true);
|
||||
m_outputFormatter->setPlainTextEdit(m_exportLogs);
|
||||
m_stackedWidget->addWidget(m_exportLogs);
|
||||
switchView(false);
|
||||
|
||||
connect(m_buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, [this] {
|
||||
m_buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false);
|
||||
m_assetExporter.cancel();
|
||||
});
|
||||
|
||||
m_exportBtn = m_buttonBox->addButton(tr("Export"), QDialogButtonBox::AcceptRole);
|
||||
m_exportBtn->setEnabled(false);
|
||||
connect(m_exportBtn, &QPushButton::clicked, this, &AssetExportDialog::onExport);
|
||||
connect(&m_filePathModel, &FilePathModel::modelReset, this, [this] {
|
||||
m_exportProgress->setRange(0, 1000);
|
||||
m_exportProgress->setValue(0);
|
||||
m_exportBtn->setEnabled(true);
|
||||
});
|
||||
|
||||
connect(m_buttonBox->button(QDialogButtonBox::Close), &QPushButton::clicked, [this] {
|
||||
close();
|
||||
});
|
||||
m_buttonBox->button(QDialogButtonBox::Close)->setVisible(false);
|
||||
|
||||
connect(&m_assetExporter, &AssetExporter::stateChanged,
|
||||
this, &AssetExportDialog::onExportStateChanged);
|
||||
connect(&m_assetExporter, &AssetExporter::exportProgressChanged,
|
||||
this, &AssetExportDialog::updateExportProgress);
|
||||
|
||||
connect(&taskHub(), &TaskHub::taskAdded, this, &AssetExportDialog::onTaskAdded);
|
||||
|
||||
using namespace Layouting;
|
||||
|
||||
Column {
|
||||
m_exportAssetsCheck,
|
||||
m_perComponentExportCheck,
|
||||
st,
|
||||
noMargin,
|
||||
}.attachTo(optionsWidget);
|
||||
|
||||
Column {
|
||||
Form { Tr::tr("Export path:"), m_exportPath },
|
||||
advancedOptions,
|
||||
m_stackedWidget,
|
||||
m_exportProgress,
|
||||
m_buttonBox,
|
||||
}.attachTo(this);
|
||||
}
|
||||
|
||||
AssetExportDialog::~AssetExportDialog()
|
||||
{
|
||||
m_assetExporter.cancel();
|
||||
}
|
||||
|
||||
void AssetExportDialog::onExport()
|
||||
{
|
||||
switchView(true);
|
||||
|
||||
updateExportProgress(0.0);
|
||||
TaskHub::clearTasks(Constants::TASK_CATEGORY_ASSET_EXPORT);
|
||||
m_exportLogs->clear();
|
||||
|
||||
Utils::FilePath selectedPath = m_exportPath->filePath();
|
||||
Utils::FilePath exportPath = m_perComponentExportCheck->isChecked() ?
|
||||
(selectedPath.isDir() ? selectedPath : selectedPath.parentDir()) :
|
||||
selectedPath;
|
||||
|
||||
m_assetExporter.exportQml(m_filePathModel.files(), exportPath,
|
||||
m_exportAssetsCheck->isChecked(),
|
||||
m_perComponentExportCheck->isChecked());
|
||||
}
|
||||
|
||||
void AssetExportDialog::onExportStateChanged(AssetExporter::ParsingState newState)
|
||||
{
|
||||
switch (newState) {
|
||||
case AssetExporter::ParsingState::ExportingDone:
|
||||
m_exportBtn->setVisible(false);
|
||||
m_buttonBox->button(QDialogButtonBox::Close)->setVisible(true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
m_exportBtn->setEnabled(newState == AssetExporter::ParsingState::ExportingDone);
|
||||
m_buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(m_assetExporter.isBusy());
|
||||
}
|
||||
|
||||
void AssetExportDialog::updateExportProgress(double value)
|
||||
{
|
||||
value = std::max(0.0, std::min(1.0, value));
|
||||
m_exportProgress->setValue(std::round(value * 1000));
|
||||
}
|
||||
|
||||
void AssetExportDialog::switchView(bool showExportView)
|
||||
{
|
||||
if (showExportView)
|
||||
m_stackedWidget->setCurrentWidget(m_exportLogs);
|
||||
else
|
||||
m_stackedWidget->setCurrentWidget(m_filesView);
|
||||
}
|
||||
|
||||
void AssetExportDialog::onTaskAdded(const ProjectExplorer::Task &task)
|
||||
{
|
||||
Utils::OutputFormat format = Utils::NormalMessageFormat;
|
||||
if (task.category == Constants::TASK_CATEGORY_ASSET_EXPORT) {
|
||||
switch (task.type) {
|
||||
case ProjectExplorer::Task::Error:
|
||||
format = Utils::StdErrFormat;
|
||||
break;
|
||||
case ProjectExplorer::Task::Warning:
|
||||
format = Utils::StdOutFormat;
|
||||
break;
|
||||
default:
|
||||
format = Utils::NormalMessageFormat;
|
||||
}
|
||||
addFormattedMessage(m_outputFormatter, task.description(), format);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,67 +0,0 @@
|
||||
// Copyright (C) 2020 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "assetexporter.h"
|
||||
|
||||
#include <utils/filepath.h>
|
||||
|
||||
#include <QDialog>
|
||||
#include <QStringListModel>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QPushButton;
|
||||
class QCheckBox;
|
||||
class QDialogButtonBox;
|
||||
class QListView;
|
||||
class QPlainTextEdit;
|
||||
class QProgressBar;
|
||||
class QStackedWidget;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace Utils {
|
||||
class OutputFormatter;
|
||||
class PathChooser;
|
||||
}
|
||||
|
||||
namespace ProjectExplorer {
|
||||
class Task;
|
||||
}
|
||||
|
||||
namespace QmlDesigner {
|
||||
|
||||
class FilePathModel;
|
||||
|
||||
class AssetExportDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AssetExportDialog(const Utils::FilePath &exportPath, AssetExporter &assetExporter,
|
||||
FilePathModel& model, QWidget *parent = nullptr);
|
||||
~AssetExportDialog();
|
||||
|
||||
private:
|
||||
void onExport();
|
||||
void onExportStateChanged(AssetExporter::ParsingState newState);
|
||||
void updateExportProgress(double value);
|
||||
void switchView(bool showExportView);
|
||||
void onTaskAdded(const ProjectExplorer::Task &task);
|
||||
|
||||
private:
|
||||
AssetExporter &m_assetExporter;
|
||||
FilePathModel &m_filePathModel;
|
||||
QPushButton *m_exportBtn = nullptr;
|
||||
QCheckBox *m_exportAssetsCheck = nullptr;
|
||||
QCheckBox *m_perComponentExportCheck = nullptr;
|
||||
QListView *m_filesView = nullptr;
|
||||
QPlainTextEdit *m_exportLogs = nullptr;
|
||||
Utils::OutputFormatter *m_outputFormatter = nullptr;
|
||||
Utils::PathChooser *m_exportPath = nullptr;
|
||||
QDialogButtonBox *m_buttonBox = nullptr;
|
||||
QStackedWidget *m_stackedWidget = nullptr;
|
||||
QProgressBar *m_exportProgress = nullptr;
|
||||
};
|
||||
|
||||
} // QmlDesigner
|
@@ -1,550 +0,0 @@
|
||||
// Copyright (C) 2020 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "assetexporter.h"
|
||||
#include "componentexporter.h"
|
||||
#include "exportnotification.h"
|
||||
|
||||
#include <designdocument.h>
|
||||
#include <modelutils.h>
|
||||
#include <nodemetainfo.h>
|
||||
#include <qmldesignerplugin.h>
|
||||
#include <qmldesignertr.h>
|
||||
#include <qmlitemnode.h>
|
||||
#include <qmlobjectnode.h>
|
||||
#include <rewriterview.h>
|
||||
|
||||
#include <coreplugin/editormanager/editormanager.h>
|
||||
|
||||
#include <projectexplorer/project.h>
|
||||
#include <projectexplorer/projectmanager.h>
|
||||
|
||||
#include <utils/async.h>
|
||||
#include <utils/fileutils.h>
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
#include <auxiliarydataproperties.h>
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QDir>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QLoggingCategory>
|
||||
#include <QPlainTextEdit>
|
||||
#include <QWaitCondition>
|
||||
|
||||
#include <random>
|
||||
#include <queue>
|
||||
|
||||
using namespace ProjectExplorer;
|
||||
using namespace std;
|
||||
namespace {
|
||||
bool makeParentPath(const Utils::FilePath &path)
|
||||
{
|
||||
QDir d;
|
||||
return d.mkpath(path.toFileInfo().absolutePath());
|
||||
}
|
||||
|
||||
QByteArray generateHash(const QString &token) {
|
||||
static uint counter = 0;
|
||||
std::mt19937 gen(std::random_device().operator()());
|
||||
std::uniform_int_distribution<> distribution(1, 99999);
|
||||
QByteArray data = QString("%1%2%3").arg(token).arg(++counter).arg(distribution(gen)).toLatin1();
|
||||
return QCryptographicHash::hash(data, QCryptographicHash::Md5).toHex();
|
||||
}
|
||||
|
||||
Q_LOGGING_CATEGORY(loggerInfo, "qtc.designer.assetExportPlugin.assetExporter", QtInfoMsg)
|
||||
Q_LOGGING_CATEGORY(loggerWarn, "qtc.designer.assetExportPlugin.assetExporter", QtWarningMsg)
|
||||
Q_LOGGING_CATEGORY(loggerError, "qtc.designer.assetExportPlugin.assetExporter", QtCriticalMsg)
|
||||
}
|
||||
|
||||
namespace QmlDesigner {
|
||||
|
||||
class AssetDumper
|
||||
{
|
||||
public:
|
||||
AssetDumper();
|
||||
~AssetDumper();
|
||||
|
||||
void dumpAsset(const QPixmap &p, const Utils::FilePath &path);
|
||||
|
||||
/* Keeps on dumping until all assets are dumped, then quits */
|
||||
void quitDumper();
|
||||
|
||||
/* Aborts dumping */
|
||||
void abortDumper();
|
||||
|
||||
private:
|
||||
void addAsset(const QPixmap &p, const Utils::FilePath &path);
|
||||
void doDumping(QPromise<void> &promise);
|
||||
void savePixmap(const QPixmap &p, Utils::FilePath &path) const;
|
||||
|
||||
QFuture<void> m_dumpFuture;
|
||||
QMutex m_queueMutex;
|
||||
QWaitCondition m_queueCondition;
|
||||
std::queue<std::pair<QPixmap, Utils::FilePath>> m_assets;
|
||||
std::atomic<bool> m_quitDumper;
|
||||
};
|
||||
|
||||
AssetExporter::AssetExporter(AssetExporterView *view,
|
||||
ProjectExplorer::Project *project,
|
||||
ProjectStorageDependencies projectStorageDependencies)
|
||||
: m_currentState(*this)
|
||||
, m_project(project)
|
||||
, m_view(view)
|
||||
, m_projectStorageDependencies{projectStorageDependencies}
|
||||
{
|
||||
connect(m_view, &AssetExporterView::loadingFinished, this, &AssetExporter::onQmlFileLoaded);
|
||||
connect(m_view, &AssetExporterView::loadingError, this, &AssetExporter::notifyLoadError);
|
||||
}
|
||||
|
||||
AssetExporter::~AssetExporter()
|
||||
{
|
||||
cancel();
|
||||
}
|
||||
|
||||
void AssetExporter::exportQml(const Utils::FilePaths &qmlFiles, const Utils::FilePath &exportPath,
|
||||
bool exportAssets, bool perComponentExport)
|
||||
{
|
||||
m_perComponentExport = perComponentExport;
|
||||
ExportNotification::addInfo(tr("Export root directory: %1.\nExporting assets: %2")
|
||||
.arg(exportPath.isDir()
|
||||
? exportPath.toUserOutput()
|
||||
: exportPath.parentDir().toUserOutput())
|
||||
.arg(exportAssets? tr("Yes") : tr("No")));
|
||||
|
||||
if (m_perComponentExport)
|
||||
ExportNotification::addInfo(tr("Each component is exported separately."));
|
||||
|
||||
notifyProgress(0.0);
|
||||
m_exportFiles = qmlFiles;
|
||||
m_totalFileCount = m_exportFiles.count();
|
||||
m_components.clear();
|
||||
m_componentUuidCache.clear();
|
||||
m_exportPath = exportPath.isDir() ? exportPath : exportPath.parentDir();
|
||||
m_exportFile = exportPath.fileName();
|
||||
m_currentState.change(ParsingState::Parsing);
|
||||
if (exportAssets)
|
||||
m_assetDumper = make_unique<AssetDumper>();
|
||||
else
|
||||
m_assetDumper.reset();
|
||||
|
||||
QTimer::singleShot(0, this, &AssetExporter::beginExport);
|
||||
}
|
||||
|
||||
void AssetExporter::beginExport()
|
||||
{
|
||||
for (const Utils::FilePath &p : std::as_const(m_exportFiles)) {
|
||||
if (m_cancelled)
|
||||
break;
|
||||
preprocessQmlFile(p);
|
||||
}
|
||||
|
||||
if (!m_cancelled)
|
||||
triggerLoadNextFile();
|
||||
}
|
||||
|
||||
void AssetExporter::cancel()
|
||||
{
|
||||
if (!m_cancelled) {
|
||||
ExportNotification::addInfo(tr("Canceling export."));
|
||||
m_assetDumper.reset();
|
||||
m_cancelled = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool AssetExporter::isBusy() const
|
||||
{
|
||||
return m_currentState == AssetExporter::ParsingState::Parsing ||
|
||||
m_currentState == AssetExporter::ParsingState::ExportingAssets ||
|
||||
m_currentState == AssetExporter::ParsingState::WritingJson;
|
||||
}
|
||||
|
||||
const QPixmap &AssetExporter::generateAsset(const ModelNode &node)
|
||||
{
|
||||
static QPixmap nullPixmap;
|
||||
if (m_cancelled)
|
||||
return nullPixmap;
|
||||
|
||||
const QString uuid = node.auxiliaryDataWithDefault(uuidProperty).toString();
|
||||
QTC_ASSERT(!uuid.isEmpty(), return nullPixmap);
|
||||
|
||||
if (!m_assets.contains(uuid)) {
|
||||
// Generate asset.
|
||||
QmlObjectNode objectNode(node);
|
||||
QPixmap asset = objectNode.toQmlItemNode().instanceRenderPixmap();
|
||||
m_assets[uuid] = asset;
|
||||
}
|
||||
return m_assets[uuid];
|
||||
}
|
||||
|
||||
Utils::FilePath AssetExporter::assetPath(const ModelNode &node, const Component *component,
|
||||
const QString &suffix) const
|
||||
{
|
||||
const QString uuid = node.auxiliaryDataWithDefault(uuidProperty).toString();
|
||||
if (!component || uuid.isEmpty())
|
||||
return {};
|
||||
|
||||
const Utils::FilePath assetExportDir =
|
||||
m_perComponentExport ? componentExportDir(component) : m_exportPath;
|
||||
const Utils::FilePath assetPath = assetExportDir.pathAppended("assets")
|
||||
.pathAppended(uuid + suffix + ".png");
|
||||
|
||||
return assetPath;
|
||||
}
|
||||
|
||||
void AssetExporter::exportAsset(const QPixmap &asset, const Utils::FilePath &path)
|
||||
{
|
||||
if (m_cancelled || !m_assetDumper)
|
||||
return;
|
||||
|
||||
m_assetDumper->dumpAsset(asset, path);
|
||||
}
|
||||
|
||||
void AssetExporter::exportComponent(const ModelNode &rootNode)
|
||||
{
|
||||
qCDebug(loggerInfo) << "Exporting component" << rootNode.id();
|
||||
m_components.push_back(make_unique<Component>(*this, rootNode));
|
||||
m_components.back()->exportComponent();
|
||||
}
|
||||
|
||||
void AssetExporter::notifyLoadError(AssetExporterView::LoadState state)
|
||||
{
|
||||
QString errorStr = tr("Unknown error.");
|
||||
switch (state) {
|
||||
case AssetExporterView::LoadState::Exausted:
|
||||
errorStr = tr("Loading file is taking too long.");
|
||||
break;
|
||||
case AssetExporterView::LoadState::QmlErrorState:
|
||||
errorStr = tr("Cannot parse. The file contains coding errors.");
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
qCDebug(loggerError) << "QML load error:" << errorStr;
|
||||
ExportNotification::addError(tr("Loading components failed. %1").arg(errorStr));
|
||||
}
|
||||
|
||||
void AssetExporter::notifyProgress(double value) const
|
||||
{
|
||||
emit exportProgressChanged(value);
|
||||
}
|
||||
|
||||
void AssetExporter::onQmlFileLoaded()
|
||||
{
|
||||
QTC_ASSERT(m_view && m_view->model(), qCDebug(loggerError) << "Null model"; return);
|
||||
qCDebug(loggerInfo) << "Qml file load done" << m_view->model()->fileUrl();
|
||||
|
||||
QmlDesigner::DesignDocument *designDocument = QmlDesigner::QmlDesignerPlugin::instance()
|
||||
->documentManager()
|
||||
.currentDesignDocument();
|
||||
if (designDocument->hasQmlParseErrors()) {
|
||||
ExportNotification::addError(tr("Cannot export component. Document \"%1\" has parsing errors.")
|
||||
.arg(designDocument->displayName()));
|
||||
} else {
|
||||
exportComponent(m_view->rootModelNode());
|
||||
if (Utils::Result res = m_view->saveQmlFile(); !res) {
|
||||
ExportNotification::addError(tr("Error saving component file. %1")
|
||||
.arg(res.error().isEmpty()? tr("Unknown") : res.error()));
|
||||
}
|
||||
}
|
||||
notifyProgress((m_totalFileCount - m_exportFiles.count()) * 0.8 / m_totalFileCount);
|
||||
triggerLoadNextFile();
|
||||
}
|
||||
|
||||
Utils::FilePath AssetExporter::componentExportDir(const Component *component) const
|
||||
{
|
||||
return m_exportPath.pathAppended(component->name());
|
||||
}
|
||||
|
||||
void AssetExporter::preprocessQmlFile(const Utils::FilePath &path)
|
||||
{
|
||||
// Load the QML file and assign UUIDs to items having none.
|
||||
// Meanwhile cache the Component UUIDs as well
|
||||
#ifdef QDS_USE_PROJECTSTORAGE
|
||||
ModelPointer model = Model::create(m_projectStorageDependencies,
|
||||
"Item",
|
||||
{Import::createLibraryImport("QtQuick")},
|
||||
path.path());
|
||||
#else
|
||||
ModelPointer model = Model::create("Item", 2, 7);
|
||||
#endif
|
||||
Utils::FileReader reader;
|
||||
if (!reader.fetch(path)) {
|
||||
ExportNotification::addError(tr("Cannot preprocess file: %1. Error %2")
|
||||
.arg(path.toUserOutput()).arg(reader.errorString()));
|
||||
return;
|
||||
}
|
||||
|
||||
QPlainTextEdit textEdit;
|
||||
textEdit.setPlainText(QString::fromUtf8(reader.data()));
|
||||
NotIndentingTextEditModifier *modifier = new NotIndentingTextEditModifier(textEdit.document());
|
||||
modifier->setParent(model.get());
|
||||
auto rewriterView = std::make_unique<RewriterView>(m_view->externalDependencies(),
|
||||
QmlDesigner::RewriterView::Validate);
|
||||
rewriterView->setCheckSemanticErrors(false);
|
||||
rewriterView->setTextModifier(modifier);
|
||||
model->attachView(rewriterView.get());
|
||||
rewriterView->restoreAuxiliaryData();
|
||||
ModelNode rootNode = rewriterView->rootModelNode();
|
||||
if (!rootNode.isValid()) {
|
||||
ExportNotification::addError(tr("Cannot preprocess file: %1").arg(path.toUrlishString()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (assignUuids(rootNode)) {
|
||||
// Some UUIDs were assigned. Rewrite the file.
|
||||
rewriterView->writeAuxiliaryData();
|
||||
const QByteArray data = textEdit.toPlainText().toUtf8();
|
||||
Utils::FileSaver saver(path, QIODevice::Text);
|
||||
saver.write(data);
|
||||
if (!saver.finalize()) {
|
||||
ExportNotification::addError(tr("Cannot update %1.\n%2")
|
||||
.arg(path.toUserOutput()).arg(saver.errorString()));
|
||||
return;
|
||||
}
|
||||
|
||||
// Close the document if already open.
|
||||
// UUIDS are changed and editor must reopen the document, otherwise stale state of the
|
||||
// document is loaded.
|
||||
for (Core::IDocument *doc : Core::DocumentModel::openedDocuments()) {
|
||||
if (doc->filePath() == path) {
|
||||
Core::EditorManager::closeDocuments({doc}, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cache component UUID
|
||||
const QString uuid = rootNode.auxiliaryDataWithDefault(uuidProperty).toString();
|
||||
m_componentUuidCache[path.toUrlishString()] = uuid;
|
||||
}
|
||||
|
||||
bool AssetExporter::assignUuids(const ModelNode &root)
|
||||
{
|
||||
// Assign an UUID to the node without one.
|
||||
// Return true if an assignment takes place.
|
||||
bool changed = false;
|
||||
for (const ModelNode &node : root.allSubModelNodesAndThisNode()) {
|
||||
const QString uuid = node.auxiliaryDataWithDefault(uuidProperty).toString();
|
||||
if (uuid.isEmpty()) {
|
||||
// Assign an unique identifier to the node.
|
||||
QByteArray uuid = generateUuid(node);
|
||||
node.setAuxiliaryData(uuidProperty, QString::fromLatin1(uuid));
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
QByteArray AssetExporter::generateUuid(const ModelNode &node)
|
||||
{
|
||||
QByteArray uuid;
|
||||
do {
|
||||
uuid = generateHash(node.id());
|
||||
} while (m_usedHashes.contains(uuid));
|
||||
m_usedHashes.insert(uuid);
|
||||
return uuid;
|
||||
}
|
||||
|
||||
QString AssetExporter::componentUuid(const ModelNode &instance) const
|
||||
{
|
||||
// Returns the UUID of the component's root node
|
||||
// Empty string is returned if the node is not an instance of a component within
|
||||
// the project.
|
||||
if (instance) {
|
||||
const QString path = ModelUtils::componentFilePath(instance);
|
||||
return m_componentUuidCache.value(path);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void AssetExporter::triggerLoadNextFile()
|
||||
{
|
||||
QTimer::singleShot(0, this, &AssetExporter::loadNextFile);
|
||||
}
|
||||
|
||||
void AssetExporter::loadNextFile()
|
||||
{
|
||||
if (m_cancelled || m_exportFiles.isEmpty()) {
|
||||
notifyProgress(0.8);
|
||||
m_currentState.change(ParsingState::ParsingFinished);
|
||||
writeMetadata();
|
||||
return;
|
||||
}
|
||||
|
||||
// Load the next pending file.
|
||||
const Utils::FilePath file = m_exportFiles.takeFirst();
|
||||
ExportNotification::addInfo(tr("Exporting file %1.").arg(file.toUserOutput()));
|
||||
qCDebug(loggerInfo) << "Loading next file" << file;
|
||||
m_view->loadQmlFile(file);
|
||||
}
|
||||
|
||||
void AssetExporter::writeMetadata() const
|
||||
{
|
||||
if (m_cancelled) {
|
||||
notifyProgress(1.0);
|
||||
ExportNotification::addInfo(tr("Export canceled."));
|
||||
m_currentState.change(ParsingState::ExportingDone);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
auto writeFile = [](const Utils::FilePath &path, const QJsonArray &artboards) {
|
||||
if (!makeParentPath(path)) {
|
||||
ExportNotification::addError(tr("Writing metadata failed. Cannot create file %1").
|
||||
arg(path.toUrlishString()));
|
||||
return;
|
||||
}
|
||||
|
||||
ExportNotification::addInfo(tr("Writing metadata to file %1.").arg(path.toUserOutput()));
|
||||
|
||||
QJsonObject jsonRoot; // TODO: Write plugin info to root
|
||||
jsonRoot.insert("artboards", artboards);
|
||||
QJsonDocument doc(jsonRoot);
|
||||
if (doc.isNull() || doc.isEmpty()) {
|
||||
ExportNotification::addError(tr("Empty JSON document."));
|
||||
return;
|
||||
}
|
||||
|
||||
Utils::FileSaver saver(path, QIODevice::Text);
|
||||
saver.write(doc.toJson(QJsonDocument::Indented));
|
||||
if (!saver.finalize()) {
|
||||
ExportNotification::addError(tr("Writing metadata failed. %1").
|
||||
arg(saver.errorString()));
|
||||
}
|
||||
};
|
||||
|
||||
m_currentState.change(ParsingState::WritingJson);
|
||||
|
||||
auto const startupProject = ProjectExplorer::ProjectManager::startupProject();
|
||||
QTC_ASSERT(startupProject, return);
|
||||
const QString projectName = startupProject->displayName();
|
||||
|
||||
if (m_perComponentExport) {
|
||||
for (auto &component : m_components) {
|
||||
const Utils::FilePath path = componentExportDir(component.get());
|
||||
writeFile(path.pathAppended(component->name() + ".metadata"), {component->json()});
|
||||
}
|
||||
} else {
|
||||
QJsonArray artboards;
|
||||
std::transform(m_components.cbegin(), m_components.cend(), back_inserter(artboards),
|
||||
[](const unique_ptr<Component> &c) {return c->json(); });
|
||||
writeFile(m_exportPath.pathAppended(m_exportFile), artboards);
|
||||
}
|
||||
notifyProgress(1.0);
|
||||
ExportNotification::addInfo(tr("Export finished."));
|
||||
if (m_assetDumper)
|
||||
m_assetDumper->quitDumper();
|
||||
m_currentState.change(ParsingState::ExportingDone);
|
||||
}
|
||||
|
||||
AssetExporter::State::State(AssetExporter &exporter) :
|
||||
m_assetExporter(exporter)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void AssetExporter::State::change(const ParsingState &state)
|
||||
{
|
||||
qCDebug(loggerInfo()) << "Assetimporter State change: Old: " << m_state << "New: " << state;
|
||||
if (m_state != state) {
|
||||
m_state = state;
|
||||
emit m_assetExporter.stateChanged(m_state);
|
||||
}
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug os, const AssetExporter::ParsingState &s)
|
||||
{
|
||||
os << static_cast<std::underlying_type<QmlDesigner::AssetExporter::ParsingState>::type>(s);
|
||||
return os;
|
||||
}
|
||||
|
||||
AssetDumper::AssetDumper():
|
||||
m_quitDumper(false)
|
||||
{
|
||||
m_dumpFuture = Utils::asyncRun(&AssetDumper::doDumping, this);
|
||||
}
|
||||
|
||||
AssetDumper::~AssetDumper()
|
||||
{
|
||||
abortDumper();
|
||||
}
|
||||
|
||||
void AssetDumper::dumpAsset(const QPixmap &p, const Utils::FilePath &path)
|
||||
{
|
||||
addAsset(p, path);
|
||||
}
|
||||
|
||||
void AssetDumper::quitDumper()
|
||||
{
|
||||
m_quitDumper = true;
|
||||
m_queueCondition.wakeAll();
|
||||
if (!m_dumpFuture.isFinished())
|
||||
m_dumpFuture.waitForFinished();
|
||||
}
|
||||
|
||||
void AssetDumper::abortDumper()
|
||||
{
|
||||
if (!m_dumpFuture.isFinished()) {
|
||||
m_dumpFuture.cancel();
|
||||
m_queueCondition.wakeAll();
|
||||
m_dumpFuture.waitForFinished();
|
||||
}
|
||||
}
|
||||
|
||||
void AssetDumper::addAsset(const QPixmap &p, const Utils::FilePath &path)
|
||||
{
|
||||
QMutexLocker locker(&m_queueMutex);
|
||||
qDebug() << "Save Asset:" << path;
|
||||
m_assets.push({p, path});
|
||||
}
|
||||
|
||||
void AssetDumper::doDumping(QPromise<void> &promise)
|
||||
{
|
||||
auto haveAsset = [this] (std::pair<QPixmap, Utils::FilePath> *asset) {
|
||||
QMutexLocker locker(&m_queueMutex);
|
||||
if (m_assets.empty())
|
||||
return false;
|
||||
*asset = m_assets.front();
|
||||
m_assets.pop();
|
||||
return true;
|
||||
};
|
||||
|
||||
forever {
|
||||
std::pair<QPixmap, Utils::FilePath> asset;
|
||||
if (haveAsset(&asset)) {
|
||||
if (promise.isCanceled())
|
||||
break;
|
||||
savePixmap(asset.first, asset.second);
|
||||
} else {
|
||||
if (m_quitDumper)
|
||||
break;
|
||||
QMutexLocker locker(&m_queueMutex);
|
||||
m_queueCondition.wait(&m_queueMutex);
|
||||
}
|
||||
|
||||
if (promise.isCanceled())
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AssetDumper::savePixmap(const QPixmap &p, Utils::FilePath &path) const
|
||||
{
|
||||
if (p.isNull()) {
|
||||
qCDebug(loggerWarn) << "Dumping null pixmap" << path;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!makeParentPath(path)) {
|
||||
ExportNotification::addError(Tr::tr("Error creating asset directory. %1").arg(path.fileName()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!p.save(path.toUrlishString())) {
|
||||
ExportNotification::addError(Tr::tr("Error saving asset. %1").arg(path.fileName()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,99 +0,0 @@
|
||||
// Copyright (C) 2020 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "assetexporterview.h"
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QSet>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace ProjectExplorer { class Project; }
|
||||
|
||||
namespace QmlDesigner {
|
||||
class AssetDumper;
|
||||
class Component;
|
||||
|
||||
class AssetExporter : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
enum class ParsingState {
|
||||
Idle = 0,
|
||||
Parsing,
|
||||
ParsingFinished,
|
||||
ExportingAssets,
|
||||
ExportingAssetsFinished,
|
||||
WritingJson,
|
||||
ExportingDone
|
||||
};
|
||||
|
||||
AssetExporter(AssetExporterView *view,
|
||||
ProjectExplorer::Project *project,
|
||||
ProjectStorageDependencies projectStorageDependencies);
|
||||
~AssetExporter();
|
||||
|
||||
void exportQml(const Utils::FilePaths &qmlFiles, const Utils::FilePath &exportPath,
|
||||
bool exportAssets, bool perComponentExport);
|
||||
|
||||
void cancel();
|
||||
bool isBusy() const;
|
||||
|
||||
const QPixmap &generateAsset(const ModelNode &node);
|
||||
Utils::FilePath assetPath(const ModelNode &node, const Component *component,
|
||||
const QString &suffix = {}) const;
|
||||
void exportAsset(const QPixmap &asset, const Utils::FilePath &path);
|
||||
QByteArray generateUuid(const ModelNode &node);
|
||||
QString componentUuid(const ModelNode &instance) const;
|
||||
|
||||
signals:
|
||||
void stateChanged(ParsingState);
|
||||
void exportProgressChanged(double) const;
|
||||
|
||||
private:
|
||||
ParsingState currentState() const { return m_currentState.m_state; }
|
||||
void exportComponent(const ModelNode &rootNode);
|
||||
void writeMetadata() const;
|
||||
void notifyLoadError(AssetExporterView::LoadState state);
|
||||
void notifyProgress(double value) const;
|
||||
void triggerLoadNextFile();
|
||||
void loadNextFile();
|
||||
|
||||
void onQmlFileLoaded();
|
||||
Utils::FilePath componentExportDir(const Component *component) const;
|
||||
|
||||
void beginExport();
|
||||
void preprocessQmlFile(const Utils::FilePath &path);
|
||||
bool assignUuids(const ModelNode &root);
|
||||
|
||||
private:
|
||||
mutable class State {
|
||||
public:
|
||||
State(AssetExporter&);
|
||||
void change(const ParsingState &state);
|
||||
operator ParsingState() const { return m_state; }
|
||||
AssetExporter &m_assetExporter;
|
||||
ParsingState m_state = ParsingState::Idle;
|
||||
} m_currentState;
|
||||
ProjectExplorer::Project *m_project = nullptr;
|
||||
AssetExporterView *m_view = nullptr;
|
||||
Utils::FilePaths m_exportFiles;
|
||||
unsigned int m_totalFileCount = 0;
|
||||
Utils::FilePath m_exportPath;
|
||||
QString m_exportFile;
|
||||
bool m_perComponentExport = false;
|
||||
std::vector<std::unique_ptr<Component>> m_components;
|
||||
QHash<QString, QString> m_componentUuidCache;
|
||||
QSet<QByteArray> m_usedHashes;
|
||||
QHash<QString, QPixmap> m_assets;
|
||||
ProjectStorageDependencies m_projectStorageDependencies;
|
||||
std::unique_ptr<AssetDumper> m_assetDumper;
|
||||
bool m_cancelled = false;
|
||||
};
|
||||
QDebug operator<< (QDebug os, const QmlDesigner::AssetExporter::ParsingState& s);
|
||||
|
||||
} // namespace QmlDesigner
|
@@ -1,115 +0,0 @@
|
||||
// Copyright (C) 2020 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "assetexporterplugin.h"
|
||||
|
||||
#include "assetexportdialog.h"
|
||||
#include "assetexporter.h"
|
||||
#include "assetexporterview.h"
|
||||
#include "assetexportpluginconstants.h"
|
||||
#include "componentexporter.h"
|
||||
#include "filepathmodel.h"
|
||||
#include <qmldesignerprojectmanager.h>
|
||||
|
||||
#include "dumpers/itemnodedumper.h"
|
||||
#include "dumpers/textnodedumper.h"
|
||||
#include "dumpers/assetnodedumper.h"
|
||||
|
||||
#include "coreplugin/actionmanager/actionmanager.h"
|
||||
#include "coreplugin/actionmanager/actioncontainer.h"
|
||||
#include "coreplugin/documentmanager.h"
|
||||
#include "qmldesigner/qmldesignerplugin.h"
|
||||
#include "projectexplorer/projectexplorerconstants.h"
|
||||
#include "projectexplorer/projectmanager.h"
|
||||
#include "projectexplorer/project.h"
|
||||
#include "projectexplorer/projectmanager.h"
|
||||
#include "projectexplorer/taskhub.h"
|
||||
|
||||
#include "extensionsystem/pluginmanager.h"
|
||||
#include "extensionsystem/pluginspec.h"
|
||||
|
||||
#include "utils/algorithm.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QAction>
|
||||
|
||||
#include <QLoggingCategory>
|
||||
|
||||
namespace QmlDesigner {
|
||||
|
||||
AssetExporterPlugin::AssetExporterPlugin()
|
||||
: m_projectManager{QmlDesigner::QmlDesignerPlugin::projectManagerForPluginInitializationOnly()}
|
||||
{
|
||||
ProjectExplorer::TaskHub::addCategory({Constants::TASK_CATEGORY_ASSET_EXPORT,
|
||||
tr("Asset Export"),
|
||||
tr("Issues with exporting assets."),
|
||||
false});
|
||||
|
||||
auto *designerPlugin = QmlDesigner::QmlDesignerPlugin::instance();
|
||||
|
||||
auto &viewManager = designerPlugin->viewManager();
|
||||
m_view = viewManager.registerView(std::make_unique<AssetExporterView>(
|
||||
designerPlugin->externalDependenciesForPluginInitializationOnly()));
|
||||
|
||||
// Add dumper templates for factory instantiation.
|
||||
Component::addNodeDumper<ItemNodeDumper>();
|
||||
Component::addNodeDumper<TextNodeDumper>();
|
||||
Component::addNodeDumper<AssetNodeDumper>();
|
||||
|
||||
// Instantiate actions created by the plugin.
|
||||
addActions();
|
||||
|
||||
connect(ProjectExplorer::ProjectManager::instance(),
|
||||
&ProjectExplorer::ProjectManager::startupProjectChanged,
|
||||
this, &AssetExporterPlugin::updateActions);
|
||||
|
||||
updateActions();
|
||||
}
|
||||
|
||||
QString AssetExporterPlugin::pluginName() const
|
||||
{
|
||||
return QLatin1String("AssetExporterPlugin");
|
||||
}
|
||||
|
||||
void AssetExporterPlugin::onExport()
|
||||
{
|
||||
auto startupProject = ProjectExplorer::ProjectManager::startupProject();
|
||||
if (!startupProject)
|
||||
return;
|
||||
|
||||
FilePathModel model(startupProject);
|
||||
auto exportDir = startupProject->projectFilePath().parentDir();
|
||||
if (!exportDir.parentDir().isEmpty())
|
||||
exportDir = exportDir.parentDir();
|
||||
exportDir = exportDir.pathAppended(startupProject->displayName() + "_export");
|
||||
AssetExporter assetExporter(m_view, startupProject, m_projectManager.projectStorageDependencies());
|
||||
AssetExportDialog assetExporterDialog(exportDir, assetExporter, model);
|
||||
assetExporterDialog.exec();
|
||||
}
|
||||
|
||||
void AssetExporterPlugin::addActions()
|
||||
{
|
||||
auto exportAction = new QAction(tr("Export Components"), this);
|
||||
exportAction->setToolTip(tr("Export components in the current project."));
|
||||
connect(exportAction, &QAction::triggered, this, &AssetExporterPlugin::onExport);
|
||||
Core::Command *cmd = Core::ActionManager::registerAction(exportAction, Constants::EXPORT_QML);
|
||||
|
||||
// Add action to build menu
|
||||
Core::ActionContainer *buildMenu =
|
||||
Core::ActionManager::actionContainer(ProjectExplorer::Constants::M_BUILDPROJECT);
|
||||
buildMenu->addAction(cmd, ProjectExplorer::Constants::G_BUILD_RUN);
|
||||
}
|
||||
|
||||
void AssetExporterPlugin::updateActions()
|
||||
{
|
||||
auto project = ProjectExplorer::ProjectManager::startupProject();
|
||||
QAction* const exportAction = Core::ActionManager::command(Constants::EXPORT_QML)->action();
|
||||
exportAction->setEnabled(project && !project->needsConfiguration());
|
||||
}
|
||||
|
||||
QString AssetExporterPlugin::metaInfo() const
|
||||
{
|
||||
return QLatin1String(":/assetexporterplugin/assetexporterplugin.metainfo");
|
||||
}
|
||||
|
||||
} //QmlDesigner
|
@@ -1,35 +0,0 @@
|
||||
// Copyright (C) 2020 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
#pragma once
|
||||
|
||||
#include <iwidgetplugin.h>
|
||||
|
||||
|
||||
namespace QmlDesigner {
|
||||
class AssetExporter;
|
||||
class AssetExporterView;
|
||||
class AssetExporterPlugin : public QObject, QmlDesigner::IWidgetPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QmlDesignerPlugin" FILE "assetexporterplugin.json")
|
||||
|
||||
Q_DISABLE_COPY(AssetExporterPlugin)
|
||||
Q_INTERFACES(QmlDesigner::IWidgetPlugin)
|
||||
|
||||
public:
|
||||
AssetExporterPlugin();
|
||||
|
||||
QString metaInfo() const final;
|
||||
QString pluginName() const final;
|
||||
|
||||
private:
|
||||
void onExport();
|
||||
void addActions();
|
||||
void updateActions();
|
||||
|
||||
AssetExporterView *m_view = nullptr;
|
||||
class QmlDesignerProjectManager &m_projectManager;
|
||||
};
|
||||
|
||||
} // namespace QmlDesigner
|
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"Vendor" : "The Qt Company Ltd",
|
||||
"Category" : "Qt Quick",
|
||||
"Description" : "Plugin for exporting assets and QML from QmlDesigner",
|
||||
"Url" : "http://www.qt.io"
|
||||
}
|
@@ -1,2 +0,0 @@
|
||||
MetaInfo {
|
||||
}
|
@@ -1,5 +0,0 @@
|
||||
<RCC>
|
||||
<qresource prefix="/assetexporterplugin">
|
||||
<file>assetexporterplugin.metainfo</file>
|
||||
</qresource>
|
||||
</RCC>
|
@@ -1,142 +0,0 @@
|
||||
// Copyright (C) 2020 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
#include "assetexporterview.h"
|
||||
|
||||
#include "qmlitemnode.h"
|
||||
#include "rewriterview.h"
|
||||
|
||||
#include "coreplugin/editormanager/editormanager.h"
|
||||
#include "coreplugin/editormanager/ieditor.h"
|
||||
#include "coreplugin/modemanager.h"
|
||||
#include "coreplugin/coreconstants.h"
|
||||
|
||||
#include <QLoggingCategory>
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(loggerInfo, "qtc.designer.assetExportPlugin.view", QtInfoMsg)
|
||||
Q_LOGGING_CATEGORY(loggerWarn, "qtc.designer.assetExportPlugin.view", QtWarningMsg)
|
||||
//Q_LOGGING_CATEGORY(loggerError, "qtc.designer.assetExportPlugin.view", QtCriticalMsg)
|
||||
|
||||
const int RetryIntervalMs = 500;
|
||||
const int MinRetry = 2;
|
||||
}
|
||||
|
||||
namespace QmlDesigner {
|
||||
|
||||
AssetExporterView::AssetExporterView(ExternalDependenciesInterface &externalDependencies)
|
||||
: AbstractView(externalDependencies)
|
||||
, m_timer(this)
|
||||
{
|
||||
m_timer.setInterval(RetryIntervalMs);
|
||||
// We periodically check if file is loaded.
|
||||
connect(&m_timer, &QTimer::timeout, this, &AssetExporterView::handleTimerTimeout);
|
||||
}
|
||||
|
||||
|
||||
bool AssetExporterView::loadQmlFile(const Utils::FilePath &path, uint timeoutSecs)
|
||||
{
|
||||
qCDebug(loggerInfo) << "Load file" << path;
|
||||
if (loadingState() == LoadState::Busy)
|
||||
return false;
|
||||
|
||||
setState(LoadState::Busy);
|
||||
m_retryCount = std::max(MinRetry, static_cast<int>((timeoutSecs * 1000) / RetryIntervalMs));
|
||||
m_currentEditor = Core::EditorManager::openEditor(path, Utils::Id(),
|
||||
Core::EditorManager::DoNotMakeVisible);
|
||||
Core::ModeManager::activateMode(Core::Constants::MODE_DESIGN);
|
||||
Core::ModeManager::setFocusToCurrentMode();
|
||||
m_timer.start();
|
||||
return true;
|
||||
}
|
||||
|
||||
Utils::Result AssetExporterView::saveQmlFile() const
|
||||
{
|
||||
if (!m_currentEditor) {
|
||||
qCDebug(loggerWarn) << "Saving QML file failed. No editor.";
|
||||
return Utils::Result::Error("Saving QML file failed. No editor.");
|
||||
}
|
||||
|
||||
return m_currentEditor->document()->save();
|
||||
}
|
||||
|
||||
void AssetExporterView::modelAttached(Model *model)
|
||||
{
|
||||
if (model->rewriterView() && !model->rewriterView()->errors().isEmpty())
|
||||
setState(LoadState::QmlErrorState);
|
||||
|
||||
AbstractView::modelAttached(model);
|
||||
}
|
||||
|
||||
void AssetExporterView::
|
||||
instanceInformationsChanged(const QMultiHash<ModelNode, InformationName> &informationChangeHash)
|
||||
{
|
||||
if (inErrorState() || loadingState() == LoadState::Loaded)
|
||||
return; // Already reached error or connected state.
|
||||
|
||||
// We expect correct dimensions are available if the rootnode's
|
||||
// information change message is received.
|
||||
const auto nodes = informationChangeHash.keys();
|
||||
bool hasRootNode = std::any_of(nodes.begin(), nodes.end(), [](const ModelNode &n) {
|
||||
return n.isRootNode();
|
||||
});
|
||||
if (hasRootNode)
|
||||
handleMaybeDone();
|
||||
}
|
||||
|
||||
void AssetExporterView::instancesPreviewImageChanged([[maybe_unused]] const QVector<ModelNode> &nodeList)
|
||||
{
|
||||
emit previewChanged();
|
||||
}
|
||||
|
||||
bool AssetExporterView::inErrorState() const
|
||||
{
|
||||
return m_state == LoadState::Exausted || m_state == LoadState::QmlErrorState;
|
||||
}
|
||||
|
||||
bool AssetExporterView::isLoaded() const
|
||||
{
|
||||
return isAttached() && QmlItemNode(rootModelNode()).isValid();
|
||||
}
|
||||
|
||||
void AssetExporterView::setState(AssetExporterView::LoadState state)
|
||||
{
|
||||
if (state != m_state) {
|
||||
m_state = state;
|
||||
qCDebug(loggerInfo) << "Loading state changed" << m_state;
|
||||
if (inErrorState() || m_state == LoadState::Loaded) {
|
||||
m_timer.stop();
|
||||
// TODO: Send the loaded signal with a delay. The assumption that model attached and a
|
||||
// valid root object is enough to declare a QML file is ready is incorrect. A ideal
|
||||
// solution would be that the QML Puppet notifies file ready signal.
|
||||
if (m_state == LoadState::Loaded)
|
||||
QTimer::singleShot(2000, this, &AssetExporterView::loadingFinished);
|
||||
else
|
||||
emit loadingError(m_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AssetExporterView::handleMaybeDone()
|
||||
{
|
||||
if (isLoaded())
|
||||
setState(LoadState::Loaded);
|
||||
}
|
||||
|
||||
void AssetExporterView::handleTimerTimeout()
|
||||
{
|
||||
if (!inErrorState() && loadingState() != LoadState::Loaded)
|
||||
handleMaybeDone();
|
||||
|
||||
if (--m_retryCount < 0)
|
||||
setState(LoadState::Exausted);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
QDebug operator<<(QDebug os, const QmlDesigner::AssetExporterView::LoadState &s)
|
||||
{
|
||||
os << static_cast<std::underlying_type<QmlDesigner::AssetExporterView::LoadState>::type>(s);
|
||||
return os;
|
||||
}
|
||||
QT_END_NAMESPACE
|
@@ -1,62 +0,0 @@
|
||||
// Copyright (C) 2020 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
#pragma once
|
||||
|
||||
#include "abstractview.h"
|
||||
|
||||
#include <utils/filepath.h>
|
||||
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
namespace Core { class IEditor; }
|
||||
|
||||
namespace QmlDesigner {
|
||||
|
||||
class AssetExporterView : public AbstractView
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum class LoadState {
|
||||
Idle = 1,
|
||||
Busy,
|
||||
Exausted,
|
||||
QmlErrorState,
|
||||
Loaded
|
||||
};
|
||||
|
||||
AssetExporterView(ExternalDependenciesInterface &externalDependencies);
|
||||
|
||||
bool loadQmlFile(const Utils::FilePath &path, uint timeoutSecs = 10);
|
||||
Utils::Result saveQmlFile() const;
|
||||
|
||||
void modelAttached(Model *model) override;
|
||||
void instanceInformationsChanged(const QMultiHash<ModelNode, InformationName> &informationChangeHash) override;
|
||||
void instancesPreviewImageChanged(const QVector<ModelNode> &nodeList) override;
|
||||
|
||||
LoadState loadingState() const { return m_state; }
|
||||
bool inErrorState() const;
|
||||
|
||||
signals:
|
||||
void loadingFinished();
|
||||
void loadingError(LoadState);
|
||||
void previewChanged();
|
||||
|
||||
private:
|
||||
bool isLoaded() const;
|
||||
void setState(LoadState state);
|
||||
void handleMaybeDone();
|
||||
void handleTimerTimeout();
|
||||
|
||||
Core::IEditor *m_currentEditor = nullptr;
|
||||
QTimer m_timer;
|
||||
int m_retryCount = 0;
|
||||
LoadState m_state = LoadState::Idle;
|
||||
bool m_waitForPuppet = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
QDebug operator<<(QDebug os, const QmlDesigner::AssetExporterView::LoadState &s);
|
||||
QT_END_NAMESPACE
|
@@ -1,66 +0,0 @@
|
||||
// Copyright (C) 2020 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
#pragma once
|
||||
|
||||
#include <auxiliarydata.h>
|
||||
|
||||
namespace QmlDesigner {
|
||||
namespace Constants {
|
||||
|
||||
const char EXPORT_QML[] = "Designer.ExportPlugin.ExportQml";
|
||||
|
||||
const char TASK_CATEGORY_ASSET_EXPORT[] = "AssetExporter.Export";
|
||||
|
||||
//***************************************************************************
|
||||
// Metadata tags
|
||||
//***************************************************************************
|
||||
// Plugin info tags
|
||||
const char PluginInfoTag[] = "pluginInfo";
|
||||
const char MetadataVersionTag[] = "metadataVersion";
|
||||
|
||||
const char DocumentInfoTag[] = "documentInfo";
|
||||
const char DocumentNameTag[] = "name";
|
||||
|
||||
// Layer data tags
|
||||
const char ArtboardListTag[] = "artboards";
|
||||
|
||||
const char NameTag[] = "name";
|
||||
|
||||
const char XPosTag[] = "x";
|
||||
const char YPosTag[] = "y";
|
||||
const char WidthTag[] = "width";
|
||||
const char HeightTag[] = "height";
|
||||
|
||||
const char MetadataTag[] = "metadata";
|
||||
const char ChildrenTag[] = "children";
|
||||
const char CustomIdTag[] = "customId";
|
||||
const char QmlIdTag[] = "qmlId";
|
||||
const char ExportTypeTag[] = "exportType";
|
||||
const char ExportTypeComponent[] = "component";
|
||||
const char ExportTypeChild[] = "child";
|
||||
const char QmlPropertiesTag[] = "qmlProperties";
|
||||
const char ImportsTag[] = "extraImports";
|
||||
const char UuidTag[] = "uuid";
|
||||
const char ClipTag[] = "clip";
|
||||
const char AssetDataTag[] = "assetData";
|
||||
const char ReferenceAssetTag[] = "referenceAsset";
|
||||
const char AssetPathTag[] = "assetPath";
|
||||
const char AssetBoundsTag[] = "assetBounds";
|
||||
const char OpacityTag[] = "opacity";
|
||||
const char TypeNameTag[] = "typeName";
|
||||
const char TypeIdTag[] = "typeId";
|
||||
|
||||
const char TextDetailsTag[] = "textDetails";
|
||||
const char FontFamilyTag[] = "fontFamily";
|
||||
const char FontSizeTag[] = "fontSize";
|
||||
const char FontStyleTag[] = "fontStyle";
|
||||
const char LetterSpacingTag[] = "kerning";
|
||||
const char TextColorTag[] = "textColor";
|
||||
const char TextContentTag[] = "contents";
|
||||
const char IsMultilineTag[] = "multiline";
|
||||
const char LineHeightTag[] = "lineHeight";
|
||||
const char HAlignTag[] = "horizontalAlignment";
|
||||
const char VAlignTag[] = "verticalAlignment";
|
||||
|
||||
}
|
||||
}
|
@@ -1,158 +0,0 @@
|
||||
// Copyright (C) 2020 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
#include "componentexporter.h"
|
||||
#include "assetexporter.h"
|
||||
#include "assetexportpluginconstants.h"
|
||||
#include "exportnotification.h"
|
||||
#include "dumpers/nodedumper.h"
|
||||
|
||||
#include "model.h"
|
||||
#include "nodeabstractproperty.h"
|
||||
#include "nodemetainfo.h"
|
||||
#include "qmlitemnode.h"
|
||||
#include "rewriterview.h"
|
||||
|
||||
#include "utils/qtcassert.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QLoggingCategory>
|
||||
#include <QPainter>
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(loggerInfo, "qtc.designer.assetExportPlugin.modelExporter", QtInfoMsg)
|
||||
}
|
||||
|
||||
namespace QmlDesigner {
|
||||
using namespace Constants;
|
||||
|
||||
std::vector<std::unique_ptr<Internal::NodeDumperCreatorBase>> Component::m_readers;
|
||||
Component::Component(AssetExporter &exporter, const ModelNode &rootNode):
|
||||
m_exporter(exporter),
|
||||
m_rootNode(rootNode)
|
||||
{
|
||||
m_name = m_rootNode.id();
|
||||
if (m_name.isEmpty())
|
||||
m_name = QString::fromUtf8(m_rootNode.type());
|
||||
}
|
||||
|
||||
QJsonObject Component::json() const
|
||||
{
|
||||
return m_json;
|
||||
}
|
||||
|
||||
AssetExporter &Component::exporter()
|
||||
{
|
||||
return m_exporter;
|
||||
}
|
||||
|
||||
void Component::exportComponent()
|
||||
{
|
||||
QTC_ASSERT(m_rootNode.isValid(), return);
|
||||
m_json = nodeToJson(m_rootNode);
|
||||
// Change the export type to component
|
||||
QJsonObject metadata = m_json.value(MetadataTag).toObject();
|
||||
metadata.insert(ExportTypeTag, ExportTypeComponent);
|
||||
addReferenceAsset(metadata);
|
||||
m_json.insert(MetadataTag, metadata);
|
||||
addImports();
|
||||
}
|
||||
|
||||
const QString &Component::name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
NodeDumper *Component::createNodeDumper(const ModelNode &node) const
|
||||
{
|
||||
std::unique_ptr<NodeDumper> reader;
|
||||
for (auto &dumperCreator: m_readers) {
|
||||
std::unique_ptr<NodeDumper> r(dumperCreator->instance(node));
|
||||
if (r->isExportable()) {
|
||||
if (reader) {
|
||||
if (reader->priority() < r->priority())
|
||||
reader = std::move(r);
|
||||
} else {
|
||||
reader = std::move(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!reader)
|
||||
qCDebug(loggerInfo()) << "No dumper for node" << node;
|
||||
|
||||
return reader.release();
|
||||
}
|
||||
|
||||
QJsonObject Component::nodeToJson(const ModelNode &node)
|
||||
{
|
||||
QJsonObject jsonObject;
|
||||
|
||||
// Don't export States, Connection, Timeline etc nodes.
|
||||
if (!node.metaInfo().isQtQuickItem())
|
||||
return {};
|
||||
|
||||
std::unique_ptr<NodeDumper> dumper(createNodeDumper(node));
|
||||
if (dumper) {
|
||||
jsonObject = dumper->json(*this);
|
||||
} else {
|
||||
ExportNotification::addError(tr("Error exporting node %1. Cannot parse type %2.")
|
||||
.arg(node.id()).arg(QString::fromUtf8(node.type())));
|
||||
}
|
||||
|
||||
QJsonArray children;
|
||||
for (const ModelNode &childnode : node.directSubModelNodes()) {
|
||||
const QJsonObject childJson = nodeToJson(childnode);
|
||||
if (!childJson.isEmpty())
|
||||
children.append(childJson);
|
||||
}
|
||||
|
||||
if (!children.isEmpty())
|
||||
jsonObject.insert(ChildrenTag, children);
|
||||
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
void Component::addReferenceAsset(QJsonObject &metadataObject) const
|
||||
{
|
||||
QPixmap refAsset = m_exporter.generateAsset(m_rootNode);
|
||||
stichChildrendAssets(m_rootNode, refAsset);
|
||||
Utils::FilePath refAssetPath = m_exporter.assetPath(m_rootNode, this, "_ref");
|
||||
m_exporter.exportAsset(refAsset, refAssetPath);
|
||||
QJsonObject assetData;
|
||||
if (metadataObject.contains(AssetDataTag))
|
||||
assetData = metadataObject[AssetDataTag].toObject();
|
||||
assetData.insert(ReferenceAssetTag, refAssetPath.toUrlishString());
|
||||
metadataObject.insert(AssetDataTag, assetData);
|
||||
}
|
||||
|
||||
void Component::stichChildrendAssets(const ModelNode &node, QPixmap &parentPixmap) const
|
||||
{
|
||||
if (!node.hasAnySubModelNodes())
|
||||
return;
|
||||
|
||||
QPainter painter(&parentPixmap);
|
||||
for (const ModelNode &child : node.directSubModelNodes()) {
|
||||
QPixmap childPixmap = m_exporter.generateAsset(child);
|
||||
if (childPixmap.isNull())
|
||||
continue;
|
||||
stichChildrendAssets(child, childPixmap);
|
||||
QTransform cTransform = QmlObjectNode(child).toQmlItemNode().instanceTransform();
|
||||
painter.setTransform(cTransform);
|
||||
painter.drawPixmap(QPoint(0, 0), childPixmap);
|
||||
}
|
||||
painter.end();
|
||||
}
|
||||
|
||||
void Component::addImports()
|
||||
{
|
||||
QJsonArray importsArray;
|
||||
for (const Import &import : m_rootNode.model()->imports())
|
||||
importsArray.append(import.toString());
|
||||
|
||||
if (!importsArray.empty())
|
||||
m_json.insert(Constants::ImportsTag, importsArray);
|
||||
}
|
||||
|
||||
|
||||
} // namespace QmlDesigner
|
@@ -1,78 +0,0 @@
|
||||
// Copyright (C) 2020 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
#pragma once
|
||||
|
||||
#include <QByteArrayList>
|
||||
#include <QCoreApplication>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "utils/qtcassert.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QJsonArray;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace QmlDesigner {
|
||||
class AssetExporter;
|
||||
class ModelNode;
|
||||
class Component;
|
||||
class NodeDumper;
|
||||
|
||||
namespace Internal {
|
||||
class NodeDumperCreatorBase
|
||||
{
|
||||
public:
|
||||
virtual ~NodeDumperCreatorBase() {}
|
||||
protected:
|
||||
virtual NodeDumper *instance(const ModelNode &) const = 0;
|
||||
friend Component;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
class NodeDumperCreator : public NodeDumperCreatorBase
|
||||
{
|
||||
public:
|
||||
NodeDumperCreator() = default;
|
||||
~NodeDumperCreator() = default;
|
||||
|
||||
protected:
|
||||
NodeDumper *instance(const ModelNode &node) const { return new T(node); }
|
||||
};
|
||||
} //Internal
|
||||
|
||||
class Component
|
||||
{
|
||||
Q_DECLARE_TR_FUNCTIONS(Component);
|
||||
|
||||
public:
|
||||
Component(AssetExporter& exporter, const ModelNode &rootNode);
|
||||
|
||||
void exportComponent();
|
||||
const QString &name() const;
|
||||
QJsonObject json() const;
|
||||
|
||||
AssetExporter &exporter();
|
||||
|
||||
template<typename T> static void addNodeDumper()
|
||||
{
|
||||
QTC_ASSERT((std::is_base_of<NodeDumper, T>::value), return);
|
||||
m_readers.push_back(std::make_unique<Internal::NodeDumperCreator<T>>());
|
||||
}
|
||||
private:
|
||||
NodeDumper* createNodeDumper(const ModelNode &node) const;
|
||||
QJsonObject nodeToJson(const ModelNode &node);
|
||||
void addReferenceAsset(QJsonObject &metadataObject) const;
|
||||
void stichChildrendAssets(const ModelNode &node, QPixmap &parentPixmap) const;
|
||||
void addImports();
|
||||
|
||||
private:
|
||||
AssetExporter& m_exporter;
|
||||
const ModelNode &m_rootNode;
|
||||
QString m_name;
|
||||
QJsonObject m_json;
|
||||
static std::vector<std::unique_ptr<Internal::NodeDumperCreatorBase>> m_readers;
|
||||
};
|
||||
}
|
@@ -1,47 +0,0 @@
|
||||
// Copyright (C) 2020 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "assetnodedumper.h"
|
||||
#include "assetexportpluginconstants.h"
|
||||
#include "assetexporter.h"
|
||||
|
||||
#include "qmlitemnode.h"
|
||||
#include "componentexporter.h"
|
||||
|
||||
#include "utils/fileutils.h"
|
||||
|
||||
#include <QPixmap>
|
||||
|
||||
namespace QmlDesigner {
|
||||
using namespace Constants;
|
||||
|
||||
AssetNodeDumper::AssetNodeDumper(const ModelNode &node)
|
||||
: ItemNodeDumper(node)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool AssetNodeDumper::isExportable() const
|
||||
{
|
||||
auto qtQuickImageMetaInfo = model()->qtQuickImageMetaInfo();
|
||||
auto qtQuickRectangleMetaInfo = model()->qtQuickRectangleMetaInfo();
|
||||
return metaInfo().isBasedOn(qtQuickImageMetaInfo, qtQuickRectangleMetaInfo);
|
||||
}
|
||||
|
||||
QJsonObject AssetNodeDumper::json(Component &component) const
|
||||
{
|
||||
QJsonObject jsonObject = ItemNodeDumper::json(component);
|
||||
|
||||
AssetExporter &exporter = component.exporter();
|
||||
const Utils::FilePath assetPath = exporter.assetPath(m_node, &component);
|
||||
exporter.exportAsset(exporter.generateAsset(m_node), assetPath);
|
||||
|
||||
QJsonObject assetData;
|
||||
assetData.insert(AssetPathTag, assetPath.toUrlishString());
|
||||
QJsonObject metadata = jsonObject.value(MetadataTag).toObject();
|
||||
metadata.insert(AssetDataTag, assetData);
|
||||
jsonObject.insert(MetadataTag, metadata);
|
||||
return jsonObject;
|
||||
}
|
||||
}
|
||||
|
@@ -1,20 +0,0 @@
|
||||
// Copyright (C) 2020 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
#pragma once
|
||||
|
||||
#include "itemnodedumper.h"
|
||||
|
||||
namespace QmlDesigner {
|
||||
class Component;
|
||||
|
||||
class AssetNodeDumper : public ItemNodeDumper
|
||||
{
|
||||
public:
|
||||
AssetNodeDumper(const ModelNode &node);
|
||||
~AssetNodeDumper() override = default;
|
||||
|
||||
bool isExportable() const override;
|
||||
int priority() const override { return 200; }
|
||||
QJsonObject json(Component &component) const override;
|
||||
};
|
||||
}
|
@@ -1,76 +0,0 @@
|
||||
// Copyright (C) 2020 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "itemnodedumper.h"
|
||||
#include "assetexportpluginconstants.h"
|
||||
#include "assetexporter.h"
|
||||
#include "componentexporter.h"
|
||||
|
||||
#include "qmlitemnode.h"
|
||||
#include "annotation.h"
|
||||
|
||||
namespace {
|
||||
static QString capitalize(const QString &str)
|
||||
{
|
||||
if (str.isEmpty())
|
||||
return {};
|
||||
QString tmp = str;
|
||||
tmp[0] = QChar(str[0]).toUpper().toLatin1();
|
||||
return tmp;
|
||||
}
|
||||
}
|
||||
|
||||
namespace QmlDesigner {
|
||||
using namespace Constants;
|
||||
|
||||
ItemNodeDumper::ItemNodeDumper(const ModelNode &node)
|
||||
: NodeDumper(node)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool QmlDesigner::ItemNodeDumper::isExportable() const
|
||||
{
|
||||
return metaInfo().isQtQuickItem();
|
||||
}
|
||||
|
||||
QJsonObject QmlDesigner::ItemNodeDumper::json([[maybe_unused]] QmlDesigner::Component &component) const
|
||||
{
|
||||
const QmlObjectNode &qmlObjectNode = objectNode();
|
||||
QJsonObject jsonObject;
|
||||
|
||||
const QString qmlId = qmlObjectNode.id();
|
||||
QString name = m_node.simplifiedTypeName();
|
||||
if (!qmlId.isEmpty())
|
||||
name.append("_" + capitalize(qmlId));
|
||||
|
||||
jsonObject.insert(NameTag, name);
|
||||
|
||||
// Position relative to parent
|
||||
QmlItemNode itemNode = qmlObjectNode.toQmlItemNode();
|
||||
QPointF pos = itemNode.instancePosition();
|
||||
jsonObject.insert(XPosTag, pos.x());
|
||||
jsonObject.insert(YPosTag, pos.y());
|
||||
|
||||
// size
|
||||
QSizeF size = itemNode.instanceSize();
|
||||
jsonObject.insert(WidthTag, size.width());
|
||||
jsonObject.insert(HeightTag, size.height());
|
||||
|
||||
QJsonObject metadata;
|
||||
metadata.insert(QmlIdTag, qmlId);
|
||||
metadata.insert(UuidTag, uuid());
|
||||
metadata.insert(ExportTypeTag, ExportTypeChild);
|
||||
metadata.insert(TypeNameTag, QString::fromLatin1(m_node.type()));
|
||||
|
||||
if (m_node.hasCustomId())
|
||||
metadata.insert(CustomIdTag, m_node.customId());
|
||||
|
||||
QString typeId = component.exporter().componentUuid(m_node);
|
||||
if (!typeId.isEmpty())
|
||||
metadata.insert(TypeIdTag, typeId);
|
||||
|
||||
jsonObject.insert(MetadataTag, metadata);
|
||||
return jsonObject;
|
||||
}
|
||||
}
|
@@ -1,22 +0,0 @@
|
||||
// Copyright (C) 2020 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
#pragma once
|
||||
|
||||
#include "nodedumper.h"
|
||||
|
||||
namespace QmlDesigner {
|
||||
class ModelNode;
|
||||
class Component;
|
||||
|
||||
class ItemNodeDumper : public NodeDumper
|
||||
{
|
||||
public:
|
||||
ItemNodeDumper(const ModelNode &node);
|
||||
|
||||
~ItemNodeDumper() override = default;
|
||||
|
||||
int priority() const override { return 100; }
|
||||
bool isExportable() const override;
|
||||
QJsonObject json(Component &component) const override;
|
||||
};
|
||||
}
|
@@ -1,28 +0,0 @@
|
||||
// Copyright (C) 2020 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
#include "nodedumper.h"
|
||||
#include "assetexportpluginconstants.h"
|
||||
|
||||
#include <auxiliarydataproperties.h>
|
||||
|
||||
namespace QmlDesigner {
|
||||
NodeDumper::NodeDumper(const ModelNode &node)
|
||||
: m_node(node)
|
||||
, m_objectNode(node)
|
||||
, m_metaInfo(node.metaInfo())
|
||||
, m_model{node.model()}
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QVariant NodeDumper::propertyValue(const PropertyName &name) const
|
||||
{
|
||||
return m_objectNode.instanceValue(name);
|
||||
}
|
||||
|
||||
QString NodeDumper::uuid() const
|
||||
{
|
||||
return m_node.auxiliaryDataWithDefault(uuidProperty).toString();
|
||||
}
|
||||
|
||||
}
|
@@ -1,40 +0,0 @@
|
||||
// Copyright (C) 2020 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
#pragma once
|
||||
|
||||
#include "qmlobjectnode.h"
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QByteArrayList>
|
||||
|
||||
namespace QmlDesigner {
|
||||
class Component;
|
||||
class ModelNode;
|
||||
|
||||
class NodeDumper
|
||||
{
|
||||
public:
|
||||
NodeDumper(const ModelNode &node);
|
||||
|
||||
virtual ~NodeDumper() = default;
|
||||
|
||||
virtual int priority() const = 0;
|
||||
virtual bool isExportable() const = 0;
|
||||
virtual QJsonObject json(Component& component) const = 0;
|
||||
|
||||
const NodeMetaInfo &metaInfo() const { return m_metaInfo; }
|
||||
const QmlObjectNode& objectNode() const { return m_objectNode; }
|
||||
QVariant propertyValue(const PropertyName &name) const;
|
||||
QString uuid() const;
|
||||
|
||||
Model *model() const { return m_model; }
|
||||
|
||||
protected:
|
||||
const ModelNode &m_node;
|
||||
|
||||
private:
|
||||
QmlObjectNode m_objectNode;
|
||||
NodeMetaInfo m_metaInfo;
|
||||
Model *m_model = nullptr;
|
||||
};
|
||||
}
|
@@ -1,90 +0,0 @@
|
||||
// Copyright (C) 2020 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "textnodedumper.h"
|
||||
#include "assetexportpluginconstants.h"
|
||||
|
||||
#include <model.h>
|
||||
|
||||
#include <QColor>
|
||||
#include <QFontInfo>
|
||||
#include <QFontMetricsF>
|
||||
#include <QHash>
|
||||
#include <QtMath>
|
||||
|
||||
#include <private/qquicktext_p.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
const QHash<QString, QString> AlignMapping{
|
||||
{"AlignRight", "RIGHT"},
|
||||
{"AlignHCenter", "CENTER"},
|
||||
{"AlignJustify", "JUSTIFIED"},
|
||||
{"AlignLeft", "LEFT"},
|
||||
{"AlignTop", "TOP"},
|
||||
{"AlignVCenter", "CENTER"},
|
||||
{"AlignBottom", "BOTTOM"}
|
||||
};
|
||||
|
||||
QString toJsonAlignEnum(QString value) {
|
||||
if (value.isEmpty() || !AlignMapping.contains(value))
|
||||
return {};
|
||||
return AlignMapping[value];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
namespace QmlDesigner {
|
||||
using namespace Constants;
|
||||
|
||||
TextNodeDumper::TextNodeDumper(const ModelNode &node)
|
||||
: ItemNodeDumper(node)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool TextNodeDumper::isExportable() const
|
||||
{
|
||||
auto qtQuickTextMetaInfo = model()->qtQuickTextMetaInfo();
|
||||
auto qtQuickControlsLabelMetaInfo = model()->qtQuickControlsLabelMetaInfo();
|
||||
return metaInfo().isBasedOn(qtQuickTextMetaInfo, qtQuickControlsLabelMetaInfo);
|
||||
}
|
||||
|
||||
QJsonObject TextNodeDumper::json([[maybe_unused]] Component &component) const
|
||||
{
|
||||
QJsonObject jsonObject = ItemNodeDumper::json(component);
|
||||
|
||||
QJsonObject textDetails;
|
||||
textDetails.insert(TextContentTag, propertyValue("text").toString());
|
||||
|
||||
QFont font = propertyValue("font").value<QFont>();
|
||||
QFontInfo fontInfo(font);
|
||||
textDetails.insert(FontFamilyTag, fontInfo.family());
|
||||
textDetails.insert(FontStyleTag, fontInfo.styleName());
|
||||
textDetails.insert(FontSizeTag, fontInfo.pixelSize());
|
||||
textDetails.insert(LetterSpacingTag, font.letterSpacing());
|
||||
|
||||
|
||||
QColor fontColor(propertyValue("font.color").toString());
|
||||
textDetails.insert(TextColorTag, fontColor.name(QColor::HexArgb));
|
||||
|
||||
textDetails.insert(HAlignTag, toJsonAlignEnum(propertyValue("horizontalAlignment").toString()));
|
||||
textDetails.insert(VAlignTag, toJsonAlignEnum(propertyValue("verticalAlignment").toString()));
|
||||
|
||||
textDetails.insert(IsMultilineTag, propertyValue("wrapMode").toString().compare("NoWrap") != 0);
|
||||
|
||||
// Calculate line height in pixels
|
||||
QFontMetricsF fm(font);
|
||||
auto lineHeightMode = propertyValue("lineHeightMode").value<QQuickText::LineHeightMode>();
|
||||
double lineHeight = propertyValue("lineHeight").toDouble();
|
||||
qreal lineHeightPx = (lineHeightMode == QQuickText::FixedHeight) ?
|
||||
lineHeight : qCeil(fm.height()) * lineHeight;
|
||||
textDetails.insert(LineHeightTag, lineHeightPx);
|
||||
|
||||
QJsonObject metadata = jsonObject.value(MetadataTag).toObject();
|
||||
metadata.insert(TextDetailsTag, textDetails);
|
||||
jsonObject.insert(MetadataTag, metadata);
|
||||
return jsonObject;
|
||||
}
|
||||
}
|
@@ -1,21 +0,0 @@
|
||||
// Copyright (C) 2020 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
#pragma once
|
||||
|
||||
#include "itemnodedumper.h"
|
||||
|
||||
namespace QmlDesigner {
|
||||
class Component;
|
||||
|
||||
class TextNodeDumper : public ItemNodeDumper
|
||||
{
|
||||
public:
|
||||
TextNodeDumper(const ModelNode &node);
|
||||
~TextNodeDumper() override = default;
|
||||
|
||||
bool isExportable() const override;
|
||||
int priority() const override { return 200; }
|
||||
QJsonObject json(Component &component) const override;
|
||||
};
|
||||
|
||||
}
|
@@ -1,41 +0,0 @@
|
||||
// Copyright (C) 2020 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "exportnotification.h"
|
||||
#include "assetexportpluginconstants.h"
|
||||
|
||||
#include "projectexplorer/taskhub.h"
|
||||
|
||||
#include <QLoggingCategory>
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(loggerDebug, "qtc.designer.assetExportPlugin.exportNotification", QtDebugMsg)
|
||||
}
|
||||
|
||||
using namespace ProjectExplorer;
|
||||
namespace {
|
||||
static void addTask(Task::TaskType type, const QString &desc)
|
||||
{
|
||||
qCDebug(loggerDebug) << desc;
|
||||
Task task(type, desc, {}, -1, QmlDesigner::Constants::TASK_CATEGORY_ASSET_EXPORT);
|
||||
TaskHub::addTask(task);
|
||||
}
|
||||
}
|
||||
|
||||
namespace QmlDesigner {
|
||||
|
||||
void ExportNotification::addError(const QString &errMsg)
|
||||
{
|
||||
addTask(Task::Error, errMsg);
|
||||
}
|
||||
|
||||
void ExportNotification::addWarning(const QString &warningMsg)
|
||||
{
|
||||
addTask(Task::Warning, warningMsg);
|
||||
}
|
||||
|
||||
void ExportNotification::addInfo(const QString &infoMsg)
|
||||
{
|
||||
addTask(Task::Unknown, infoMsg);
|
||||
}
|
||||
} // QmlDesigner
|
@@ -1,16 +0,0 @@
|
||||
// Copyright (C) 2020 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace QmlDesigner {
|
||||
class ExportNotification
|
||||
{
|
||||
public:
|
||||
static void addError(const QString &errMsg);
|
||||
static void addWarning(const QString &warningMsg);
|
||||
static void addInfo(const QString &infoMsg);
|
||||
};
|
||||
} // QmlDesigner
|
@@ -1,143 +0,0 @@
|
||||
// Copyright (C) 2020 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
#include "filepathmodel.h"
|
||||
|
||||
#include "exportnotification.h"
|
||||
|
||||
#include <projectexplorer/project.h>
|
||||
#include <projectexplorer/projectnodes.h>
|
||||
|
||||
#include <utils/async.h>
|
||||
|
||||
#include <QLoggingCategory>
|
||||
#include <QTimer>
|
||||
|
||||
using namespace ProjectExplorer;
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(loggerError, "qtc.designer.assetExportPlugin.filePathModel", QtCriticalMsg)
|
||||
Q_LOGGING_CATEGORY(loggerInfo, "qtc.designer.assetExportPlugin.filePathModel", QtInfoMsg)
|
||||
|
||||
void findQmlFiles(QPromise<Utils::FilePath> &promise, const Project *project)
|
||||
{
|
||||
if (!project || promise.isCanceled())
|
||||
return;
|
||||
|
||||
int index = 0;
|
||||
project->files([&promise, &index](const Node* node) ->bool {
|
||||
if (promise.isCanceled())
|
||||
return false;
|
||||
Utils::FilePath path = node->filePath();
|
||||
bool isComponent = !path.fileName().isEmpty() && path.fileName().front().isUpper();
|
||||
if (isComponent && node->filePath().endsWith(".ui.qml"))
|
||||
promise.addResult(path, index++);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
namespace QmlDesigner {
|
||||
|
||||
FilePathModel::FilePathModel(ProjectExplorer::Project *project, QObject *parent)
|
||||
: QAbstractListModel(parent),
|
||||
m_project(project)
|
||||
{
|
||||
QTimer::singleShot(0, this, &FilePathModel::processProject);
|
||||
}
|
||||
|
||||
FilePathModel::~FilePathModel()
|
||||
{
|
||||
if (m_preprocessWatcher && !m_preprocessWatcher->isCanceled() &&
|
||||
!m_preprocessWatcher->isFinished()) {
|
||||
ExportNotification::addInfo(tr("Canceling file preparation."));
|
||||
m_preprocessWatcher->cancel();
|
||||
m_preprocessWatcher->waitForFinished();
|
||||
qCDebug(loggerInfo) << "Canceled file preparation.";
|
||||
}
|
||||
}
|
||||
|
||||
Qt::ItemFlags FilePathModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
Qt::ItemFlags itemFlags = QAbstractListModel::flags(index);
|
||||
if (index.isValid())
|
||||
itemFlags |= Qt::ItemIsUserCheckable;
|
||||
return itemFlags;
|
||||
}
|
||||
|
||||
int FilePathModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
if (!parent.isValid())
|
||||
return m_files.size();
|
||||
return 0;
|
||||
}
|
||||
|
||||
QVariant FilePathModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return {};
|
||||
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
return m_files[index.row()].toUserOutput();
|
||||
case Qt::CheckStateRole:
|
||||
return m_skipped.count(m_files[index.row()]) ? Qt::Unchecked : Qt::Checked;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool FilePathModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
||||
{
|
||||
if (!index.isValid() || role != Qt::CheckStateRole)
|
||||
return false;
|
||||
|
||||
const Utils::FilePath path = m_files[index.row()];
|
||||
if (value == Qt::Checked)
|
||||
m_skipped.erase(path);
|
||||
else
|
||||
m_skipped.insert(path);
|
||||
|
||||
emit dataChanged(index, index);
|
||||
return true;
|
||||
}
|
||||
|
||||
Utils::FilePaths FilePathModel::files() const
|
||||
{
|
||||
Utils::FilePaths selectedPaths;
|
||||
std::copy_if(m_files.begin(), m_files.end(), std::back_inserter(selectedPaths),
|
||||
[this](const Utils::FilePath &path) {
|
||||
return !m_skipped.count(path);
|
||||
});
|
||||
return selectedPaths;
|
||||
}
|
||||
|
||||
void FilePathModel::processProject()
|
||||
{
|
||||
if (m_preprocessWatcher && !m_preprocessWatcher->isCanceled() &&
|
||||
!m_preprocessWatcher->isFinished()) {
|
||||
qCDebug(loggerError) << "Previous model load not finished.";
|
||||
return;
|
||||
}
|
||||
|
||||
beginResetModel();
|
||||
m_preprocessWatcher.reset(new QFutureWatcher<Utils::FilePath>(this));
|
||||
connect(m_preprocessWatcher.get(),
|
||||
&QFutureWatcher<Utils::FilePath>::resultReadyAt,
|
||||
this,
|
||||
[this](int resultIndex) {
|
||||
beginInsertRows(index(0, 0), m_files.size(), m_files.size());
|
||||
m_files.append(m_preprocessWatcher->resultAt(resultIndex));
|
||||
endInsertRows();
|
||||
});
|
||||
|
||||
connect(m_preprocessWatcher.get(), &QFutureWatcher<Utils::FilePath>::finished,
|
||||
this, &FilePathModel::endResetModel);
|
||||
|
||||
QFuture<Utils::FilePath> f = Utils::asyncRun(&findQmlFiles, m_project);
|
||||
m_preprocessWatcher->setFuture(f);
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -1,43 +0,0 @@
|
||||
// Copyright (C) 2020 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utils/filepath.h>
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QCoreApplication>
|
||||
#include <QFutureWatcher>
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace ProjectExplorer {
|
||||
class Project;
|
||||
}
|
||||
|
||||
namespace QmlDesigner {
|
||||
class FilePathModel : public QAbstractListModel
|
||||
{
|
||||
Q_DECLARE_TR_FUNCTIONS(QmlDesigner::FilePathModel)
|
||||
|
||||
public:
|
||||
FilePathModel(ProjectExplorer::Project *project, QObject *parent = nullptr);
|
||||
~FilePathModel() override;
|
||||
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
|
||||
|
||||
Utils::FilePaths files() const;
|
||||
private:
|
||||
void processProject();
|
||||
|
||||
ProjectExplorer::Project *m_project = nullptr;
|
||||
std::unique_ptr<QFutureWatcher<Utils::FilePath>> m_preprocessWatcher;
|
||||
std::unordered_set<Utils::FilePath> m_skipped;
|
||||
Utils::FilePaths m_files;
|
||||
};
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user